博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
spring cglib实现嵌套方法拦截
阅读量:4334 次
发布时间:2019-06-07

本文共 8544 字,大约阅读时间需要 28 分钟。

使用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() {                @Override                public Object run() {                    invokeAwareMethods(beanName, bean);                    return null;                }            }, getAccessControlContext());        }        else {            invokeAwareMethods(beanName, bean);        }        Object wrappedBean = bean;        if (mbd == null || !mbd.isSynthetic()) {            wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);        }        try {            invokeInitMethods(beanName, wrappedBean, mbd);        }        catch (Throwable ex) {            throw new BeanCreationException(                    (mbd != null ? mbd.getResourceDescription() : null),                    beanName, "Invocation of init method failed", ex);        }        if (mbd == null || !mbd.isSynthetic()) {            wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);        }        return wrappedBean;    }11.2 注册BeanPostProcessor的注册是非常简单的,我们只需要把它当做一个普通的bean定义到Spring的bean容器中,Spring将能够自动检测到它,并将它注册到当前的bean容器中。BeanPostProcessor是容器绑定的,即BeanPostProcessor只能对跟它属于同一个bean容器中的bean进行回调,即BeanPostProcessor不能对属于它父容器或子容器中的bean进行回调。在bean容器中定义了BeanPostProcessor之后,Spring将最先将BeanPostProcessor对应的bean进行实例化,如果我们制定BeanPostProcessor的lazy-initialization=”true”或default-lazy-initialization=”true”,Spring将对其进行忽略,即这些配置对BeanPostProcessor不起作用。这也很好理解,因为只有这样之后在实例化其它bean的时候这些BeanPostProcessor才能派上用场。鉴于这种机制,所以这里有一个问题需要注意,Spring在初始化bean的时候将优先初始化depends-on属性指定的bean,所以当我们的BeanPostProcessor通过depends-on指定了对其它bean的依赖时,其它bean是不会被BeanPostProcessor所回调的,当然这里也包括简介的depends-on对应的bean。此外,在BeanPostProcessor实例化后需要直接或间接的进行注入的bean也由于实例化时间提前不会被BeanPostProcessor回调。还有就是BeanPostProcessor之间不会进行回调,即BeanPostProcessorA不会在BeanPostProcessorB初始化时对其进行回调。BeanPostProcessor在Spring内部也是用的比较多的,尤其是AOP代理部分。包括用户需要自己实现BeanPostProcessor实现代理功能时也需要注意BeanPostProcessor直接或间接关联的bean是不会被回调的,即不会被代理成功的。11.3 示例接下来看一个简单的定制自己的BeanPostProcessor的示例,在示例中我们将仅仅简单的实现一个BeanPostProcessor,在postProcessBeforeInitialization()和postProcessAfterInitialization()方法中都直接返回对应的bean,然后在postProcessBeforeInitialization()方法中简单的打印一下对应的bean名称。public class HelloBeanPostProcessor implements BeanPostProcessor {    public Object postProcessBeforeInitialization(Object bean, String beanName)            throws BeansException {        System.out.println("beanName-----------" + beanName);        return bean;    }    public Object postProcessAfterInitialization(Object bean, String beanName)            throws BeansException {        return bean;    }}实现了BeanPostProcessor之后就可以将其定义到bean容器中,其定义方式跟普通bean的定义方式是一样的。
11.4 回调顺序在bean容器中我们可以同时定义多个BeanPostProcessor,这样在新实例化一个bean后将依次使用每个BeanPostProcessor回调一遍,当然,如果某一个BeanPostProcessor回调后的返回的bean为null,则不再继续往下回调,将直接返回null,这个时候bean容器中对应beanName对应的bean也是null。当在一个bean容器中同时定义有多个BeanPostProcessor时,默认将根据BeanPostProcessor在bean容器中定义的先后顺序对新实例化的bean进行回调。还有一种定义BeanPostProcessor回调顺序的方法是将我们自定义的BeanPostProcessor实现类同时实现Ordered接口,然后Spring将根据Ordered接口定义的getOrder()方法的返回值来决定BeanPostProcessor回调的先后顺序,getOrder()返回值越小的越先进行回调。此外,实现了Ordered接口的BeanPostProcessor总是比没有实现Ordered接口的BeanPostProcessor先进行回调,为了便于管理,推荐要么都实现Ordered接口,要么都不实现。以下是一个实现了Ordered接口,并把getOrder()方法的返回值作为一个参数进行配置的示例。public class HelloBeanPostProcessor implements BeanPostProcessor, Ordered { private int order; public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { System.out.println("beanName-----------" + beanName); return bean; } public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { return bean; } public void setOrder(int order) { this.order = order; } public int getOrder() { return order; }}之后就可以在配置的时候通过参数order来指定我们的getOrder()方法的返回值。

 

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

 

转载于:https://www.cnblogs.com/devilwind/p/8724483.html

你可能感兴趣的文章
Alpha通道
查看>>
[HNOI2002]彩票
查看>>
[HNOI2009]无归岛
查看>>
[SDOI2008]Sandy的卡片
查看>>
html元素li移动动态效果
查看>>
程序员的情书!
查看>>
【题解】 bzoj2748 [HAOI2012]音量调节 (动态规划)
查看>>
get改造成post请求
查看>>
BZOJ1718: [Usaco2006 Jan] Redundant Paths 分离的路径
查看>>
1284 2 3 5 7的倍数 分类: 51nod ...
查看>>
关于target=标签
查看>>
设计模式(二)工厂模式
查看>>
HDU 1426 Sudoku Killer【DFS 数独】
查看>>
二分图-匈牙利算法模板
查看>>
Linux下修改mysql root密码
查看>>
zoj 3329 概率dp
查看>>
Tomcat工作原理
查看>>
spring IOC篇二:xml的核心逻辑处理(doLoadBeanDefinitions(inputSource, encodedResource.getResource()))...
查看>>
JavaScript原理学习
查看>>
一个简单遮罩弹窗效果
查看>>