这是我参与11月更文挑战的第19天,活动详情查看:2021最后一次更文挑战
AOP (Aspect Orient Programming),直译过来就是 面向切面编程。AOP 是一种编程思想,是面向对象编程(OOP)的一种补充。面向对象编程将程序抽象成各个层次的对象,而面向切面编程是将程序抽象成各个切面。
面向切面编程实现:通过动态代理(Java动态代理或者Gglib动态代理)方式实现
- 什么是代理模式?Java中实现:静态代理和动态代理
- Java动态代理和Cglib的动态代理的区别?
- Java动态代理:必须存在接口和实现类(符合编程习惯,面向接口开发,建立约定),代理类必须实现
- Code Generation Library:用类实现代理,对执行目标类(被代理类)动态生成一个子类,子类将重写父类的方法,有上述的约定,那么目标类必须可以被继承,所以目标类不能使用final修饰
代理对象=目标对象(被代理对象)+增强代码
总之一句话:AOP是指在程序的运行期间动态的将某段代码切入到指定方法、指定位置进行运行的编程方式。AOP的底层是使用动态代理实现的。个人理解,在不改变原来的代码基础增强新的功能。
搭建环境
1.导入AOP依赖
要想搭建AOP环境,首先,我们就需要在项目的pom.xml文件中引入AOP的依赖,如下所示,我使用的模块开发,版本继承于父模块
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
</dependency>
</dependencies>
复制代码
2.定义目标类
在com.hanpang.service.MathService
包下创建一个MathService类,用于处理数学计算上的一些逻辑。比如,我们在MathService类中定义了一个加法操作,返回两个整数类型值的和,如下所示:
package com.hanpang.service;
public class MathService {
public int sum(int i,int j){
System.out.println("目标方法执行");
return i+j;
}
}
复制代码
3.定义切面类
在com.hanpang.aspect
包下创建一个LogAspect切面类:
package com.hanpang.aspect;
import org.aspectj.lang.annotation.Aspect;
/**
* 打造切面类(包裹目标类)
*/
//告知Spring容器,该类是切面类
@Aspect
public class LogAspect {
}
复制代码
4.将目标类和切面类键入到IoC容器中,但是他们之间还没有产生联系
在com.hanpang.config
包中,新建AopConfig类,并使用@Configuration注解标注这是一个Spring的配置类,同时使用@EnableAspectJAutoProxy注解开启基于注解的AOP模式。在SpringConfig类中,使用@Bean注解将MathService类和LogAspect类加入到IOC容器中,如下所示:
package com.hanpang.config;
import com.hanpang.aspect.LogAspect;
import com.hanpang.service.MathService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
/**@Configuration配置类*/
@Configuration
/**开启AOP的注解模式,可以视同AspectJ第三方实现的注解*/
@EnableAspectJAutoProxy
public class SpringConfig {
/**
* 目标对象
*/
@Bean
public MathService mathService(){
return new MathService();
}
/**
* AOP对象,因为在LogAspect使用@Aspect注解
*/
@Bean
public LogAspect logAspect(){
return new LogAspect();
}
}
复制代码
5.通过表达式建立联系:实际就是通知什么时候什么地方执行什么方法?
package com.hanpang.aspect;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
/**
* 打造切面类(包裹目标类)
*/
//告知Spring容器,该类是切面类
@Aspect
public class LogAspect {
/*
* 切面类中,有提供AOP注解和表达式
* */
/**=====完整的标注写法:增强代码要跟MathServce类的sum方法产生反应======*/
/**修饰符[可省略] 返回值类型 目标类全路径.方法名称(形参个数和类型))*/
@Before("execution(public int com.hanpang.service.MathService.sum(int,int))")
public void logStart(){
System.out.println("加法运行开始之前,增加了功能");
}
}
复制代码
6.创建测试类
在 com.hanpang.test
包中创建AopTest测试类,并在AopTest类中创建testAop01()方法,如下所示。
package io.mykit.spring.test;
import com.hanpang.service.MathService;
import com.hanpang.aspect.AopConfig;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class AopTest {
@Test
public void testAop01(){
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AopConfig.class);
MathService mathService = context.getBean(MathService.class);
mathService.add(1, 2);
context.close();
}
}
复制代码
运行AopTest类中的testAop01()方法,输出的结果信息如下所示。
加法运行开始之前,增加了功能
目标方法执行
复制代码
可以看到,执行了切面类中的方法,并打印出了相关信息。但是没有打印参数列表和运行结果。
7.切面类中获取参数列表
那如果需要打印出参数列表和运行结果,该怎么办呢?别急,我们继续往下看。
要想打印出参数列表和运行结果,就需要对LogAspect类中的方法进行优化,优化后的结果如下所示。
/*
返回值类型使用:通配符*,代表任意的返回值类型
com.hanpang..service: 代表以com.hanpang开头并且以service包结尾,中间可以包含任意的层次
*Service:目标类必须是以为Service结尾的类
方法:通配符*,代表该类下的所有方法
形参中:使用..代表是任意个形参个数和类型
*/
@Before("execution(* com.hanpang..service.*Service.*(..))")
public void logStart02(JoinPoint joinPoint){
System.out.println("增强代码:获取参数");
System.out.println("参数:");
Arrays.stream(joinPoint.getArgs()).forEach(System.out::println);
System.out.println("增强的方法:"+joinPoint.getSignature().getName());
System.out.println("增强的目标类:"+joinPoint.getTarget().getClass().getName());
}
复制代码
这里,需要注意的是:JoinPoint参数一定要放在参数的第一位。
至此,我们的AOP测试环境就搭建成功了。
通知类型
通过代码来描述一下通知类型
package com.hanpang.aspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
/**当我容器创建对象,不确定是哪个层次的时候,就使用Component注解,标注该组件进行实例化*/
@Component
@Aspect//标注该类是切面类
public class LogAdviceAspect {
//通过表达式定义切面,在什么地方
@Pointcut("execution(public int com.hanpang.service.MathService.sum(int,int))")
public void pointCutSum(){}
@Pointcut("execution(public int com.hanpang.service.MathService.div(int,int))")
public void pointCutDiv(){}
@Pointcut("execution(public int com.hanpang.service.MathService.sum(int,int)) || execution(public int com.hanpang.service.MathService.div(int,int))")
public void pointCutAA(){}
@Pointcut("pointCutSum() || pointCutDiv()")
public void pointCut(){}
//在什么地方的什么时候做什么
//告知在什么时候执行
@Before("pointCut()")
public void logStart(JoinPoint jp){
//增强代码:做了什么
System.out.println("前置通知:无论执行代码是否正确,都会在执行代码之前,执行");
}
@After("pointCut()")
public void logEnd(JoinPoint jp){
//增强代码:做了什么
System.out.println("后置通知:无论执行代码是否正确,都会在执行代码之后,执行");
}
@AfterReturning(value="pointCut()",returning = "result")
public void logReturn(JoinPoint jp,int result){
//增强代码:做了什么
System.out.println("返回通知:执行代码是正确,执行,"+result);
}
@AfterThrowing(value="pointCut()",throwing = "ex")
public void logException(JoinPoint jp,Exception ex){
//增强代码:做了什么
System.out.println("异常通知:执行代码出现异常,执行,"+ex.getMessage());
}
}
复制代码
package com.hanpang.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import java.util.Arrays;
@Component
@Aspect
public class AroundAdviceAspect {
@Pointcut("execution(* com.hanpang..service.*Service.*(..))")
public void pointCut(){}
/**
* 环绕通知必须有返回值类型,包含了刚才我们使用所有通知类型
* */
@Around("pointCut()")
public Object aroundAdviceLog(ProceedingJoinPoint proceedingJoinPoint){
Object result = -1;
System.out.println("前置通知....前置增减...想想一下,调用了别方法,写了一些乱七八糟的代码");
System.out.println(Arrays.asList(proceedingJoinPoint.getArgs()));
try {
result = proceedingJoinPoint.proceed();
System.out.println("后置通知...后置增强...."+result);
System.out.println(proceedingJoinPoint.getTarget().getClass().getName());
} catch (Throwable e) {
System.out.println("异常通知...异常增强...."+e.getMessage());
}
System.out.println("后置通知...后置增强..如果异常处理进行throw new Exception或者return处理,不会执行");
return result;
}
}
复制代码
专业术语
通知(Advice):包含了需要用于多个应用对象的横切行为(增强的方法),“什么时候”和“做什么”
连接点(Join Point):是程序执行过程中通知哪些点(方法)
切点(PointCut):是定义在“什么地方”进行切入,哪些连接点会得到通知
切面(Aspect):是通知和切点的结合,通知和切点共同定义了切面的全部内容,“是什么”
增强的方法方式,可以通过引入和织入的方式进行代理
- 引入(Introduction):允许我们向现有的类中添加新方法或者属性
- 织入(Weaving):是包切面应用到目标对象并且创建新的代理对象的过程,(编译器织入、类加载期织入和运行期织入)