1、为什么要使用AOP
实验类:
package com.xianhuii; public class ArithmeticCalculatorImpl implements ArithmeticCalculator { @Override public int add(int i, int j) { // 非业务需求1 System.out.println("The method add begins with [" + i + ", " + j + "]"); // 业务代码 int result = i + j; // 非业务需求2 System.out.println("The method add edns with [" + result + "]"); return result; } @Override public int sub(int i, int j) { // 非业务需求1 System.out.println("The method sub begins with [" + i + ", " + j + "]"); // 业务代码 int result = i - j; // 非业务需求2 System.out.println("The method sub edns with [" + result + "]"); return result; } @Override public int mul(int i, int j) { // 非业务需求1 System.out.println("The method mul begins with [" + i + ", " + j + "]"); // 业务代码 int result = i * j; // 非业务需求2 System.out.println("The method mul edns with [" + result + "]"); return result; } @Override public int div(int i, int j) { // 非业务需求1 System.out.println("The method div begins with [" + i + ", " + j + "]"); // 业务代码 int result = i / j; // 非业务需求2 System.out.println("The method div edns with [" + result + "]"); return result; } }
问题:
- 代码混乱:越来越多的非业务需求(日志和验证等)加入后,原有的业务方法急剧膨胀,每个方法在处理核心逻辑的同时还必须兼顾其他多个关注点。
- 代码分散:以日志需求为例,只是为了满足这个单一需求,就不得不在多个模块、方法里多次重复相同的日志代码。如果日志需求发生变化,必须修改所有模块。
2 动态***解决问题
接口:
package com.xianhuii; public interface ArithmeticCalculator { int add(int i, int j); int sub(int i, int j); int mul(int i, int j); int div(int i, int j); }
业务类:
package com.xianhuii; public class ArithmeticCalculatorImpl implements ArithmeticCalculator { @Override public int add(int i, int j) { int result = i + j; return result; } @Override public int sub(int i, int j) { int result = i - j; return result; } @Override public int mul(int i, int j) { int result = i * j; return result; } @Override public int div(int i, int j) { int result = i / j; return result; } }
动态***类:
package com.xianhuii; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.Arrays; public class ArithmeticCalculatorLogginProxy { // 被***的对象(目标对象) private ArithmeticCalculator target; // 通过构造器的方式将目标对象传入 public ArithmeticCalculatorLogginProxy( ArithmeticCalculator target) { this.target = target; } // 获取***对象 public ArithmeticCalculator getLoggingProxy() { // 定义***对象 ArithmeticCalculator proxy; /** * loader:ClassLoader(类加载器) * interfaces:目标类的所有接口,目的是获取接口中的方法 * handler:InvocationHandler */ ClassLoader loader = target.getClass().getClassLoader(); Class<?>[] interfaces = target.getClass().getInterfaces(); InvocationHandler handler = new InvocationHandler() { /** * * @param proxy:***对象,在invoke方法中一般不会用 * @param method:正在调用的方法 * @param args:调用方法传入的参数 * @return * @throws Throwable */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { String methodName = method.getName(); // 加日志 System.out.println("The method " + methodName + " begins with" + Arrays.asList(args)); // 执行目标方法 Object result = method.invoke(target, args); // 加日志 System.out.println("The method " + methodName + " ends with " + result); return result; } }; proxy = (ArithmeticCalculator) Proxy.newProxyInstance(loader, interfaces, handler); return proxy; } }
测试:
package com.xianhuii.test; import com.xianhuii.ArithmeticCalculator; import com.xianhuii.ArithmeticCalculatorImpl; import com.xianhuii.ArithmeticCalculatorLogginProxy; public class SpringTest { public static void main(String[] args) { // 目标对象 ArithmeticCalculator target = new ArithmeticCalculatorImpl(); // 获取***对象 ArithmeticCalculator proxy = new ArithmeticCalculatorLogginProxy(target).getLoggingProxy(); int result = proxy.add(1, 1); // 实际上要去执行invoke()方法 System.out.println("结果" + result); } }
测试结果:
3 Spring AOP相关概念
- 切面(Aspect):横切关注点(跨越应用程序多个模块的功能)被模块化的特殊对象(整合了一系列通知的类的对象)。
- 通知(Advice):切面必须要完成的功能(特定的非业务方法,如日志)。
- 目标(Target):被通知的对象(业务类对象)。
- (Proxy):向目标对象应用通知之后创建的对象(类对象)。
- 连接点(Joinpoint):程序指定的某个特定位置,如某个方法调用前、调用后、方法抛出异常后。连接点由两个信息确定:方法表示的程序执行点、相对点表示的方位。
- 切点(Pointcut):每个类都有多个连接点。AOP通过切点定位到特定的连接点。切点使用类和方法作为连接点的查询条件。
4 使用AspectJ注解声明切面
要在Spring中声明AspectJ切面,只需要在IOC容器中将切面声明为Bean实例(@Component)。当在Spring IOC容器中初始化AspectJ切面之后,Spring IOC容器就会为那些与AspectJ切面向匹配的Bean创建***。
在AspectJ注解中,切面只是一个带有@Aspect注解的Java类。
通知是标注有某种注解的简单的Java方法。
AspectJ支持5种类型的通知注解:
- @Before:前置通知,在方法执行之前执行。
- @After:后置通知,在方法执行之后执行。
- @AfterRunning:返回通知,在方法返回结果之后执行。
- @AfterThrowing:异常通知,在方法抛出异常之后执行。
- @Around:环绕通知,围绕着方法执行。
新增AspectJ的jar包:
aspectjweaver.jar
配置:
<?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: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.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- 开启注解扫描 --> <context:component-scan base-package="com.xianhuii"></context:component-scan> <!-- 基于注解开发,开启aop自动proxy --> <aop:aspectj-autoproxy/> </beans>
目标类接口:
package com.xianhuii; public interface ArithmeticCalculator { int add(int i, int j); int sub(int i, int j); int mul(int i, int j); int div(int i, int j); }
目标类:
package com.xianhuii; import org.springframework.stereotype.Component; @Component public class ArithmeticCalculatorImpl implements ArithmeticCalculator { @Override public int add(int i, int j) { int result = i + j; return result; } @Override public int sub(int i, int j) { int result = i - j; return result; } @Override public int mul(int i, int j) { int result = i * j; return result; } @Override public int div(int i, int j) { int result = i / j; return result; } }
切面类:
package com.xianhuii; 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 LoggingAspect { /** * 通知:前置、后置、返回、异常、环绕 */ /** * 前置通知:在方法执行之前执行。 * JoinPoint:连接点对象,包含了连接点相关信息:方法名、方法参数等。 */ // @Before("execution(public int com.xianhuii.ArithmeticCalculator.add(int,int ))") 指定方法 // @Before("execution(public int com.xianhuii.ArithmeticCalculator.*(int,int ))") 指定类下的所有方法 @Before("execution(* com.xianhuii.*.*(..))") // 任意修饰符任意返回值 com.xianhuii包下的任意类中的任意方法(任意形参) public void beforeMethod(JoinPoint joinPoint) { // 获取方法名 String methodName = joinPoint.getSignature().getName(); // 获取参数 Object[] args = joinPoint.getArgs(); System.out.println("The method " + methodName + " begins with " + Arrays.asList(args)); } /** * 后置通知:在方法执行后执行,不管方法有没有抛出异常。 * 后置通知访问不到方法的返回值。 */ @After("execution(* com.xianhuii.*.*(..))") public void afterMethod(JoinPoint joinPoint) { String methodName = joinPoint.getSignature().getName(); System.out.println("The methods " + methodName + " ends."); } /** * 返回通知:在方法正常执行返回结果以后执行。 * 返回通知可以访问到方法的返回值。 */ @AfterReturning(value = "execution(* com.xianhuii.*.*(..))", returning = "result") public void afterReturningMethod(JoinPoint joinPoint, Object result) { String methodName = joinPoint.getSignature().getName(); System.out.println("The method " + methodName + " ends with " + result); } /** * 异常通知:方法执行过程中抛出异常后执行的。 * 可以指定抛出特定的异常再执行异常通知。在形参的位置指定异常的类型即可。 */ @AfterThrowing(value = "execution(* com.xianhuii.*.*(..))", throwing = "ex") public void afterThrowingMethod(JoinPoint joinPoint, ArithmeticException ex) { String methodName = joinPoint.getSignature().getName(); System.out.println("The method " + methodName + " occurs exception: " + ex.getMessage()); } /** * 环绕通知:类似于动态***的整个过程。 */ @Around("execution(* com.xianhuii.*.*(..))") public Object aroundMethod(ProceedingJoinPoint pjp) { String methodName = pjp.getSignature().getName(); Object[] args = pjp.getArgs(); try { // 前置通知 System.out.println("The method " + methodName + " begins with " + Arrays.asList(args)); // 执行目标方法 Object result = pjp.proceed(); // 返回通知 System.out.println("The method " + methodName + " ends with " + result); return result; } catch (Throwable throwable) { // 异常通知 System.out.println("The method " + methodName + " occues exception: " + throwable); throwable.printStackTrace(); } finally { System.out.println("The method " + methodName + " ends."); } return null; } }
测试:
package com.xianhuii.test; import com.xianhuii.ArithmeticCalculator; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class SpringTest { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); ArithmeticCalculator calculator = (ArithmeticCalculator) context.getBean("arithmeticCalculatorImpl"); System.out.println(calculator.getClass()); // ***对象:class com.sun.proxy.$Proxy14 int result = calculator.div(1, 1); System.out.println("AspectJ result: " + result); } }
5 切面优先级
@Order:指定切面的优先级,值越小,优先级越高。
标注@Order的切面比不标注@Order切面的优先级高。
package com.xianhuii; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; import java.util.Arrays; @Component @Aspect @Order// 数值越小优先级越高 public class ValidatorAspect { @Before("execution(* com.xianhuii.*.*(..))") public void beforeMethod(JoinPoint joinPoint) { // 获取方法名 String methodName = joinPoint.getSignature().getName(); // 获取参数 Object[] args = joinPoint.getArgs(); System.out.println("ValidatorAspect: the method " + methodName + " begins with " + Arrays.asList(args)); } }
6 重用切入点表达式
@Pointcut("execution(* com.xianhuii..(..))")。
使用本类中切入点表达式:
package com.xianhuii; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; import java.util.Arrays; @Component @Aspect @Order // 数值越小优先级越高 public class ValidatorAspect { /** * 重用切入点表达式 */ @Pointcut("execution(* com.xianhuii.*.*(..))") public void suibain() { } @Before("suibain()") public void beforeMethod(JoinPoint joinPoint) { // 获取方法名 String methodName = joinPoint.getSignature().getName(); // 获取参数 Object[] args = joinPoint.getArgs(); System.out.println("ValidatorAspect: the method " + methodName + " begins with " + Arrays.asList(args)); } }
使用其他类的切入点表达式:
package com.xianhuii; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.*; import org.springframework.stereotype.Component; @Component // 标识为组件 @Aspect // 标识为切面 public class LoggingAspect { @Before("com.xianhuii.ValidatorAspect.suibain()") public void beforeMethod(JoinPoint joinPoint) { } }
7 使用XML配置切面
目标类接口:
package com.xianhuii; public interface ArithmeticCalculator { int add(int i, int j); int sub(int i, int j); int mul(int i, int j); int div(int i, int j); }
目标类:
package com.xianhuii; public class ArithmeticCalculatorImpl implements ArithmeticCalculator { @Override public int add(int i, int j) { int result = i + j; return result; } @Override public int sub(int i, int j) { int result = i - j; return result; } @Override public int mul(int i, int j) { int result = i * j; return result; } @Override public int div(int i, int j) { int result = i / j; return result; } }
切面类:
package com.xianhuii; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import java.util.Arrays; /** * 日志切面 */ public class LoggingAspect { public void beforeMethod(JoinPoint joinPoint) { // 获取方法名 String methodName = joinPoint.getSignature().getName(); // 获取参数 Object[] args = joinPoint.getArgs(); System.out.println("The method " + methodName + " begins with " + Arrays.asList(args)); } public void afterMethod(JoinPoint joinPoint) { String methodName = joinPoint.getSignature().getName(); System.out.println("The methods " + methodName + " ends."); } public void afterReturningMethod(JoinPoint joinPoint, Object result) { String methodName = joinPoint.getSignature().getName(); System.out.println("The method " + methodName + " ends with " + result); } public void afterThrowingMethod(JoinPoint joinPoint, ArithmeticException ex) { String methodName = joinPoint.getSignature().getName(); System.out.println("The method " + methodName + " occurs exception: " + ex.getMessage()); } public Object aroundMethod(ProceedingJoinPoint pjp) { String methodName = pjp.getSignature().getName(); Object[] args = pjp.getArgs(); try { // 前置通知 System.out.println("The method " + methodName + " begins with " + Arrays.asList(args)); // 执行目标方法 Object result = pjp.proceed(); // 返回通知 System.out.println("The method " + methodName + " ends with " + result); return result; } catch (Throwable throwable) { // 异常通知 System.out.println("The method " + methodName + " occues exception: " + throwable); throwable.printStackTrace(); } finally { System.out.println("The method " + methodName + " ends."); } return null; } }
配置:
<?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: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.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- 配置切面 --> <bean id="loggingAspect" class="com.xianhuii.LoggingAspect"></bean> <bean id="validatorAspect" class="com.xianhuii.ValidatorAspect"></bean> <!-- 配置目标bean --> <bean id="arithmeticCalculatorImpl" class="com.xianhuii.ArithmeticCalculatorImpl"></bean> <!-- 配置aop --> <aop:config> <!-- 配置切入点表达式 --> <aop:pointcut id="myPointCut" expression="execution(* com.xianhuii.*.*(..))"></aop:pointcut> <!-- 配置切面及通知 --> <aop:aspect ref="loggingAspect" order="1"> <!--<aop:before method="beforeMethod" pointcut="execution(* com.xianhuii.*.*(..))"/>--> <aop:before method="beforeMethod" pointcut-ref="myPointCut"/> <aop:after method="afterMethod" pointcut-ref="myPointCut"/> <aop:after-returning method="afterReturningMethod" pointcut-ref="myPointCut" returning="result"/> <aop:after-throwing method="afterThrowingMethod" pointcut-ref="myPointCut" throwing="ex"/> </aop:aspect> <aop:aspect ref="validatorAspect" order="2"></aop:aspect> </aop:config> </beans>