• spring---aop(9)---Spring AOP中引入增强


    写在前面

      Spring将introduction通知看作一种特殊类型的拦截通知。用Spring的行话来讲,对方法的增强叫做Wearing(织入),而对类的增强叫introduction(引入)。Introduction Advice(引入增强)就是对类的功能增强,它也是Spring AOP 提供的一种特殊增强。

    一个简单的例子(以CGLIB的代理实现)

      定义一个接口(这个例子演示了一个mixin。我们想要能够 将被通知对象类型转换为Lockable,不管它们的类型,并且调用lock和unlock方法。如果我们调用 lock()方法,我们希望所有setter方法抛出LockedException异常。 这样我们能添加一个方面使的对象不可变,而它们不需要知道这一点:这是一个很好的AOP例 子。

    public interface Lockable {
        void lock();
        void unlock();
        boolean locked();
    }

      首先,我们需要一个做大量转化的IntroductionInterceptor。 在这里,我们继承 org.springframework.aop.support.DelegatingIntroductionInterceptor 实用类。我们可以直接实现IntroductionInterceptor接口,但是大多数情况下 DelegatingIntroductionInterceptor是最合适的。

      这样,LockMixin继承DelegatingIntroductionInterceptor 并自己实现Lockable。父类自动选择支持导入的Lockable,所以我们不需要指定它。 用这种方法我们可以导入任意数量的接口。

    public class LockMixin extends DelegatingIntroductionInterceptor 
        implements Lockable {
        private boolean locked;
        public void lock() {
            this.locked = true;
        }
        public void unlock() {
            this.locked = false;
        }
        public boolean locked() {
            return this.locked;
        }
        public Object invoke(MethodInvocation invocation) throws Throwable {
            if (locked() && invocation.getMethod().getName().indexOf("set") == 0)
                throw new LockedException();
            return super.invoke(invocation);
        }
    }

      通常不要需要改写invoke()方法:实现 DelegatingIntroductionInterceptor就足够了,如果是导入的方法, DelegatingIntroductionInterceptor实现会调用委托方法, 否则继续沿着连接点处理。在现在的情况下,我们需要添加一个检查:在上锁 状态下不能调用setter方法。

      在spring中的配置如下

        <bean id="aServiceImpl" class="com.xxx.plus.aop.demo.AServiceImpl" />  
        <bean id="lockAdvice" class="com.xxx.plus.aop.demo.LockMixin" />
        <bean id="aServiceImplProxy" class="org.springframework.aop.framework.ProxyFactoryBean">  
            <property name="interfaces" value="com.xxx.plus.aop.demo.Lockable" />  
            <property name="target" ref="aServiceImpl" />  
            <property name="proxyTargetClass" value="true"></property>
            <property name="interceptorNames">  
                <list>  
                    <value>lockAdvice</value>  
                </list>  
            </property>  
        </bean>  

    客户的代码代用如下

    ApplicationContext context =new FileSystemXmlApplicationContext( "beans-config.xml");
            AService aServiceProxy =(AService) context.getBean("proxyFactoryBean");
            // 物件没有被锁定,可以呼叫 set 方法 some.setSome("justin"); System.out.println(some.getSome());
            try {
                // (重点一)
                ((LockAble) aServiceProxy).lock();
                // 无法呼叫 set 方法,丢出例外
                aServiceProxy.setSome("momor");
                // 由于会丢出例外,所以下面的这行程式无法被执行
                System.out.println(aServiceProxy.getSome());
            }
            catch(Throwable e) {
                e.printStackTrace();
            }
            some.setSome("momor");
         System.out.println(aServiceProxy.getSome());

      重点一:这里的lock()方法可以直接被aServiceProxy调用,这里将aServiceProxy由AService强转为LockAble 没有报错。原理是在生产代理的时候,代理对象动态的实现了LockAble 接口。

        public Object getProxy(ClassLoader classLoader) {
            ......
                Enhancer enhancer = createEnhancer();
                if (classLoader != null) {
                    enhancer.setClassLoader(classLoader);
                    if (classLoader instanceof SmartClassLoader &&
                            ((SmartClassLoader) classLoader).isClassReloadable(proxySuperClass)) {
                        enhancer.setUseCache(false);
                    }
                }
                enhancer.setSuperclass(proxySuperClass);
           //重点1.1(将advised 的接口也给了代理对象,获得完整的代理接口)
           enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised));
                enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
                enhancer.setStrategy(new UndeclaredThrowableStrategy(UndeclaredThrowableException.class));
    
                Callback[] callbacks = getCallbacks(rootClass);
                Class<?>[] types = new Class<?>[callbacks.length];
          .......
        }

        重点1.1(所以上面的强转是不会报错的)

        public static Class<?>[] completeProxiedInterfaces(AdvisedSupport advised) {
         //获取所有的代理接口 Class
    <?>[] specifiedInterfaces = advised.getProxiedInterfaces(); if (specifiedInterfaces.length == 0) { // No user-specified interfaces: check whether target class is an interface. Class<?> targetClass = advised.getTargetClass(); if (targetClass != null) { if (targetClass.isInterface()) { advised.setInterfaces(targetClass); } else if (Proxy.isProxyClass(targetClass)) { advised.setInterfaces(targetClass.getInterfaces()); } specifiedInterfaces = advised.getProxiedInterfaces(); } } boolean addSpringProxy = !advised.isInterfaceProxied(SpringProxy.class); boolean addAdvised = !advised.isOpaque() && !advised.isInterfaceProxied(Advised.class); int nonUserIfcCount = 0; if (addSpringProxy) { nonUserIfcCount++; } if (addAdvised) { nonUserIfcCount++; } Class<?>[] proxiedInterfaces = new Class<?>[specifiedInterfaces.length + nonUserIfcCount]; System.arraycopy(specifiedInterfaces, 0, proxiedInterfaces, 0, specifiedInterfaces.length); if (addSpringProxy) { proxiedInterfaces[specifiedInterfaces.length] = SpringProxy.class; } if (addAdvised) { proxiedInterfaces[proxiedInterfaces.length - 1] = Advised.class; } return proxiedInterfaces; }

      客户的的代码

         aService.barA();
        //h会发现这里的强制转换时成功的   Lockable ku
    = ((Lockable) aService);    ku.lock();

      输出结果:

    AServiceImpl.barA()
    is lock

    引入增强(JDK 代理实现)

    接口与实现类,引入增强类

    public interface ISome {
        public void doSome();
    }
    public class Some implements ISome {
        public void doSome() {
            System.out.println("原来物件的职责。。。");
        }
    }
    public class OtherIntroduction extends DelegatingIntroductionInterceptor implements IOther {
        @Override
        public Object invoke(MethodInvocation methodInvocation) throws Throwable {
            return super.invoke(methodInvocation);
        }
        public void doOther() {
            System.out.println("增加的职责。。。");
        }
    }

       配置文件

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE beans PUBLIC "-//SPRING/DTD BEAN/EN" "http://www.springframework.org/dtd/spring-beans.dtd">
    <beans>
        <bean id="some" class="com.xxx.Some"/>
        <bean id="otherIntroduction" class="com.xxx.OtherIntroduction"/>
        <bean id="otherAdvisor" class="org.springframework.aop.support.DefaultIntroductionAdvisor">
            <constructor-arg index="0">
                <ref bean="otherIntroduction"/>
            </constructor-arg>
            <constructor-arg index="1">
                <value>onlyfun.caterpillar.IOther</value>
            </constructor-arg>
        </bean>
        <bean id="proxyFactoryBean" class="org.springframework.aop.framework.ProxyFactoryBean">
            <property name="proxyInterfaces">
                <value>com.xxx.ISome</value>
            </property>
            <property name="target">
                <ref bean="some"/>
            </property>
            <property name="interceptorNames">
                <list>
                    <value>otherAdvisor</value>
                </list>
            </property>
        </bean>
    </beans>

     测试代码

        public static void main(String[] args) {
            ApplicationContext context = new FileSystemXmlApplicationContext("classpath:beans-config.xml");
            ISome some = (ISome) context.getBean("proxyFactoryBean");
            some.doSome();
            // 看来好像 some 物件动态增加了职责
            ((IOther) some).doOther();
        }

    运行结果

    原来物件的职责。。。
    增加的职责。。。

    分析:JDK 在代理接口的时候动态的代理了增强类实现的接口

        @Override
        public Object getProxy(ClassLoader classLoader) {
          //重点1 这里获取的所有完整的代理接口
            Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised);
            findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
            return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
        }    

     这里其实和CGLIB 代理是一样的。获取所有的需要代理的接口

        public static Class<?>[] completeProxiedInterfaces(AdvisedSupport advised) {
            Class<?>[] specifiedInterfaces = advised.getProxiedInterfaces();
            if (specifiedInterfaces.length == 0) {
                // No user-specified interfaces: check whether target class is an interface.
                Class<?> targetClass = advised.getTargetClass();
                if (targetClass != null) {
                    if (targetClass.isInterface()) {
                        advised.setInterfaces(targetClass);
                    }
                    else if (Proxy.isProxyClass(targetClass)) {
                        advised.setInterfaces(targetClass.getInterfaces());
                    }
                    specifiedInterfaces = advised.getProxiedInterfaces();
                }
            }
            boolean addSpringProxy = !advised.isInterfaceProxied(SpringProxy.class);
            boolean addAdvised = !advised.isOpaque() && !advised.isInterfaceProxied(Advised.class);
            int nonUserIfcCount = 0;
            if (addSpringProxy) {
                nonUserIfcCount++;
            }
            if (addAdvised) {
                nonUserIfcCount++;
            }
            Class<?>[] proxiedInterfaces = new Class<?>[specifiedInterfaces.length + nonUserIfcCount];
            System.arraycopy(specifiedInterfaces, 0, proxiedInterfaces, 0, specifiedInterfaces.length);
            if (addSpringProxy) {
                proxiedInterfaces[specifiedInterfaces.length] = SpringProxy.class;
            }
            if (addAdvised) {
                proxiedInterfaces[proxiedInterfaces.length - 1] = Advised.class;
            }
            return proxiedInterfaces;
        }

     然后动态的实现接口,这样就可以实现强转了

  • 相关阅读:
    How to Setup a Private Proxy Server on EC2 in Under 10 Minutes
    How to Create a Cron Job (Scheduled Task) for Your Website or Blog
    NPM, BOWER, GIT, AND BASH PROXY CONFIGURATIONS
    Android: 通过Runtime.getRuntime().exec调用底层Linux下的程序或脚本
    Proguard breaking audio file in assets or raw
    SOCKSify Ruby
    UNIX / Linux: 2 Ways to Add Swap Space Using dd, mkswap and swapon
    窗体之间传值的方法
    扩展方法,
    反射 type 的基本用法,动态加载插件
  • 原文地址:https://www.cnblogs.com/chihirotan/p/7365890.html
Copyright © 2020-2023  润新知