简单来说就是:在方法中增加新的业务逻辑,但是不修改方法中的代码。
AOP采取横向抽取机制,将分散在各个方法中的重复代码提取出来,然后在程序编译或运行时,再将这些提取出来的代码应用到需要执行的地方。
AOP的基本概念
- 连接点:方法的调用
- 切入点:实际被增强的方法
- 通知/增强处理:AOP框架在特定的切入点执行的增强处理,即在定义好的切入点处要执行的程序代码。可以理解为切面类中的方法,它是切面的具体表现
- 切面:把通知/增强应用到切入点的过程
- 目标对象:所有被通知的对象,也称为被增强对象。如果AOP框架采用的是动态的AOP实现,那么该对象就是一个被代理对象
- 代理:将通知应用到目标对象之后,被动态创建的对象
- 织入:将切面代码插入到目标对象上,从而生成代理对象的过程
AOP的实现原理
-
有接口情况下,使用JDK动态代理(需要目标对象实现业务接口,代理类实现InvocationHandler 接口)
-
没有接口的情况下,使用CGLIB代理
JDK动态代理代码演示
public interface UserDao {
void addUser();
void deleteUser();
}
//被代理类(目标对象)
public class UserDaoImpl implements UserDao {
@Override
public void addUser() {
System.out.println("添加用户");
}
@Override
public void deleteUser() {
System.out.println("删除用户");
}
}
//切面类:可以存在多个通知 Advice(即增强的方法)
public class MyAspect {
//方法表示切面中的通知
public void Permissions() {
System.out.println("模拟检查权限. . . ");
}
public void log() {
System.out.println("模拟记录日志. . . ");
}
}
//代理类 需要实现 InvocationHandler 接口
public class JdkProxy implements InvocationHandler {
private UserDao userDao;
//创建代理方法
public Object createProxy(UserDao userDao) {
this.userDao = userDao;
//1.类加载器
ClassLoader classLoader = JdkProxy.class.getClassLoader();
//被代理对象实现的所有接口
Class<?>[] clazz = userDao.getClass().getInterfaces();
//使用代理类 进行增强 返回的是代理后的对象 (创建代理对象) this指的是代理类 JdkProxy 本身
return Proxy.newProxyInstance(classLoader,clazz,this);
}
/**
* 所有动态代理类的的方法调用,都会交由 invoke() 方法来处理
* @param proxy 被代理后的对象
* @param method 将要被执行的方法信息(反射)
* @param args 执行方法时需要的参数
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//声明切面
MyAspect myAspect = new MyAspect();
//前增强
myAspect.Permissions();
Object invoke = method.invoke(userDao, args);
//后增强
myAspect.log();
return invoke;
}
}
public class JdkTest {
public static void main(String[] args) {
//创建代理对象
JdkProxy jdkProxy = new JdkProxy();
//创建目标对象
UserDao userDao = new UserDaoImpl();
//从代理对象中获取增强后的目标对象
UserDao userDao1 = (UserDao) jdkProxy.createProxy(userDao);
//执行方法
userDao1.addUser();
userDao1.deleteUser();
}
}
目标对象UserDaoImpl实现了业务接口UserDao
代理类 JdkProxy 实现了 InvocationHandler
在程序运行时运用反射机制动态创建而成
SpringAOP,如果不强制使用CGLIB包,默认情况下使用的是JDK的动态代理来代理接口
CGLIB代理代码演示
如果目标对象没有实现接口的类进行处理,那么就可以使用CGLIB代理(子类代理)。
CGLIB是一个高性能开源的代码生成包,它采用非常底层的字节码技术,对指定的目标类生成一个子类,并对子类进行增强。Spring核心包已经集成了CGLIB所需要的包。
底层是通过使用字节码处理框架ASM来转换字节码并生成新的类
//目标对象
public class UserDao {
public void addUser() {
System.out.println("添加用户");
}
public void deleteUser() {
System.out.println("删除用户");
}
}
//切面类:可以存在多个通知 Advice(即增强的方法)
public class MyAspect {
//方法表示切面中的通知
public void Permissions() {
System.out.println("模拟检查权限. . . ");
}
public void log() {
System.out.println("模拟记录日志. . . ");
}
}
/**
* 代理类
* 创建动态对象 Enhancer(核心库)
* 然后调用了Enhancer类的setSuperclass()方法来确定目标对象
* 接下来调用了setCallback()方法添加回调函数 this代表的是代理类 Cglibproxy
* 最后通过return语句将创建的代理类对象返回
* intercept()方法会在程序执行目标方法时被调用,方法运行时将会执行切面类中的增强方法
*/
public class Cglibproxy implements MethodInterceptor {
//创建一个动态类对象
public Object createProxy(Object target) {
//创建一个动态类对象(CGLIB的核心库)
Enhancer enhancer = new Enhancer();
// 确定需要增强的类,设置其父类
enhancer.setSuperclass(target.getClass());
//添加回调函数
enhancer.setCallback(this);
//返回创建的代理类
return enhancer.create();
}
/**
* @param proxy CGlib根据指定父类生成的代理对象
* @param method 拦截的方法
* @param args 拦截方法的参数数组
* @param methodProxy 方法的代理对象,用于执行父类的方法
* @return
* @throws Throwable
*/
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
//创建切面类对象
MyAspect myAspect = new MyAspect();
//前增强
myAspect.Permissions();
Object obj = methodProxy.invokeSuper(proxy, args);
//后增强
myAspect.log();
return obj;
}
}
public class CglibTest {
public static void main(String[] args) {
//创建代理对象
Cglibproxy cglibproxy = new Cglibproxy();
//创建目标对象
UserDao userDao = new UserDao();
//获取增强后的目标对象
UserDao userDao1 = (UserDao) cglibproxy.createProxy(userDao);
//执行方法
userDao1.addUser();
userDao1.deleteUser();
}
}
注意:目标对象不能为final,否则报错 java.lang.IllegalArgumentException
基于代理类的AOP实现
在Spring中,使用ProxyFactoryBean是创建AOP代理的最基本方式
Spring的通知类型
Spring中的通知按照在目标类方法的连接点位置,可以分为以下5种类型:
- 前置通知:在目标方法执行前执行,可以应用于权限管理等功能
- 后置通知:在目标方法执行完执行,准备执行return的代码时通知,通常用作执行结果日志输出、结果加密等
- 环绕通知:在目标方法执行前和目标方法执行后都要执行,通常用作方法性能统计、接口耗时、统一加密、解密等
- 异常通知:程序抛出异常时执行,通常用作告警处理、事务回滚等
- 最终通知:相当于finally中执行的代码,通常用在关闭资源、清理缓存等业务逻辑中
ProxyFactoryBean
ProxyFactoryBean是FactoryBean接口的实现类,FactoryBean负责实例化一个Bean,而ProxyFactoryBean负责为其他Bean创建代理实例。
ProxyFactoryBean类中的常用可配置属性:
属性名称 | 描述 |
target | 代理的目标对象 |
proxyInterfaces | 代理要实现的接口 |
proxyTargetClass | 是否对类代理而不是接口,设置为true时,使用CGLIB代理 |
interceptorNames | 需要织入目标的Advice |
singleton | 返回的代理是否为单例,默认为true |
optimize | 当设置为true时,强制使用CGLIB |
代码演示
//被代理类(目标对象)
public class UserDaoImpl implements UserDao {
@Override
public void addUser() {
System.out.println("添加用户");
}
@Override
public void deleteUser() {
System.out.println("删除用户");
}
}
//切面类
public class MyAspect implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
permissions();
//执行目标方法
Object proceed = invocation.proceed();
log();
return proceed;
}
//方法表示切面中的通知
public void permissions() {
System.out.println("模拟检查权限. . . ");
}
public void log() {
System.out.println("模拟记录日志. . . ");
}
}
注意:切面类要实现的包全路径是: import org.aopalliance.intercept.MethodInterceptor;
配置文件如下:
<!--1.目标类 -->
<bean id="userDao" class="com.dfbz.dao.Impl.UserDaoImpl"></bean>
<!--2.切面类 -->
<bean id="myAspect" class="com.dfbz.factorybean.MyAspect"></bean>
<!--3.使用Spring代理工厂定义一个名称为 userDaoProxy的代理对象-->
<bean id="userDaoProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<!--3.1指定代理要实现的接口-->
<property name="proxyInterfaces" value="com.dfbz.dao.UserDao"></property>
<!--3.2指定目标对象-->
<property name="target" ref="userDao"></property>
<!--3.3指定切面,植入环绕通知-->
<property name="interceptorNames" value="myAspect"></property>
<!--3.4指定代理方式 true: CGLIB代理; false:JDK动态代理-->
<property name="proxyTargetClass" value="true"></property>
</bean>
首先通过<bean>元素定义了目标类和切面,然后使用ProxyFactoryBean类定义了代理对象。
在定义的代理对象中,分别通过<property>子元素指定了代理实现的接口、代理的目标对象、需要织入目标类的通知以及代理方式。
public class ProxyFactoryBeanTest {
public static void main(String[] args) {
//获取核心容器
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
//从spring容器中获取内容
UserDao userDao = (UserDao) ac.getBean("userDaoProxy");
//执行方法
userDao.addUser();
userDao.deleteUser();
}
}