SpringBoot 基于注解实现接口的代理Bean注入

在springboot加载时需自己手动将接口的代理bean注入到spring容器中,这样在service层注入该接口类型即可,

1.在SpringBoot启动类上添加EnableProxyBeanScan注解

  

EnableProxyBeanScan为自定义注解,通过Import注解扫描被ProxyBean注解的类或者被ProxyBean修饰的注解注解的类("注解继承")

ProxyBeanDefinitionRegistrar实现ImportBeanDefinitionRegistrar 通过ProxyInterfaceBeanBeanDefinitionScanner 来进行bean的加载

ProxyFactoryBean为bean的工厂类,提供代理bean

ProxyHandler为代理业务逻辑接口,提供三个参数: Class(被代理的类),Method(被代理的方法),Object[] 入参参数


@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Import(EnableProxyBeanScan.ProxyBeanDefinitionRegistrar.class)
public @interface EnableProxyBeanScan {

String[] basePackages() default {};

class ProxyBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
ProxyInterfaceBeanBeanDefinitionScanner scanner = new ProxyInterfaceBeanBeanDefinitionScanner(registry);
scanner.scan(getBasePackages(importingClassMetadata));
}

private String[] getBasePackages(AnnotationMetadata importingClassMetadata){
Map<String, Object> attributes = importingClassMetadata.getAnnotationAttributes(EnableProxyBeanScan.class.getCanonicalName());
Set<String> basePackages = new HashSet();
String[] basePackagesArr = (String[])((String[])attributes.get("basePackages"));
for(String item: basePackagesArr){
if(StringUtils.hasText(item))
basePackages.add(item);
}

if (basePackages.isEmpty()) {
basePackages.add(ClassUtils.getPackageName(importingClassMetadata.getClassName()));
}

return basePackages.toArray(new String[basePackages.size()]);
}
}
}
 
public class ProxyInterfaceBeanBeanDefinitionScanner extends ClassPathBeanDefinitionScanner {

    public ProxyInterfaceBeanBeanDefinitionScanner(BeanDefinitionRegistry registry) {
//registry是Spring的Bean注册中心
// false表示不使用ClassPathBeanDefinitionScanner默认的TypeFilter
// 默认的TypeFilter只会扫描带有@Service,@Controller,@Repository,@Component注解的类
super(registry,false);
} @Override
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
addIncludeFilter(new AnnotationTypeFilter(ProxyBean.class));
Set<BeanDefinitionHolder> beanDefinitionHolders = super.doScan(basePackages);
if (beanDefinitionHolders.isEmpty()){
System.err.println("No Interface Found!");
}else{
//创建代理对象
createBeanDefinition(beanDefinitionHolders);
}
return beanDefinitionHolders;
} @Override
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
AnnotationMetadata metadata = beanDefinition.getMetadata();
return metadata.isInterface() || metadata.isAbstract();
} /**
* 为扫描到的接口创建代理对象
* @param beanDefinitionHolders
*/
private void createBeanDefinition(Set<BeanDefinitionHolder> beanDefinitionHolders) {
for (BeanDefinitionHolder beanDefinitionHolder : beanDefinitionHolders) {
GenericBeanDefinition beanDefinition = ((GenericBeanDefinition) beanDefinitionHolder.getBeanDefinition());
//将bean的真实类型改变为FactoryBean
beanDefinition.getConstructorArgumentValues().
addGenericArgumentValue(beanDefinition.getBeanClassName());
beanDefinition.setBeanClass(ProxyFactoryBean.class);
beanDefinition.setAutowireMode(GenericBeanDefinition.AUTOWIRE_BY_TYPE);
}
}
}
@Target({ElementType.TYPE,ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ProxyBean { Class<? extends ProxyHandler> value();
}
public interface ProxyHandler{

    Object execute(Class<?> proxyType,Object proxy, Method proxyMethod, Object[] args);
}
public class ProxyFactoryBean<T> implements FactoryBean {

    private static final Map<Class<? extends ProxyHandler>,ProxyHandler> ProxyHandlers = new ConcurrentHashMap<>();

    private Class<T> interfaceClass;
private Class<? extends ProxyHandler> proxyHandlerType; public ProxyFactoryBean(Class<T> interfaceClass) {
this.interfaceClass = interfaceClass;
this.proxyHandlerType = AnnotationUtils.findAnnotation(interfaceClass, ProxyBean.class).value();
if(!ProxyFactoryBean.ProxyHandlers.containsKey(proxyHandlerType)) {
ProxyHandler proxyHandler = ClassUtils.newInstance(proxyHandlerType);
SpringBean.inject(proxyHandler);
ProxyFactoryBean.ProxyHandlers.put(proxyHandlerType, proxyHandler);
}
} @Override
public T getObject() throws Exception {
final ProxyHandler proxyHandler = ProxyFactoryBean.ProxyHandlers.get(proxyHandlerType);
return (T) Proxy.newProxyInstance(
interfaceClass.getClassLoader(),
new Class[]{interfaceClass},
(proxy,method,args) -> proxyHandler.execute(interfaceClass,proxy,method,args)
);
} @Override
public Class<T> getObjectType() {
return interfaceClass;
}
}

简单的例子:

  类似spring-feign的接口发送Http请求

1.先定义一个注解HttpClient,和HttpClientProxyHandler

@Target({ElementType.TYPE,ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@ProxyBean(HttpClient.HttpClientProxyHandler.class)
public @interface HttpClient { @Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@interface Request{
String url();
RequestMethod method() default RequestMethod.POST;
}
/**简单定义下进行测试,实际实现肯定要比这个复杂*/
class HttpClientProxyHandler implements ProxyHandler {
/**这个类虽然没有被Spring管理,不过通过这个注解可以实现SpringBean的注入和使用,
* 见ProxyFactoryBean构造方法的代码
* SpringBean.inject(proxyHandler);
*/
@Autowired
private RestTemplate template;
@Override
public Object execute(Class<?> proxyType,Object proxy, Method proxyMethod, Object[] args) {
return template.postForObject(
proxyMethod.getAnnotation(Request.class).url()
,args[0]
,proxyMethod.getReturnType()
);
}
}
}

2.被代理的接口

@HttpClient
public interface LoginService { @HttpClient.Request(url="ddd")
String getUserAge(ExamineReqDto username);
}

3.测试,

测试这里没有细致的测,RestTemplate这里是成功拿到了,不影响后续的使用

最后,附Bean注入的代码:


@Component
public class SpringBean implements ApplicationContextAware {

private static final Logger log = LoggerFactory.getLogger(SpringBean.class);

private static ApplicationContext applicationContext;

private SpringBean(){}

@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
SpringBean.applicationContext = applicationContext;
}

public static <T> T getSpringBean(Class<T> clazz){
return SpringBean.applicationContext.getBean(clazz);
}
@SuppressWarnings("unchecked")
public static <T> T getSpringBean(String beanName){
return (T) SpringBean.applicationContext.getBean(beanName);
}

public static void inject(Object object){
if(object == null)
return;
Class clazz = object.getClass();
while (clazz != Object.class) {
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
Autowired annotation = field.getAnnotation(Autowired.class);
if (annotation != null) {
Reflector.setFieldValue(object,field,SpringBean.getSpringBean(field.getType()));
}

Resource resource = field.getAnnotation(Resource.class);
if (resource != null) {
Reflector.setFieldValue(object,field,SpringBean.getSpringBean(field.getName()));
}
}
clazz = clazz.getSuperclass();
}
}

}
 

 补全Http请求代理接口

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@ProxyCustomizer(HttpClientProxyHandler.class)
public @interface HttpClient { }

import com.sinosoft.demo.componment.proxy.core.ProxyHandler;
import javafx.util.Builder;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.BufferingClientHttpRequestFactory;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.util.ClassUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.client.RestTemplate;

import java.lang.invoke.MethodHandles;
import java.lang.reflect.*;
import java.util.*;

import static org.springframework.objenesis.instantiator.util.ClassUtils.newInstance;

public class HttpClientProxyHandler implements ProxyHandler {

@Override
public Object execute(Class<?> proxyType, Object proxy ,Method proxyMethod, Object[] args) {

if(proxyMethod.isDefault()){ //不对default 的方法进行代理
try {
Constructor<MethodHandles.Lookup> constructor = MethodHandles.Lookup.class
.getDeclaredConstructor(Class.class, int.class);
constructor.setAccessible(true);

Class<?> declaringClass = proxyMethod.getDeclaringClass();
int allModes = MethodHandles.Lookup.PUBLIC | MethodHandles.Lookup.PRIVATE | MethodHandles.Lookup.PROTECTED | MethodHandles.Lookup.PACKAGE;

return constructor.newInstance(declaringClass, allModes)
.unreflectSpecial(proxyMethod, declaringClass)
.bindTo(proxy)
.invokeWithArguments(args);
}catch(Exception ex) {
throw new RuntimeException(ex);
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}

RequestMapping requestMapping = AnnotationUtils.getAnnotation(proxyMethod, RequestMapping.class);
String url = getRequestUrl(requestMapping);
Object invokeParam = handleRequestObject(proxyMethod,args);
Class<?> returnType = getReturnType(proxyMethod);
MultiValueMap<String, String> httpAttributes = getHttpAttributes(proxyType, proxyMethod);
RestTemplate template = createRestTemplate(httpAttributes,proxyType,proxyMethod);
HttpEntity entity = new HttpEntity(invokeParam,headers(httpAttributes));
ResponseEntity<?> responseEntity = template.exchange(url,getHttpMethod(requestMapping),entity,returnType,args);
return handleReturnObject(proxyMethod,invokeParam,responseEntity);
}

private String getRequestUrl(RequestMapping requestMapping){
Map<String, Object> annotationAttributes = AnnotationUtils.getAnnotationAttributes(requestMapping);
String[] path = (String[]) annotationAttributes.get("path");
if(path.length>0)
return path[0];
throw new UnsupportedOperationException("url not be null!");
}

private Class<?> getReturnType(Method proxyMethod){
Class<?> returnType = proxyMethod.getReturnType();
if(ClassUtils.isAssignable(CallBack.class,returnType)){
Type[] interfaces = returnType.getGenericInterfaces();
for (int i = 0; i < interfaces.length; i++) {
if (interfaces[i] instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) interfaces[i];
if (parameterizedType.getRawType() == CallBack.class) {
return (Class<?>) parameterizedType.getActualTypeArguments()[0];
}
}
}
}
if(ClassUtils.isAssignable(ResponseEntity.class,returnType)){
Type genericReturnType = proxyMethod.getGenericReturnType();
if (genericReturnType instanceof ParameterizedType) {
Type[] actualTypeArguments = ((ParameterizedType) genericReturnType).getActualTypeArguments();
return (Class<?>) actualTypeArguments[0];
}
}
return returnType;
}

private Object handleRequestObject(Method proxyMethod,Object[] args){
Object invokeParam;
if(args.length==1){
if(args[0] instanceof Builder)
invokeParam = ((Builder)args[0]).build();
else
invokeParam = args[0];
}else{
Map<String,Object> paramsMap = new LinkedHashMap<>();
Parameter[] parameters = proxyMethod.getParameters();
for(int i=0;i<parameters.length;i++){
RequestParam annotation = AnnotationUtils.findAnnotation(parameters[i], RequestParam.class);
if(annotation!=null)
paramsMap.put((String)AnnotationUtils.getDefaultValue(annotation),args[i]);
else
paramsMap.put(parameters[i].getName(),args[i]);
}
invokeParam = paramsMap;
}
return invokeParam;
}

private Object handleReturnObject(Method proxyMethod,Object invokeParam,ResponseEntity<?> responseEntity){
Object result = null;
if(ClassUtils.isAssignable(CallBack.class,proxyMethod.getReturnType())){
CallBack callBack = (CallBack) newInstance(proxyMethod.getReturnType());
callBack.call(responseEntity,invokeParam);
result = callBack;
}else if(ClassUtils.isAssignable(ResponseEntity.class,proxyMethod.getReturnType())){
result = responseEntity;
}else{
result = responseEntity.getBody();
}
return result;
}

private HttpMethod getHttpMethod(RequestMapping requestMapping){
RequestMethod[] requestMethod = requestMapping.method();
if(requestMethod.length>0)
return HttpMethod.valueOf(requestMethod[0].name());
return HttpMethod.POST;
}

private MultiValueMap<String,String> getHttpAttributes(Class<?> proxyType, Method proxyMethod){
MultiValueMap<String,String> attributes= new LinkedMultiValueMap<>();
HttpAttribute[] proxyTypeAttributeAnnotations = proxyType.getAnnotationsByType(HttpAttribute.class);
for(HttpAttribute attribute : proxyTypeAttributeAnnotations)
attributes.add(attribute.name(),attribute.value());
HttpAttribute[] proxyMethodAttributeAnnotations = proxyMethod.getAnnotationsByType(HttpAttribute.class);
for(HttpAttribute attribute : proxyMethodAttributeAnnotations)
attributes.add(attribute.name(),attribute.value());
return attributes;
}

private HttpHeaders headers(MultiValueMap<String,String> multiValueMap){
if(multiValueMap.containsKey(HttpAttribute.CONNECTION_TIMEOUT))
multiValueMap.remove(HttpAttribute.CONNECTION_TIMEOUT);
if(multiValueMap.containsKey(HttpAttribute.SOCKET_TIMEOUT))
multiValueMap.remove(HttpAttribute.SOCKET_TIMEOUT);
return new HttpHeaders(multiValueMap);
}

private RestTemplate createRestTemplate(MultiValueMap<String,String> attributes,Class<?> proxyType, Method proxyMethod){
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
if(attributes.containsKey(HttpAttribute.CONNECTION_TIMEOUT))
factory.setConnectTimeout(Integer.valueOf(attributes.getFirst(HttpAttribute.CONNECTION_TIMEOUT)));
if(attributes.containsKey(HttpAttribute.SOCKET_TIMEOUT))
factory.setReadTimeout(Integer.valueOf(attributes.getFirst(HttpAttribute.SOCKET_TIMEOUT)));
RestTemplate template = new RestTemplate();
template.setRequestFactory(new BufferingClientHttpRequestFactory(factory));

HashSet<Class<? extends ClientHttpRequestInterceptor>> interceptors = new LinkedHashSet<>();
HttpRequestInterceptors proxyTypeAnnotation = proxyType.getAnnotation(HttpRequestInterceptors.class);
HttpRequestInterceptors proxyMethodAnnotation = proxyMethod.getAnnotation(HttpRequestInterceptors.class);
if(proxyTypeAnnotation!=null) {
for (Class<? extends ClientHttpRequestInterceptor> interceptor : proxyTypeAnnotation.include())
interceptors.add(interceptor);
for (Class<? extends ClientHttpRequestInterceptor> interceptor : proxyTypeAnnotation.unInclude())
interceptors.remove(interceptor);
}
if(proxyMethodAnnotation!=null) {
for (Class<? extends ClientHttpRequestInterceptor> interceptor : proxyMethodAnnotation.include())
interceptors.add(interceptor);
for (Class<? extends ClientHttpRequestInterceptor> interceptor : proxyMethodAnnotation.unInclude())
interceptors.remove(interceptor);
}
List<ClientHttpRequestInterceptor> interceptorsList = template.getInterceptors();
for( Class<? extends ClientHttpRequestInterceptor> interceptor :interceptors)
interceptorsList.add(newInstance(interceptor));
return template;
}

}
 
public interface CallBack<T,R> {

    void call(ResponseEntity<T> entity, R requestParam);

}
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(HttpAttribute.HttpAttributes.class)
public @interface HttpAttribute { String name();
String value(); String SOCKET_TIMEOUT = "http.socket.timeout";
String CONNECTION_TIMEOUT = "http.connection.timeout"; @Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@interface HttpAttributes {
HttpAttribute[] value();
}
}
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface HttpRequestInterceptors { Class<? extends ClientHttpRequestInterceptor>[] include() default {}; Class<? extends ClientHttpRequestInterceptor>[] unInclude() default {}; }

 CallBack(补充)---"集合"类结果数据

  CallBack类型返回值作为对响应报文实体的进一步封装,Http接口为我们有时可能希望返回集合类型的数据结果(List<Pojo>),该代理类没有直接提供针对集合类型返回值的封装,

比如保险系统针对被保人进行风控校验(第三方接口来校验,自己解析结果,"集合">0表示有风控风险,进行提示或者其他后续处理)

这里通过实现callBack来变相实现对象的迭代(foreach)操作,

public class InsuredRiskWarnInfo implements CallBack<ResponseDto,RequestDto>,Iterable<InsuredRiskWarnInfo>{

    private List<InsuredRiskWarnInfo> data;

    @Getter
private String name; //姓名 @Getter
private String code; //错误码 @Getter
private String message;//错误信息 public void merge(InsuredRiskWarnInfo info){
if(this.data == null)
this.data = info.data;
else
this.data.addAll(info.data);
} @Override
public void call(ResponseEntity<ResponseDto> entity, RequestDto requestParam) {
data = new ArrayList<>();
// if(entity.getStatusCode() != HttpStatus.OK)
// throw new RuntimeException(entity.getStatusCode().getReasonPhrase());
/**这里是具体的对响应对象的数据封装逻辑,假设一些数据*/
Map<String,String> map = new HashMap<>();
map.put("1","错误1");
map.put("2","错误2");
map.put("3","错误3");
for(Map.Entry<String,String> item : map.entrySet()){
InsuredRiskWarnInfo info = new InsuredRiskWarnInfo();
info.name = "某某人"; //可以从请求对象requestParam中取
info.code = item.getKey();
info.message = item.getValue();
info.data = this.data;
this.data.add(info);
}
} @Override
public Iterator<InsuredRiskWarnInfo> iterator() {
return data.iterator();
}

调用示例 :

        /**模拟请求后*/
InsuredRiskWarnInfo insuredRiskWarnInfo = new InsuredRiskWarnInfo();
insuredRiskWarnInfo.call(null,null); String format = "%s风控失败%s:%s";
for (InsuredRiskWarnInfo info : insuredRiskWarnInfo){
System.err.println(String.format(format,info.getName(),info.getCode(),info.getMessage()));
} /**
某某人风控失败1:错误1
某某人风控失败2:错误2
某某人风控失败3:错误3
*/

 

SpringBoot 基于注解实现接口的代理Bean注入的更多相关文章

  1. 基于注解的接口限流+统一session认证

    代码心得: 一个基本的做法:对于用户身份认证做到拦截器里,针对HandlerMethod进行统一拦截认证,根据方法上的注解标识,判别是否需要身份验证,并将查找出来的User实体存入ThreadLoca ...

  2. SpringBoot | 问题 | 注解方式下无法发现Bean

    在排除注解的问题后,考虑扫描类的位置, [SpringBoot项目的Bean装配默认规则是根据Application类所在的包位置从上往下扫描! “Application类”是指SpringBoot项 ...

  3. springboot基于注解动态配置多数据源以及多数据源的事务统一

    参考文档:https://www.cnblogs.com/zhangboyu/p/7622412.html https://blog.csdn.net/qq_34322777/article/deta ...

  4. springboot 基于@Scheduled注解 实现定时任务

    前言 使用SpringBoot创建定时任务非常简单,目前主要有以下三种创建方式: 一.基于注解(@Scheduled) 二.基于接口(SchedulingConfigurer) 前者相信大家都很熟悉, ...

  5. Java开发学习(十三)----基于注解开发定义第三方bean及注解开发总结

    在前面的博客中定义bean的时候都是在自己开发的类上面写个注解就完成了,但如果是第三方的类,这些类都是在jar包中,我们没有办法在类上面添加注解,这个时候该怎么办? 遇到上述问题,我们就需要有一种更加 ...

  6. Spring学习笔记 - 第二章 - 注解开发、配置管理第三方Bean、注解管理第三方Bean、Spring 整合 MyBatis 和 Junit 案例

    Spring 学习笔记全系列传送门: Spring学习笔记 - 第一章 - IoC(控制反转).IoC容器.Bean的实例化与生命周期.DI(依赖注入) [本章]Spring学习笔记 - 第二章 - ...

  7. Spring 代理对象,cglib,jdk的问题思考,AOP 配置注解拦截 的一些问题.为什么不要注解在接口,以及抽象方法.

    可以被继承 首先注解在类上是可以被继承的 在注解上用@Inherited /** * Created by laizhenwei on 17:49 2017-10-14 */ @Target({Ele ...

  8. 8 -- 深入使用Spring -- 4...5 AOP代理:基于注解的“零配置”方式

    8.4.5 基于注解的“零配置”方式 AspectJ允许使用注解定义切面.切入点和增强处理,而Spring框架则可识别并根据这些注解来生成AOP代理.Spring只是使用了和AspectJ 5 一样的 ...

  9. Springboot + redis + 注解 + 拦截器来实现接口幂等性校验

    Springboot + redis + 注解 + 拦截器来实现接口幂等性校验   1. SpringBoot 整合篇 2. 手写一套迷你版HTTP服务器 3. 记住:永远不要在MySQL中使用UTF ...

  10. 基于注解的springboot+mybatis的多数据源组件的实现

    通常业务开发中,我们会使用到多个数据源,比如,部分数据存在mysql实例中,部分数据是在oracle数据库中,那这时候,项目基于springboot和mybatis,其实只需要配置两个数据源即可,只需 ...

随机推荐

  1. 《探索Python Requests中的代理应用与实践》

    requests加代理 高匿API代理 此处使用的小象代理:1元100个,便宜,可以购买尝试加下代理 存活期1到2分钟 import time import requests from lxml im ...

  2. ScreenToGif:一款开源免费且好用的录屏转Gif软件

    ScreenToGif介绍 GitHub上的介绍:此工具允许您记录屏幕的选定区域.来自网络摄像头的实时提要或来自草图板的实时绘图.之后,您可以编辑动画并将其保存为 gif.apng.视频.psd 或 ...

  3. 日常工作中需要避免的9个React坏习惯

    前言 React是前端开发领域中最受欢迎的JavaScript库之一,但有时候在编写React应用程序时,可能陷入一些不佳的习惯和错误做法.这些不佳的习惯可能导致性能下降.代码难以维护,以及其他问题. ...

  4. SwiftUI学习01-基本使用

    SwiftUI 是苹果推出的一种现代化方式,用于创建跨所有 Apple 平台的用户界面.它通过声明性语法简化了 UI 的开发流程.下面是一个基本的 SwiftUI 示例,展示了如何使用 SwiftUI ...

  5. MiniAuth 一个轻量 ASP.NET Core Identity Web 后台管理中间插件

    MiniAuth 一个轻量 ASP.NET Core Identity Web 后台管理中间插件 「一行代码」为「新.旧项目」 添加 Identity 系统跟用户.权限管理网页后台系统 开箱即用,避免 ...

  6. Odoo 美化登录界面

    实践环境 Odoo 14.0-20221212 (Community Edition) Odoo Web Login Screen 14.0 https://apps.odoo.com/apps/mo ...

  7. Python 正则表达式实战之Java日志解析

    需求描述 基于生产监控告警需求,需要对Java日志进行解析,提取相关信息,作为告警通知消息的内容部分. 提取思路 具体怎么提取,提取哪些内容呢?这里笔者分析了大量不同形态的生产日志,最后总结出4种形态 ...

  8. CSP2023-J/S 游记

    本人 初二 \(\texttt{HA}\) CSP2023 成绩: CSP-J 第一轮:\(86.5\) CSP-S 第一轮:\(41.5\) CSP-J 第二轮:\(100+100+100+0=30 ...

  9. springsecurity流程梳理与总结

    springsecurity的基本使用方法学习完了,还是有些懵圈,再回过头来梳理一下流程以及使用情况 1-4.传一个User实体,new一个UserPasswordAuthenticationToke ...

  10. 【转载】 5:0!AI战胜人类教官,AlphaDogfight大赛落幕

    原文:https://baijiahao.baidu.com/s?id=1675621109599102721&wfr=spider&for=pc ------------------ ...