Spring学习笔记-5(AOP)

文章目录

  • 1.概述
  • 2.基本概念
  • 3.Spring中实现AOP
    • 3.1.Schema-base方式
      • 3.1.1.编写切点类
      • 3.1.2.创建通知
      • 3.1.3.配置applicationContext.xml文件
      • 3.1.4.测试
    • 3.2.AspectJ方式
      • 3.2.1.编写切点类
      • 3.2.2.创建通知
      • 3.2.3.配置applicationContext.xml文件
      • 3.2.4.测试

1.概述

  • AOP(aspect oriented programming,面向切面编程)通过预编译方式和运行期间通过动态代理(利用java的反射机制或字节码技术)在不修改源代码的情况下给程序动态添加功能的一种技术。
  • 如下图所示,取款和显示余额流程都需要进行用户验证和记录日志。利用AOP,可以把把这些重复性比较高的代码提取出来,在需要的流程中进行动态添加,提高了程序的可扩展性。
    Spring学习笔记-5(AOP)_第1张图片
  • 其本质就是一系列纵向的流程中,把相同的子流程提取出来成一个横向的面,然后切入到需要添加这个子流程的纵向流程中。

2.基本概念

Spring学习笔记-5(AOP)_第2张图片

  • 切点:原有程序的功能,如上述的取款、查询、转账等。在Spring框架中,切点需要是一个特定的方法。
  • 前置通知:添加到切点之前执行的功能,如上述的验证用户。
  • 后置通知:添加到切点之后执行的功能,如上述的记录日志。
  • 异常通知:切点所在执行过程中国出现异常执行的功能。
  • 环绕通知:将前置通知和后置通知整合到同一个通知中。
  • 织入:将通知添加到切点中的过程。

3.Spring中实现AOP

spring中提供了两种方法实现AOP,Schema-base和AspectJ。

3.1.Schema-base方式

该方法要求每个通知继承相应的类或实现相应的接口。然后配置applicationContext.xml中的aop:config标签。

3.1.1.编写切点类

这里在同一个包下定义两个类,包含了多种方法,用于配置不同的切点。

public class Demo {

    public void function1() {
        System.out.println("pointcut-Demo.function1");
    }

    public void function2() throws Exception{
        int x = 5 / 0;      //用于抛出异常
        System.out.println("pointcut-Demo.function2");
    }

    public void function3() {
        System.out.println("pointcut-Demo.function3");
    }
}
public class Demo2 {

    public boolean function1(String message, int num) {
        System.out.println("pointcut-Demo2.function1");
        if (num > 10) {
            return true;
        }
        return false;
    }
}

3.1.2.创建通知

  • 前置通知
public class MyBeforeAdvice implements MethodBeforeAdvice {
    /**
     * @param method    切点方法对象
     * @param objects   切点方法参数列表
     * @param o         切点所在类的对象
     */
    @Override
    public void before(Method method, Object[] objects, Object o) {
        System.out.println("前置通知");
    }
}
  • 后置通知
public class MyAfterAdvice implements AfterReturningAdvice {
    /**
     * @param o         切点方法返回值
     * @param method    切点方法对象
     * @param objects   切点方法参数列表
     * @param o1        切点所在类的对象
     */
    @Override
    public void afterReturning(Object o, Method method, Object[] objects, Object o1) {
        System.out.println("后置通知");
    }
}
  • 异常通知
    虽然需要实现ThrowsAdvice接口,但是afterThrowing并非为必须重写的方法。
public class MyThrowingAdvice implements ThrowsAdvice {

    public void afterThrowing(Exception ex) throws Throwable {
         System.out.println("执行异常通知:异常-》" + ex.getMessage() + " schema-base方式");
    }
}
  • 环绕通知
public class MyAroundAdvice implements MethodInterceptor {

    @Override
    public Object invoke(MethodInvocation methodInvocation) throws Throwable {
        System.out.println("环绕-前置通知");
        Object result = methodInvocation.proceed();     //执行切点中的内容
        System.out.println("环绕-后置通知");
        return result;
    }
}

3.1.3.配置applicationContext.xml文件

  • 一个beans标签中可以定义多个aop:config标签来配置多组切点与通知。
  • schema-base方式直接在aop:config标签中配置切点与通知。
  • aop:pointcut中的expression属性用于指定一个或多个方法作为切点。*为通配符,用途为:1.通配方法的返回值(最前面的星号)、2.通配多个包路径(可以同时指定多个方法作为切点)。…为通配符,用于通配切点方法的形式参数。
  • aop:advice中的advice-ref属性用于引用相应的通知实例。
  • aop:advice中的pointcut-ref属性用于引用相应的切点。
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">
    
    <bean id="demo" class="com.bear.sxt.pointcut.Demo"/>
    <bean id="demo2" class="com.bear.sxt.pointcut.Demo2"/>
    
    <bean id="myBeforeAdvice" class="com.bear.sxt.advice.MyBeforeAdvice"/>
    <bean id="myAfterAdvice" class="com.bear.sxt.advice.MyAfterAdvice"/>
    <bean id="myThrows" class="com.bear.sxt.advice.MyThrowingAdvice"/>
    <bean id="myAroundAdvice" class="com.bear.sxt.advice.MyAroundAdvice"/>
    
    
    <aop:config>
        
        
        

        
        <aop:pointcut id="pointcut" expression="execution(* com.bear.sxt.pointcut.*.function1(..))"/>
        
        <aop:advisor advice-ref="myBeforeAdvice" pointcut-ref="pointcut"/>
        <aop:advisor advice-ref="myAfterAdvice" pointcut-ref="pointcut"/>
    aop:config>
    <aop:config>
        <aop:pointcut id="pointcutThrows" expression="execution(* com.bear.sxt.pointcut.Demo.function2())"/>
        <aop:advisor advice-ref="myThrows" pointcut-ref="pointcutThrows"/>
    aop:config>
    <aop:config>
        <aop:pointcut id="pointcutAround" expression="execution(* com.bear.sxt.pointcut.Demo.function3())"/>
        <aop:advisor advice-ref="myAroundAdvice" pointcut-ref="pointcutAround"/>
    aop:config>
beans>    

3.1.4.测试

  • 测试代码
public class Test {

    public static void main(String[] args) {

        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
        Demo demo = ac.getBean("demo", Demo.class);
        Demo2 demo2 = ac.getBean("demo2", Demo2.class);
        /*使用schema-base方式*/
        System.out.println("schema-base方式");
        System.out.println("----------》不带参数的切点");
        demo.function1();
        System.out.println("----------》带参数的切点");
        demo2.function1("hello", 20);
        System.out.println("----------》带异常的切点");
        try {
            demo.function2();
        } catch (Exception e) {
            //e.printStackTrace();
        }
        System.out.println("----------》带环绕通知的切点");
        demo.function3();
    }
}
  • 运行结果
    Spring学习笔记-5(AOP)_第3张图片

3.2.AspectJ方式

该方法不要求每个通知继承相应的类或实现相应的接口,只需要配置applicationContext.xml中的aop:config标签中的aop:aspect子标签即可。最终实现的效果和schema-base方式是一样的。

3.2.1.编写切点类

这里用到的切点类有两个带参数和返回值的方法。

public class DemoAspectJ {

    public Object function(String message,int num) {
        if (0 != num) {
            System.out.println("pointcut-DemoAspectJ.function message:" + message);
            return true;
        } else {
            int x = 5 / 0;
        }
        return false;
    }

    public Object function2(String message,int num) {
        System.out.println("pointcut-DemoAspectJ.function message:" + message);
        return 0;
    }
}

3.2.2.创建通知

由于采用aspectJ方式,通知无需继承相应的类和实现相应的接口,所以每一个通知操作可以写成一个方法,多种通知可以写在同一个类中。

public class MyAdvice {

    public void myBefore() {
        System.out.println("前置通知");
    }

    /*message,num为切点参数*/
    public void myBefore2(String message, int num) {
        System.out.println("前置通知 " + message + " " + num);
    }

    public void myAfter() {
        System.out.println("后置通知-出现异常继续执行");
    }

    public void myAfterReturning() {
        System.out.println("后置通知-出现异常不执行");
    }

    public void myThrows(Exception e) {
        System.out.println("执行异常通知:" + e.getMessage() + " aspectJ方式");
    }

    public void myAround(ProceedingJoinPoint p) throws Throwable {
        System.out.println("环绕-前置");
        Object result = p.proceed();
        System.out.println("环绕-后置");
    }
}

3.2.3.配置applicationContext.xml文件

  • 一个aop:config标签中可以配置多个aop:aspect标签,一个aop:aspect标签相当于一个切面,包含了切点和通知。
  • 由于所有的通知写在了同一个类中,所以需要在aop:aspect标签中使用ref属性引用通知所在类的实例。
  • aop:pointcut标签用于配置切点。
  • aop:before标签用于配置前置通知。
  • aop:after标签由于配置后置通知(切点出现异常依然可以运行通知中的内容)。
  • after-returning标签用于配置后置通知(切点出现异常后不执行通知中的内容)。
  • aop:around标签用于配置环绕通知(通知需要定义一个ProceedingJoinPoint对象作为输入参数,利用该对象可执行切点方法)。
  • aop:after-throwing标签用于配置异常通知,如果通知方法中使用了异常对象作为输入参数,则需要配置throwing属性,属性值为异常参数的参数名。

  • 如果希望将切点的传入参数传递给前置通知,需要在aop:pointcut的expression属性中配置参数类型和参数名,并在相应的通知标签中配置arg-names属性。


<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">

    
    <bean id="demoAspectJ" class="com.bear.sxt.pointcut.DemoAspectJ"/>
    
    
    <bean id="myAdvice" class="com.bear.sxt.advice.MyAdvice"/>

    
    <aop:config>
        
        
        <aop:aspect ref="myAdvice">
            
            <aop:pointcut id="pointcut2" expression="execution(* com.bear.sxt.pointcut.DemoAspectJ.function(..))"/>
            
            <aop:around method="myAround" pointcut-ref="pointcut2"/>
            <aop:before method="myBefore" pointcut-ref="pointcut2"/>
            <aop:after method="myAfter" pointcut-ref="pointcut2"/>
            <aop:after-returning method="myAfterReturning" pointcut-ref="pointcut2"/>
            
            <aop:after-throwing method="myThrows" pointcut-ref="pointcut2" throwing="e"/>
        aop:aspect>

        
        <aop:aspect ref="myAdvice">
            <aop:pointcut id="pointcut3" expression="execution(* com.bear.sxt.pointcut.DemoAspectJ.function2(String,int)) and args(message,num)"/>
            <aop:before method="myBefore2" pointcut-ref="pointcut3" arg-names="message,num"/>
        aop:aspect>
    aop:config>
beans>

3.2.4.测试

  • 测试代码
public class Test {

    public static void main(String[] args) {

        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
        DemoAspectJ demoAspectJ = ac.getBean("demoAspectJ", DemoAspectJ.class);
        
        /*使用AspectJ方式*/
        System.out.println("aspectJ方式");
        System.out.println("----------》不传递参数的通知");
        demoAspectJ.function("张三", 10);
        System.out.println("----------》传递参数的通知");
        demoAspectJ.function2("李四", 20);
    }
}
  • 测试结果
    Spring学习笔记-5(AOP)_第4张图片

你可能感兴趣的:(框架,#Spring,spring,aop)