期望目标
- 提供一个可以扫描指定包的注解
- 该包下,全部为接口类型
- 在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/>
</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;
public class MyProxyInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
Class<?> targetClass = obj.getClass();
boolean aopProxy = AopUtils.isAopProxy(obj);
if (aopProxy) {
targetClass = AopUtils.getTargetClass(obj);
}
String name = targetClass.getName();
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;
public class MyProxyBean<T> implements FactoryBean<T> {
private Class<T> myInterfaceClass;
public MyProxyBean(Class<T> myInterfaceClass) {
this.myInterfaceClass = myInterfaceClass;
}
@Override
public boolean isSingleton() {
return FactoryBean.super.isSingleton();
}
@Override
public T getObject() throws Exception {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(myInterfaceClass);
enhancer.setCallback(new MyProxyInterceptor());
return (T)enhancer.create();
}
@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;
public class MyProxyScan extends ClassPathBeanDefinitionScanner {
public MyProxyScan(BeanDefinitionRegistry registry) {
super(registry);
}
@Override
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
return super.doScan(basePackages);
}
@Override
protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
return metadataReader.getClassMetadata().isInterface();
}
@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;
@Configuration
public class MyProxyBeanImportSelector implements ImportSelector, BeanFactoryAware {
private BeanFactory beanFactory;
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) beanFactory;
Map<String, Object> annotationAttributes = importingClassMetadata.getAnnotationAttributes(MyProxy.class.getName());
if (!CollectionUtils.isEmpty(annotationAttributes)) {
String[] scanPackages = (String[]) annotationAttributes.get("scanPackages");
MyProxyScan myProxyScan = new MyProxyScan(defaultListableBeanFactory);
Set<BeanDefinitionHolder> beanDefinitionHolders = myProxyScan.doScan(scanPackages);
for (BeanDefinitionHolder beanDefinitionHolder : beanDefinitionHolders) {
AbstractBeanDefinition beanDefinition = (AbstractBeanDefinition) beanDefinitionHolder.getBeanDefinition();
try {
String beanClassName = beanDefinition.getBeanClassName();
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.*;
@Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(MyProxyBeanImportSelector.class)
public @interface MyProxy {
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-----------------------------------------");
}
}