目标
- 实现对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
,就是启用了代理