Spring AOP概述
- Spring AOP 意为面向切面编程,以程序预编译方式和运行期动态代理的方式在不修改源代码的前提下程序动态的新添加功能的一种技术,AOP的原理是动态代理,
- 个人认为学习AOP之前一定要先掌握动态代理技术,
- AOP把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处基本相似,比如权限认证、日志、事物。AOP的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。
我们先了解一下AOP中几个名词概念
-
横切关注点:与我们业务逻辑无关的,但是我们需要对主业务方法进行拦截,拦截后该怎么处理,比如在主业务前后增加一些前置日志或者后置日志,验证信息等等,这些关注点就被称为横切关注点
-
切面(ASPECT):横切关注点 被模块化 的特殊对象。个人认为切面就是我们AOP横切的入口,我们需要在此类中横向切入一些 与主业务无关的部分,如日志安全等信息。
-
通知(Advice):切面必须要完成的工作。即,它是类中的一个方法。
-
目标(Target):被通知对象。
-
代理(Proxy):向目标对象应用通知之后创建的对象。
-
切入点(PointCut):对连接点进行拦截的定义。
-
连接点(JointPoint):与切入点匹配的执行点 也就是spring拦截到的主业务中的方法。
Advice 的类型
-
before advice, 在 join point 前被执行的 advice. 虽然 before advice 是在 join point 前被执行, 但是它并不能够阻止 join point 的执行, 除非发生了异常(即我们在 before advice 代码中, 不能人为地决定是否继续执行 join point 中的代码)
-
after return advice, 在一个 join point 正常返回后执行的 advice
-
after throwing advice, 当一个 join point 抛出异常后执行的 advice
-
after(final) advice, 无论一个 join point 是正常退出还是发生了异常, 都会被执行的 advice.
-
around advice, 在 join point 前和 joint point 退出后都执行的 advice. 这个是最常用的 advice.
-
introduction,introduction可以为原有的对象增加新的属性和方法
使用Spring 实现AOP
**使用AOP织入,需要导入依赖包 **
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
第一种方式:通过SpringAPI实现
业务接口及实现类
public interface UserService {
public void add();
public void delete();
public void update();
public void select();
}
public class UserServiceImpl implements UserService{
public void add() {
System.out.println("增加了一个用户");
}
public void delete() {
System.out.println("删除了一个用户");
}
public void update() {
System.out.println("更新了一个用户");
}
public void select() {
System.out.println("查询了一个用户");
}
}
第二步编写两个增强类,前置增强和后置增强
前置增强类实现MethodBeforeAdvice接口,在主业务方法执行前执行
public class BeforeAdvice implements MethodBeforeAdvice {
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("前置日志");
}
}
后置增强类实现AfterReturningAdvice 接口,在主业务方法执行后执行
public class AfterAdvice implements AfterReturningAdvice {
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("后置日志");
}
}
最后在xml中注册bean并实现AOP
<?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:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<context:annotation-config/>
注册bean
<bean id="userService" class="com.service.UserServiceImpl"/>
<bean id="beforeAdvice" class="com.MyLog.BeforeAdvice"/>
<bean id="afterAdvice" class="com.MyLog.AfterAdvice"/>
<aop:config>
这一步定义切入点 expresssion表达式中表示在UserServiceImpl类中所有方法
<aop:pointcut id="pointcut" expression="execution(* com.service.UserServiceImpl.*(..))"/>
这里第一个参数是执行环绕, 第二个参数是切入点
<aop:advisor advice-ref="beforeAdvice" pointcut-ref="pointcut"/>
<aop:advisor advice-ref="afterAdvice" pointcut-ref="pointcut"/>
</aop:config>
</beans>
测试
@Test
public void springTest(){
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
UserService userService = context.getBean("userService", UserService.class);
userService.addUser();
}
输出结果
第二种方式自定义类实现AOP
第一步 : 写我们自己的一个切入类 主业务依然是UserService
public class CustomPointcut {
public void before(){
System.out.println("前置日志");
}
public void after(){
System.out.println("后置日志");
}
}
在xml中注册bean并实现AOP
<bean id="custom" class="com.MyLog.CustomPointcut"/>
<aop:config>
<aop:aspect ref="custom">
<aop:pointcut id="customPointcut" expression="execution(* com.service.UserServiceImpl.*(..))"/>
<aop:before method="before" pointcut-ref="customPointcut"/>
<aop:after method="after" pointcut-ref="customPointcut"/>
</aop:aspect>
</aop:config>
测试类
@Test
public void springTest(){
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
UserService userService = context.getBean("userService", UserService.class);
userService.deleteUser();
System.out.println("=========================");
userService.addUser();
}
输出结果
第三种方式使用注解实现
使用注解的方式,切面由容器中的Bean使用@Aspect注解实现 ,我们将不在xml配置文件中进行配置,
第一步:编写一个基于注解实现的增强类, 我们的业务对象依然是方式依然UserService
@Component
@Aspect
public class AnnotationPointcut {
@Before("execution(* com.service.UserServiceImpl.*(..))")
public void before(){
System.out.println("前置日志");
}
@After("execution(* com.service.UserServiceImpl.*(..))")
public void after(){
System.out.println("后置日志");
}
@Around("execution(* com.service.UserServiceImpl.*(..))")
public void around(ProceedingJoinPoint jp) throws Throwable {
System.out.println("环绕前");
//执行主业务目标方法
jp.proceed();
System.out.println("环绕后");
}
}
测试
@Test
public void springTest(){
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
UserService userService = context.getBean("userService", UserService.class);
userService.deleteUser();
System.out.println("==================");
userService.addUser();
}
输出结果
可以看出切面执行环绕方法的环绕前后信息都是在最开始或者最终执行