目录
- 1. 什么是 AOP
- 2. 利用 Spring API 实现 AOP
- 3. 自定义类实现 AOP
- 4. 利用注解实现 AOP
1. 什么是 AOP
AOP(Aspect Oriented Programming),即面向切面编程,就是程序运行过程中在不改变程序源码的情况下,通过 预编译 和 运行期动态代理 来增强方法。利用 AOP 能够对业务逻辑的各部分进行隔离,从而降低业务逻辑各部分之间的耦合度,提高程序的可重用性,提高开发效率。
在 AOP 中,常见概念有如下几点:
- 横切关注点:横跨于应用程序多个模块的方法或功能,大多与我们的业务逻辑无关,但我们需要关注的部分,如日志、安全、缓存、事务等;
- 切入点(PointCut):即要添加代码的地方;
- 通知(Advice):即向切点动态添加的代码;
- 切面(ASPECT):切点 通知,是一个类;
- 连接点(JointPoint):和切点一样,是要添加代码的地方;
- 代理(Proxy):向目标对象应用通知后创建的对象;
- 目标(Target):被通知的对象;
Spring AOP 中,通过通知(Advice)来定义横切逻辑,支持 5 种类型的 Advice;
通知类型 | 连接点 | 接口 |
---|---|---|
前置通知 | 方法前 | org.springframework.aop.MethodBeforeAdvice |
后置通知 | 方法后 | org.springframework.aop.AfterReturningAdvice |
环绕通知 | 方法前后 | org.springframework.aop.MethodInterceptor |
异常抛出通知 | 方法抛出异常 | org.springframework.aop.ThrowsAdvice |
引介通知 | 类中添加新的方法属性 | org.springframework.aop.IntroductionInterceptor |
AOP 实际上是基于 Java 动态代理来实现,因此主要有以下三种实现方式:
- 使用 Spring API 实现
- 自定义类来实现 AOP
- 使用注解实现
2. 利用 Spring API 实现 AOP
- 准备业务接口
package com.cunyu.service;
/**
* @InterfaceName : UserService
* @Author : cunyu
* @Date : 2020/7/20 8:36
* @Version : 1.0
* @Description : 业务接口
**/
public interface UserService {
public void add();
public void delete();
public void update();
public void search();
}
- 实现业务接口
package com.cunyu.service;
/**
* @author : cunyu
* @version : 1.0
* @className : UserServiceImpl
* @date : 2020/7/20 8:37
* @description : 业务接口实现类
*/
public class UserServiceImpl implements UserService{
@Override
public void add() {
System.out.println("add user");
}
@Override
public void delete() {
System.out.println("delete user");
}
@Override
public void update() {
System.out.println("update user");
}
@Override
public void search() {
System.out.println("search user");
}
}
- 编写增强类
package com.cunyu.service;
import org.springframework.aop.AfterReturningAdvice;
import java.lang.reflect.Method;
/**
* @author : cunyu
* @version : 1.0
* @className : StrenthUSerAfter
* @date : 2020/7/20 8:48
* @description : 后置增强
*/
public class StrenthUSerAfter implements AfterReturningAdvice {
/**
* @param o 返回值
* @param method 被调用的方法
* @param objects 被调用方法的对象的参数
* @param o1 被调用的目标对象
* @return
* @description TODO
* @date 2020/7/20 8:49
* @author cunyu1943
* @version 1.0
*/
@Override
public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable {
System.out.println(o1.getClass().getName() " 类的 " method.getName() " 方法被执行,返回值是 " o);
}
}
- 配置 Spring 配置文件
<?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: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/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- Bean 注册 -->
<bean id="userService" class="com.cunyu.service.UserServiceImpl"/>
<bean id="userBefore" class="com.cunyu.service.StrenthUSerBefore"/>
<bean id="userAfter" class="com.cunyu.service.StrenthUSerAfter"/>
<!-- AOP 配置 -->
<aop:config>
<!--切入点 expression:表达式匹配要执行的方法-->
<aop:pointcut id="pointcut" expression="execution(* com.cunyu.service.UserServiceImpl.*(..))"/>
<!--执行环绕; advice-ref执行方法 . pointcut-ref切入点-->
<aop:advisor advice-ref="userAfter" pointcut-ref="pointcut"/>
</aop:config>
</beans>
- 进行单元测试
package com.cunyu.service;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @author : cunyu
* @version : 1.0
* @className : UserServiceTest
* @date : 2020/7/20 8:58
* @description : 测试类
*/
public class UserServiceTest {
@Test
public void testAdd() {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = (UserService) applicationContext.getBean("userService");
userService.add();
}
}
3. 自定义类实现 AOP
- 准备业务接口(同第一种方法)
- 实现业务接口(同第一种方法)
- 准备我们的自定义切入类
package com.cunyu.service;
/**
* @author : cunyu
* @version : 1.0
* @className : MyPointCut
* @date : 2020/7/20 9:08
* @description : 自定义切入类
*/
public class MyPointCut {
public void after() {
System.out.println("方法执行后...");
}
}
- Spring 配置文件中去配置
<?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: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/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- Bean 注册 -->
<bean id="userService" class="com.cunyu.service.UserServiceImpl"/>
<bean id="myPointCut" class="com.cunyu.service.MyPointCut"/>
<!-- AOP 配置 -->
<aop:config>
<aop:aspect ref="myPointCut">
<aop:pointcut id="mypointcut1" expression="execution(* com.cunyu.service.UserServiceImpl.*(..))"/>
<aop:after pointcut-ref="mypointcut1" method="after"/>
</aop:aspect>
</aop:config>
</beans>
- 单元测试
package com.cunyu.service;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @author : cunyu
* @version : 1.0
* @className : UserServiceTest
* @date : 2020/7/20 8:58
* @description : 测试类
*/
public class UserServiceTest {
@Test
public void testDelete() {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = (UserService) applicationContext.getBean("userService");
userService.delete();
}
}
4. 利用注解实现 AOP
- 准备业务接口(同第一种方法)
- 实现业务接口(同第一种方法)
- 编写一个使用注解实现的增强类
package com.cunyu.config;
import org.aspectj.lang.annotation.After;
/**
* @author : cunyu
* @version : 1.0
* @className : AnnotationPointCut
* @date : 2020/7/20 9:39
* @description : 基于注解实现的增强类
*/
public class AnnotationPointCut {
@After("execution(* com.cunyu.service.UserServiceImpl.*(..))")
public void after() {
System.out.println("注解实现,方法执行后...");
}
}
- 配置 Spring 配置
<?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: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/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- Bean 注册,并增加支持注解的配置 -->
<bean id="userService" class="com.cunyu.service.UserServiceImpl"/>
<bean id="annotationPointCut" class="com.cunyu.config.AnnotationPointCut"/>
<aop:aspectj-autoproxy/>
</beans>
- 单元测试
package com.cunyu.service;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @author : cunyu
* @version : 1.0
* @className : UserServiceTest
* @date : 2020/7/20 8:58
* @description : 测试类
*/
public class UserServiceTest {
@Test
public void testUpdate() {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = (UserService) applicationContext.getBean("userService");
userService.update();
}
}
注意: <aop:aspectj-autoproxy/>
声明会自动为 Spring 容器中配置 @Aspect
切面的 Bean 创建代理,植入切面。但在 Spring 内部,依然采用AnnotationAwareAspectJAutoProxyCreator
进行自动代理的创建工作,只是具体实现的细节已经被 <aop:aspectj-autoproxy />
隐藏起来了。此外,还有一个proxy-target-class
属性,默认为false
,表示使用 Jdk 动态代理注入增强,此时如果目标类没有声明接口,则 Spring 将自动使用CGLib
动态代理。当为 true
时,表示使用 CGLib
动态代理技术注入增强。
- END -
往期精选
Spring 代理模式知多少
利用注解进行 Spring 开发
Spring 中的自动装配