最近有需求是将某些类用代理来调用,屏蔽一些通信上的细节。直接写的话需要手动创建代理对象来用,比较麻烦,转成Spring来进行管理。利用自定义注解来规定需要bean容器管理的类,之后在BeanPostProcessor
中进行增强处理即可。把这部分抽离出来写了个demo记录下。
作用在类上的注解,Spring扫描到该注解类会进行bean创建并管理。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ServiceRegisterClass {
}
作用在成员上的注解,在Bean初始化过程中进行该对象的代理对象的注入。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface ServiceRegisterField {
}
作用在调用主方法的类上的注解,scanPackage用来标记扫描范围。这个注解最重要的是import了ServiceScannerRegistrar.class
。这个类实现了ImportBeanDefinitionRegistrar
来注册我们的自定义注解给bean容器。
// 自定义扫描注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(ServiceScannerRegistrar.class)
public @interface ServiceScan {
// 用于设置扫描的包
String[] scanPackage();
}
这个类是最关键的之一,通过实现ImportBeanDefinitionRegistrar
中的registerBeanDefinitions
方法,就可以进行增加我们的自定义注解到bean容器扫描的范围中。
public class ServiceScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {
private ResourceLoader resourceLoader;
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
首先对获取注解的元信息,找到注解中属性scanPackage
的值,未找到就默认扫描当前启动类所在包。
// 获取自定义扫描注解的信息
AnnotationAttributes annotationAttributes = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(ServiceScan.class.getName()));
String[] scanPackages = new String[0];
if (annotationAttributes != null) {
scanPackages = annotationAttributes.getStringArray("scanPackage");
}
if (scanPackages.length == 0) {
// 未设置就默认为自定义注解作用类所在包
scanPackages = new String[]{((StandardAnnotationMetadata) importingClassMetadata).getIntrospectedClass().getPackage().getName()};
}
除了Spring自带的bean注解(@Component等),增加自定义注解@ServiceRegisterClass
作为bean注解让bean容器进行解析。ClassPathBeanDefinitionScanner
构造方法的第二个参数为是否保留自带的bean注解。
// 增加自定义注解的扫描,同时保留spring预设bean注解的扫描(@Component等)
ClassPathBeanDefinitionScanner serviceScanner = new ClassPathBeanDefinitionScanner(registry,true);
serviceScanner.addIncludeFilter(new AnnotationTypeFilter(ServiceRegisterClass.class));
if (resourceLoader != null) {
serviceScanner.setResourceLoader(resourceLoader);
}
最后扫描即可。
// 开始扫描
int beanCount = serviceScanner.scan(scanPackages);
System.out.println("bean count:" + beanCount);
}
实现BeanPostProcessor
,在处理bean的时候寻找@ServiceRegisterField
注解的对象并注入代理对象。
// BeanPostProcessor对bean创建过程处理
@Component
public class ServiceBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
// 获取bean的field中的注解信息,注入有ServiceRegisterField注解的成员
System.out.println("bean:" + beanName + " created");
Field[] fields = bean.getClass().getDeclaredFields();
for (Field field:fields) {
ServiceRegisterField serviceRegisterField = field.getAnnotation(ServiceRegisterField.class);
// 存在则注入
if (serviceRegisterField != null) {
System.out.println("find service field");
Enhancer enhancer = new Enhancer();
enhancer.setClassLoader(field.getType().getClassLoader());
enhancer.setSuperclass(field.getType());
enhancer.setCallback(new CGLibInterceptor());
Object proxy = enhancer.create();
field.setAccessible(true);
try {
field.set(bean, proxy);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
return bean;
}
}
要注意的是BeanPostProcessor
需要被Spring bean管理才能生效,所以加了个@Component
。
因为没有接口所以用了CGLib来演示代理。
public class CGLibInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("CGLib proxying, do something in proxy");
return methodProxy.invokeSuper(o, objects);
}
}
Hello.class
@ServiceRegisterClass
public class Hello {
public String hello(){
return "hello world test class";
}
}
Hello2.class
public class Hello2 {
public String hello(){
return "hello world test field";
}
}
MainController
@Component
public class MainController {
@ServiceRegisterField
Hello2 hello2;
@Autowired
Hello hello;
public void printHello(){
System.out.println("output1:" + hello.hello());
System.out.println("output2:" + hello2.hello());
}
}
启动类 StartApplication.class
// 调用类
// 自定义扫描注解
@ServiceScan(scanPackage = {"com.huiluczp"})
public class StartApplication {
public static void main(String[] args) {
// 基于注解的容器
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(StartApplication.class);
MainController mainController = (MainController)applicationContext.getBean(MainController.class);
mainController.printHello();
}
}
输出结果:
总结:
把注入过程交给spring管理确实方便很多,通过代理屏蔽细节在调用对象的时候只需要打上对应注解就行,拓展很方便挺好的。简单demo记录,觉得有用就看看吧。