间接调用
设计思想
,是软件设计领域中的面向切面编程补充和完善
用户验证、日志管理、事务处理、数据缓存
都属于横切关注点通知方法
前
执行成功结束
后执行(代码异常则不执行)异常结束
后执行最终结束
后执行(肯定会执行,类似finally)整个
被代理的目标方法,包括上面四种通知对应的所有位置封装通知方法的类
被代理的目标对象
向目标对象应用通知之后创建的代理对象
连接点
*号代替
“权限修饰符”和“返回值”部分表示“权限修饰符”和“返回值”不限
*..
”表示包名任意、包的层次深度任意(..)
表示参数列表任意execution(public int *..*Service.*(.., int))
正确execution(* int *..*Service.*(.., int))
错误表达式格式:包名.* 或者 包名..*
within(com.xc.service.*)
:拦截包中任意方法,不包含子包中的方法within(com.xc.service..*)
:拦截包或者子包中定义的方法包
和接口
、类
级别this(com.xc.service.AccountServiceImpl)
:AccountService接口、AccountServiceImpl实现类 target(com.xc.service.AccountService)
args(com.xc.UserModel)
:匹配只有一个参数,且类型为UserModelargs(type1,type2,typeN)
:匹配多个参数args(com.xc.UserModel,..)
:匹配任意多个参数@target(com.xc.MyAnnotation)
@within(com.xc.MyAnnotation)
@target对父类所有方法生效,@within只对重载过的方法生效
@annotation(com.xc.MyAnnotation)
@args(com.xc.Annotation1)
:匹配1个参数,且第1个参数所属的类中有Annotation1注解@args(com.xc.Annotation1,com.xc.Annotation2)
:匹配多个参数,且多个参数所属的类型上都有指定的注解@args(com.xc.Annotation1,..)
:匹配多个参数,且第一个参数所属的类中有Annotation1注解添加依赖
<!--spring context依赖-->
<!--当你引入Spring Context依赖之后,表示将Spring的基础依赖引入了-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>6.0.2</version>
</dependency>
<!--spring aop依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>6.0.2</version>
</dependency>
<!--spring aspects依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>6.0.2</version>
</dependency>
准备被代理的目标资源(计算器接口-加减乘除)
接口:
public interface Calculator {
int add(int i, int j);
int sub(int i, int j);
int mul(int i, int j);
int div(int i, int j);
}
实现类:
@Component
public class CalculatorImpl implements Calculator {
@Override
public int add(int i, int j) {
int result = i + j;
System.out.println("方法内部 result = " + result);
return result;
}
@Override
public int sub(int i, int j) {
int result = i - j;
System.out.println("方法内部 result = " + result);
return result;
}
@Override
public int mul(int i, int j) {
int result = i * j;
System.out.println("方法内部 result = " + result);
return result;
}
@Override
public int div(int i, int j) {
int result = i / j;
System.out.println("方法内部 result = " + result);
return result;
}
}
@Aspect //切面类
@Component //ioc容器
public class LogAspect {
//设置切入点和通知类型
//切入点表达式: execution(访问修饰符 增强方法返回类型 增强方法所在类全路径.方法名称(方法参数))
//通知类型:
// 前置 @Before(value="切入点表达式配置切入点")
//@Before(value = "execution(* com.xc.spring6.aop.annoaop.CalculatorImpl.*(..))")
@Before(value = "execution(public int com.xc.spring6.aop.annoaop.CalculatorImpl.*(..))")
public void beforeMethod(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
System.out.println("前置通知,方法名称:" + methodName + ",参数:" + Arrays.toString(args));
}
// 后置 @After()
//@After(value = "com.xc.spring6.aop.annoaop.LogAspect.pointCut()")
@After(value = "pointCut()")
public void afterMethod(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
System.out.println("后置通知,方法名称:" + methodName);
}
// 返回 @AfterReturning
@AfterReturning(value = "execution(* com.xc.spring6.aop.annoaop.CalculatorImpl.*(..))", returning = "result")
public void afterReturningMethod(JoinPoint joinPoint, Object result) {
String methodName = joinPoint.getSignature().getName();
System.out.println("返回通知,方法名称:" + methodName + ",返回结果:" + result);
}
// 异常 @AfterThrowing 获取到目标方法异常信息
//目标方法出现异常,这个通知执行
@AfterThrowing(value = "execution(* com.xc.spring6.aop.annoaop.CalculatorImpl.*(..))", throwing = "ex")
public void afterThrowingMethod(JoinPoint joinPoint, Throwable ex) {
String methodName = joinPoint.getSignature().getName();
System.out.println("异常通知,方法名称:" + methodName + ",异常信息:" + ex);
}
// 环绕 @Around()
@Around("execution(* com.xc.spring6.aop.annoaop.CalculatorImpl.*(..))")
public Object aroundMethod(ProceedingJoinPoint joinPoint) {
// 方法名、参数名
String methodName = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
Object result = null;
try {
System.out.println("环绕通知 ==> 目标方法之前执行");
//调用目标方法
result = joinPoint.proceed();
System.out.println("环绕通知 ==> 目标方法返回值之后");
} catch (Throwable throwable) {
throwable.printStackTrace();
System.out.println("环绕通知 ==> 目标方法出现异常执行");
} finally {
System.out.println("环绕通知 ==> 目标方法执行完毕执行");
}
return result;
}
//重用切入点表达式
@Pointcut(value = "execution(* com.xc.spring6.aop.annoaop.CalculatorImpl.*(..))")
public void pointCut() {
}
}
配置类:
@Configuration
@ComponentScan(basePackages = "com.xc")
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class AopConfig {
}
public class Client {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(AopConfig.class);
Calculator calculator = context.getBean(Calculator.class);
int result = calculator.add(2, 3);
System.out.println("执行结果:"+result);
}
}
输出结果:
环绕通知 ==> 目标方法之前执行
前置通知,方法名称:add,参数:[2, 3]
方法内部 result = 5
返回通知,方法名称:add,返回结果:5
后置通知,方法名称:add
环绕通知 ==> 目标方法返回值之后
环绕通知 ==> 目标方法执行完毕执行
执行结果:5
多个切面
时,切面的优先级控制切面的内外嵌套顺序 两个切面的执行顺序
新版顺序:环绕通知(后)放在最后执行,后置通知(最终通知)在返回通知后面
环绕通知 ==> 目标方法之前执行
前置通知,方法名称:add,参数:[2, 1]
方法内部 result = 3
环绕通知 ==> 目标方法返回值之后
后置通知,方法名称:add
返回通知,方法名称:add,返回结果:3
ajc
Spring AOP与单独使用@AspectJ区别
aspectj的类加载期织入的实现方式
asm
做字节码替换来达到AOP的目的编译期
(Compile-time weaving):把aspect类(aop的切面)和目标类(被aop的类)放在一起用ajc编译编译期后
(Post-compile weaving):目标类可能已经被打成了一个jar包,这时候也可以用ajc命令将jar再织入一次类加载期
(Load-time weaving):在jvm加载类的时候,做字节码替换