• spring cglib实现嵌套方法拦截


    使用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

     

  • 相关阅读:
    Smart Client智能客户端技术
    SaaS介绍
    SaaS介绍
    开源软件介绍
    SaaS介绍
    SaaS介绍
    开源软件介绍
    开源软件介绍
    GPUCUDA安装
    Linux 下的段错误(Segmentation fault)调试方法
  • 原文地址:https://www.cnblogs.com/devilwind/p/8724483.html
Copyright © 2020-2023  润新知