AOP
AOP面向切面编程(Aspect Oriented Programming), 是一种编程范式
AOP 的主要目的是将横切关注点(如日志记录、事务管理、安全检查等)从业务逻辑中分离出来,以提高代码的模块化和可维护性。
AOP 的实现方式通常通过动态代理、字节码增强等技术。它使得开发者能够在不修改原有业务逻辑代码的情况下,对其进行功能增强和扩展。
利用AOP可以实现业务逻辑各个部分的隔离,从而使得业务逻辑各个部分的耦合性降低,提高程序的可重用性,同时提高开发效率。
相关术语
Joinpoint(连接点)
需要增强/代理的方法
Pointcut(切入点)
拦截方法设置的规则,类似于Filter
Advice(通知)
增强代码/代理代码(与连接点绑定)
-
前置通知(Before):执行连接点方法之前执行
-
环绕通知(Around):环绕连接点方法执行,执行前与执行后,包含原代码的执行
-
返回通知(After Running):在连接点方法返回结果之后执行,如果方法出现异常则不会执行此通知(通常是最后执行)
-
后置通知(After):执行连接点方法之后执行,出现异常也会执行
-
异常通知(After Throwing):在连接点方法抛出异常之后执行
target(目标对象)
被代理的对象,连接点方法所属的对象
Weaving(织入)
织入是动词,指的是将通知应用到切入点的形成切面的过程
Proxy(代理)
当织入完成后产生的代理类
Aspect(切面)
拦截处理类
切面(Aspect)=切入点(Pointcut)+通知(Advice)
Spring中的AOP
AspectJ
AspectJ是一个面向切面的编程,扩展的Java语言,定义了AOP语法
在Spring2.0以上版本中,可以使用基于AspectJ注解或基于XML配置的AOP
多切面的编程应该从外到里编写(增强需求从第一个或最后一个代理类开始编写)
切入点表达式
通过表达式定位一个或多个具体的连接点
语法
execution( [权限修饰符] [返回值类型] [简单类名/全类名] [方法名] ([参数列表]) )
tips:
表示任意权限修饰符和返回类型只需要一个 * 即可
表示任意参数列表可以使用 (..)
表示参数列表的某个参数后的后续参数任意:(Obj , ..)
任意类名下的某个类的所有方法:* . 类 . *
一级目录下的类的所有方法:类 . *
所有类下的某个方法:* . 方法名
基于XML配置AOP
Maven依赖:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>cn.xxx</groupId>
<artifactId>01_Spring</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<!--spring环境依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.12.RELEASE</version>
</dependency>
<!--spring整合aspectj的依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.12.RELEASE</version>
</dependency>
<!--spring整合Junit的依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.2.12.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
目标对象
public class UserDao {
public void save() {
System.out.println("新增用户成功...");
}
public void delete(Integer id) {
System.out.println("删除用户【" + id + "】成功...");
}
public void update() {
System.out.println("修改用户成功...");
}
public String query() {
System.out.println("查询用户成功...");
return "success";
}
}
切面类
public class MyAspect {
public void before(){
System.out.println("前置通知...");
}
public Object around(ProceedingJoinPoint around) throws Throwable { //环绕通知
System.out.println("环绕通知之前...");
// 执行下一个切面的通知,如果没有下一个切面那么执行目标方法
Object proceed = around.proceed();
System.out.println("环绕通知之后...");
return proceed; //将目标方法的返回值返回
}
public void afterReturning(){
System.out.println("返回通知...");
}
public void after(){
System.out.println("后置通知...");
}
public void afterThrowing(){
System.out.println("异常通知...");
}
}
Spring配置
-
<aop:aspectj-autoproxy />:开启AOP代理,当Spring IOC容器侦测到bean配置文件中的aop:aspectj-autoproxy元素时,会自动为与AspectJ切面匹配的bean创建代理
-
aop:config:aop核心配置,在此标签内配置切点、切面等
-
aop:pointcut:配置切入点
-
<aop:aspect ref="myAspect">:配置切面,并指定切面
-
<aop:before method="" pointcut-ref="">:前置通知,指定前置通知方法和采用哪个切点
-
<aop:around method="" pointcut-ref="">:环绕通知
-
<aop:after method="" pointcut-ref="">:后置通知
-
<aop:after-returning method="" pointcut-ref="">:后置返回通知
-
<aop:after-throwing method="" pointcut-ref="">:异常通知
<bean class="cn.xxx.dao.UserDao"></bean>
<!--
开启AOP自动代理
proxy-target-class: 是否采用GBLIB代理
true: 采用CGlib代理
false: 采用JDK动态代理(必须要有接口的时候才可以选择为false,如果没有接口会默认采用GBLIB)
-->
<aop:aspectj-autoproxy />
<!--配置切面类-->
<bean id="myAspect" class="cn.xxx.aspect.MyAspect"></bean>
<!--aop核心配置-->
<aop:config>
<!--
配置切点
id:切点名称
expression:切点表达式
-->
<aop:pointcut id="myPoint" expression="execution(public void cn.xxx.dao.UserDao.save(..))"/>
<!--
配置切面
before:前置通知
after-returning:返回通知
around:环绕通知
after:后置通知
after-throwing:异常通知
-->
<aop:aspect ref="myAspect">
<!--【前置通知】-->
<aop:before method="before" pointcut-ref="myPoint"/>
<!--【环绕通知】-->
<aop:around method="around" pointcut-ref="myPoint"/>
<!--【返回通知】-->
<aop:after-returning method="afterReturning" pointcut-ref="myPoint"/>
<!--【后置通知】-->
<aop:after method="after" pointcut-ref="myPoint"/>
<!--【异常通知】-->
<aop:after-throwing method="afterThrowing" pointcut-ref="myPoint"/>
</aop:aspect>
</aop:config>
测试代码
@RunWith(SpringRunner.class)
@ContextConfiguration("classpath:spring.xml")
public class Demo01 {
@Autowired
private UserDao userDao; //AOP中代理对象是实例对象的子类
@Test
public void test1()throws Exception{
userDao.save();
}
}
执行通知顺序(理想顺序)
-
正常情况:前置通知--->环绕通知--->目标方法--->环绕通知--->返回通知--->后置通知
-
出现异常情况:前置通知--->环绕通知--->目标方法--->环绕通知--->后置通知--->异常通知
AOP相关API
ProceedingJoinPoint类
ProceedingJoinPoint类是AOP编程中用于获取目标对象/方法信息的一个类;提供的方法如下:
-
getStaticPart():获取切入点表达式
-
getTarget():获取目标对象
-
getThis():获取当前对象(代理对象)
-
getArgs():获取代理对象在调用方法时传递的参数
-
getSignature():获取签名
-
proceed():执行目标方法
Signature类
Signature主要是描述目标对象的类;其提供的常用方法如下: ●getDeclaringType():获取目标对象 ●getDeclaringTypeName():获取接口的全类名 ●getName():获取目标方法名称
测试代码
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
// 需要执行目标方法
// 切入点表达式(execution(void cn.xxx.dao.UserDao.query()))
System.out.println(joinPoint.getStaticPart());
// 目标对象(class cn.xxx.dao.UserDao)
System.out.println(joinPoint.getTarget().getClass());
// 获取到代理对象(class cn.xxx.dao.UserDao$$EnhancerBySpringCGLIB$$4354d856)
System.out.println(joinPoint.getThis().getClass());
// 代理对象在调用方法时传递的参数
System.out.println(Arrays.toString(joinPoint.getArgs()));
System.out.println("----------------------");
// 获取方法签名
Signature signature = joinPoint.getSignature();
// 获取目标对象(class cn.xxx.dao.UserDao)
System.out.println(signature.getDeclaringType());
// 获取目标对象全类名(cn.xxx.dao.UserDao)
System.out.println(signature.getDeclaringTypeName());
// 获取执行目标方法的名称(query)
System.out.println(signature.getName());
// 执行下一个切面的通知,如果没有下一个切面那么执行目标方法,并获取目标方法的返回值
Object result = joinPoint.proceed();
// 将目标方法的返回值原封不动的返回了给了方法的调用者
return result;
}
注解配置切面
xml和注解搭配
spring.xml
<!--包扫描-->
<context:component-scan base-package="cn.xxx" />
<!-- 开启AOP自动代理 -->
<aop:aspectj-autoproxy />
切面类
@Aspect // 标注这是一个配置类
@Component // 交给IOC管理
public class MyAspect {
// 定义切点
@Pointcut("execution(* cn.xxx.dao.UserDao.save())")
public void pt(){}
// @Before("execution(* cn.xxx.dao.UserDao.*())") // 单独定义切点
@Before("pt()") // 引用定义的切点
public void before(){
System.out.println("前置通知...");
}
@Around("pt()")
public Object around(ProceedingJoinPoint around) throws Throwable { //环绕通知
System.out.println("环绕通知之前...");
Object proceed = around.proceed(); //执行目标方法
System.out.println("环绕通知之后...");
return proceed; //将目标方法的返回值返回
}
@After("pt()")
public void after(){
System.out.println("后置通知...");
}
@AfterReturning("pt()")
public void afterReturning(){
System.out.println("返回通知...");
}
@AfterThrowing("pt()")
public void afterThrowing(){
System.out.println("异常通知...");
}
}
纯注解
主要就是<aop:aspectj-autoproxy />改为注解配置
配置类
@ContextConfiguration
@ComponentScan("cn.xxx")
@EnableAspectJAutoProxy // 开启AOP自动代理(是否启用CGLIB代理)
public class SpringConfig {
}
多切面配置
使用@Order()注解来配置,数字越小,执行顺序越靠前
执行顺序:前置1——前置2——环绕1——环绕2——返回2——后置2——返回1——后置1
CGLIB的代理
Cglib代理,也叫作子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能的扩展;
Cglib是一个强大的高性能的代码生成包,它可以在运行期扩展java类与实现java接口。
CGLIB代理的使用
UserDao
@Repository
public class UserDao implements UserDaoInterface {
public void save(){
System.out.println("save..");
}
public void delete(){
System.out.println("delete..");
}
public void update(){
System.out.println("update..");
}
public void query(){
System.out.println("query..");
}
}
使用CGLIB代理UserDao
public class Demo04 {
@Test
public void test1() {
//返回一个service的子类
UserDao proxy = (UserDao) Enhancer.create(UserDao.class, new MethodInterceptor() {
/*
*
* proxy:代理对象
* method:要执行的方法对象
* args:要执行的方法的参数
* methodProxy:代理对象的方法对象
* */
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("增强的代码");
Object invoke = method.invoke(new UserDao());
/*
methodProxy:
代理对象的方法类,因为cglib代理对目标对象进行继承派生出子类,因此代理对象是目标对象的子类
此代理对象方法类也可以执行本代理对象方法
*/
// Object invoke = methodProxy.invoke(new UserDao(), args);
System.out.println("增强的代码");
return invoke;
}
});
proxy.save();
}
}
Tips:cgblib代理类时,这个类不能被final修饰;
AOP和CGLIB代理
修改代理方式
<!--
proxy-target-class: 是否要开启CGLIB代理
true: 开启CGLIB代理
false: 关闭CGLIB代理,采用JDK代理(默认值),如果该类没有实现任何接口的话,还是会采用GBLIB代理
-->
<aop:aspectj-autoproxy proxy-target-class="false"></aop:aspectj-autoproxy>
如果目标类没有实现任何接口的话,即使设置为false,还是会采用GBLIB代理
在 Spring 框架中,如果目标类实现了接口,默认使用 JDK 动态代理;如果目标类没有实现接口,则默认使用 CGLIB 代理。
使用JDK代理需要编写代理接口类并让目标对象实现接口,由于生成的代理类与目标对象是兄弟关系,所以代理类可以使用目标对象来接
使用CGLIB代理时,由于生成的代理类与目标对象是父子关系,所以代理类需要使用代理接口来接