• Spring AOP (上)


    1、概念术语  

      在开始之前,需要理解Spring aop 的一些基本的概念术语(总结的个人理解,并非Spring官方定义):

      切面(aspect):用来切插业务方法的类。

      连接点(joinpoint):是切面类和业务类的连接点,其实就是封装了业务方法的一些基本属性,作为通知的参数来解析。

      通知(advice):在切面类中,声明对业务方法做额外处理的方法。

      切入点(pointcut):业务类中指定的方法,作为切面切入的点。其实就是指定某个方法作为切面切的地方。

      目标对象(target object):被代理对象。

      AOP代理(aop proxy):代理对象。

      通知:

      前置通知(before advice):在切入点之前执行。

      后置通知(after returning advice):在切入点执行完成后,执行通知。

      环绕通知(around advice):包围切入点,调用方法前后完成自定义行为。

      异常通知(after throwing advice):在切入点抛出异常后,执行通知。

    2、Spring AOP环境

      要在项目中使用Spring AOP 则需要在项目中导入除了spring jar包之外,还有aspectjweaver.jar,aopalliance.jar ,asm.jar 和cglib.jar 。

    好了,前提工作准备完成,Spring 提供了很多的实现AOP的方式,在学习过程中,循序渐进。进行Spring 接口方式,schema配置方式和注解的三种方式进行学习。好了废话不多说了,开始spring aop学习之旅:

    3、方式一:AOP接口

      利用Spring AOP接口实现AOP,主要是为了指定自定义通知来供spring AOP机制识别。主要接口:前置通知 MethodBeforeAdvice ,后置通知:AfterReturningAdvice,环绕通知:MethodInterceptor,异常通知:ThrowsAdvice 。见例子代码:

    a、业务接口:

    复制代码
    /**
     * 代理类接口,也是业务类接口<br>
     * 
     * 利用接口的方式,spring aop 将默认通过jdk 动态代理来实现代理类<br>
     * 不利用接口,则spring aop 将通过cglib 来实现代理类
     * 
     * @author yanbin
     * 
     */
    public interface IBaseBusiness {
    
        /**
         * 用作代理的切入点方法
         * 
         * @param obj
         * @return
         */
        public String delete(String obj);
    
        /**
         * 这方法不被切面切
         * 
         * @param obj
         * @return
         */
        public String add(String obj);
    
        /**
         * 这方法切不切呢?可以设置
         * 
         * @param obj
         * @return
         */
        public String modify(String obj);
    
    }
    复制代码

    b、业务类:

    复制代码
    /**
     * 业务类,也是目标对象
     * 
     * @author yanbin
     * 
     */
    public class BaseBusiness implements IBaseBusiness {
    
        /**
         * 切入点
         */
        public String delete(String obj) {
            System.out.println("==========调用切入点:" + obj + "说:你敢删除我!===========
    ");
            return obj + ":瞄~";
        }
    
        public String add(String obj) {
            System.out.println("================这个方法不能被切。。。============== 
    ");
            return obj + ":瞄~ 嘿嘿!";
        }
    
        public String modify(String obj) {
            System.out.println("=================这个也设置加入切吧====================
    ");
            return obj + ":瞄改瞄啊!";
        }
    
    }
    复制代码

    c、通知类:

    前置通知:

    复制代码
    /**
     * 前置通知。
     * 
     * @author yanbin
     * 
     */
    public class BaseBeforeAdvice implements MethodBeforeAdvice {
    
        /**
         * method : 切入的方法 <br>
         * args :切入方法的参数 <br>
         * target :目标对象
         */
        @Override
        public void before(Method method, Object[] args, Object target) throws Throwable {
            System.out.println("===========进入beforeAdvice()============ 
    ");
    
            System.out.print("准备在" + target + "对象上用");
            System.out.print(method + "方法进行对 '");
            System.out.print(args[0] + "'进行删除!
    
    ");
    
            System.out.println("要进入切入点方法了 
    ");
        }
    
    }
    复制代码

    后置通知:

    复制代码
    /**
     * 后置通知
     * 
     * @author yanbin
     * 
     */
    public class BaseAfterReturnAdvice implements AfterReturningAdvice {
    
        /**
         * returnValue :切入点执行完方法的返回值,但不能修改 <br>
         * method :切入点方法 <br>
         * args :切入点方法的参数数组 <br>
         * target :目标对象
         */
        @Override
        public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
            System.out.println("==========进入afterReturning()=========== 
    ");
            System.out.println("切入点方法执行完了 
    ");
    
            System.out.print(args[0] + "在");
            System.out.print(target + "对象上被");
            System.out.print(method + "方法删除了");
            System.out.print("只留下:" + returnValue + "
    
    ");
        }
    
    }
    复制代码

    环绕通知:

    复制代码
    /**
     * 环绕通知
     * 
     * @author yanbin
     * 
     */
    public class BaseAroundAdvice implements MethodInterceptor {
    
        /**
         * invocation :连接点
         */
        @Override
        public Object invoke(MethodInvocation invocation) throws Throwable {
            System.out.println("===========进入around环绕方法!=========== 
    ");
    
            // 调用目标方法之前执行的动作
            System.out.println("调用方法之前: 执行!
    ");
    
            // 调用方法的参数
            Object[] args = invocation.getArguments();
            // 调用的方法
            Method method = invocation.getMethod();
            // 获取目标对象
            Object target = invocation.getThis();
            // 执行完方法的返回值:调用proceed()方法,就会触发切入点方法执行
            Object returnValue = invocation.proceed();
    
            System.out.println("===========结束进入around环绕方法!=========== 
    ");
            
            System.out.println("输出:" + args[0] + ";" + method + ";" + target + ";" + returnValue + "
    ");
    
            System.out.println("调用方法结束:之后执行!
    ");
    
            return returnValue;
        }
    
    }
    复制代码

    异常通知:

    复制代码
    /**
     * 异常通知,接口没有包含任何方法。通知方法自定义
     * 
     * @author yanbin
     * 
     */
    public class BaseAfterThrowsAdvice implements ThrowsAdvice {
    
        /**
         * 通知方法,需要按照这种格式书写
         * 
         * @param method
         *            可选:切入的方法
         * @param args
         *            可选:切入的方法的参数
         * @param target
         *            可选:目标对象
         * @param throwable
         *            必填 : 异常子类,出现这个异常类的子类,则会进入这个通知。
         */
        public void afterThrowing(Method method, Object[] args, Object target, Throwable throwable) {
            System.out.println("删除出错啦");
        }
    
    }
    复制代码

    d、定义指定切点:

    复制代码
    /**
     * 定义一个切点,指定对应方法匹配。来供切面来针对方法进行处理<br>
     * 
     * 继承NameMatchMethodPointcut类,来用方法名匹配
     * 
     * @author yanbin
     * 
     */
    public class Pointcut extends NameMatchMethodPointcut {
    
        private static final long serialVersionUID = 3990456017285944475L;
    
        @SuppressWarnings("rawtypes")
        @Override
        public boolean matches(Method method, Class targetClass) {
            // 设置单个方法匹配
            this.setMappedName("delete");
            // 设置多个方法匹配
            String[] methods = { "delete", "modify" };
            
            //也可以用“ * ” 来做匹配符号
            // this.setMappedName("get*");
            
            this.setMappedNames(methods);
    
            return super.matches(method, targetClass);
        }
    
    }
    复制代码

    e、配置: 

    复制代码
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
        xmlns:context="http://www.springframework.org/schema/context"
        xmlns:aop="http://www.springframework.org/schema/aop"
        xsi:schemaLocation="     
              http://www.springframework.org/schema/beans     
              http://www.springframework.org/schema/beans/spring-beans-3.0.xsd     
              http://www.springframework.org/schema/context     
              http://www.springframework.org/schema/context/spring-context-3.0.xsd 
              http://www.springframework.org/schema/aop     
              http://www.springframework.org/schema/aop/spring-aop-3.0.xsd"
        default-autowire="byName">
    
        <!-- ==============================利用spring自己的aop配置================================ -->
        <!-- 声明一个业务类 -->
        <bean id="baseBusiness" class="aop.base.BaseBusiness" />
        
        <!-- 声明通知类 -->
        <bean id="baseBefore" class="aop.base.advice.BaseBeforeAdvice" />
        <bean id="baseAfterReturn" class="aop.base.advice.BaseAfterReturnAdvice" />
        <bean id="baseAfterThrows" class="aop.base.advice.BaseAfterThrowsAdvice" />
        <bean id="baseAround" class="aop.base.advice.BaseAroundAdvice" />
    
        <!-- 指定切点匹配类 -->
        <bean id="pointcut" class="aop.base.pointcut.Pointcut" />
    
        <!-- 包装通知,指定切点 -->
        <bean id="matchBeforeAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
            <property name="pointcut">
                <ref bean="pointcut" />
            </property>
            <property name="advice">
                <ref bean="baseBefore" />
            </property>
        </bean>
    
        <!-- 使用ProxyFactoryBean 产生代理对象 -->
        <bean id="businessProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
            <!-- 代理对象所实现的接口 ,如果有接口可以这样设置 -->
            <property name="proxyInterfaces">
                <value>aop.base.IBaseBusiness</value>
            </property>
    
            <!-- 设置目标对象 -->
            <property name="target">
                <ref local="baseBusiness" />
            </property>
            <!-- 代理对象所使用的拦截器 -->
            <property name="interceptorNames">
                <list>
                    <value>matchBeforeAdvisor</value>
                    <value>baseAfterReturn</value>
                    <value>baseAround</value>
                </list>
            </property>
        </bean>
    </beans>
    复制代码

    f、测试类:

    复制代码
    public class Debug {
    
    public static void main(String[] args) {
            ApplicationContext context = new ClassPathXmlApplicationContext("aop/schema_aop.xml");
            IBaseBusiness business = (IBaseBusiness ) context.getBean("businessProxy");
            business.delete("猫");
        }
    
    }
    复制代码

    g、测试结果:运行下测试类,清晰明了。由于结果呈现太长就不贴了。

      具体的代码实现可以从代码注释中很容易理解 接口方式的实现。结果也可想而知,前置方法会在切入点方法之前执行,后置会在切入点方法执行之后执行,环绕则会在切入点方法执行前执行,同时方法结束也会执行对应的部分。主要是调用proceed()方法来执行切入点方法。来作为环绕通知前后方法的分水岭。然后在实现的过程中,有几点却是可以细揣摩一下的。

      可以看出在xml 配置 businessProxy这个bean的时候,ProxyFactoryBean类中指定了,proxyInterfaces参数。这里我把他配置了 IBaseBusiness接口。因为在项目开发过程中,往往业务类都会有对应的接口,以方便利用IOC解耦。但Spring AOP却也能支持没有接口的代理。这就是为什么需要导入cglib.jar的包了。看过spring的源码,知道在目标切入对象如果有实现接 口,spring会默认走jdk动态代理来实现代理类。如果没有接口,则会通过cglib来实现代理类。

      这个业务类现在有 前置通知,后置通知,环绕三个通知同时作用,可能以及更多的通知进行作用。那么这些通知的执行顺序是怎么样的?就这个例子而言,同时实现了三个通知。在例 子xml中,则显示执行before通知,然后执行around的前处理,执行切点方法,再执行return处理。最后执行around的后处理。经过测 试,知道spring 处理顺序是按照xml配置顺序依次处理通知,以队列的方式存放前通知,以压栈的方式存放后通知。所以是前通知依次执行,后通知到切入点执行完之后,从栈里 在后进先出的形式把后通知执行。

      在实现过程中发现通知执行对应目标对象的整个类中的方法,如何精确到某个方法,则需要定义一个切点匹配的方式:spring提供了方法名匹配或正则方式来匹配。然后通过DefaultPointcutAdvisor来包装通知,指定切点。

    文章出处:http://www.cnblogs.com/yanbincn/archive/2012/08/13/2635413.html

  • 相关阅读:
    弄懂Java为何只有值传递
    反转链表进阶
    剑指Offer-16:合并两个有序链表
    剑指Offer-15:反转链表
    剑指Offer-14:输入一个链表,输出该链表中倒数第k个结点。
    剑指Offer-13:调整数组位置使奇数位于偶数前面
    Java实现二分查找
    LDAP
    关于Prometheus运维实践项目
    LDAP-openldap服务部署和测试(YUM安装)
  • 原文地址:https://www.cnblogs.com/vincent-blog/p/4386244.html
Copyright © 2020-2023  润新知