AOP前奏

  1. 需求1-日志:在程序执行期间追踪正在发生的活动
  2. 需求2-验证:希望计算器只能处理正整数的运算
  1. public class Main {
  2. public static void main(String args[]) {
  3. ArithmeticCalculator arithmeticCalculator = null;
  4. arithmeticCalculator = new ArithmeticCalculatorImpl();
  5. int result = arithmeticCalculator.add(1, 2);
  6. System.out.println("-->" + result);
  7. result = arithmeticCalculator.sub(2, 1);
  8. System.out.println("-->" + result);
  9. }
  10. }
  11. public interface ArthmeticCalculator {
  12. int add(int i, int j);
  13. int sub(int i, int j);
  14. int mul(int i, int j);
  15. int div(int i, int j);
  16. }
  17. public class ArthmeticCalculatorImpl implements ArthmeticCalculator {
  18. @Override
  19. public int add(int i, int j) {
  20. System.out.println("The method add begins with[" + i + "," + j + "]");
  21. int result = i + j;
  22. System.out.println("The method add ends with " + result);
  23. return result;
  24. };
  25. @Override
  26. public int sub(int i, int j) {
  27. System.out.println("The method sub begins with[" + i + "," + j + "]");
  28. int result = i - j;
  29. System.out.println("The method sub ends with " + result);
  30. return result;
  31. };
  32. @Override
  33. public int mul(int i, int j) {
  34. System.out.println("The method mul begins with[" + i + "," + j + "]");
  35. int result = i * j;
  36. System.out.println("The method mul ends with " + result);
  37. return result;
  38. };
  39. @Override
  40. public int div(int i, int j) {
  41. System.out.println("The method div begins with[" + i + "," + j + "]");
  42. int result = i / j;
  43. System.out.println("The method div ends with " + result);
  44. return result;
  45. };
  46. }

上面代码的问题

  • 代码混乱:越来越多的非业务需求(日志和验证等)加入后,原有的业务方法急剧膨胀,每个方法在处理核心逻辑的同时还要兼顾其他多个关注点。
  • 代码分散:已日志的需求为例,只是为了满足这个单一需求,就不得不再多个模块(方法)里多次重复相同的日志代码,如果日志发生变化,必须修改所有的模块

    使用动态代理解决上述问题

  • 代理设计模式的原理:使用一个代理将对象包装起来,然后用该代理对象取代原始对象,任何对原始对象的调用都要通过代理,代理对象决定是否以及何时将方法调用转到原始对象上

-调用者发起调用
-验证代理,验证参数
-日志代理,方法日志开始
-计算器
-日志代理,方法日志结束
-返回结果给调用者

  1. public class ArthmeticCalculatorImpl implements ArthmeticCalculator {
  2. @Override
  3. public int add(int i, int j) {
  4. int result = i + j;
  5. return result;
  6. };
  7. @Override
  8. public int sub(int i, int j) {
  9. int result = i - j;
  10. return result;
  11. };
  12. @Override
  13. public int mul(int i, int j) {
  14. int result = i * j;
  15. return result;
  16. };
  17. @Override
  18. public int div(int i, int j) {
  19. int result = i / j;
  20. return result;
  21. };
  22. }
  23. public class Main {
  24. public static void main(String args[]) {
  25. ArithmeticCalculator target = new ArithmeticCalculatorImpl();
  26. ArithmeticCalculatorLoggingProxy proxy = new ArithmeticCalculatorLoggingProxy(target)
  27. .getLoggingProxy();
  28. int result = proxy.add(1, 2);
  29. System.out.println("-->" + result);
  30. result = proxy.sub(2, 1);
  31. System.out.println("-->" + result);
  32. }
  33. }
  34. import java.lang.reflect.InvocationHandler;
  35. import java.lang.reflect.Proxy;
  36. import java.lang.reflect.Method;
  37. public class ArithmeticCalculatorLoggingProxy {
  38. // 要代理的对象
  39. private ArithmeticCalculator target;
  40. public ArithmeticCalculatorLoggingProxy(ArithmeticCalculator target) {
  41. this.target = target;
  42. }
  43. public ArithmeticCalculator getLoggingProxy() {
  44. ArithmeticCalculator proxy = null;
  45. // 代理对象由哪一个类加载起负责加载
  46. ClassLoader loader = target.getClass().getClassLoader();
  47. // 代理对象的类型,即其中由那些方法
  48. Class[] interfaces = new Class[]{ArithmeticCalculator.class}
  49. // 当调用代理对象其中的方法时,该执行的代码
  50. InvocationHandler h = new InvocationHandler() {
  51. /**
  52. * proxy : 正在返回的那个代理对象,一般情况下,在invoke方法中都不使用此对象
  53. * method :正在被调用的方法
  54. * args :调用方法是传入的参数
  55. */
  56. @Override
  57. public Object invoke(Object proxy, Method method, Object[] args)
  58. throws Throwable {
  59. String methodName = method.getName();
  60. System.out.println("The method " + methodName + " begins with "
  61. + Arrays.asList(args));
  62. Object result = null;
  63. try {
  64. // 前置通知
  65. result = method.invoke(target, args);
  66. // 返回通知
  67. } catch (Exception e) {
  68. e.printStackTrace();
  69. // 异常通知
  70. }
  71. // 后置通知,因为可能异常所以可能拿不到结果
  72. System.out.println("The method " + methodName + " ends with "
  73. + result);
  74. return result;
  75. }
  76. }
  77. proxy = Proxy.newProxyInstance(loader, )
  78. }
  79. }

AOP 简介

  • AOP(Aspect-Oriented Programming,面向切面编程):是一种新的方法论,是对传统OOP(Object-Oriented Programming,面向对象编程)的补充。
  • AOP的主要编程对象是切面(aspect),而切面模块化横切关注点。
  • 在应用AOP编程时,仍然需要定义公共功能,但可以明确的定义这个功能在哪里,以什么方式应用,并且不必修改受到影响的类。这样一来横切关注点就被模块化到特殊的对象(切面)里。
  • AOP的好处

-每个事物逻辑位于一个位置,代码不分散,便于维护
-业务模块更简洁,只包含核心业务代码

AOP术语

  • 切面(Aspect):横切关注点(跨越应用程序多个模块的功能)被模块化的特殊对象
  • 通知(Advice):切面必修要完成的工作
  • 目标(Target):被通知的对象
  • 代理(Proxy):向目标对象应用通知之后创建的对象
  • 连接点(JoinPoint):程序执行的某个特定的位置:如类某个方法调用前,调用后,方法抛出异常后等。连接点由两个信息确定,方法表示程序的执行点(方位执行的位置);相对点表示的方位(方位为该方法执行前的位置)。
  • 切点(pointcut):每个类都拥有多个连接点,例如ArithmeticCalculator所有的方法都是连接点,即连接点是程序类中客观存在的事务。AOP通过切点定位到特定的连接点。类比:连接点相当于数据库中的纪录,切点相当于查询条件。切点与连接点不是一对一的关系,一个切点匹配多个连接点,切点通过org.springframework.aop.Pointcut接口进行描述,它使用类和方法作为连接点的查询条件。

    Spring前置通知

    Spring AOP

  • AspectJ:Java社区里最完整最流行的AOP框架。

  • 在Spring2.0以上版本中,可以使用基于AspectJ注解或基于XML配置的AOP

    在Spring中启用AspectJ注解支持

  • 要在Spring应用中使用AspectJ注解,必须在classpath下包含AspectJ的类库:aopalliance.jar,aspectj.weaver.jar和spring-aspects.jar

  • 将aopSchema添加到根元素中
  • 要在SpringIOC容器中启用AspectJ注解支持,只要在Bean配置文件中定义一个空的xml元素
  • 当SpringIOC容器侦测到Bean配置文件中的元素时,会自动为与AspectJ切面匹配的Bean创建代理。

用AspectJ注解声明切面

  • 要在Spring中声明AspectJ切面,只需要在IOC容器中将切面声明为Bean实例,当在SpringIOC容器中初始化AspectJ切面之后,SpringIOC容器将会为那些与AspectJ切面相匹配的Bean创建代理。
  • 在AspectJ注解中,切面只是一个带有@Aspect注解的Java类。
  • 通知是标注有某种注解的简单Java方法。
  • AspectJ支持5种类型的通知注解:

-@before,在方法执行之前执行
-@After ,在方法执行之后执行
-@AfterReturning:返回通知,在方法返回结果之后执行
-@AfterThrowwing:异常通知,在方法抛出异常之后
-@Around:环绕通知,围绕着方法执行

利用方法签名便携ApectJ切点表达式

  • 最典型的切入点表达式时根据方法的签名来匹配的各种方法

-execution com.lijunyang.model.ArithmeticCalculator.(..) 第一个匹配任意修饰符以及任意返回值,第二个表示任意方法
-execution public com.lijunyang.model.ArithmeticCalculator.(…) 匹配ArithmeticCalculator接口的所有公共有方法
-execution public double com.lijunyang.model.ArithmeticCalculator.(…) 匹配返回值是double的方法
-execution public double com.lijunyang.model.ArithmeticCalculator.
(double,..)匹配第一个参数是double的任意方法

  1. // src/beans.xml
  2. <beans>
  3. <context:component-scan base-package="com.lijunyang.model" />
  4. <aop:aspectj-autoproxy />
  5. </beans>
  1. // src/com/lijunyang/test/Main.java
  2. public class Main {
  3. public static void main(String args[]) {
  4. ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
  5. ArthmeticCalculator arthmeticCalculator = ctx.getBean(ArthmeticCalculator.class);
  6. int result = arthmeticCalculator.add(1, 2);
  7. System.out.println("result: " + result);
  8. }
  9. }
  10. // src/com/lijunyang/model/LoggingAspect.java
  11. import org.springframework.stereotype.Component;
  12. import org.aspectj.lang.annotation.Aspect;
  13. import org.aspectj.lang.JoinPoint;
  14. import org.aspectj.lang.annotation.Before;
  15. import org.aspectj.lang.annotation.After;
  16. import org.aspectj.lang.annotation.AfterReturning;
  17. import org.aspectj.lang.annotation.AfterThrowwing;
  18. import java.awt.List;
  19. import java.util.Arrays;
  20. @Aspect
  21. @Component
  22. public class LoggingAspect {
  23. /*
  24. @Around("execution(public int com.lijunyang.model.ArithmeticCalculator.add(int, int))")
  25. public Object AroundMethod(ProceedingJoinPoint pjd) {
  26. Object result = null;
  27. String methodName = pjd.getSignature().getName();
  28. try {
  29. result = pjd.proceed();
  30. }catch(Throwable e) {
  31. throw new RuntimeException(e);
  32. }
  33. return result;
  34. }
  35. */
  36. // @Before("execution(public int com.lijunyang.model.ArithmeticCalculator.add(int, int))")
  37. @Before("execution(public int com.lijunyang.model.ArithmeticCalculator.*(int, int))")
  38. public void beforeMethod(JoinPoint joinPoint){
  39. String methodName = joinPoint.getSignature().getName();
  40. List<Object> args = Arrays.asList(joinPoint.getArgs());
  41. System.out.println("The " + methodName + " begins with " + args);
  42. }
  43. @After("execution(public int com.lijunyang.model.ArithmeticCalculator.*(int, int))")
  44. public void afterMethod(JoinPoint joinPoint) {
  45. // 后置通知:在目标方法执行后(无论是否发生异常),执行的通知。
  46. // 在后置通知中还不能访问目标方法的执行结果
  47. String methodName = joinPoint.getSignature().getName();
  48. System.out.println("The " + methodName + " ends ");
  49. }
  50. @AfterReturning("execution(* com.lijunyang.model.ArithmeticCalculator.*(int, int))"
  51. ,returning="result")
  52. public void afterReturningMethod(JoinPoint joinPoint, Object result) {
  53. // 返回通知可以获取到返回值
  54. // 当异常时,不执行
  55. String methodName = joinPoint.getSignature().getName();
  56. System.out.println("The " + methodName + " ends " + result);
  57. }
  58. @AfterThrowwing(value="execution(* com.lijunyang.model.ArithmeticCalculator.*(int, int))"
  59. ,throwing=ex)
  60. public void afterReturningMethod(JoinPoint joinPoint,NullPointerException ex) {
  61. String methodName = joinPoint.getSignature().getName();
  62. System.out.println("The " + methodName + " occurs exception: " + ex);
  63. }
  64. }
  65. // src/com/lijunyang/model/ArthmeticCalculator.java
  66. public interface ArthmeticCalculator {
  67. int add(int i, int j);
  68. int sub(int i, int j);
  69. int mul(int i, int j);
  70. int div(int i, int j);
  71. }
  72. // src/com/lijunyang/model/ArthmeticCalculatorImpl.java
  73. import org.springframework.stereotype.Component;
  74. @Component("arthmeticCalculator")
  75. public class ArthmeticCalculatorImpl implements ArthmeticCalculator {
  76. @Override
  77. public int add(int i, int j) {
  78. System.out.println("The method add begins with[" + i + "," + j + "]");
  79. int result = i + j;
  80. System.out.println("The method add ends with " + result);
  81. return result;
  82. };
  83. @Override
  84. public int sub(int i, int j) {
  85. System.out.println("The method sub begins with[" + i + "," + j + "]");
  86. int result = i - j;
  87. System.out.println("The method sub ends with " + result);
  88. return result;
  89. };
  90. @Override
  91. public int mul(int i, int j) {
  92. System.out.println("The method mul begins with[" + i + "," + j + "]");
  93. int result = i * j;
  94. System.out.println("The method mul ends with " + result);
  95. return result;
  96. };
  97. @Override
  98. public int div(int i, int j) {
  99. System.out.println("The method div begins with[" + i + "," + j + "]");
  100. int result = i / j;
  101. System.out.println("The method div ends with " + result);
  102. return result;
  103. };
  104. }

AOP的优先级与重用切点表达式

image.png

  1. @order(1) // 通过这个设置优先级,值越小优先级越高
  2. @Aspect
  3. @Component
  4. public class VlidationAspect {
  5. // @Before("execution(public int com.lijunyang.model.ArithmeticCalculator.*(int, int))")
  6. @Before("LoggingAspect.declareJointPointExpression()")
  7. public void validateArgs(JoinPoint joinPoint) {
  8. System.out.println("validate:" + Arrays.asList(joinPoint.getArgs()));
  9. }
  10. }
  11. @order(2)
  12. @Aspect
  13. @Component
  14. public class LoggingAspect {
  15. @Pointcut("execution(public int com.lijunyang.model.ArithmeticCalculator.*(..))")
  16. public void declareJointPointExpression() {}
  17. /*
  18. @Around("execution(public int com.lijunyang.model.ArithmeticCalculator.add(int, int))")
  19. public Object AroundMethod(ProceedingJoinPoint pjd) {
  20. Object result = null;
  21. String methodName = pjd.getSignature().getName();
  22. try {
  23. result = pjd.proceed();
  24. }catch(Throwable e) {
  25. throw new RuntimeException(e);
  26. }
  27. return result;
  28. }
  29. */
  30. // @Before("declareJointPointExpression()")
  31. @Before("execution(public int com.lijunyang.model.ArithmeticCalculator.*(int, int))")
  32. public void beforeMethod(JoinPoint joinPoint){
  33. String methodName = joinPoint.getSignature().getName();
  34. List<Object> args = Arrays.asList(joinPoint.getArgs());
  35. System.out.println("The " + methodName + " begins with " + args);
  36. }
  37. @After("declareJointPointExpression()")
  38. public void afterMethod(JoinPoint joinPoint) {
  39. // 后置通知:在目标方法执行后(无论是否发生异常),执行的通知。
  40. // 在后置通知中还不能访问目标方法的执行结果
  41. String methodName = joinPoint.getSignature().getName();
  42. System.out.println("The " + methodName + " ends ");
  43. }
  44. @AfterReturning("declareJointPointExpression()"
  45. ,returning="result")
  46. public void afterReturningMethod(JoinPoint joinPoint, Object result) {
  47. // 返回通知可以获取到返回值
  48. // 当异常时,不执行
  49. String methodName = joinPoint.getSignature().getName();
  50. System.out.println("The " + methodName + " ends " + result);
  51. }
  52. @AfterThrowwing(value="declareJointPointExpression()"
  53. ,throwing=ex)
  54. public void afterReturningMethod(JoinPoint joinPoint,NullPointerException ex) {
  55. String methodName = joinPoint.getSignature().getName();
  56. System.out.println("The " + methodName + " occurs exception: " + ex);
  57. }
  58. }

引用通知(内容缺失)

  • 引入通知是一种特殊的通知类型,它通过为接口提供实现类,允许对象动态的实现接口,就像对象已经在运行时扩展了实现类一样。
  • 引入通知可以使用两个实现类MaxCalculatorImpl和MinCalculatorImpl,让ArthmeticCalculatorImpl动态的实现MaxCalculator和MinCalculator接口,而这与从MaxCalculatorImpl和MinCalculatorImpl实现多继承的效果相同,但却不需要修改ArthmeticCalculatorImpl的源代码。
  • 引入通知也必须在切面中声明
  • 在切面中,通过任意字段添加@DeclareParents注解来引入声明。
  • 注解类型的value属性表示那些类是当前引入通知的目标,value属性值也可以是一个AspectJ类型的表达式,以将一个即可引入到多个类中,defaultImpl属性中指定了这个接口的使用实现类

基于配置文件的方式配置AOP

  1. // src/beans.xml
  2. <beans>
  3. <bean id="arithmeticCalculator" class="com.lijunyang.model.ArithmeticCalculatorImpl" />
  4. <bean id="vlidationAspect" class="com.lijunyang.model.VlidationAspect"/>
  5. <bean id="loggingAspect" class="com.lijunyang.model.LoggingAspect" />
  6. <aop:config>
  7. <aop:pointcut id="pointcut"
  8. expression="execution(* com.lijunyang.model.ArithmeticCalculator.*(int, int))" />
  9. <aop:aspect ref="vlidationAspect" order="1">
  10. <aop:before method="validateArgs" pointcut-ref="pointcut" />
  11. </aop:aspect>
  12. <aop:aspect ref="loggingAspect" order="2">
  13. <aop:before method="beforeMethod" pointcut-ref="pointcut" />
  14. <aop:before method="afterMethod" pointcut-ref="pointcut" />
  15. <aop:before method="afterReturningMethod"
  16. returning="result" pointcut-ref="pointcut" />
  17. <aop:before method="afterTorowingMethod"
  18. throwing="ex" pointcut-ref="pointcut" />
  19. <!--<aop:around method="aroundMethod" pointcut-ref="pointcut" />-->
  20. </aop:aspect>
  21. </aop:config>
  22. </beans>