在IOC所需依赖的基础上再加入下面的依赖:
pom.xml
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-aspectsartifactId>
<version>5.3.1version>
dependency>
使用之前创建过的类作为被代理的目标资源
在此基础上对CalculatorImpl.java
添加@Component
注释
import org.springframework.stereotype.Component;
@Component //标记为组件类,使其能够放入IOC容器
public class CalculatorImpl implements Calculator {
@Override
public int add(int i, int j) {
int result = i + j;
System.out.println("方法内部 result = " + result);
return result;
}
//省略类似的sub、mul、
}
创建切面类LoggerAspect.java
,并使用注解进行标记
package com.atguigu.spring.proxy;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
@Component //标记为组件类,使其能够放入IOC容器
@Aspect //表示这个类是一个切面类
public class LoggerAspect {
}
创建spring-aop-annotation.xml
<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"
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">
<context:component-scan base-package="com.atguigu.spring.proxy"/>
beans>
spring-aop-annotation.xml
<aop:aspectj-autoproxy/>
LoggerAspect.java
package com.atguigu.spring.proxy;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Component //标记为组件类,使其能够放入IOC容器
@Aspect //表示这个类是一个切面类
public class LoggerAspect {
//标记为前置通知的方法,value值为切入点表达式
@Before("execution(public int com.atguigu.spring.proxy.CalculatorImpl.add(int,int))") //得具体到某个包和类中的方法
public void beforeAdviceMethod(){
System.out.println("LoggerAspect,前置通知");
}
}
SpringTest.java
@Test
public void testAOPByAnnotation(){
ApplicationContext ioc = new ClassPathXmlApplicationContext("spring-aop-annotation.xml");
//AOP会根据注解和需要获取的类型自动去找对应的实现类(实现类标记为组件类),所以这里必须得获取接口类,不能是它的实现类(注意:IOC通过annotation获取bean只要满足【对象 instanceof 指定的类型】)
Calculator calculator = ioc.getBean(Calculator.class);
calculator.add(1,2);
}
LoggerAspect,前置通知
方法内部 result = 3
try
语句的最后一句)catch
语句)finally
语句)try...catch...finally
结构围绕整个被代理的目标方法,包括上面四种通知对应的所有位置各种通知的执行顺序:
- Spring版本5.3.x以前:
- 前置通知
- 目标操作
- 后置通知
- 返回通知或异常通知
- Spring版本5.3.x以后:(现在在使用的)
- 前置通知
- 目标操作
- 返回通知或异常通知
- 后置通知
语法细节:
"*"
号代替“权限修饰符”和“返回值”部分表示“权限修饰符”和“返回值”不限“*”
号只能代表包的层次结构中的一层,表示这一层是任意的
*.Hello
匹配com.Hello
,不匹配com.atguigu.Hello
"*.."
表示包名任意、包的层次深度任意"*"
号代替,表示类名任意"*"
号代替类名的一部分
*Service
匹配所有名称以Service结尾的类或接口"*"
号表示方法名任意"*"
号代替方法名的一部分
*Operation
匹配所有方法名以Operation结尾的方法"(..)"
表示参数列表任意"(int,..)"
表示参数列表以一个int类型的参数开头execution(public int ..Service.*(.., int))
正确execution(* int ..Service.*(.., int))
错误公共切入点表达式:
@Pointcut
注解将其value值标记为公共表达式,在使用时调用方法名调用即可在方法的形参列表添加JoinPoint joinPoint
LoggerAspect.java
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import java.util.Arrays;
@Component //标记为组件类,使其能够放入IOC容器
@Aspect //表示这个类是一个切面类
public class LoggerAspect {
//重用切入点表达式
@Pointcut("execution(* com.atguigu.spring.proxy.CalculatorImpl.*(..))")
public void testPointCut(){}
//标记为前置通知的方法,value值为切入点表达式
@Before("testPointCut()") //得具体到某个包和类中的方法
public void beforeAdviceMethod(JoinPoint joinPoint){
//获取连接点所对应方法的签名信息
Signature signature = joinPoint.getSignature();
//获取方法名字
String methodName = signature.getName();
//获取目标方法到的实参信息
String args = Arrays.toString(joinPoint.getArgs());
//打印输出
System.out.println("Logger-->前置通知,方法名:"+methodName+",参数:"+args);
}
}
SpringTest.java
@Test
public void testAOPByAnnotation(){
ApplicationContext ioc = new ClassPathXmlApplicationContext("spring-aop-annotation.xml");
//AOP底层使用的是动态代理,所以要获取代理对象,而获取代理对象需要通过接口来获取(实现类标记为组件类),所以这里不能获取它的实现类
Calculator calculator = ioc.getBean(Calculator.class);
calculator.add(1,2);
}
Logger-->前置通知,方法名:add,参数:[1, 2]
方法内部 result = 3
@AfterReturning
中的属性returning
,用来将通知方法的某个形参指定为接收目标方法的返回值的参数
LoggerAspect.java
@AfterReturning(value = "testPointCut()",returning = "result")
public void afterReturningMethod(JoinPoint joinPoint, Object result){
String methodName = joinPoint.getSignature().getName();
System.out.println("Logger-->返回通知,方法名:"+methodName+",结果:"+result);
}
Logger-->前置通知,方法名:add,参数:[1, 2]
方法内部 result = 3
Logger-->返回通知,方法名:add,结果:3
@AfterThrowing
中的属性throwing
,用来将通知方法的某个形参指定为接收目标方法的返回值的参数
在SpringTest.java
中使用div(1,0)
触发异常
LoggerAspect.java
@AfterThrowing(value = "testPointCut()",throwing = "ex")
public void afterThrowingMethod(JoinPoint joinPoint, Throwable ex){
String methodName = joinPoint.getSignature().getName();
System.out.println("Logger-->异常通知,方法名:"+methodName+",异常:"+ex);
}
Logger-->前置通知,方法名:div,参数:[1, 0]
Logger-->异常通知,方法名:div,异常:java.lang.ArithmeticException: / by zero
LoggerAspect.java
@Around("testPointCut()")
public Object aroundMethod(ProceedingJoinPoint proceedingJoinPoint){//ProceedingJoinPoint为可执行的连接点对象
String methodName = proceedingJoinPoint.getSignature().getName();
String args = Arrays.toString(proceedingJoinPoint.getArgs());
Object result = null;
try{
System.out.println("环绕通知-->前置通知,方法名:"+methodName+",参数:"+args);
//目标方法的执行,如果调用的目标方法存在返回值,则一定要将返回值返回给外界调用者
result = proceedingJoinPoint.proceed();
System.out.println("环绕通知-->返回通知,方法名:"+methodName+",结果:"+result);
} catch (Throwable throwable) {
throwable.printStackTrace();
System.out.println("环绕通知-->异常通知,方法名:"+methodName+",异常:"+throwable);
} finally {
System.out.println("环绕通知-->后置通知,方法名:"+methodName);
}
return result;
}
SpringTest.java
@Test
public void testAOPByAnnotation(){
ApplicationContext ioc = new ClassPathXmlApplicationContext("spring-aop-annotation.xml");
//AOP底层使用的是动态代理,所以要获取代理对象,而获取代理对象需要通过接口来获取(实现类标记为组件类),所以这里不能获取它的实现类
Calculator calculator = ioc.getBean(Calculator.class);
calculator.div(1,1);
}
方法内部 result = 1
环绕通知-->返回通知,方法名:div,结果:1
环绕通知-->后置通知,方法名:div
单例通知和环绕通知一起执行(此时顺序不可预估):
环绕通知-->前置通知,方法名:div,参数:[1, 1]
Logger-->前置通知,方法名:div,参数:[1, 1]
方法内部 result = 1
Logger-->返回通知,方法名:div,结果:1
环绕通知-->返回通知,方法名:div,结果:1
环绕通知-->后置通知,方法名:div
相同目标方法上同时存在多个切面时,切面的优先级控制切面的内外嵌套顺序
使用@Order注解可以控制切面的优先级:(默认为Integer最大值)