使用spring 的拦截器对方法进行拦截,不管是动态代理,还是cglib, 只能拦截到被代理对象的调用方法,对于被调用方法里再调用同一对象里的其他方法就无法拦截到,就是我们说的嵌套拦截,之前文章里提及过加载器改写实现拦截(美团cat方式) , 今天试验出另外一种方法
我们要在spring初始化对象后对其用cglib加强修改,重新注入到容器当中
刚开始想在容器初始化完毕后修改bean, 利用实现ApplicationListener接口
public class InstantiationTracingBeanPostProcessor implementsApplicationListener{@Overridepublic void onApplicationEvent(ContextRefreshedEvent event) {//cglib 改写}}
参考了另外一篇文章
https://www.cnblogs.com/007sx/p/5785914.html
在做web项目开发中,尤其是企业级应用开发的时候,往往会在工程启动的时候做许多的前置检查。比如检查是否使用了我们组禁止使用的Mysql的group_concat函数,如果使用了项目就不能启动,并指出哪个文件的xml文件使用了这个函数。而在Spring的web项目中,我们可以介入Spring的启动过程。我们希望在Spring容器将所有的Bean都初始化完成之后,做一些操作,这个时候我们就可以实现一个接口:复制代码package com.yk.test.executor.processorpublic class InstantiationTracingBeanPostProcessor implements ApplicationListener{@Overridepublic void onApplicationEvent(ContextRefreshedEvent event) {//需要执行的逻辑代码,当spring容器初始化完成后就会执行该方法。}}复制代码同时在Spring的配置文件中,添加注入: 但是这个时候,会存在一个问题,在web 项目中(spring mvc),系统会存在两个容器,一个是root application context ,另一个就是我们自己的 projectName-servlet context(作为root application context的子容器)。这种情况下,就会造成onApplicationEvent方法被执行两次。为了避免上面提到的问题,我们可以只在root application context初始化完成后调用逻辑代码,其他的容器的初始化完成,则不做任何处理,修改后代码如下:@Overridepublic void onApplicationEvent(ContextRefreshedEvent event) {if(event.getApplicationContext().getParent() == null){//root application context 没有parent,他就是老大.//需要执行的逻辑代码,当spring容器初始化完成后就会执行该方法。}} 其实更简单的方法是使用注解:`@PostConstruct`,只需要在需要启动的时候执行的方法上标注这个注解就搞定了。 例子: 复制代码 /** * 服务启动时就执行--添加超级管理员 */ @PostConstruct public void addDefaultAdmin() { try { User user = new User(); user.setCreateTime(new Date()); try { user.setPassword(Md5.md5Encode("admin")); } catch (Exception e) { e.printStackTrace(); } user.setUserName("admin"); user.setRole(FrameConstant.USER_SUPER_ADMIN); userDao.save(user); log.debug("初始化完毕!"); } catch (Exception e) { log.debug("初始化完毕!"); } }复制代码
但是我在改写完bean后,却没有办法把bean重新注入
再换方法,在每个bean初始化完成后,cglib对其修改重新注入,通过实现BeanPostProcessor 来实现
参考文章 https://blog.csdn.net/elim168/article/details/76146351
BeanPostProcessor是Spring中定义的一个接口,其与之前介绍的InitializingBean和DisposableBean接口类似,也是供Spring进行回调的。Spring将在初始化bean前后对BeanPostProcessor实现类进行回调,与InitializingBean和DisposableBean接口不同的是BeanPostProcessor接口将对所有的bean都起作用,即所有的bean初始化前后都会回调BeanPostProcessor实现类,而InitializingBean和DisposableBean接口是针对单个bean的,即只有在对应的bean实现了InitializingBean或DisposableBean接口才会对其进行回调。BeanPostProcessor接口的定义如下:public interface BeanPostProcessor { Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException; Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;}如你所见,BeanPostProcessor接口中定义了两个方法,其中方法postProcessBeforeInitialization()将在一个bean被完全初始化前进行回调,此时对应的bean已经实例化了,但是对应的属性注入等还没有进行,即在调用InitializingBean的afterPropertiesSet()方法或bean对应的init-method之前;而方法postProcessAfterInitialization()将在bean被完全初始化后进行回调,此时对应的依赖注入已经完成,即在调用InitializingBean的afterPropertiesSet()方法或对应init-method方法之后。两个方法的参数以及返回值对应的意义都是一样的,其中参数bean表示当前状态的bean,参数beanName表示当前bean的名称,而方法对应的返回值即表示需要放入到bean容器中的bean,所以用户如果有需要完全可以在这两个方法中对bean进行修改,即封装自己的bean进行返回。以下是Spring源码中对bean进行初始化的逻辑,从源码中我们可以看到是先通过applyBeanPostProcessorsBeforeInitialization()方法使用注册的BeanPostProcessor的postProcessBeforeInitialization()方法依次回调,然后是通过invokeInitMethods()方法依次调用当前bean对应的初始化方法,再通过applyBeanPostProcessorsAfterInitialization方法使用注册的BeanPostProcessor的postProcessorAfterInitialization()方法依次进行回调。 protected Object initializeBean(final String beanName, final Object bean, RootBeanDefinition mbd) { if (System.getSecurityManager() != null) { AccessController.doPrivileged(new PrivilegedAction
public class TraceBeanPostProcessor implements BeanPostProcessor { @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { System.out.println("#################post bean:"+bean); //对Service注解过的方法实现修改 Service service = (Service) bean.getClass().getAnnotation(Service.class); if(service!=null){ Class cls = bean.getClass(); System.out.println("post @@@@@@@bean"+cls); Object newbean = TraceCGLibUtil.createBean(cls); return newbean; } return bean; }}
PS: 如果Service注解过的类被其他拦截器加强处理过,这里无法无法通过
Service service = (Service) bean.getClass().getAnnotation(Service.class); 去获取class了,service==null