JDK动态代理与CGLib动态代理均是实现Spring AOP的基础,切点,切面,如何定义切点,前置、后置、放回、异常、环绕通知

源码获取github

1.切点、切面

  1. 红色的地方就是切面,增加额外的功能
  2. 连接点+增加功能的位置 = 切点

2.项目结构

3.jar包

<dependencies>
   <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
   </dependency>

   <!-- https://mvnrepository.com/artifact/org.springframework/spring-aop -->
   <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aop</artifactId>
      <version>4.3.6.RELEASE</version>
   </dependency>
   <!-- https://mvnrepository.com/artifact/org.springframework/spring-aspects -->
   <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aspects</artifactId>
      <version>4.3.6.RELEASE</version>
   </dependency>
   <!-- https://mvnrepository.com/artifact/org.springframework/spring-beans -->
   <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-beans</artifactId>
      <version>4.3.6.RELEASE</version>
   </dependency>
   <!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
   <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>4.3.6.RELEASE</version>
   </dependency>
   <!-- https://mvnrepository.com/artifact/org.springframework/spring-context-support -->
   <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context-support</artifactId>
      <version>4.3.6.RELEASE</version>
   </dependency>
   <!-- https://mvnrepository.com/artifact/org.springframework/spring-core -->
   <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-core</artifactId>
      <version>4.3.6.RELEASE</version>
   </dependency>
   <!-- https://mvnrepository.com/artifact/org.springframework/spring-expression -->
   <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-expression</artifactId>
      <version>4.3.6.RELEASE</version>
   </dependency>
   <!-- https://mvnrepository.com/artifact/org.springframework/spring-tx -->
   <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-tx</artifactId>
      <version>4.3.6.RELEASE</version>
   </dependency>

<!--新增的jar-->
   <!-- https://mvnrepository.com/artifact/aopalliance/aopalliance -->
   <dependency>
      <groupId>aopalliance</groupId>
      <artifactId>aopalliance</artifactId>
      <version>1.0</version>
   </dependency>
   <!-- https://mvnrepository.com/artifact/org.aspectj/aspectjrt -->
   <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjrt</artifactId>
      <version>1.8.13</version>
   </dependency>
   <!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
   <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjweaver</artifactId>
      <version>1.8.6</version>
   </dependency>

</dependencies>

4.核心配置文件beans.xml

<?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:mvc="http://www.springframework.org/schema/mvc" xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p" xmlns:util="http://www.springframework.org/schema/util" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.3.xsd">
   <!--1.启动Spring注解-->
   <context:annotation-config></context:annotation-config>
   <!--2.扫描-->
   <context:component-scan base-package="com.hs"/>
   <!--Spring提供了多种AOP的实现方式,但是我们只用第三方的AOP标准方式-->
   <!--3.启动AOP注解,false是使用默认的java代理,true是使用CGLIB代理-->
   <aop:aspectj-autoproxy proxy-target-class="true"/>
</beans>

5.核心代码

package com.hs.service;

/** * 对外提供的核心业务,完成了加法/减法/乘法运算 */
public interface ArithmeticService {
   int add(int x, int y);

   int sub(int x, int y);

   int mul(int x, int y);

   int div(int x, int y);
}
package com.hs.service.impl;

import com.hs.service.ArithmeticService;
import org.springframework.stereotype.Service;

/** * 核心代码 */
@Service
public class ArithmeticServiceImpl implements ArithmeticService {
   @Override
   public int add(int x, int y) {
      //核心代码
      int result = x + y;
      return result;
   }

   @Override
   public int sub(int x, int y) {
      int result = x - y;
      return result;
   }

   @Override
   public int mul(int x, int y) {
      int result = x * y;
      return result;
   }

   @Override
   public int div(int x, int y) {
      int result = x / y;
      return result;
   }
}

6.定义切面

package com.hs.service.aspect;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

import java.util.Arrays;

@Component
@Aspect //标记这是切面
public class Logger {

   //在你运行方法之前执行,并且监控的是接口的执行Add方法,value可以省略
   @Before(value = "execution(int com.hs.service.ArithmeticService.add(int ,int))")
   //这句话的意思,
   //执行返回类型为int,com.hs.service.ArithmeticService接口下的add方法,传递的两个参数类型为int,int
   //简单理解为,接口里写的方法,只是少了变量值 int add(int x, int y);
   public void test01() {
      System.out.println("@Before在程序之前运行");
   }
}

测试

package com.hs.test;

import com.hs.service.ArithmeticService;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class ArithmeticServiceTest {
   private ArithmeticService arithmeticService;
   private ApplicationContext ac;

   @Before
   public void init() {
      this.ac = new ClassPathXmlApplicationContext("beans.xml");
      this.arithmeticService = this.ac.getBean("arithmeticServiceImpl", ArithmeticService.class);
   }

   @Test
   public void testAddMethod() {
      int result = this.arithmeticService.add(2,1);
      System.out.println("运行的结果:"+ result);
   }
}

控制台输出:

@Before在程序之前运行
运行的结果:3

7.如何定义注解切点说明

1.使用逻辑运算符

@Before(value = "execution(int com.hs.service.ArithmeticService.add(int ,int)) or execution(int com.hs.service.ArithmeticService.sub(int ,int))")
public void test02() {
   System.out.println("我可以使用逻辑运算符 &&(and) ||(or)");
}

2.使用通配符——推荐

/** * 比较推荐 * 在execution表达式中可以使用通配符,但是请注意任意的表达式是 .. * 第一个*位置:代表返回任意的数据类型 * ..表示任意的意思,而不是我们之前学习的** * 第二个* 监控的以Service结尾的类(接口) * 第三个* 监控接口或者类下的所有的方法 * ..标识任意个参数类型或者参数的个数 */
@Before(value = "execution(* com.hs..*Service.*(..))")
public void test03() {
   System.out.println("我可以使用通配符");
}

3.如何定义切点

//如何定义切点(可以随意组合)
@Pointcut(value = "execution(int com.hs.service.ArithmeticService.add(int ,int))")
public void addPointCut() { }   //切点的名称是“addPointCut()”
@Pointcut(value = "execution(int com.hs.service.ArithmeticService.sub(int ,int))")
public void subPointCut() { }   //切点的名称是“subPointCut()”
@Pointcut(value = "execution(int com.hs.service.ArithmeticService.mul(int ,int))")
public void mulPointCut() { }   //切点的名称是“mulPointCut()”
@Pointcut(value = "addPointCut() || mulPointCut()")
public void addAndMulPointCut() { } //切点的名称是“addAndMulPointCut()”
@Pointcut(value = "execution(* com.hs..service.*Service.*(..))")
    public void allMethodPointCut(){}

//@Before 叫前置通知,也叫前置增强
@Before(value = "addAndMulPointCut()")
public void test04() {
   System.out.println("如何定义切点");
}

8.前置通知:不管程序是否正确都会执行

//1.前置通知(增强)特点:不管程序是否正确都会执行
    @Before(value = "allMethodPointCut()")
    public void test05(JoinPoint jp) {
        System.out.println("==前置通知==");
        //获取get
        System.out.println(Arrays.asList(jp.getArgs()));
        System.out.println(jp.getSignature().getName());
        System.out.println(jp.getTarget().getClass().getName());
        System.out.println("==前置通知==");

    }

9.后置通知:不管程序是否正确都会执行

//2.后置通知(增强)特点:不管程序是否正确都会执行
@After(value = "allMethodPointCut()")
public void test06(JoinPoint jp) {
   //获取get
   System.out.println("==后置增强==");
   System.out.println(Arrays.asList(jp.getArgs()));
   System.out.println(jp.getSignature().getName());
   System.out.println(jp.getTarget().getClass().getName());
   System.out.println("==后置增强==");

}

10.返回通知:只有程序是正确的时候才会执行,并且可以获取运行后的数据

//3.返回通知(增强):只有程序是正确的时候才会执行,并且可以获取运行后的数据
@AfterReturning(value = "allMethodPointCut()",returning = "hs")
public void test07(JoinPoint jp,int hs) {
   System.out.println("==返回通知==");
   System.out.println("程序正常运行,正确的运行结果为==>"+hs);
   System.out.println("==返回通知==");
}

11.异常通知:只有程序是错误的时候才会执行

//4.异常通知(增强):只有程序是错误的时候才会执行,
@AfterThrowing(value = "allMethodPointCut()",throwing = "hs")
public void test08(JoinPoint jp,ArithmeticException hs) {
   System.out.println("==异常通知==");
   System.out.println("程序运行错误==>"+hs);
   System.out.println("==异常通知==");
}

12.环绕通知

//5.环绕通知
@Around("allMethodPointCut()")
public Object test09(ProceedingJoinPoint pjp) {
   System.out.println("写某个功能,前置通知");
   Object result = null;
   try {
      result = pjp.proceed(); //执行程序返回运行的结果
      System.out.println("写某个功能,返回通知");
   } catch (Throwable throwable) {
      throwable.printStackTrace();
      System.out.println("写某个功能,异常通知");
   }
   System.out.println("写某个功能,后置通知");
   return result;
}

测试

package com.hs.test;

import com.hs.service.ArithmeticService;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class ArithmeticServiceTest {
   private ArithmeticService arithmeticService;
   private ApplicationContext ac;

   @Before
   public void init() {
      this.ac = new ClassPathXmlApplicationContext("beans.xml");
      this.arithmeticService = this.ac.getBean("arithmeticServiceImpl", ArithmeticService.class);
   }

   @Test
   public void testAddMethod() {
      int result = this.arithmeticService.add(2,1);
      System.out.println("运行的结果:"+ result);
   }
}