期望目标
- 提供一个可以扫描指定包的注解
- 该包下,全部为接口类型
- 在spring boot环境中,可以正常注入扫描包内的全部接口
- 扫描包内的接口,使用代理模式实现,且,方法执行时,执行自定义代码段
思路解析
- 自定义一个注解
- 该注解需要具备一个数组参数,用于存储扫描包的路径
- 已知包内均为接口类型,也就是说,没有实现类,ioc注入必然报错,需要动态创建代理类
- 代理模式两种方案,jdk需要预先实现相关接口,不太方便,cglib可能会更方便一些,所以使用cglib去实现相关功能
- 该代理实现需要被ioc管理
代码实现
pom.xml
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.4</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo</name>
<description>demo</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
设计CGLIB代理拦截器,进行方法拦截
package com.example.demo.bean;
import org.springframework.aop.support.AopUtils;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
* cglib 代理模式核心方法拦截器
* @author chunyang.leng
* @date 2022-01-21 7:25 PM
*/
public class MyProxyInterceptor implements MethodInterceptor {
/**
* 执行方法拦截器
* @param obj 被代理的目标对象实例对象,该对象为cglib代理实现,为自定义实现类
* @param method 被代理接口正在执行的方法
* @param args 被代理接口正在执行方法的参数
* @param proxy 这是啥,我也不知道。。。。。。
* @return 方法执行结果,如果方法无返回值,可以返回null
* @throws Throwable
*/
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
// 获取被代理目标的真实 class
Class<?> targetClass = obj.getClass();
boolean aopProxy = AopUtils.isAopProxy(obj);
if (aopProxy) {
targetClass = AopUtils.getTargetClass(obj);
}
String name = targetClass.getName();
// 参考cglib代理实现命名规范
String[] split = name.split("\\$\\$");
System.out.println("class name =====>" + split[0]);
System.out.println("method name =====>" + method.getName());
return null;
}
}
设计一个代理实现类 MyProxyBean
package com.example.demo.bean;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.cglib.proxy.Enhancer;
/**
* @author chunyang.leng
* @date 2022-01-21 3:46 PM
*/
public class MyProxyBean<T> implements FactoryBean<T> {
/**
* 被代理的接口
*/
private Class<T> myInterfaceClass;
/**
* 构造函数
* @param myInterfaceClass 被代理的接口类型,该参数,由注解解析器自动赋值
*/
public MyProxyBean(Class<T> myInterfaceClass) {
this.myInterfaceClass = myInterfaceClass;
}
/**
* 返回该bean是否为单例类型
* @return
*/
@Override
public boolean isSingleton() {
return FactoryBean.super.isSingleton();
}
/**
* 当 ioc 容器提取对象时,调用此方法获取一个代理对象
* @return
* @throws Exception
*/
@Override
public T getObject() throws Exception {
//用于创建代理对象的增强器,可以对目标对象进行扩展
Enhancer enhancer = new Enhancer();
//将目标对象设置为父类
enhancer.setSuperclass(myInterfaceClass);
//设置目标拦截器
enhancer.setCallback(new MyProxyInterceptor());
// 创建代理对象
return (T)enhancer.create();
}
/**
* 当 ioc 容器获取类型时,从此方法获取类型
* @return
*/
@Override
public Class<T> getObjectType() {
return myInterfaceClass;
}
}
自定义路径扫描器 MyProxyScan
package com.example.demo.bean;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
import org.springframework.core.type.classreading.MetadataReader;
import java.io.IOException;
import java.util.Set;
/**
* 自定义路径扫描器
* @author chunyang.leng
* @date 2022-01-21 4:23 PM
*/
public class MyProxyScan extends ClassPathBeanDefinitionScanner {
/**
* 创建一个自定义的路径扫描器
* @param registry 需要传入 BeanDefinitionRegistry,该对象默认实现为 DefaultListableBeanFactory
*/
public MyProxyScan(BeanDefinitionRegistry registry) {
super(registry);
}
@Override
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
return super.doScan(basePackages);
}
/**
* 修改判断实现逻辑,仅判断该类型是否是接口类型
* @param metadataReader 元数据
* @return true 是候选组件
* @throws IOException
*/
@Override
protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
return metadataReader.getClassMetadata().isInterface();
}
/**
* 修改判断实现逻辑,仅判断该类型是否是接口类型
* @param beanDefinition 元数据
* @return true 是候选组件
* @throws IOException
*/
@Override
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
return beanDefinition.getMetadata().isInterface();
}
}
自定义注解解析器 MyProxyBeanImportSelector
package com.example.demo.bean;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.util.CollectionUtils;
import java.util.Map;
import java.util.Set;
/**
* MyProxy 注解解析器,用于实现自定义扫描和bean注册
*
* @author chunyang.leng
* @date 2022-01-21 3:47 PM
*/
@Configuration
public class MyProxyBeanImportSelector implements ImportSelector, BeanFactoryAware {
/**
* 暂存的bean工厂对象,用于注册和扫描bean
*/
private BeanFactory beanFactory;
/**
* 通过实现 BeanFactoryAware 接口,获取 BeanFactory 具体实现,用于bean扫描和注册
*
* @param beanFactory BeanFactory 的具体实现是 DefaultListableBeanFactory
* @throws BeansException
*/
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
/**
* 实现具体的扫描流程和注册流程
*
* @param importingClassMetadata 注解元数据
* @return 故意返回0,使用其它方式注册代理bean
*/
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
// 转换为 DefaultListableBeanFactory 便于操作
DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) beanFactory;
// 获取自定义注解的具体数据信息
Map<String, Object> annotationAttributes = importingClassMetadata.getAnnotationAttributes(MyProxy.class.getName());
if (!CollectionUtils.isEmpty(annotationAttributes)) {
// 获取配置的扫描包路径
String[] scanPackages = (String[]) annotationAttributes.get("scanPackages");
// 自定义扫描器,获取 BeanDefinitionHolder
MyProxyScan myProxyScan = new MyProxyScan(defaultListableBeanFactory);
Set<BeanDefinitionHolder> beanDefinitionHolders = myProxyScan.doScan(scanPackages);
// 循环处理每个被扫描到的bean对象,创建代理实现类
for (BeanDefinitionHolder beanDefinitionHolder : beanDefinitionHolders) {
// 转换为抽象实现,便于操作具体对象
AbstractBeanDefinition beanDefinition = (AbstractBeanDefinition) beanDefinitionHolder.getBeanDefinition();
try {
// 获取当前被扫描到到接口到具体 class name
String beanClassName = beanDefinition.getBeanClassName();
// 装载 class
Class<?> targetInterface = Class.forName(beanClassName);
// 为该自定义的代理实现类,提供构造方法所需要的原始接口类型参数
beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(targetInterface);
// 修改被扫描接口到具体实现类型,替换为自定义到代理实现类
beanDefinition.setBeanClassName(MyProxyBean.class.getName());
String beanName = beanDefinitionHolder.getBeanName();
System.out.println("已修改===>" + beanName);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
// 故意返回空
return new String[0];
}
}
自定义注解 MyProxy
package com.example.demo.bean;
import org.springframework.context.annotation.Import;
import java.lang.annotation.*;
/**
* @author chunyang.leng
* @date 2022-01-21 3:55 PM
*/
@Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(MyProxyBeanImportSelector.class)
public @interface MyProxy {
/**
* scan package
* @return
*/
String[] value();
}
在启动器添加自定义注解
package com.example.demo;
import com.example.demo.bean.MyProxy;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@MyProxy(scanPackages = "com.example.demo.post")
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
System.out.println("-------------------------------start done-----------------------------------------");
}
}