1 介绍

1.1 什么是AOP

  • AOP (Aspect Orient Programming),直译过来就是 面向切面编程。AOP 是一种编程思想,是面向对象编程(OOP)的一种补充。面向对象编程将程序抽象成各个层次的对象,而面向切面编程是将程序抽象成各个切面。

    1.2 为什么需要AOP

    想象下面的场景,开发中在多个模块间有某段重复的代码,我们通常是怎么处理的?显然,没有人会靠“复制粘贴”吧。在传统的面向过程编程中,我们也会将这段代码,抽象成一个方法,然后在需要的地方分别调用这个方法,这样当这段代码需要修改时,我们只需要改变这个方法就可以了。然而需求总是变化的,有一天,新增了一个需求,需要再多出做修改,我们需要再抽象出一个方法,然后再在需要的地方分别调用这个方法,又或者我们不需要这个方法了,我们还是得删除掉每一处调用该方法的地方。实际上涉及到多个地方具有相同的修改的问题我们都可以通过 AOP 来解决。

    2 API说明

    2.1 AOP术语

  • 通知(Advice): AOP 框架中的增强处理。通知描述了切面何时执行以及如何执行增强处理。

  • 连接点(join point): 连接点表示应用执行过程中能够插入切面的一个点,这个点可以是方法的调用、异常的抛出。在 Spring AOP 中,连接点总是方法的调用。
  • 切点(PointCut): 可以插入增强处理的连接点。
  • 切面(Aspect): 切面是通知和切点的结合。
  • 引入(Introduction):引入允许我们向现有的类添加新的方法或者属性。
  • 织入(Weaving): 将增强处理添加到目标对象中,并创建一个被增强的对象,这个过程就是织入。

    2.2 AspectJ 切点指示器

    | AspectJ切点指示器 | 描述 | | —- | —- | | arg() | 限制连接点匹配参数为指定类型的执行方法 | | @args() | 限制连接点匹配参数由指定注解标注的执行方法 | | execution() | 用于匹配是连接点的执行方法 | | this() | 限制连接点匹配AOP代理的Bean引用为指定类型的类 | | target() | 限制连接点匹配目标对象为指定类型的类 | | @target() | 限制连接点匹配特定的执行对象,这些对象对应的类药具备指定类型的注解 | | within() | 限制连接点匹配指定的类型 | | @within() | 限制连接点匹配指定注解锁标注的类型 | | @annotation | 限制匹配带有指定注解连接点 |

2.3 通知类型

注解 通知
@Before 通知方法会在目标方法调用之前执行
@After 通知方法会在目标方法返回或异常后调用
@AfterReturning 通知方法会在目标方法返回后调用
@AfterThrowing 通知方法会在目标方法抛出异常后调用
@Around 通知方法会将目标方法封装起来

3 AOP底层原理

3.1 概述

  • AOP的底层原理:

    • ①使用JDK的动态代理。
    • ②使用CGLIB的动态代理。

      3.2 应用示例

      3.2.1 自定义AOP-JDK动态代理

      3.2.1.1 创建用户增加和修改方法

      ```java public interface UserDao {

      int add(int a ,int b);

      String update(String id);

}

  1. <a name="du4gy"></a>
  2. ### 3.2.1.2 实现用户接口
  3. ```java
  4. public class UserDaoImpl implements UserDao {
  5. @Override
  6. public int add(int a, int b) {
  7. System.out.println("add...执行了");
  8. return a + b;
  9. }
  10. @Override
  11. public String update(String id) {
  12. System.out.println("update...执行了");
  13. return id;
  14. }
  15. }

3.2.1.3 JDK动态代理测试

  1. public class SpringTest {
  2. public static void main(String[] args) {
  3. UserDaoImpl userDao = new UserDaoImpl();
  4. // 代理用户类,
  5. UserDao proxy = (UserDao) Proxy.newProxyInstance(userDao.getClass().getClassLoader(), userDao.getClass().getInterfaces(), new InvocationHandler() {
  6. // 每次执行方法前后调用提示内容
  7. @Override
  8. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  9. // 方法执行前的提示内容
  10. System.out.println("在" + method.getName() + "之前执行");
  11. // 主方法调用
  12. Object result = method.invoke(userDao, args);
  13. // 方法执行后的提示内容
  14. System.out.println("在" + method.getName() + "之后执行");
  15. return result;
  16. }
  17. });
  18. // 调用代理后的方法
  19. int result = proxy.add(1, 2);
  20. System.out.println("result = " + result);
  21. String id = proxy.update("1");
  22. System.out.println("id = " + id);
  23. }
  24. }

3.2.1.4 结果

  1. add之前执行
  2. add...执行了
  3. add之后执行
  4. result = 3
  5. update之前执行
  6. update...执行了
  7. update之后执行
  8. id = 1

4 入门案例

4.1 注解方式

4.1.1 依赖引入

  1. <dependencies>
  2. <dependency>
  3. <groupId>org.springframework</groupId>
  4. <artifactId>spring-context</artifactId>
  5. <version>5.2.7.RELEASE</version>
  6. </dependency>
  7. <dependency>
  8. <groupId>org.springframework</groupId>
  9. <artifactId>spring-aop</artifactId>
  10. <version>5.2.7.RELEASE</version>
  11. </dependency>
  12. <dependency>
  13. <groupId>org.springframework</groupId>
  14. <artifactId>spring-aspects</artifactId>
  15. <version>5.2.7.RELEASE</version>
  16. </dependency>
  17. <dependency>
  18. <groupId>com.alibaba</groupId>
  19. <artifactId>druid</artifactId>
  20. <version>1.1.23</version>
  21. </dependency>
  22. <dependency>
  23. <groupId>junit</groupId>
  24. <artifactId>junit</artifactId>
  25. <version>RELEASE</version>
  26. <scope>test</scope>
  27. </dependency>
  28. </dependencies>

4.1.2 创建用户的增加方法

  1. package com.hikktn.service;
  2. public interface User {
  3. void add();
  4. }

4.1.3 实现用户的增加方法

  1. package com.hikktn.service.impl;
  2. import com.hikktn.service.User;
  3. import org.springframework.stereotype.Component;
  4. @Component
  5. public class UserImpl implements User {
  6. @Override
  7. public void add() {
  8. System.out.println("add");
  9. }
  10. }

4.1.4 切面类实现

  1. package com.hikktn.config;
  2. import org.aspectj.lang.ProceedingJoinPoint;
  3. import org.aspectj.lang.annotation.*;
  4. import org.springframework.context.annotation.ComponentScan;
  5. import org.springframework.context.annotation.EnableAspectJAutoProxy;
  6. import org.springframework.core.annotation.Order;
  7. import org.springframework.stereotype.Component;
  8. /**
  9. * 增强的类
  10. */
  11. @Component
  12. @Aspect //生成代理对象
  13. @Order(1) //在多个增强类对同一个方法进行增强,设置增强类的优先级,@Order中的value属性值越小优先级越高
  14. public class UserAspect {
  15. /**
  16. * 切入点表达式
  17. */
  18. @Pointcut("execution(* com.hikktn.service.impl.UserImpl.add(..))")
  19. public void pointcut() {
  20. }
  21. /**
  22. * 前置通知
  23. */
  24. @Before(value = "pointcut()")
  25. public void beforeAdd() {
  26. System.out.println("before add ...");
  27. }
  28. /**
  29. * 后置通知
  30. */
  31. @AfterReturning(value = "pointcut()", returning = "obj")
  32. public void afterReturningAdd(Object obj) {
  33. System.out.println("afterReturning add ..." + obj);
  34. }
  35. /**
  36. * 环绕通知
  37. */
  38. @Around(value = "pointcut()")
  39. public void aroundAdd(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
  40. System.out.println("环绕之前");
  41. proceedingJoinPoint.proceed();
  42. System.out.println("环绕之后");
  43. }
  44. /**
  45. * 异常通知
  46. */
  47. @AfterThrowing(value = "pointcut()", throwing = "ex")
  48. public void afterThrowingAdd(Exception ex) {
  49. System.out.println("afterThrowing add ..." + ex);
  50. }
  51. /**
  52. * 最终通知
  53. */
  54. @After(value = "pointcut()")
  55. public void afterAdd() {
  56. System.out.println("after add ...");
  57. }
  58. }

4.1.5 XML配置

applicationContext.xml

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  3. xmlns:context="http://www.springframework.org/schema/context"
  4. xmlns:aop="http://www.springframework.org/schema/aop"
  5. xmlns="http://www.springframework.org/schema/beans"
  6. xsi:schemaLocation="http://www.springframework.org/schema/beans
  7. http://www.springframework.org/schema/beans/spring-beans.xsd
  8. http://www.springframework.org/schema/context
  9. http://www.springframework.org/schema/context/spring-context.xsd
  10. http://www.springframework.org/schema/aop
  11. http://www.springframework.org/schema/aop/spring-aop.xsd">
  12. <!-- 开启组件扫描 -->
  13. <context:component-scan base-package="com.hikktn"></context:component-scan>
  14. <!-- 开启aspectj生成代理对象 -->
  15. <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
  16. </beans>

4.1.6 测试

  1. public class SpringTest {
  2. public static void main(String[] args) {
  3. ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
  4. User user = context.getBean(User.class);
  5. user.add();
  6. }
  7. }

4.1.7 结果

  1. 环绕之前
  2. before add ...
  3. add
  4. afterReturning add ...null
  5. after add ...
  6. 环绕之后

4.1.8 全注解方式

  1. package com.hikktn.config;
  2. import org.springframework.context.annotation.ComponentScan;
  3. import org.springframework.context.annotation.Configuration;
  4. import org.springframework.context.annotation.EnableAspectJAutoProxy;
  5. @Configuration
  6. @ComponentScan(value = "com.hikktn")
  7. @EnableAspectJAutoProxy(proxyTargetClass = true)
  8. public class AppConfig {
  9. }

4.1.9 测试

  1. public class SpringTest {
  2. public static void main(String[] args) {
  3. // ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
  4. AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
  5. User user = context.getBean(User.class);
  6. user.add();
  7. }
  8. }

4.1.10 结果

  1. 环绕之前
  2. before add ...
  3. add
  4. afterReturning add ...null
  5. after add ...
  6. 环绕之后

4.2 XML方式

4.2.1 依赖引入

  1. <dependencies>
  2. <dependency>
  3. <groupId>org.springframework</groupId>
  4. <artifactId>spring-context</artifactId>
  5. <version>5.2.7.RELEASE</version>
  6. </dependency>
  7. <dependency>
  8. <groupId>org.springframework</groupId>
  9. <artifactId>spring-aop</artifactId>
  10. <version>5.2.7.RELEASE</version>
  11. </dependency>
  12. <dependency>
  13. <groupId>org.springframework</groupId>
  14. <artifactId>spring-aspects</artifactId>
  15. <version>5.2.7.RELEASE</version>
  16. </dependency>
  17. <dependency>
  18. <groupId>com.alibaba</groupId>
  19. <artifactId>druid</artifactId>
  20. <version>1.1.23</version>
  21. </dependency>
  22. <dependency>
  23. <groupId>junit</groupId>
  24. <artifactId>junit</artifactId>
  25. <version>RELEASE</version>
  26. <scope>test</scope>
  27. </dependency>
  28. </dependencies>

4.2.2 创建用户的增加方法

  1. package com.hikktn.service;
  2. public interface User {
  3. void add();
  4. }

4.2.3 实现用户的增加方法

  1. package com.hikktn.service;
  2. public interface User {
  3. void add();
  4. }

4.2.4 切面类

  1. package com.hikktn.config;
  2. /**
  3. * 增强的类
  4. */
  5. public class UserAspect {
  6. /**
  7. * 前置通知
  8. */
  9. public void beforeAdd() {
  10. System.out.println("before add ...");
  11. }
  12. }

4.2.5 XML配置

  • applicationContext.xml ```xml <?xml version=”1.0” encoding=”UTF-8”?>

    1. <!-- 配置切入点 -->
    2. <aop:pointcut id="pointcut" expression="execution(* com.hikktn.service.User.add(..)))"/>
    3. <!-- 配置切面 -->
    4. <aop:aspect ref="userAspect">
    5. <!-- 配置增强作用在具体的方法上 -->
    6. <aop:before method="beforeAdd" pointcut-ref="pointcut"></aop:before>
    7. </aop:aspect>

  1. <a name="gsFH9"></a>
  2. ### 4.2.6 测试
  3. ```java
  4. import com.hikktn.service.User;
  5. import org.springframework.context.ApplicationContext;
  6. import org.springframework.context.support.ClassPathXmlApplicationContext;
  7. public class SpringTest {
  8. public static void main(String[] args) {
  9. ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
  10. User user = context.getBean(User.class);
  11. user.add();
  12. }
  13. }

4.2.7 结果

  1. before add ...
  2. add

4.3 注解多参数方式

4.3.1 依赖引入

  1. <dependencies>
  2. <dependency>
  3. <groupId>org.springframework</groupId>
  4. <artifactId>spring-context</artifactId>
  5. <version>5.2.7.RELEASE</version>
  6. </dependency>
  7. <dependency>
  8. <groupId>org.springframework</groupId>
  9. <artifactId>spring-aop</artifactId>
  10. <version>5.2.7.RELEASE</version>
  11. </dependency>
  12. <dependency>
  13. <groupId>org.springframework</groupId>
  14. <artifactId>spring-aspects</artifactId>
  15. <version>5.2.7.RELEASE</version>
  16. </dependency>
  17. <dependency>
  18. <groupId>com.alibaba</groupId>
  19. <artifactId>druid</artifactId>
  20. <version>1.1.23</version>
  21. </dependency>
  22. <dependency>
  23. <groupId>junit</groupId>
  24. <artifactId>junit</artifactId>
  25. <version>RELEASE</version>
  26. <scope>test</scope>
  27. </dependency>
  28. </dependencies>

4.3.2 创建购买接口方法

  1. package com.hikktn.service;
  2. public interface IBuy {
  3. String buy(double price);
  4. }

4.3.3 实现购买方法-男孩

  1. package com.hikktn.service.impl;
  2. import com.hikktn.service.IBuy;
  3. import org.springframework.stereotype.Component;
  4. @Component
  5. public class Boy implements IBuy {
  6. @Override
  7. public String buy(double price) {
  8. System.out.println(String.format("男孩花了%s元买了一个游戏机", price));
  9. return "游戏机";
  10. }
  11. }

4.3.4 实现购买方法-女孩

  1. package com.hikktn.service.impl;
  2. import com.hikktn.service.IBuy;
  3. import org.springframework.stereotype.Component;
  4. @Component
  5. public class Girl implements IBuy {
  6. @Override
  7. public String buy(double price) {
  8. System.out.println(String.format("女孩花了%s元买了一件漂亮的衣服", price));
  9. return "衣服";
  10. }
  11. }

4.3.5 切面类

  1. package com.hikktn.config;
  2. import org.aspectj.lang.ProceedingJoinPoint;
  3. import org.aspectj.lang.annotation.Around;
  4. import org.aspectj.lang.annotation.Aspect;
  5. import org.aspectj.lang.annotation.Before;
  6. import org.aspectj.lang.annotation.Pointcut;
  7. import org.springframework.stereotype.Component;
  8. @Aspect
  9. @Component
  10. public class BuyAspectJ {
  11. /**
  12. * 该切面方法只会在Girl类调用起作用
  13. */
  14. @Before("execution(* com.hikktn.service.IBuy.buy(..)) && within(com.hikktn.service.impl.*) && bean(girl)")
  15. public void hehe(){
  16. System.out.println("女孩超级喜欢的东西");
  17. }
  18. @Pointcut("execution(String com.hikktn.service.IBuy.buy(double)) && args(price) && bean(girl)")
  19. public void gif(double price) {
  20. }
  21. @Around("gif(price)")
  22. public String hehe(ProceedingJoinPoint pj, double price){
  23. try {
  24. pj.proceed();
  25. if (price > 68) {
  26. System.out.println("女孩买衣服超过了68元,赠送一双袜子");
  27. return "衣服和袜子";
  28. }
  29. } catch (Throwable throwable) {
  30. throwable.printStackTrace();
  31. }
  32. return "衣服";
  33. }
  34. }

4.3.6 配置类

  1. package com.hikktn.config;
  2. import org.springframework.context.annotation.ComponentScan;
  3. import org.springframework.context.annotation.Configuration;
  4. import org.springframework.context.annotation.EnableAspectJAutoProxy;
  5. @Configuration
  6. @ComponentScan(basePackages = "com.hikktn")
  7. // 选用CGLib代理
  8. @EnableAspectJAutoProxy(proxyTargetClass = true)
  9. public class AppConfig {
  10. }

4.3.7 测试

  1. import com.hikktn.config.AppConfig;
  2. import com.hikktn.service.impl.Boy;
  3. import com.hikktn.service.impl.Girl;
  4. import org.springframework.context.annotation.AnnotationConfigApplicationContext;
  5. public class AppTest {
  6. public static void main(String[] args) {
  7. AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
  8. Boy boy = context.getBean("boy", Boy.class);
  9. Girl girl = (Girl) context.getBean("girl");
  10. String boyBought = boy.buy(35);
  11. String girlBought = girl.buy(99.8);
  12. System.out.println("男孩买到了:" + boyBought);
  13. System.out.println("女孩买到了:" + girlBought);
  14. }
  15. }

4.3.8 结果

  1. 男孩花了35.0元买了一个游戏机
  2. 女孩超级喜欢的东西
  3. 女孩花了99.8元买了一件漂亮的衣服
  4. 女孩买衣服超过了68元,赠送一双袜子
  5. 男孩买到了:游戏机
  6. 女孩买到了:衣服和袜子