相关概念:
BeanPostProcessor接口是Bean后置处理器。接口实现类有postProcessBeforeInitialization和afterProcessBeforeInitialization两个方法,分别在bean创建前和创建后出发调用(bean创建/初始化的顺序,需要进一步深化看一下)。因此bean创建之前或者之后对对应bean进行一些处理。这两个方法最终的返回值还是bean。可以是本身,也可以是加工过之后的(比如这里的代理之后的bean)
一般来说,spring工程中编写一个实现了BeanPostProcessor接口的实现类,这个类会在Spring容器启动过程中在实例化每个bean的时候都出发一次响应的postProcessBeforeInitialization和afterProcessBeforeInitialization方法(个人觉得这样会造成很大的启动效率降低)。 利用这一特性,有一个思路:利用beanpostProcessor,捕获部分bean的加载,并对这些bean生成动态代理,并将代理类放到spring容器中,后续调用原本这个bean类的方法时,就会直接进代理类的invoke方法,实现对某些类的某些方法(利用自定义注解)的增强等。这里有以下几个点:
1.MyPostProcessor implements BeanPostProcessor实现类需要将自身也放入spring容器托管,直接@Component或者用@Configure注解类都可以,涉及(@Component和@Configure配置方式的区别)
2.动态代理:动态代理类是用下面的形式创建,实现InvocationHandler接口,实现必须的invoke方法。 这样,被代理类的所有方法调用都会转为调用这个代理类的invole方法,invoke方法内部,method。invoke是执行原来的方法本身,其余代码就可以做增强用。
代理类: invoke方法中,可以利用解析method方法上的注解,来实现仅对有无注解标注的方法进行区别处理。值的注意的是,因为这里Proxy是JDK自身的动态代理,代理的是接口,因此生成的代理类是接口的实现子类,不是原实现类的子类,这里的method也是接口的方法,因此只有注解加在接口上才能生效(加在实现类上不生效 )
public class TrainOuterServiceProxy implements InvocationHandler {
private Object obj;
public Object getObj() {
return obj;
}
public void setObj(Object obj) {
this.obj = obj;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
UseProxy useAnnotation = method.getAnnotation(UseProxy.class);
if(useAnnotation != null){ //仅仅代理增强指定方法
System.out.println("进入需要增强的方法:" + method.getName());
return method.invoke(obj, args);
}
System.out.println("进入没有增强注解的方法:" + method.getName());
return method.invoke(obj, args);
}
}
创建代理类:利用Proxy.newProxyInstance()方法创建真正的代理类。在postProcessAfterInitialization方法中。 返回的代理类接口的实现子类,不是原实现类的子类。这里是对接口进行代理。
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
// MyProxy proxy = new MyProxy();
TrainOuterServiceProxy proxy = new TrainOuterServiceProxy();
if(beanName.contains("trainOuterService")){
proxy.setObj(bean);
Class[] iterClass = bean.getClass().getInterfaces();
if(iterClass.length > 0){
log.info("初始化时生成代理");
Object proxyO = Proxy.newProxyInstance(bean.getClass().getClassLoader(),iterClass,proxy);
return proxyO;
}else {
log.info(beanName + "没有接口不进行代理!");
return bean;
}
}else{
return bean;
}
}
以上述代码为例,此时在Spring容器中托管的原来的trainOuterSerivice实现类,已经由TrainOuterSServiceProxy代理实例取代 , 因此在其他类中通过DI注入的trainOuterService对象就已经是一个代理对象。调用此Service的所有方法,都会被代理类的invoke方法取代,从而实现对方法的部分增强。这里是直接在spring容器启动中利用BeanPostProcessor将容器中托管的bean替换为代理类,如果想在实际需要的时候,才生成对应的代理类。需要像下面这样:
//测试自定义的代理
TrainOuterServiceProxy trainProxy = new TrainOuterServiceProxy();
TrainOuterService trainimpl = new TrainOuterServiceImpl();
trainProxy.setObj(trainimpl);
Class[] iterClass = trainimpl.getClass().getInterfaces();
Object proxyO = Proxy.newProxyInstance(trainimpl.getClass().getClassLoader(),iterClass,trainProxy);
TrainOuterService trainproxy = (TrainOuterService)proxyO;
trainproxy.testYanfei("1234567");
trainproxy.testYanfei2("1234455");
其中,由于Proxy.newProxyInstance生成的代理类是Object类,所以在倒数第三行的Object转实际对象的时候,建议想上面代码中那样转成接口类型。
有一个技术细节点:通过@Configuration类和@Component注解 将bean装配在Spring容器中的区别,下面这篇文章有较详细的解释:https://blog.csdn.net/long476964/article/details/80626930。