一. AOP术语
横切关注点: 对哪些方法进行拦截,拦截后怎么处理,这些关注点称之为横切关注点。
切面(aspect): 类是对物体特征的抽象,切面就是对横切关注点的抽象。
通知(advice): 切面拦截到连接点之后要执行的代码,通知分为前置、后置、异常、最终、环绕通知五类。
目标(target): 被代理(通知)的对象。
代理(proxy): 代理对象。向目标对象应用通知之后创建的对象。
连接点(joinpoint):是切面类和业务类的连接点,其实就是封装了业务方法的一些基本属性,作为通知的参数来解析。
切点(pointcut):业务类中指定的方法,作为切面切入的点。其实就是指定某个方法作为切面切的地方。相当于查询条件,在哪些业务方法中需要加入通知,spring自动生成新的代理对象。
通知:
前置通知:在切入点之前执行。
后置通知:在切入点执行完成后,执行通知。
返回通知: 返回通知, 在方法返回结果之后执行。
环绕通知(around advice):包围切入点,调用方法前后完成自定义行为。
异常通知(after throwing advice):在切入点抛出异常后,执行通知。
二. AspectJ注解
在 AspectJ 注解中,
切面只是一个带有 @Aspect 注解的 Java 类。
通知是标注有某种注解的简单的 Java 方法。
AspectJ 支持 5 种类型的通知注解:
* @Before: 前置通知, 在方法执行之前执行。
* @After: 后置通知, 在方法执行之后执行。
* @AfterRunning: 返回通知, 在方法返回结果之后执行。
* @AfterThrowing: 异常通知, 在方法抛出异常之后。
* @Around: 环绕通知, 围绕着方法执行。
三. 导入Spring AOP jar包
使用maven pom.xml导入依赖jar。
<properties>
<spring.version>4.3.8.RELEASE</spring.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-aop -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-aspects -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>${spring.version}</version>
</dependency>
<dependencies>
四.启用AspjectJ注解
<?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 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">
<!--自动扫描-->
<context:component-scan base-package="com.cd.aop.impl"></context:component-scan>
<!-- 启用AspjectJ 注解: 自动为匹配的类生成代理对象 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
五.前置、后置、 返回、异常通知
Calculator.java
package com.cd.spring.bean;
public interface Calculator {
public int add(int num1, int num2);
public int div(int num1, int num2);
}
CalculatorImpl.java
package com.cd.spring.bean;
import org.springframework.stereotype.Component;
@Component
public class CalculatorImpl implements Calculator{
public int add(int num1, int num2){
System.out.println("Calculator add...");
return num1 + num2;
}
public int div(int num1, int num2){
System.out.println("Calculator div...");
return num1 / num2;
}
}
切面类
CalculatorAspect.java
package com.cd.spring.bean;
import java.util.Arrays;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
// 使用注解@Aspect声明一个切面类
@Aspect
@Component
public class CalculatorAspect {
// 通知
@Before(value="execution(* com.cd.spring.bean.*(..))")
public void beforeMethod(JoinPoint jp){ // JoinPoint切入点
String methodName = jp.getSignature().getName();
System.out.println(methodName);
System.out.println("before method execute,args are "+Arrays.toString(jp.getArgs()));
}
@After("execution(* com.cd.spring.bean.*(..))")
public void afterMethod(JoinPoint jp){
System.out.println("after method execute,args are "+Arrays.toString(jp.getArgs()));
}
@AfterThrowing(value="execution(* com.cd.spring.bean.*(..))",throwing="ex")
public void afterThrow(Exception ex){
System.out.println("afterThrow"+ex.getMessage());
}
@AfterReturning(value="execution(* com.cd.spring.bean.*(..))",returning="result")
public void afterReturn(Object result){
System.out.println("the result is "+result);
}
}
调用:
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
Calculator calc = (Calculator) context.getBean("calculatorImpl");
calc.add(50, 50); //
calc.div(100, 0); // 异常通知
调用结果:
add
before method execute,args are [50, 50]
Calculator add…
after method execute,args are [50, 50]
the result is 100
div
before method execute,args are [100, 0]
after method execute,args are [100, 0]
afterThrow/ by zero
六. 环绕通知
package com.cd.spring.bean;
import java.util.Arrays;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class CalculatorAspectAround {
@Around(value="execution(* execution(* com.cd.spring.bean.*(..))")
public Object aroundMethod(ProceedingJoinPoint pjp){
Object result = null;
String methodName = pjp.getSignature().getName();
try {
System.out.println(methodName + "前置通知...);
result = jointPoint.proceed();
System.out.println(methodName + "后置通知...");
} catch (Throwable e) {
System.out.println(methodName + "异常通知...");
throw new RuntimeException(e);
}
System.out.println(methodName + "返回通知.....");
return result;
}
}
注意:@Around环绕通知,必须携带对象ProceedingJoinPoint参数
环绕通知类似于动态代理的全部过程,可以决定是否执行目标方法
七. 指定切面的优先级
在同一个连接点上应用不止一个切面时, 除非明确指定, 否则它们的优先级是不确定的.
切面的优先级可以通过实现 Ordered 接口或利用 @Order 注解指定。
使用@Order注解,值越小,优先级越高。
@Order(1)
@Aspect
@Component
public class LoggingAspect {
}
八. 重用切入点定义
在 AspectJ 切面中, 可以通过 @Pointcut注解将一个切入点声明成简单的方法。切入点的方法体通常是空的。
切入点方法的访问控制符同时也控制着这个切入点的可见性。
如果切入点要在多个切面中共用, 最好将它们集中在一个公共的类中. 在这种情况下, 它们必须被声明为 public。 在引入这个切入点时, 必须将类名也包括在内。 如果类没有与这个切面放在同一个包中, 还必须包含包名。
其他通知可以通过方法名称引入该切入点。
CalculatorAspect.java
package com.cd.spring.bean;
import java.util.Arrays;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
// 使用注解@Aspect声明一个切面类
@Aspect
@Component
public class CalculatorAspect {
/**
* 定义一个方法, 用于声明切入点表达式。
* 使用 @Pointcut 来声明切入点表达式。
*/
@Pointcut("execution(public int com.cd.spring.bean.*(..))")
public void declareJointPointExpression(){}
// 通知
@Before(value="declareJointPointExpression()")
public void beforeMethod(JoinPoint jp){ // JoinPoint切入点
String methodName = jp.getSignature().getName();
System.out.println(methodName);
System.out.println("before method execute,args are "+Arrays.toString(jp.getArgs()));
}
@After("com.cd.spring.bean.CalculatorAspect.declareJointPointExpression()")
public void afterMethod(JoinPoint jp){
System.out.println("after method execute,args are "+Arrays.toString(jp.getArgs()));
}
@AfterThrowing(value="declareJointPointExpression()",throwing="ex")
public void afterThrow(Exception ex){
System.out.println("afterThrow"+ex.getMessage());
}
@AfterReturning(value="declareJointPointExpression()",returning="result")
public void afterReturn(Object result){
System.out.println("the result is "+result);
}
}
九. 基于xml配置切面
将Calculator、CalculatorImpl、CalculatorAspect类移到其他包里面,去掉类以及方法中的所有注解。
定义新的配置文件applicationContext-xml.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:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd">
<!-- 配置业务bean -->
<bean id="calculator" class="com.cd.spring.xml.CalculatorImpl"></bean>
<!-- 配置切面bean -->
<bean id="calculatorAspect" class="com.cd.spring.xml.CalculatorAspect"></bean>
<!-- 配置AOP -->
<aop:config>
<!--配置切点表达式-->
<aop:pointcut
expression="execution(* com.cd.spring.xml.Calculator.*(int,int))" id="pointCut" />
<!--配置切面及通知-->
<aop:aspect ref="calculatorAspect" order="1">
<aop:before method="beforeMethod" pointcut-ref="pointCut" />
<aop:after method="afterMethod" pointcut-ref="pointcut"/>
<aop:after-throwing method="afterThrowing" pointcut-ref="pointcut" throwing="e"/>
<aop:after-returning method="afterReturning" pointcut-ref="pointcut" returning="result"/>
</aop:aspect>
</aop:config>
</beans>