简介
AOP的原理:基于代理思想,对原来的目标对象,创建代理对象。在不修改原对象代码情况下,通过代理对象,调用增加功能的代码,对原有业务方法进行增强。
应用场景:
- 记录日志
- 监控方法运行时间
- 权限控制
- 缓存优化
- 事务管理
Spring AOP两种编程方式
- Spring 1.2 开始支持AOP编程 (传统SpringAOP 编程)
- Spring 2.0 之后支持第三方 AOP框架(AspectJ )
AOP 编程底层实现机制
Spring AOP 是基于动态代理的,两种方式
- JDK 动态代理
- CGLIB 动态代理
动态代理和静态代理的区别?
动态代理:在虚拟机内部,运行的时候,动态生成代理类(运行时生成,runtime生成) ,并不是真正存在的类,Proxy$$(Proxy$$Customer)
静态代理:存在代理类
JDK 动态代理 (对象必须有接口)
必须对接口生成代理
-
采用Proxy对象,通过newProxyInstance方法为目标创建代理对象
该方法接收三个参数 :目标对象类加载器、目标对象实现的接口、代理后的处理程序 InvocationHandler
使用 Proxy类提供 newProxyInstance 方法对目标对象接口进行代理
实现 InvocationHandler 接口中 invoke方法,在目标对象每个方法调用时,都会执行invoke
代码
JdkProxyFactory.java
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class JdkProxyFactory implements InvocationHandler {
private Object target;
//构造注入
public JdkProxyFactory(Object target) {
this.target = target;
}
public Object getProxyObject() {
//匿名内部内
/* Object proxyObject = Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass()
.getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//....
return null;
}
});
return proxyObject;*/
/**
* loader - 定义代理类的类加载器
* interfaces - 目标对象的所有接口(原因:jdk要根据目标的接口来生成子对象)
* h - 回调函数,代理对象要调用的方法。(可以写增强的代码)
*
*/
return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(),this);
//this代表当前实现类的对象, 实际上就相当于 new JdkProxyFactory()
}
/**
* @param proxy 代理对象(慎用,会递归)
* @param method 目标对象原来的方法
* @param args 目标对象的参数
*
* @return Object方法调用的返回值, 不是 method.incoke()这个返回值
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if ("save".equals(method.getName())) {
//增强代码
writeLog();
//执行原来的代码
return method.invoke(target, args);
}
return method.invoke(target, args);
}
private static void writeLog() {
System.out.println("增强了写日志方法...");
}
/*
//内部内
private class MyInvocationHandler implements InvocationHandler{
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//.....
return null;
}
}*/
}
Cglib 动态代理 (不需要接口也可以代理)
Cglib的引入为了解决类的直接代理问题(生成代理子类),**不需要接口也可以代理 **
代码
pom.xml
<dependencies>
<!--Spring核心容器-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<!--SpringAOP相关的坐标-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.7</version>
</dependency>
<!--Spring整合单元测试-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<!--单元测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
CglibProxyFactory.java
import org.springframework.cglib.proxy.Callback;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class CglibProxyFactory implements MethodInterceptor {
private Object target;
public CglibProxyFactory(Object target) {
this.target = target;
}
public Object getProxyObject() {
//实例化Cglib增强代理器
Enhancer enhancer = new Enhancer();
//2.在增强器上设置相应的属性
// 设置目标的类,通过目标对象生成代理的子对象
enhancer.setSuperclass(target.getClass());
//设置回调方法的函数
enhancer.setCallback(this);
//通过增强器,获取带代理对象
Object proxyObject = enhancer.create();
return proxyObject;
}
/**
* @param proxy 代理子对象
* @param method 目标的方法对象
* @param agrs 目标的方法的参数
* @param methodProxy 代理对象的方法(已经被增强过的)
* @return
* @throws Throwable
*/
@Override
public Object intercept(Object proxy, Method method, Object[] agrs, MethodProxy methodProxy) throws Throwable {
//增强代码
System.out.println("增强了写日志...");
//执行目标对象之前的代码
Object object = method.invoke(target, agrs);
return object;
}
}
jdk:基于接口的代理,会生成目标对象的接口类型的子对象。
cglib:基于类的代理,会生成目标对象类型的子对象。
总结:
- Spring AOP 优先使用接口代理 (JDK动态代理)
- 如果没有接口,使用Cglib动态代理
AspectJ 切面编程 (xml)
AOP 相关术语
需求: 在save()方法逻辑调用之前进行权限的校验
Joinpoint 连接点:所有的方法都是连接点
Pointcut 切入点:就是需要增强的方法 eg:sava()方法需要增强,save()就是切入点
Advice 通知/增强:增强的逻辑 eg:权限的校验逻辑
Target 目标对象
Weaving 织入
Proxy 代理对象
-
Aspect 切面:把通知和切入点进行结合,就是把通知的逻辑用在切入点。
换句话:对哪些方法进行怎样的代码增强
通知类型
- 前置通知 : 在目标方法调用之前调用 (权限校验)
- 后置通知 : 在目标方法调用之后调用 returning 返回值
- 环绕通知 : 前置+后置 (事务) 在方法之前和方法之后执行. 特点:可以阻止目标方法执行
- 异常通知: 目标方法出现异常执行. 如果方法没有异常,不会执行. 特点:可以获得异常的信息
- 最终通知:指的是无论是否有异常,总是被执行的。
开发步骤
确定目标 bean (xml配置要增强的目标)
编写 Advice通知 (普通的JavaBean即可,不需实现接口)
配置aop: 切入点和切面
代码
pom.xml
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!--Spring AOP-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.7</version>
</dependency>
<!--Spring核心容器-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<!--Spring整合单元测试-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
xml 导入约束
<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"
xmlns:context="http://www.springframework.org/schema/context"
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 http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
</beans>
xml 中声明 增强的bean (扩展的bean) 和 被增强的bean (被扩展的bean)
<!--注册userService-->
<bean id="userService" class="perm.coco.service.Impl.UserServiceImpl"></bean>
<!--注册切面类-->
<bean id="myAspect" class="perm.coco.aspectj.MyAspect"></bean>
aop 配置
<!--配置Aop,切入点和切面-->
<aop:config>
<!--切入点-->
<aop:pointcut id="pointcut01" expression="execution(* perm.coco.service.Impl.UserServiceImpl.save(..))"/>
<aop:pointcut id="pointcut02" expression="execution(* perm.coco.service.Impl.UserServiceImpl.delete(..))"/>
<aop:pointcut id="pointcut03" expression="execution(* perm.coco.service.Impl.UserServiceImpl.findAll(..))"/>
<aop:pointcut id="pointcut04" expression="execution(* perm.coco.service.Impl.*.*(..))"/>
<aop:pointcut id="pointcut05" expression="execution(* perm.coco.service.Impl.*.*(..))"/>
<!--配置切面-->
<aop:aspect ref="myAspect">
<!--前置-->
<aop:before method="before" pointcut-ref="pointcut01"></aop:before>
<!--后置-->
<aop:after-returning method="after" pointcut-ref="pointcut02"></aop:after-returning>
<!--环绕-->
<aop:around method="around" pointcut-ref="pointcut03"></aop:around>
<!--异常通知-->
<aop:after-throwing method="afterThrowing" pointcut-ref="pointcut04" throwing="e"></aop:after-throwing>
<!--最终通知-->
<aop:after method="after" pointcut-ref="pointcut05"></aop:after>
</aop:aspect>
</aop:config>
前置通知
在原来方法执行前执行
应用: 权限控制 (权限不足,抛出异常)、 记录方法调用信息日志
<!--配置Aop,切入点和切面-->
<aop:config>
<!--切入点-->
<aop:pointcut id="pointcut01" expression="execution(* perm.coco.service.Impl.UserServiceImpl.save(..))"/>
<!--配置切面-->
<aop:aspect ref="myAspect">
<!--前置-->
<aop:before method="before" pointcut-ref="pointcut01"></aop:before>
</aop:config>
//前置
public void before() {
System.out.println("前置增强了...");
}
后置通知
<!--后置-->
<aop:after-returning method="after" pointcut-ref="pointcut02"></aop:after-returning>
//后置
public void afterReturning() {
System.out.println("后置打印日志....");
}
....待续....