使用spring 的拦截器对方法进行拦截,不管是动态代理,还是cglib, 只能拦截到被代理对象的调用方法,对于被调用方法里再调用同一对象里的其他方法就无法拦截到,就是我们说的嵌套拦截,之前文章里提及过加载器改写实现拦截(美团cat方式) , 今天试验出另外一种方法

我们要在spring初始化对象后对其用cglib加强修改,重新注入到容器当中

刚开始想在容器初始化完毕后修改bean, 利用实现ApplicationListener接口

public class InstantiationTracingBeanPostProcessor implements
ApplicationListener<ContextRefreshedEvent> { @Override
public 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.processor
public class InstantiationTracingBeanPostProcessor implements ApplicationListener<ContextRefreshedEvent> {
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
//需要执行的逻辑代码,当spring容器初始化完成后就会执行该方法。
}
}
复制代码
同时在Spring的配置文件中,添加注入: <!-- 当Spring容器启动完成后执行下面的这个Bean -->
<bean class="com.yk.test.executor.processor.InstantiationTracingBeanPostProcessor"/>
但是这个时候,会存在一个问题,在web 项目中(spring mvc),系统会存在两个容器,一个是root application context ,另一个就是我们自己的 projectName-servlet context(作为root application context的子容器)。 这种情况下,就会造成onApplicationEvent方法被执行两次。为了避免上面提到的问题,我们可以只在root application context初始化完成后调用逻辑代码,其他的容器的初始化完成,则不做任何处理,修改后代码 如下: @Override
public 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<Object>() {
@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的定义方式是一样的。 <bean class="com.app.HelloBeanPostProcessor"/>
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()方法的返回值。 <bean class="com.app.HelloBeanPostProcessor" p:order="3"/>
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

 

spring cglib实现嵌套方法拦截的更多相关文章

  1. spring中的多线程aop方法拦截

    日常开发中,常用spring的aop机制来拦截方法,记点日志.执行结果.方法执行时间啥的,很是方便,比如下面这样:(以spring-boot项目为例) 一.先定义一个Aspect import org ...

  2. 基于 Annotation 拦截的 Spring AOP 权限验证方法

    基于 Annotation 拦截的 Spring AOP 权限验证方法 转自:http://www.ibm.com/developerworks/cn/java/j-lo-springaopfilte ...

  3. spring MethodInterceptor方法拦截

    引用别的的:https://blog.csdn.net/u010739551/article/details/47754731 最近项目里加上了用户权限,有些操作需要登录,有些操作不需要,之前做项目做 ...

  4. 使用方法拦截机制在不修改原逻辑基础上为 spring MVC 工程添加 Redis 缓存

    首先,相关文件:链接: https://pan.baidu.com/s/1H-D2M4RfXWnKzNLmsbqiQQ 密码: 5dzk 文件说明: redis-2.4.5-win32-win64.z ...

  5. 实战CGLib系列之proxy篇(一):方法拦截MethodInterceptor

    实战CGLib系列文章 本篇介绍通过MethodInterceptor和Enhancer实现一个动态代理. 一.首先说一下JDK中的动态代理: JDK中的动态代理是通过反射类Proxy以及Invoca ...

  6. Spring AOP深入理解之拦截器调用

    Spring AOP深入理解之拦截器调用 Spring AOP代理对象生成回想 上一篇博客中:深入理解Spring AOP之二代理对象生成介绍了Spring代理对象是怎样生成的,当中重点介绍了JDK动 ...

  7. Spring JPA 定义查询方法

    Spring JPA 定义查询方法 翻译:Defining Query Methods ​ 存储库代理有两种方式基于方法名派生特定域的查询方式: 直接从方法名派生查询 自定义查询方式 ​ 可用选项基于 ...

  8. [心得体会]Spring注解基本使用方法

    涨知识系列 Environment environment = context.getEnvironment(); 在Spring中所有被加载到spring中的配置文件都会出现在这个环境变量中, 其中 ...

  9. Spring AOP 源码分析 - 拦截器链的执行过程

    1.简介 本篇文章是 AOP 源码分析系列文章的最后一篇文章,在前面的两篇文章中,我分别介绍了 Spring AOP 是如何为目标 bean 筛选合适的通知器,以及如何创建代理对象的过程.现在我们的得 ...

随机推荐

  1. PS学习之餐饮行业修图

    素材 红烧肉 生菜 洋葱 目标效果 开始操作: 首先打开PS,将红烧肉素材放入  将红烧肉单另扣下来(用快随选择工具 按住Alt键是减选  抠图时  将画布放大 细细的扣) 然后创建一个和当前选区大小 ...

  2. Python的文件处理

    引子 1.问题:给你一个文件 "兼职白领学生空姐模特护士联系方式.txt" ,如何查看内容? 答: 安装文本编辑器软件 选中右键,利用文本编辑器软件打开 查看 or 写入 保存,关 ...

  3. hdu3488 Tour 拆点+二分图最佳匹配

    In the kingdom of Henryy, there are N (2 <= N <= 200) cities, with M (M <= 30000) one-way r ...

  4. js---通配符选择器

    原味转自:http://blog.sina.com.cn/s/blog_6e001be701017kaz.html 1.选择器 (1)通配符: $("input[id^='code']&qu ...

  5. 【NOI2014】【BZOJ3669】【UOJ#3】魔法森林

    我学会lct辣 原题: 为了得到书法大家的真传,小E同学下定决心去拜访住在魔法森林中的隐士.魔法森林可以被看成一个包含个N节点M条边的无向图,节点标号为 1…n1…n,边标号为1…m1…m.初始时小E ...

  6. centos7升级Python版本后,yum不能正常使用

    python升级方法,使用源码编译安装即可,prefix=/usr/local/bin/python3 执行yum list,提示/usr/bin/yum 报错 我是直接在Python2.7的基础上又 ...

  7. C++中的智能指针、轻量级指针、强弱指针学习笔记

    一.智能指针学习总结 1.一个非const引用无法指向一个临时变量,但是const引用是可以的! 2.C++中的delete和C中的free()类似,delete NULL不会报"doubl ...

  8. oracle-sql内置函数

    函数  oracle 自定义函数入门 主题:ORACLE函数大全 ############################### set operators UNION, UNION ALL, INT ...

  9. taro 开发注意点

    taro 开发注意点: 注意点 原因 如果要支持 React Native 端,必须采用 Flex 布局,并且样式选择器仅支持类选择器,且不支持 组合器 Taro RN 端是基于 Expo,因此不支持 ...

  10. py-day1-4 python基本数据类型2

    # replace 替换 text = 'xiaomafafafahaoyunlianlian' v = text.replace('fa','shun') x = text.replace('fa' ...