Spring AOP
1. AOP简介:
AOP全称为Aspect Oriented Programming,表示面向切面编程
由此可以得出,切面是一种将那些与业务无关,但业务模块都需要使用的功能封装起来的技术。这样便于减少系统的重复代码,降低模块之间的耦合度。
2. AOP基本术语
连接点(Joinpoint)
连接点就是被拦截到的程序执行点,因为Spring只支持方法类型的连接点,所以在Spring中连接点就是被拦截到的方法。连接点由两个信息确定:
- 方法( 表示程序执行点,即在哪个目标方法)
- 相对点(表示方位,即目标方法的什么位置,比如调用前,后等)
切入点(Pointcut)
切入点是对连接点进行拦截的条件定义,切入点表达式如何和连接点匹配是AOP的核心,Spring缺省使用AspectJ切入点语法。 一般认为,所有的方法都可以认为是连接点,但是我们并不希望在所有的方法上都添加通知,而切入点的作用就是提供一组规则来匹配连接点,给满足规则的连接点添加通知。
通知、增强 (Advice)
可以为切入点添加额外功能,分为:前置通知、后置通知、异常通知、环绕通知等。
目标对象 (Target)
目标对象指将要被增强的对象,即包含主业务逻辑的类对象。或者说是被一个或者多个切面所通知的对象。
织入 (Weaving)
织入是将切面和业务逻辑对象连接起来, 并创建通知代理的过程。织入可以在编译时,类加载时和运行时完成。在编译时进行织入就是静态代理,而在运行时进行织入则是动态代理
代理 (Proxy)
被AOP织入通知后,产生的结果类。
切面 (Aspect)
切面是一个横切关注点的模块化,一个切面能够包含同一个类型的不同增强方法,比如说事务处理和日志处理可以理解为两个切面。切面由切入点和通知组成,它既包含了横切逻辑的定义,也包括了切入点的定义。 Spring AOP就是负责实施切面的框架,它将切面所定义的横切逻辑织入到切面所指定的连接点中。
3. AOP应用
AOP应用场景有许多,最典型的应用场景就是日志和事务。
依赖:spring aop是依赖于spring ioc的,aop的通知类都需要放在spring的ioc容器中。
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.11</version>
</dependency>
3.1 创建业务层
aop是针对业务层做的增强处理。
public interface UserService {
int delete(String name);
}
public class UserServiceImpl implements UserService {
@Override
public int delete(String name) {
System.out.println("这句话是执行删除业务");
// int i=1/0;
return 0;
}
}
3.2. 配置业务层
AOP 功能的实现是基于 IOC 的,因此,业务层对象应该纳入 IOC 容器来管理。
<?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
http://www.springframework.org/schema/aop/spring-aop.xsd
">
<!--业务层对象放入spring的ioc容器中-->
<bean id="userService" class="com.dream.spring.aop.service.impl.UserServiceImpl"></bean>
</beans>
3.3 创建通知类
aop中的通知分为五种:前置通知,后置通知,异常抛出通知,环绕通知,最终通知。
这些通知的执行顺序如下:
try {
//前置通知
//---业务执行--------
//后置通知
} catch (Throwable t){
//异常抛出通知
} finally {
//最终通知
}
前置通知类(实现MethodBeforeAdvice接口):
在业务层方法执行之前执行。
import org.springframework.aop.MethodBeforeAdvice;
public class BeforeAdvice implements MethodBeforeAdvice {
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
//获取方法名
String methodName = method.getName();
//获取类名
String className = method.getDeclaringClass().getName();
System.out.println("这是前置通知:"+methodName+"----"+className+"参数:"+ Arrays.toString(args));
}
}
后置通知类(实现AfterReturningAdvice接口):
在业务层方法执行结束后执行
import org.springframework.aop.AfterReturningAdvice;
public class AfterAdvice implements AfterReturningAdvice {
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
//获取方法名
String methodName = method.getName();
//获取类名
String className = method.getDeclaringClass().getName();
System.out.println("这是后置通知:"+methodName+"----"+className+"参数:"+ Arrays.toString(args));
}
}
异常抛出通知类(实现ThrowsAdvice接口):
在业务层执行方法抛出异常时执行。
import org.springframework.aop.ThrowsAdvice;
public class ExceptionAdvice implements ThrowsAdvice {
/**
* ThrowsAdvice接口没有实现任何的方法,但是需要自己使用特定的方法才能生效
*/
public void afterThrowing(Method method, Object[] args, Object target, Exception ex){
String methodName = method.getName();
String className = method.getDeclaringClass().getName();
System.out.println("执行方法时:" + className + "." + methodName + ",参数:" + Arrays.toString(args) + ",发生了异常:" + ex.getMessage());
}
}
最终通知类(实现AfterAdvice接口)
在业务层方法执行最终才执行,无论是否抛出异常。
注意:AfterAdvice是spring aop中的接口。
import org.springframework.aop.AfterAdvice;
public class FinallyAdvice implements AfterAdvice {
@Override
protected void finalize() throws Throwable {
System.out.println("最终通知");
}
}
环绕通知类(实现MethodInterceptor接口):
包含前置、后置和异常通知和最终通知。
public class AroundAdvice implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
Method method = invocation.getMethod(); //获取被拦截的方法对象
Object[] args = invocation.getArguments();//获取方法的参数
Object target = invocation.getThis(); //获取代理对象
String methodName = method.getName();
String className = method.getDeclaringClass().getName();
try {
System.out.println("前置:准备执行方法:" + className + "." + methodName + ",参数:" + Arrays.toString(args));
//此处执行业务层的方法
Object returnValue = method.invoke(target, args);
System.out.println("后置:执行完方法:" + className + "." + methodName + ",参数:" + Arrays.toString(args) + ",得到返回值:" + returnValue);
//返回执行结果
return returnValue;
} catch (Throwable t){
System.out.println("异常抛出:执行方法时:" + className + "." + methodName + ",参数:" + Arrays.toString(args) + ",发生了异常:" + t.getMessage());
throw t;
} finally {
System.out.println("最终通知");
}
}
}
3.4 配置通知
这些通知类创建完成,还需要在xml配置文件中配置相关信息才能使用(装入ioc容器)
<?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
http://www.springframework.org/schema/aop/spring-aop.xsd
">
<bean id="userService" class="com.dream.spring.aop.service.impl.UserServiceImpl"></bean>
<!--这是配置 前置通知 对象-->
<bean id="before" class="com.dream.spring.aop.advice.BeforeAdvice"/>
<!--这是配置 后置通知 对象-->
<bean id="after" class="com.dream.spring.aop.advice.AfterAdvice"/>
<!--这是配置 异常通知 对象-->
<bean id="exception" class="com.dream.spring.aop.advice.ExceptionAdvice"/>
<!--这是配置 最终通知 对象-->
<bean id="finally" class="com.dream.spring.aop.advice.FinallyAdvice"/>
<!--这是配置 环绕通知 对象-->
<bean id="around" class="com.dream.spring.aop.interceptor.AroundAdvice"/>
<aop:config>
<!--配置切入点-->
<!--
pointcut表示切点,也就是通知会在哪些位置触发
expression表示切点表达式,切点表达式必须是execution(), execution()方法中的参数必须配置到方法上
第一个 * 表示任意访问修饰符
com.dream.spring.aop.service.. 最后的两个..表示service包下面的所有子包中的类
*(..) 表示任意方法, 如果()中没有..,则表示不带参数的方法;有,就表示带任意参数
execution(* com.dream.spring.aop.service..*(..))
表示service包下的所有子包的所有类的所有方法,都作为切入点
-->
<aop:pointcut id="pc" expression="execution(* com.dream.spring.aop.service..*(..))"/>
<!--这里是配置前置通知器-->
<!--该通知器的bean id引用、切入点引用、-->
<aop:advisor advice-ref="before" pointcut-ref="pc"/>
<!--这里是配置后置通知器-->
<!--该通知器的bean id引用、切入点引用、-->
<aop:advisor advice-ref="after" pointcut-ref="pc"/>
<!--这里是配置异常通知器-->
<!--该通知器的bean id引用、切入点引用、-->
<aop:advisor advice-ref="exception" pointcut-ref="pc"/>
<!--这里是配置最终通知器-->
<aop:advisor advice-ref="finally" pointcut-ref="pc"/>
<!--这里是配置异常通知器-->
<aop:advisor advice-ref="around" pointcut-ref="pc"/>
</aop:config>
</beans>
配置完成在调用service层方法时,就能够触发这些通知器。
3.5 测试
@Test
public void userDeleteTest(){
//加载applicationContext.xml配置文件
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = applicationContext.getBean("userService", UserService.class);
userService.delete("dengshuo");
}
下一章介绍 aop的框架 AspectJ和java中的代理模式