文章目录
Spring AOP面向切面编程
Spring中的可插拔组件技术
Spring AOP
- Spring AOP - Aspect Oriented Programming 面向切面编程
- AOP的做法是将通用的, 与业务无关的功能抽象封装为切面类
- 切面可配置在目标方法的执行前, 后运行, 真正做到即插即用
- 最终目的: 在不修改源码的情况下对程序行为进行扩展
AOP关键概念
Spring AOP 与 AspectJ的关系
- Eclipse AspectJ 是一种基于Java平台的面向切面编程的语言
- Spring AOP使用AspectJWeaver来实现类与方法匹配
- Spring AOP利用代理模式实现对象运行时功能扩展
几个关键概念
概念 | 说明 |
---|---|
Aspect | 切面, 具体的可插拔组件功能类, 通常一个切面只实现一个通用功能 |
Target Class/Method | 目标类, 目标方法, 指真正要执行与业务相关的方法 |
PointCut | 切点, 使用execution表达式说明切面要作用在系统的哪些类上 |
JoinPoint | 连接点, 切面运行过程中是包含了目标类/目标方法元数据的对象 |
Advice | 通知, 说明具体的切面的执行时机, Spring包含了五种不同类型通知 |
AOP配置过程(XML配置)
- 依赖AspectJ
- 实现切面类, 切面方法
- 配置Aspect Bean
- 定义PointCut
- 配置Advice
JoinPoint核心方法
方法 | 说明 |
---|---|
Object getTarget() | 获取IoC容器内目标对象 |
Signature getSignature() | 获取目标方法 |
Object[] getArgs() | 获取目标方法参数值 |
PointCut切点表达式
*
通配符..
包通配符(..)
参数通配符
五种通知类型
通知类型 | 说明 |
---|---|
Before Advice | 前置通知, 目标方法运行前执行 |
After Returning Advice | 返回后通知, 目标方法返回数据后执行 |
After Throwing Advice | 异常通知, 目标方法抛出异常后执行 |
After Advice | 后置通知, 目标方法运行后执行 |
Around Advice | 环绕通知, 最强大通知, 自定义通知执行时机, 可决定目标方法是否运行 |
特殊的"通知" - 引介增强(了解)
- 引介增强(IntroductionInterceptor)是对类的增强, 而非方法
- 引介增强允许在运行时为目标类增加新属性或方法
- 引介增强允许在运行时改变类的行为, 让类随运行环境动态变更
详解环绕通知
要求:
- 环绕通知描述方法的声明中返回值必须是Object, 用来接受目标方法的返回值
- 要传入的参数类型是ProceedingJoinPoint, ProceedingJoinPoint是 JoinPoint的升级版, 在原有功能外, 还可以控制目标方法是否执行
- 通过
pjp.proceed()
方法来执行目标方法
XML配置
<bean id="methodChecker" class="com.zk.spring.aop.aspect.MethodChecker"/>
<aop:config>
<aop:pointcut id="pointcut" expression="execution(* com.zk..*.*(..))"/>
<aop:aspect ref="methodChecker">
<aop:around method="check" pointcut-ref="pointcut"/>
</aop:aspect>
</aop:config>
利用AOP进行方法性能筛查的例子
public class MethodChecker {
/**
* 环绕通知的描述方法
*
* @param pjp ProceedingJoinPoint 是 JoinPoint 的升级版, 在原有功能外, 还可以控制目标方法是否执行
* @return Object 是目标方法执行后的返回值
*/
public Object check(ProceedingJoinPoint pjp) throws Throwable {
try {
long startTime = System.currentTimeMillis();
// 执行目标方法
Object ret = pjp.proceed();
long endTime = System.currentTimeMillis();
long duration = endTime - startTime;
if (duration >= 1000) {
String className = pjp.getTarget().getClass().getName();
String methodName = pjp.getSignature().getName();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
String now = sdf.format(new Date());
System.out.println("=======" + now + ":" + className + "." + methodName
+ "(" + duration + "ms)=========");
}
return ret;
} catch (Throwable throwable) {
System.out.println("Exception message:" + throwable.getMessage());
throw throwable;
}
}
}
利用注解配置Spring AOP
-
在applicationContext.xml中增加
<!-- 开启组件扫描 --> <context:component-scan base-package="com.zk"/> <!-- 启用Spring AOP注解模式 --> <aop:aspectj-autoproxy/>
-
在切面类中使用
@Component
注解说明当前对象需要被IoC容器实例化并加载, 还要使用@Aspect
注解说明当前对象是切面类 -
在切面方法上在进行功能扩展时要加上与之对应的注解. 比如环绕通知
@Around("execution(* com.zk..*Service.*(..))")
Spring AOP实现原理
- Spring 基于代理模式实现功能动态扩展, 包含两种形式:
- 目标类拥有接口, 通过JDK动态代理实现功能扩展
- 目标类没有接口, 通过CGLib组件实现功能扩展
代理模式
- 代理模式是指通过代理对象对原对象实现功能的扩展
静态代理
静态代理是指必须手动创建代理类的代理模式使用方式
public class UserServiceProxy implements UserService {
/**
* 持有委托类的对象
*/
private UserService userService;
public UserServiceProxy(UserService userService) {
this.userService = userService;
}
@Override
public void createUser() {
System.out.println("=======" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS").format(new Date()) + "========");
userService.createUser();
}
}
// 客户类
public class Application {
public static void main(String[] args) {
// 直接面向委托类(业务逻辑实现类)
UserService userService1 = new UserServiceImpl();
userService1.createUser();
// 面向代理类
UserService userService2 = new UserServiceProxy(new UserServiceImpl());
userService2.createUser();
// 面向二房东(嵌套代理类)
UserService userService3 = new UserServiceProxy1(new UserServiceProxy(new UserServiceImpl()));
userService3.createUser();
}
}
问题:
- 因为静态代理是指必须手动创建代理类的代理模式使用方式, 每一个委托类都要创建一个代理类
- 所以如果系统中类非常多, 就要手动创建大量额外的代理类, 使代码非常臃肿
JDK动态代理
基于JDK反射的动态代理, 在运行时通过反射根据接口生成代理类
/**
* InvocationHandler是JDK提供的反射类, 用于在JDK动态代理中对目标方法进行增强
* InvocationHandler在使用角度上与切面类的环绕通知方法类似
*/
public class ProxyInvocationHandler implements InvocationHandler {
/**
* 目标对象
*/
private Object target;
public ProxyInvocationHandler(Object target) {
this.target = target;
}
/**
* 在invoke()方法中对目标方法进行增强
*
* @param proxy 代理类对象
* @param method 目标方法对象
* @param args 目标方法实参
* @return 目标方法运行后返回值
* @throws Throwable 目标方法抛出的异常
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("======" + new SimpleDateFormat("yyyy-MM-dd hh:mm:ss SSS").format(new Date()) + "==========");
// 调用目标方法, 在环绕通知方法中对应 ProceedingJoinPoint.proceed()
Object ret = method.invoke(target, args);
return ret;
}
public static void main(String[] args) {
UserService userService = new UserServiceImpl();
ProxyInvocationHandler invocationHandler = new ProxyInvocationHandler(userService);
// 动态创建代理类, 必须实现接口才可以运行
UserService userServiceProxy = (UserService) Proxy.newProxyInstance(userService.getClass().getClassLoader(),
userService.getClass().getInterfaces(),
invocationHandler);
userServiceProxy.createUser();
}
底层原理:
核心代码
Proxy.newProxyInstance(userService.getClass().getClassLoader(),
userService.getClass().getInterfaces(),
invocationHandler);
userServiceProxy.createUser();
Proxy
是JDK反射包中所提供的类, 它的作用是根据已有的接口生成代理类
CGLib实现代理类
- CGLib是运行时字节码增强技术 (Code Generation Library的缩写)
- Spring AOP扩展无接口的类时使用CGLib
- AOP会在运行时生成目标继承类字节码的方式进行行为扩展