一、AOP
AOP:面向切面编程。在开发的时候,有时候我们需要对有些类中的方法添加日志功能或者权限校验等。这些与应用的主要逻辑无关但是又相当重要的功能,被称之为横切关注点。但这些功能又不属于我们类中的业务逻辑,如果特意创建一个实例对象来维护它,势必会带来代码的重复性。为了解决这个问题,spring添加了AOP这个特性,使得业务逻辑与这些横切关注点分离开。
1.术语
- 连接点(join point):
- 是可以插入切面的点,可以被拦截的点。
- 切入点(Pointcut):
- 我们选择只需要增强的连接点,减少切面的范围,切点定义了切面的位置,在何处执行通知。
- 通知(Advice):
- 通知是指切面的功能,定义了切面是什么,何时执行。
- 切面(Aspect):
- 切面是通知和切入点的组合。一个切面代表着:要对切入的点进行什么样的操作,何时执行,以及切面是什么。
- 织入(Weaving):
- 将切面应用到目标对象并创建代理对象的过程。
2.通知类型
-
前置通知(Before):
MethodBeforeAdvice
import org.springframework.aop.MethodBeforeAdvice; public class MyBeforeAdvice implements MethodBeforeAdvice { public void before(Method method, Object[] args, Object target) throws Throwable { System.out.println("前置通知======================="); } }
-
返回通知(After-returning):
AfterReturningAdvice
import org.springframework.aop.AfterReturningAdvice; public class MyAfterAdvice implements AfterReturningAdvice { public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable { System.out.println("后置通知========="); } }
-
环绕通知(Around):
MethodInterceptor
import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; public class MyAroundAdvice implements MethodInterceptor { public Object invoke(MethodInvocation invocation) throws Throwable { //在执行被代理类方法之前,添加功能 System.out.println("环绕通知之前============="); // 执行被代理类的方法 Object obj = invocation.proceed(); //在执行被代理类方法之后 System.out.println("环绕通知之后============="); System.out.println(); return null; } }
-
异常通知(After-throwing):
ThrowsAdvice
注意:类方法名必须为
afterThrowing
import org.springframework.aop.ThrowsAdvice; public class MyThrowAdvice implements ThrowsAdvice { public void afterThrowing(Exception e )throws Throwable { System.out.println("抛出异常==========="); } }
3.切面类型
-
Advisor:
普通切面,拦截目标类中所有方法。
<bean id="userDao" class="impl.UserDaoImpl"/> <!-- 配置通知类 --> <bean id="myBeforeAdvice" class="advice.MyBeforeAdvice"/> <- 配置代理类 --> <bean id="userDaoProxy" class="org.springframework.aop.framework.ProxyFactoryBean"> <-- 配置目标类 --> <property name="target" ref="userDao"/> <!-- 配置目标类的接口 --> <property name="proxyInterfaces" value="dao.UserDao"/> <!-- 配置advice类型 --> <property name="interceptorNames" value="myBeforeAdvice"/> </bean>
-
PointcutAdvisor:
带有切点的切面,只拦截需要增强的方法。
<!-- 配置通知类 --> <bean id="myBeforeAdvice" class="advice.MyBeforeAdvice"/> <!-- 配置正则表达式的PointcutAdvisor --> <bean id="advisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor"> <!-- 配置advice --> <property name="advice" ref="myBeforeAdvice"/> <!-- 配置要拦截的方法,使用正则表达式 --> <property name="patterns" value=".*find*"/> </bean> <bean id="proxy" class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="target" ref="customerDao"/> <property name="proxyTargetClass" value="true"/> <property name="interceptorNames" value="advisor"/> </bean>
-
注意:Spring Aop是基于代理来实现的,jdk代理和CGLib。jdk代理需要传入目标类的接口,不支持没有接口的类。所以就需要CGLib了,CGLib支持不带接口的类。在配置时,选择
proxyInterfaces
,使用jdk代理;选择proxyTargetClass
,使用CGLib代理。
4.AspectJ
使用传统的spring Aop ,必须为每个目标类创建一个ProxyFactoryBean
,在其中配置目标类、目标类的接口、通知类。如果横切关注点有很多的话,创建的bean也会随之增多,这大大增加了代码量。从Spring2.0后,Spring引进了AspectJ 这个框架,其java注解大大地简化了我们使用Aop的复杂度。
-
环境配置:需要导入的jar包
-
在maven下,导入以下4个jar包
<dependency> <groupId>aopalliance</groupId> <artifactId>aopalliance</artifactId> <version>1.0</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>4.2.4.RELEASE</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.13</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>4.2.4.RELEASE</version> </dependency>
-
-
定义切面:
要点:
@Aspect
:将一个POJO类定义为切面类@EnableAspectJAutoProxy
:开启自动代理,它会为带有@Aspect的切面类创建一个代理。这样我们就不用去配置代理需要的目标类,接口和通知类,是CGLib还是jdk代理,这些自动代理全部帮助我们解决了。@Pointcut
:定义一个切点,一个切点由pointcut表达式和pointcut签名组成。- 在Advice注解中使用pointcut签名即可使用切点。
- 语法格式:@pointcut(value = “execution(修饰符 返回类型 全限定类名.方法名(参数类型))”)
- 在execution()中,使用*代表全部,在方法名()中使用…代表任意参数,即method(…)。
- 使用 || && ^ 来添加bean或者切面的表达式。
@Aspect @EnableAspectJAutoProxy public class TrackCounter { private Map<Integer, Integer> trackCounts = new HashMap<Integer, Integer>(); @Pointcut(value = "execution(* com.sean.soudSystem.CompactDisc.playTrack(int)) && args(trackNumber)") public void TrackCounter(int trackNumber) { } @Before("TrackCounter(trackNumber)") public void countTrack(int trackNumber) { int currentCount = getPlayCount(trackNumber); trackCounts.put(trackNumber, currentCount + 1); } public int getPlayCount(int trackNumber) { return trackCounts.containsKey(trackNumber) ? trackCounts.get(trackNumber) : 0; } }