1. AOP
- AOP:面向切面(方面)编程,通俗的理解就是:扩展功能不通过修改源代码实现
- AOP:采用横向抽取机制,取代了传统 纵向 继成体系 重用代码(性能监视,事务管理,安全检查,缓存)
2. AOP实现机制 – 代理机制:
- Spring 的 AOP 的底层用到两种代理机制:
- JDK 的动态代理 :针对实现了接口的类产生代理.
- Cglib 的动态代理 :针对没有实现接口的类产生代理. 应用的是底层的字节码增强的技术 生成当前类
的子类对象.
3. AOP的相关概念
-
PointCut(切入点)
切入点实际上是用来定义横切逻辑规则的组件;
所谓切入点是指我们要对哪些Joinpoint进行拦截的定义;
【糯米藕】切藕的规则:距藕节3~4cm; -
Target(目标对象)
代理的目标对象 (要增强的类)
根据切入点(pointcut)的规则,找出来的需要被增强的类 / 对象。
【糯米藕】根据切藕的规则,找出来的符号条件的藕; -
JoinPoint(连接点)
所谓连接点是指那些被拦截到的点,在Spring中,这些点指的是方法,因为Spring只支持方法类型的连接点。
连接点是根据切入点(pointcut)的规则,在目标对象(Target)上要进行切面的那个位置,代码中体现为一个特定的方法;
【糯米藕】具体的一节藕上,下刀的位置; -
Advice(通知/增强)
所谓通知是指拦截到连接点之后所要做的事情就是通知;
通知分为前置通知,后置通知,异常通知,最终通知,环绕通知 (切面要完成的任务)
通知(Advice)可以看做是添加到目标对象(Target)上的新的功能;
通知体现为类的方法;
【糯米藕】米; -
Aspect(切面)
切面(Aspect)是切入点和通知(引介)的结合
代码中的切面是一个理解性的概念;
【糯米藕】在藕上下刀,形成的横截面; -
Introduction(引介)
引介是一种特殊的通知(Advice),在不修改类的代码的前提下,Introduction可以在运行期间为类动态添加一些方法或者Field
-
Weaving(织入)
把增强的应用到目标过程。【把advice应用到target的过程】
织入在开发过程中,需要进行xml或注解配置;
【糯米藕】在藕上下刀,把米灌入藕的全过程; -
Proxy(代理)
一个类被AOP织入增强后,就产生一个结果代理类
【糯米藕】糯米藕;
4. Spring 使用 AspectJ 进行 AOP 的开发: XML 的方式
4.1 引入jar包
<!-- https://mvnrepository.com/artifact/org.springframework/spring-aspects -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>4.3.21.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/aopalliance/aopalliance -->
<dependency>
<groupId>aopalliance</groupId>
<artifactId>aopalliance</artifactId>
<version>1.0</version>
</dependency>
4.2 引入 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:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
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/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
4.3 编写目标类
import com.hxzy.service.TeacherService;
public class TeacherServiceImpl implements TeacherService{
@Override
public void dianMing() {
System.out.println("TeacherServiceImpl ... dianMing()");
teacherDao.dianMing();
}
}
4.4 整合 Junit 单元测试
- 引入 spring-test.jar
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>4.3.21.RELEASE</version>
</dependency>
4.5 通知类型
- 前置通知 : 在目标方法执行之前执行.
- 后置通知 : 在目标方法执行之后执行
- 环绕通知 : 在目标方法执行前和执行后执行
- 异常抛出通知: 在目标方法执行出现 异常的时候 执行
- 最终通知 : 无论目标方法是否出现异常 最终通知都会 执行.
4.6 切入点表达式
语法结构:
execution( 【方法修饰符】 方法返回值 方法所属类 匹配方法名 ( 方法中的形参表 ) 方法申明抛出的异常 )
其中 方法返回值、匹配方法名、 方法中的形参表 的部分时不能省略的,各部分都支持通配符 “*” 来匹配全部。
比较特殊的为形参表部分,其支持两种通配符
“*”:代表一个任意类型的参数;
“…”:代表零个或多个任意类型的参数。
例如:
()匹配一个无参方法
(…)匹配一个可接受任意数量参数和类型的方法
(*)匹配一个接受一个任意类型参数的方法
(*,Integer)匹配一个接受两个参数的方法,第一个可以为任意类型,第二个必须为Integer。
分类 | 示例 | 描述 |
通过方法签名定义切入点 | execution(public * * (..)) | 匹配所有目标类的public方法,第一个*为返回类型,第二个*为方法名 |
execution(* save* (..)) | 匹配所有目标类以save开头的方法,第一个*代表返回类型 | |
execution( * *product(*,String)) | 匹配目标类所有以product结尾的方法,并且其方法的参数表第一个参数可为任意类型,第二个参数必须为String | |
通过类定义切入点 | execution(* aop_part.Demo1.service.*(..)) | 匹配service接口及其实现子类中的所有方法 |
通过包定义切入点 | execution(* aop_part.*(..)) | 匹配aop_part包下的所有类的所有方法,但不包括子包 |
execution(* aop_part..*(..)) | 匹配aop_part包下的所有类的所有方法,包括子包。(当".."出现再类名中时,后面必须跟“*”,表示包、子孙包下的所有类) | |
execution(* aop_part..*.*service.find*(..)) | 匹配aop_part包及其子包下的所有后缀名为service的类中,所有方法名必须以find为前缀的方法 | |
通过方法形参定义切入点 | execution(*foo(String,int)) | 匹配所有方法名为foo,且有两个参数,其中,第一个的类型为String,第二个的类型为int |
execution(* foo(String,..)) | 匹配所有方法名为foo,且至少含有一个参数,并且第一个参数为String的方法(后面可以有任意个类型不限的形参) |
4.7 编写一个切面类
import java.util.Arrays;
import org.apache.log4j.Logger;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
/**
* 增强 Advice
*
* @author Administrator
*/
public class Aspect01 {
Logger logger = Logger.getLogger(Aspect01.class);
/**
* 前置增强
*/
public void before(JoinPoint jp) {
logger.debug("前置增强:before");
// jp.getTarget() : 获得类名 , 获得目标对象(Target)
// jp.getSignature().getName() : 获得方法名
// jp.getArgs() : 获得参数数组
logger.debug("调用" + jp.getTarget() + " 的 " + jp.getSignature().getName() + " 方法,方法入参:"
+ Arrays.toString(jp.getArgs()));
}
/**
* 后置增强
*/
public void afterReturn(JoinPoint jp, Object returnValue) {
logger.debug("后置增强:afterReturn ... ");
// jp.getTarget() : 获得类名
// jp.getSignature().getName() : 获得方法名
// jp.getArgs() : 获得参数数组
logger.debug("调用" + jp.getTarget() + " 的 " + jp.getSignature().getName() + " 方法," + "方法入参:"
+ Arrays.toString(jp.getArgs()) + " ,方法返回值:" + returnValue);
}
/**
* 环绕增强
*
* @return
*/
public Object around(ProceedingJoinPoint jp) {
logger.debug("环绕增强 begin");
// jp.getTarget() : 获得类名 , 获得目标对象(Target)
// jp.getSignature().getName() : 获得方法名
// jp.getArgs() : 获得参数数组
logger.debug("调用" + jp.getTarget() + " 的 " + jp.getSignature().getName() + " 方法,方法入参:"
+ Arrays.toString(jp.getArgs()));
// 在环绕增强中,可以使用连接点的对象jp,直接调用目标方法,并得到返回值。
Object returnValue = null; // 返回值
try {
returnValue = jp.proceed();
System.out.println("返回值是:" + returnValue);
} catch (Throwable e) {
e.printStackTrace();
}
logger.debug("环绕增强 end ");
return returnValue;
}
/**
* 异常增强
* @param jp
* @param e
*/
public void afterThrowing(JoinPoint jp, RuntimeException e) {
logger.info(jp.getTarget() + " 的 " + jp.getSignature().getName() + " 方法发生异常,异常信息是:" + e.getMessage());
}
/**
* 最终增强
* @return
*/
public void after(JoinPoint jp) {
logger.debug("最终增强 ");
logger.debug(jp.getTarget() + " 的 " + jp.getSignature().getName() + " 方法结束执行。");
}
}
4.8 配置完成增强
<!-- 配置切面类(增强) -->
<bean id="aspect01" class="com.hxzy.aop.Aspect01"></bean>
<!-- 进行 aop 的配置 -->
<aop:config>
<!-- 切入点:指明哪些类的哪些方法需要进行增强,是一个规则 -->
<!-- [方法访问修饰符] 方法返回值 包名.类名.方法名(方法的参数) -->
<aop:pointcut expression="execution( * com.hxzy.service..*.*(..))" id="pointcut"/>
<!-- 配置切面 , 织入 -->
<aop:aspect ref="aspect01">
<!-- 前置增强 -->
<aop:before method="before" pointcut-ref="pointcut"/>
<!-- 后置增强 -->
<aop:after-returning method="afterReturn" pointcut-ref="pointcut" returning="returnValue"/>
<!-- 环绕增强 -->
<aop:around method="around" pointcut-ref="pointcut"/>
<!-- 异常增强 -->
<aop:after-throwing method="afterThrowing" pointcut-ref="pointcut" throwing="e"/>
<!-- 最终增强 -->
<aop:after method="after" pointcut-ref="pointcut"/>
</aop:aspect>
</aop:config>
5. Spring 使用 AspectJ 进行 AOP 的开发: 注解配置
5.1 Spring配置文件中开启aop注解的自动代理
<!-- 扫描带有注解的增强,需要引入aop的命名空间 -->
<aop:aspectj-autoproxy />
5.2 AspectJ的AOP注解
- 通知类型
- @Before : 前置增强
- @AfterReturning : 后置增强
- @Around : 环绕增强
- @AfterThrowing : 异常抛出增强
- @After : 最终增强
- 切入点
- @Pointcut
5.3 增强类中使用注解获得连接点信息
import java.util.Arrays;
import org.apache.log4j.Logger;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
/**
* 增强 Advice
* @author Administrator
*/
@Component // 注册bean , 配置切面
@Aspect //配置切面类
public class Aspect02 {
Logger logger = Logger.getLogger(Aspect02.class);
// 切入点
@Pointcut(value = "execution( * com.hxzy.service.impl.*.*(..))")
private void anyMethod() {}
/**
* 前置增强
*/
// @Before(value="execution( * com.hxzy.service.impl.*.*(..))")
// @Before("execution( * com.hxzy.service.impl.*.*(..))")
@Before("anyMethod()")
public void before(JoinPoint jp) {
logger.debug("前置增强:before");
logger.debug("调用" + jp.getTarget() + " 的 " + jp.getSignature().getName() + " 方法,方法入参:"
+ Arrays.toString(jp.getArgs()));
}
/**
* 后置增强
*/
@AfterReturning(pointcut = "anyMethod()",returning = "returnValue")
public void afterReturn(JoinPoint jp, Object returnValue) {
logger.debug("后置增强:afterReturn ... ");
logger.debug("调用" + jp.getTarget() + " 的 " + jp.getSignature().getName() + " 方法," + "方法入参:"
+ Arrays.toString(jp.getArgs()) + " ,方法返回值:" + returnValue);
}
/**
* 环绕增强
* @return
*/
@Around("anyMethod()")
public Object around(ProceedingJoinPoint jp) {
logger.debug("环绕增强 begin");
logger.debug("调用" + jp.getTarget() + " 的 " + jp.getSignature().getName() + " 方法,方法入参:"
+ Arrays.toString(jp.getArgs()));
// 在环绕增强中,可以使用连接点的对象jp,直接调用目标方法,并得到返回值。
Object returnValue = null; // 返回值
try {
returnValue = jp.proceed();
System.out.println("返回值是:" + returnValue);
} catch (Throwable e) {
e.printStackTrace();
}
logger.debug("环绕增强 end ");
return returnValue;
}
/**
* 异常增强
* @param jp
* @param e
*/
@AfterThrowing(pointcut = "anyMethod()" , throwing = "e")
public void afterThrowing(JoinPoint jp, RuntimeException e) {
logger.info(jp.getTarget() + " 的 " + jp.getSignature().getName() + " 方法发生异常,异常信息是:" + e.getMessage());
}
/**
* 最终增强
* @return
*/
@After("anyMethod()")
public void after(JoinPoint jp) {
logger.debug("最终增强 ");
logger.debug(jp.getTarget() + " 的 " + jp.getSignature().getName() + " 方法结束执行。");
}
}