spring中 @EnableXXX 注解的实现

目标

  • 实现对beanName包含service的bean对象的每个方法, 都打印出其运行时间
  • beanName不包含service的不打印
  • 通过@EnableMethodCostTime注解来控制打印的开启与关闭

本文涉及知识

本文的实现@EnableXXX注解的方法可以看做是对多数spring中该类型注解实现的模拟
同时, 在bean对象初始化时, 对对象生成代理对象从而增强, 体会bean的生命周期
同时, 体会@import注解

实现

  • 首先创建maven项目, 导入依赖
	<dependencies>
        <!-- 导入Spring依赖 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.3.3</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.16</version>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
  • 为了方便,本文用到的所有的java类都在一个目录下
  • 创建bean对象, 这里三个bean对象, 分别是Service1, Service2, Controller1, 为了方便, 它们的方法都一样
package com.pmy.test1;

import org.springframework.stereotype.Component;

@Component
public class Service1 {
    
    
    public void m1(){
    
    
        System.out.println(this.getClass()+".m1()");
    }
}

@Component
public class Service2 {
    
    
    public void m1(){
    
    
        System.out.println(this.getClass()+".m1()");
    }
}


@Component
public class Controller1 {
    
    
    public void m1(){
    
    
        System.out.println(this.getClass()+".m1()");
    }
}
  • 通过cglib来创建代理类
package com.pmy.test1;

import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class CostTimeProxy implements MethodInterceptor {
    
    
    private Object target;

    public CostTimeProxy(Object target){
    
    
        this.target = target;
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
    
    
        long startTime = System.nanoTime();
        Object res = method.invoke(target, objects);
        long endTime = System.nanoTime();
        System.out.println(method+ " , 耗时: "+(endTime-startTime));
        return res;
    }

	//获取代理对象
    public static <T> T createProxy(T target){
    
    
        CostTimeProxy proxy = new CostTimeProxy(target);
        Enhancer enhancer = new Enhancer();
        enhancer.setCallback(proxy);
        enhancer.setSuperclass(target.getClass());
        return (T) enhancer.create();
    }
}
  • 定义相应的BeanPostProcessor, 它在spring容器初始化bean对象的过程中调用, 其中涉及到的方法是initialization方法, 在population之后(如果我没有记错的话)
package com.pmy.test1;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;

public class MethodCostTImeProxyBeanPostProcessor implements BeanPostProcessor {
    
    
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
    
    
        if(bean.getClass().getName().toLowerCase().contains("service")){
    
    
            return CostTimeProxy.createProxy(bean);
        }
        else return bean;
    }
}

  • 下一步就是将该BeanPostProcessor放入spring容器中, 这里的实现方式很多, 可以使用一个ImportSelector, 这样可以批量选择bean类, 或者配置类等, 写起来方便,( 也可以直接在后面我们定义注解时, @Import(MethodCostTImeProxyBeanPostProcessor.class)也可以, 不过这种写法, 如果BeanPostProcessor很多时, 不太美观)
public class MethodCostTimeImportSelector implements ImportSelector {
    
    
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
    
    
        return new String[]{
    
    MethodCostTImeProxyBeanPostProcessor.class.getName()};
    }
}
  • 定义@EnableMethodCostTime, 从而控制时间打印的开关
  • 这里是用到了@import, 将需要加载的bean, 配置类, selector给放进来
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
//@Import(MethodCostTImeProxyBeanPostProcessor.class)
@Import(MethodCostTimeImportSelector.class)
public @interface EnableMethodCostTime {
    
    
}
  • 最终来个总的配置文件
@ComponentScan
@EnableMethodCostTime
public class MainConfig {
    
    
}
  • 来测试一下
public class TestTest {
    
    
    @Test
    public void test1() {
    
    
        AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(MainConfig.class);
        Service1 s1 = ac.getBean(Service1.class);
        Service2 s2 = ac.getBean(Service2.class);
        Controller1 c1 = ac.getBean(Controller1.class);
        s1.m1();
        s2.m1();
        c1.m1();
    }
}
  • 结果如下
class com.pmy.test1.Service1.m1()
public void com.pmy.test1.Service1.m1() , 鑰楁椂: 61500
class com.pmy.test1.Service2.m1()
public void com.pmy.test1.Service2.m1() , 鑰楁椂: 30900
class com.pmy.test1.Controller1.m1()
  • 此时,我们注释掉@EnableXXX的注解, 结果如下
class com.pmy.test1.Service1.m1()
class com.pmy.test1.Service2.m1()
class com.pmy.test1.Controller1.m1()
  • 可以看到, 确实就是没有时间打印了

分析

  • 通过上面的例子可以看到, @EnableXXX的实现, 实际是通过该注解是否存在, 来控制对应功能的BeanPostProcessor是否进入spring容器(发挥作用), 这里的控制实际是来借助@import注解
  • 而相应功能的BeanPostProcessor则是来控制对原始Bean的增强, 也就是对原始Bean的代理
  • 启用了@EnableXXX,就是启用了代理

参考

路人甲微信公众号

猜你喜欢

转载自blog.csdn.net/qq_34687559/article/details/117227016