1.写在前面
虽然现在一些主流的框架基本都有现成的Springboot-Starter包供我们快速的去整合到我们的Springboot项目,然而,这样会使得我们过分的依赖这种方式,造成只会用,但是底层是怎么实现的却全然不知,一旦遇到问题就会显得手足无措。所以自己动手写一个组件可以让我们更能理解这些组件的基本套路,在遇到问题需要看源码的时候也能有一定的切入思路。
下面会编写一个基于Springboot的简单组件,通过自定义的@EnableXXX注解就可以使用,然后只需要定义一个接口,接口使用我们的自定义注解,我们可以自动为接口生成代理类,打印出接口中方法的执行参数和方法名。
2.编写一个@EnableXXX就可以引入使用的组件
2.1 先建一个空的Maven项目
然后分别引入springboot-start,pom文件如下
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.joe</groupId>
<artifactId>test-customize</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<!-- springboot-starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>2.5.0</version>
</dependency>
</dependencies>
</project>
2.2 编写我们的启动注解和想要扫描的注解
2.2.1 启动注解如下,类似于@EnableMybaties或者是@EnableFeign
package com.joe.customize.annotation;
import com.joe.customize.register.CustomizeRegister;
import org.springframework.context.annotation.Import;
import java.lang.annotation.*;
/**
* @author joe
* @date 2021/7/22 23:41
* @description
* @csdn joe#
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
// 引入注册bean的注册类
@Import(CustomizeRegister.class)
public @interface EnableCustomize {
/**
* 扫描的基础包
*/
String[] basePackages() default {};
}
注:上面的@Import(CustomizeRegister.class)是导入了我们自定义的类注册器,在下面会有说到
2.2.2 自定义扫描注解
自定义扫描注解类似于 @Mapper 或者是 @FeignClient
package com.joe.customize.annotation;
import java.lang.annotation.*;
/**
* @author joe
* @date 2021/7/22 23:39
* @description
* @csdn joe#
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CustomizeAnnotation {
}
2.3 编写自定义的工厂类
自定义工厂类里面我们会为我们扫描到的使用了我们@CustomizeAnnotation 的接口类生成我们需要的代理类
package com.joe.customize.factory;
import org.springframework.beans.factory.FactoryBean;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
/**
* @author joe
* @date 2021/7/23 0:49
* @description 自定义bean工厂,生产自己的对象
* @csdn joe#
*/
public class CustomizeFactoryBean implements FactoryBean<Object> {
private Class<?> type;
@Override
public Object getObject() {
// 这里使用jdk的动态代理生成代理类,代理类的逻辑我们可以自定义,
Object o = Proxy.newProxyInstance(type.getClassLoader(), new Class<?>[]{this.type}, (Object proxy, Method method, Object[] args) -> {
// 这里就可以自定义我们想要的逻辑
// 比如mybatis中就将我们定义的mapper接口转成执行的sql语句
// 又如OpenFeign发起请求等等
// 我这里只是简单的将方法执行的结果变成方法名加参数列表
return method.getName() + ":" + Arrays.toString(args);
});
return o;
}
@Override
public Class<?> getObjectType() {
return this.type;
}
public Class<?> getType() {
return type;
}
public void setType(Class<?> type) {
this.type = type;
}
}
2.4 重点,自定义我们的bean注册器
bean注册器会实现spirngboot提供的ImportBeanDefinitionRegistrar接口,在注册器里面我们可以生成自己的类,交给Springboot来为我们管理
package com.joe.customize.register;
import com.joe.customize.annotation.CustomizeAnnotation;
import com.joe.customize.annotation.EnableCustomize;
import com.joe.customize.factory.CustomizeFactoryBean;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* @author joe
* @date 2021/7/22 23:42
* @description
* @csdn joe#
*/
public class CustomizeRegister implements ImportBeanDefinitionRegistrar {
/**
* 扫描自定义注解,并注册成bean
* @param metadata
* @param registry
*/
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
/* 创建扫描器 */
// false,不包含默认的过滤器,下面自己添加自己的过滤器
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(registry, false){
/**
* 重写对扫描后的类进行过滤
* @param beanDefinition 扫描到的类定义
* @return
*/
@Override
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
// 类的元数据
AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
// 是独立的类并且不是注解
if (annotationMetadata.isIndependent()) {
if (!annotationMetadata.isAnnotation()){
// 满足要求的类
return true;
}
}
// 默认不满足要求
return false;
}
};
// 配置需要扫描的注解名称
AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(CustomizeAnnotation.class);
scanner.addIncludeFilter(annotationTypeFilter);
// 获取注解中的基础包配置
Map<String, Object> annotationAttributes = metadata.getAnnotationAttributes(EnableCustomize.class.getName());
String[] basePackages = (String[])annotationAttributes.get("basePackages");
// 扫描标有注解的接口
Set<BeanDefinition> allCandidateComponents = new HashSet<>();
if (basePackages.length > 0){
for (String basePackage : basePackages) {
allCandidateComponents.addAll(scanner.findCandidateComponents(basePackage));
}
}
// 对扫描到的接口生成代理对象
if (!CollectionUtils.isEmpty(allCandidateComponents)){
for (BeanDefinition candidateComponents : allCandidateComponents) {
// 先判断扫描到的接口是不是接口,有可能注解写在类上面了,也可以被扫描到
AnnotatedBeanDefinition annotatedBeanDefinition = (AnnotatedBeanDefinition) candidateComponents;
AnnotationMetadata annotationMetadata = annotatedBeanDefinition.getMetadata();
Assert.isTrue(annotationMetadata.isInterface(), "@CustomizeAnnotation注解只能用于接口上");
// 创建我们的自定义工厂实例
CustomizeFactoryBean customizeFactoryBean = new CustomizeFactoryBean();
// 设置类型
String className = annotationMetadata.getClassName();
Class clazz = ClassUtils.resolveClassName(className, null);
customizeFactoryBean.setType(clazz);
// 通过bean定义构造器来bean对象
BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder
// 这里调用了我们自定义的bean工厂来创建bean对象
.genericBeanDefinition(clazz, customizeFactoryBean::getObject);
// 设置自动注入模式,为按类型自动注入
beanDefinitionBuilder.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
// 设置是否懒加载
beanDefinitionBuilder.setLazyInit(true);
AbstractBeanDefinition beanDefinition = beanDefinitionBuilder.getBeanDefinition();
// bean的别名
String beanName = className.substring(className.lastIndexOf(".") + 1) + "Customize";
String[] beanNames = new String[]{beanName};
// 注册到Spring容器
BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
beanNames);
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}
}
}
}
完成。。
2.5 总的流程梳理
- 首先,通过在Spingboot的启动类上添加我们自定义的@EnableCustomize注解,而@EnableCustomize注解里面会通过@Import引入我们的bean注册类CustomizeRegister
- CustomizeRegister 里面会扫描标注有我们@CustomizeAnnotation 注解的接口,然后通过我们自定义的CustomizeFactoryBean来创建自定义的代理类
- 自定义代理类是通过jdk的动态代理来生成代理对象,而创建代理对象的HandlerMethod里面我们就可以自定逻辑,来实现如Mybaties或者Feign只需要编写接口不用实现类就可以使用的效果
3 测试
3.1 将我们编写的组件打成一个jar包
3.2 创建一个Springboot-starter-web工程
略
pom文件引入我们自定义的组件包
<dependency>
<groupId>com.joe</groupId>
<artifactId>test-customize</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
3.3 编写一个接口并使用我们的自定义注解,不需要实现类
package com.joe.customize.service;
import com.joe.customize.annotation.CustomizeAnnotation;
/**
* @author joe
* @date 2021/7/23 0:19
* @description
* @csdn joe#
*/
@CustomizeAnnotation
public interface CustomizeService {
public String test01(String arg1,String arg2);
}
3.4 编写一个控制器,使用自定义的接口
package com.joe.customize.conrtoller;
import com.joe.customize.service.CustomizeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author joe
* @date 2021/7/23 1:13
* @description
* @csdn joe#
*/
@RestController
@RequestMapping("/test")
public class TestController {
@Autowired
private CustomizeService customizeService;
@RequestMapping("/test01")
public String test01(){
return customizeService.test01("a","b");
}
}
3.5 启动类添加自定义的@EnableCustomize注解
package com.joe.customize;
import com.joe.customize.annotation.EnableCustomize;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@EnableCustomize(basePackages = "com.joe.customize")
@SpringBootApplication
public class CustomizeApplication {
public static void main(String[] args) {
SpringApplication.run(CustomizeApplication.class, args);
}
}