1、AspectJ的通知类型
- 前置通知:在目标方法调用前被调用的通知
- 后置通知:在目标方法调用后被调用的通知
- 异常通知:在目标方法执行出现异常时被调用的通知
- 最终通知:无论是否出现异常,最后都会被调用的通知
- 环绕通知:可以用来替代前置和后置
2、AspectJ的切入点表达式
AspectJ 定义了专门的表达式用于指定切入点。
表达式的原型如下:
execution(modifiers-pattern? ret-type-pattern
declaring-type-pattern?name-pattern(param-pattern)
throws-pattern?)
说明:
modifiers-pattern 访问权限类型
ret-type-pattern 返回值类型
declaring-type-pattern 包名类名
name-pattern(param-pattern) 方法名(参数类型和参数个数)
throws-pattern 抛出异常类型
?表示可选的部分
以上表达式共四个部分:
execution(访问权限 方法返回值 方法声明(参数) 异常类型)
切入点表达式要匹配的对象就是目标方法的方法名。所以,execution表达式中就是方法的签名。
PS:表达式中黑色文字表示可省略部分,各部分间用空格分开。在其中可以使用以下符号:
符号 | 意义 |
---|---|
* | 0-多个任意字符 |
.. | 用在方法参数中,表示任意个参数;用在包名后,表示当前及其子包路径 |
+ | 用在类名后,表示当前及其子类;用在接口后,表示当前接口及其实现类 |
示例:
execution(* com.lina.service.*.*(..))
指定切入点为:定义在 service 包里的任意类的任意方法。execution(* com.lina.service..*.*(..))
指定切入点为:定义在 service 包或者子包里的任意类的任意方法。“..”出现在类名中时,后面必须跟“*”,表示包、子包下的所有类。execution(* com.lina.service.IUserService+.*(..))
指定切入点为:IUserService 若为接口,则为接口中的任意方法及其所有实现类中的任意方法;若为类,则为该类及其子类中的任意方法。
3、注解方式实现AOP
注解方式实现AOP,只需要给切面类和核心业务类表明注解,然后交给Spring即可,Spring会根据注解的内容去创建对应的代理类,底层的实现原理就是动态代理。
3.1、引入依赖
<dependencies>
<!--spring 核心依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.13.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.13.RELEASE</version>
</dependency>
<!--测试依赖-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<!--编译插件-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
3.2、xml配置文件引入约束
开启包扫描以及注解AOP的使用,都要在beas标签里面配一下context和aop的调用。
<?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.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--包扫描-->
<context:component-scan base-package="com.AE.service, com.AE.aop"/>
<!--开启注解AOP的使用-->
<aop:aspectj-autoproxy proxy-target-class="true"/>
<!--aop:aspectj-autoproxy的底层就是由AnnotationAwareAspectjAutoProxyCreator
是基于AspectJ的注解适配自动代理生成器.通过注解扫描找到@Aspect定义的切面类,再由切面类切入点找到目标类的目标方法,再由通知类型找到切入的时间-->
</beans>
3.3、创建核心业务类
注意使用@Service注解,将该类的创建权限交给Spring。
public interface IService {
void add(int id, String name);
boolean update(int num);
}
@Service
public class TeamService implements IService{
@Override
public void add(int id, String name) {
System.out.println("TeamService---add---");
}
@Override
public boolean update(int num) {
System.out.println("TeamService---update---");
if(num < 666) return true;
return false;
}
}
@Service("nbaService")
public class NBAService implements IService{
@Override
public void add(int id, String name) {
System.out.println("NBAService---add---");
}
@Override
public boolean update(int num) {
System.out.println("NBAService---update---");
if(num > 666) return true;
return false;
}
}
3.4、创建切面类
用@Component 注解将该类的创建交给Spring。
用@Aspect 注解表示当前类是一个切面类
用@Before 注解来表示前置通知调用的方法
用@AfterReturning 注解来表示后置通知调用的方法
用@AfterThrowing 注解来表示异常通知调用的方法
用@After 注解来表示最终通知调用的方法
用@Round 注解来表示环绕通知调用的方法
用@Pointcut 注解来表示切入点表达式
@Component // 切面对象的创建对象的权限也交给了spring
@Aspect // aspectj 框架的注解 表示当前类是一个切面类
public class MyAspect {
/**
* 当很多的增强方法使用相同的切入点表达式的时候,编写和后期的维护比较麻烦
* @Pointcut 注解是aspectj提供的定义切入点表达式
*/
@Pointcut("execution(* com.AE.service..*.*(..))")
private void pointCut(){};
/**
* 声明的前置通知
*/
// @Before("execution(* com.AE.service..*.*(..))")
@Before("pointCut()")
public void before(JoinPoint jp){
System.out.println("前置通知:在目标方法之前被调用的通知");
String name = jp.getSignature().getName();
System.out.println("拦截的方法名称: " + name);
Object[] args = jp.getArgs();
System.out.println("方法的参数个数:" + args.length);
System.out.println("参数列表:");
for(Object i : args){
System.out.println("\t" + i);
}
}
/**
* 后置通知
* @param result 得到方法的返回值
* @return
*/
// @AfterReturning(value = "execution(* com.AE.service..*.*(..))", returning = "result")
@AfterReturning(value = "pointCut()", returning = "result")
public Object afterReturn(Object result) {
//这里的逻辑之事告知大家切面中可以修改方法的返回值
if(result != null) {
boolean res = (boolean) result;
if(res) {
result = false;
}
}
System.out.println("后置通知:在目标方法之后被调用,返回值result=" + result);
return result;
}
// @Around(value = "execution(* com.AE.service..*.*(..))")
@Around(value = "pointCut()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("方法执行之前执行-----");
Object proceed = pjp.proceed();
System.out.println("方法执行之后执行-----");
return proceed;
}
/**
* 异常通知
* @param jp 获取异常方法名
* @param t 获取异常信息
*/
//@AfterThrowing(value = "execution(* com.AE.service..*.*(..))", throwing="t")
@AfterThrowing(value = "pointCut()", throwing="t")
public void exception(JoinPoint jp, Throwable t) {
// 一般吧异常的时间、位置、原因都记录
System.out.println("异常通知:在目标方法执行出现异常的时候才会调用的通知,否则不执行。");
System.out.println(jp.getSignature().getName() + "方法出现异常" + t.getMessage());
}
/**
* 最终通知
*/
// @After(value = "execution(* com.AE.service..*.*(..))")
@After(value = "pointCut()")
public void myfinally() {
System.out.println("最终通知:无论是否出现异常,最后都会被调用的通知");
}
}
3.5、测试类
public void test01() {
ApplicationContext ac = new ClassPathXmlApplicationContext("spring.xml");
TeamService teamService = (TeamService) ac.getBean("teamService");
teamService.add(12, "AE");
System.out.println("--------------");
System.out.println("update结果=" + teamService.update(12));
System.out.println("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
NBAService nbaService = (NBAService) ac.getBean("nbaService");
nbaService.add(12, "AC");
System.out.println("update结果=" + nbaService.update(12));
}
4、xml形式实现AOP
核心业务类和注解方式相同
4.1、定义切面类
/**
* 定义切面类
*/
@Component // 切面对象的创建对象的权限也交给了spring
@Aspect // aspectj 框架的注解 表示当前类是一个切面类
public class MyAop {
public void before(JoinPoint jp){
System.out.println("前置通知:在目标方法之前被调用的通知");
}
public Object afterReturn(Object result) {
System.out.println("后置通知:在目标方法之后被调用,返回值result=" + result);
return result;
}
public Object around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("方法执行之前执行-----");
Object proceed = pjp.proceed();
System.out.println("方法执行之后执行-----");
return proceed;
}
public void exception(JoinPoint jp, Throwable t) {
// 一般吧异常的时间、位置、原因都记录
System.out.println("异常通知:在目标方法执行出现异常的时候才会调用的通知,否则不执行。");
System.out.println(jp.getSignature().getName() + "方法出现异常" + t.getMessage());
}
public void myfinally() {
System.out.println("最终通知:无论是否出现异常,最后都会被调用的通知");
}
}
4.2、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: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.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--包扫描-->
<context:component-scan base-package="com.AE.service, com.AE.aop"/>
<!--开启注解AOP的使用-->
<aop:aspectj-autoproxy proxy-target-class="true"/>
<!--aop:aspectj-autoproxy的底层就是由AnnotationAwareAspectjAutoProxyCreator
是基于AspectJ的注解适配自动代理生成器.通过注解扫描找到@Aspect定义的切面类,再由切面类切入点找到目标类的目标方法,再由通知类型找到切入的时间-->
<aop:config>
<!-- aop:pointcut 相当于是用来定义切入点表达式的变量 -->
<aop:pointcut id="pt1" expression="execution(* com.AE.service..*.add*(..))"/>
<aop:pointcut id="pt2" expression="execution(* com.AE.service..*.update*(..))"/>
<aop:pointcut id="pt3" expression="execution(* com.AE.service..*.del*(..))"/>
<!-- 用来绑定对应的切面类 -->
<aop:aspect ref="myAop">
<!-- before前置通知 method为切面类中前置通知方法名 pointcut为切入点表达式 -->
<aop:before method="before" pointcut="execution(* com.AE.service..*.add*(..))"/>
<!-- 假如由参数,则需要设置对应的参数 -->
<aop:after-returning method="afterReturn" pointcut-ref="pt2" returning="result"/>
<aop:after-throwing method="exception" pointcut-ref="pt1" throwing="t"/>
<aop:after method="myfinally" pointcut-ref="pt1"/>
<aop:around method="around" pointcut-ref="pt3"/>
</aop:aspect>
</aop:config>
</beans>