1.什么是AOP?
AOP的全称是Aspect -Oriented Programming,即面向切面编程(也称面向方面编程)。它是面向对象编程(00P)的一种补充,目前已成为一种比较成熟的编程方式。
在传统的业务处理代码中,通常都会进行事务处理、日志记录等操作。虽然使用0OP可以通过组合或者继承的方式来达到代码的重用,但如果要实现某个功能(如日志记录),同样的代码仍然会分散到各个方法中。这样,如果想要关闭某个功能,或者对其进行修改,就必须要修改所有的相关方法。这不但增加了开发人员的工作量,而且提高了代码的出错率。
为了解决这一-问题,AOP思想随之产生。AOP采取横向抽取机制,将分散在各个方法中的重复代码提取出来,然后在程序编译或运行时,再将这些提取出来的代码应用到需要执行的地方。这种采用横向抽取机制的方式,采用传统的0OP思想显然是无法办到的,因为OOP只能实现父子关系的纵向的重用。虽然AOP是一种新的编程思想,但却不是0OP的替代品,它只是0OP的延伸和补充。
在AOP思想中,类与切面的关系如图所示:
通过Aspect (切面)分别在Class1和Class2的方法中加入了事务、日志、权限和异常等功能。
AOP的使用,使开发人员在编写业务逻辑时可以专心于核心业务,而不用过多地关注于其他业务逻辑的实现,这不但提高了开发效率,而且增强了代码的可维护性。
目前最流行的AOP框架有两个,分别为Spring AOP和AspectJ。Spring AOP使用纯Java实现,不需要专门的编译过程和类加载器,在运行期间通过代理方式向目标类织入增强的代码。AspectJ是一个基于Java语言的AOP框架,从Spring 2.0开始,Spring AOP引入了对AspectJ的支持,AspectJ扩展了Java语言,提供了一个专门的编译器,在编译时提供横向代码的织入。
2.AOP的相关术语
在学习使用AOP之前,首先要了解一下AOP的专业术语。这些术语包括Aspect、Joinpoint、Pointcut、 Advice、 Target Object、Proxy和Weaving,对于这些专业术语的解释,具体如下。
●Aspect (切面): 在实际应用中,切面通常是指封装的用于横向插入系统功能(如事务、日志等)的类,如图3-1中的Aspect。该类要被Spring容器识别为切面,需要在配置文件中通过元素指定。
●Joinpoint (连接点):在程序执行过程中的某个阶段点,它实际上是对象的一个操作,例如方法的调用或异常的抛出。在Spring AOP中,连接点就是指方法的调用。
●Pointcut (切入点): 是指切面与程序流程的交叉点,即那些需要处理的连接点,如上图所示。通常在程序中,切入点指的是类或者方法名,如某个通知要应用到所有以add开头的方法中,那么所有满足这一规则的方法都是切入点。
●Advice(通知/增强处理): AOP框架在特定的切入点执行的增强处理,即在定义好的切入点处所要执行的程序代码。可以将其理解为切面类中的方法,它是切面的具体实现。
●Target Object (目标对象):是指所有被通知的对象,也称为被增强对象。如果AOP框架采用的是动态的AOP实现,那么该对象就是一个被代理对象。
●Proxy(代理): 将通知应用到目标对象之后,被动态创建的对象。
● Weaving (织入):将切面代码插入到目标对象上,从而生成代理对象的过程。
3.动态代理
3.1jdk动态代理
所需要的框架包的下载地址 提取码:wpti
我们已经知道AOP中的代理就是由AOP框架动态生成的一个对象,该对象可以作为目标对象使用。Spring中的AOP代理,可以是JDK动态代理,也可以是CGLIB代理。
##3.1JDK动态代理
JDK动态代理是通过ja.langr.eflect.Proxy 类来实现的,我们可以调用Proxy 类的newProxyInstance()方法来创建代理对象。对于使用业务接口的类,Spring 默认会使用JDK动态代理来实现AOP。
UserDao.java
随便创建一个Web项目,并创建com.wz.proxy.jdk的包,导入框架包。
package com.wz.proxy.jdk;
public interface UserDao {
public void addUser();
public void deleteUser();
}
UserDaoImpl.java
package com.wz.proxy.jdk;
/*
* 目标类
* 将实现类作为目标类,将对其中的方法进行增强处理
*/
public class UserDaoImpl implements UserDao{
public void addUser(){
System.out.println("添加张三用户");
}
public void deleteUser(){
System.out.println("删除李四用户");
}
}
JDKProxy.java
package com.wz.proxy.jdk;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import com.wz.aspect.MyAspect;
/*
*JDK代理类:用于代理实现一一个或多个接口的类
*1、需要实现InvocationHandler接口,在接口方法invoke()中调用目标对象的方法,
*并且在调用前增加检查权限的功能,在调用后增加记录日志的功能。
*2、需要提供createProxy()返回目标对象的代理
*/
public class JDKProxy implements InvocationHandler{
// 声明目标类接口目标对象
private UserDao userDao;
// 创建代理方法
public Object createProxy(UserDao userDao) {
this.userDao = userDao;
// 1.当前类的类加载器
ClassLoader classLoader = JDKProxy.class.getClassLoader();
// 2.被代理对象实现的所有接口
Class[] clazz = userDao.getClass().getInterfaces();
/* 3.使用代理类,进行增强,返回的是代理后的对象
*
* 1.第一个参数:当前类的类加载器
* 2.第二个参数:被代理对象实现的所有接口
* 3.this代表的是JDKProxy本身
*/
return Proxy.newProxyInstance(classLoader,clazz,this);
}
/*
* 所有动态代理类的方法调用,都会交由invoke()方法去处理
* proxy 被代理后的对象
* method 将要被执行的方法信息(反射)
* args 执行方法时需要的参数
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
// TODO Auto-generated method stub
// 声明切面
MyAspect myAspect = new MyAspect();
// 前增强
myAspect.check_Permissions();
// 在目标类上调用方法,并传入参数
Object obj = method.invoke(userDao, args);
// 后增强
myAspect.log();
return obj;
}
}
MyAcpect.java
package com.wz.aspect;
/*
* 切面类:定义了一个模拟检查权限的方法和模拟记录日志的方法
* 这两个方法就是切面中的通知Advice(即增强方法)
*/
public class MyAspect {
public void check_Permissions(){
System.out.println("正在模拟检查权限ing");
}
public void log(){
System.out.println("正在模拟记录日志ing");
}
}
jdkTest.java
package com.wz.proxy.jdk;
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();
}
}
输出结果:
userDao 实例中的添加用户和删除用户的方法已被成功调用,并且在调用前后分别增加了检查权限和记录日志的功能。这种实现了接口的代理方式,就是Spring中的JDK动态代理。
3.2CGLIB代理
JDK动态代理的使用非常简单,但它还有一定的局限性一使用动态代理的对象必须实现一个或多个接口。如果要对没有实现接口的类进行代理,那么可以使用CGLIB代理
CGLIB ( Code Generation Library)是一个高性能开源的代码生成包,它采用非常底层的字节码技术,对指定的目标类生成-一个子类,并对子类进行增强。在Spring的核心包中已经集成了CGLIB所需要的包,所以开发中不需要另外导入JAR包。
在上述创建的Web项目中,并创建com.wz.proxy,cglib的包,导入框架包。
UserDao.java
package com.wz.proxy.cglib;
public class UserDao {
public void addUser(){
System.out.println("添加zsj用户");
}
public void deleteUser(){
System.out.println("删除zsj用户");
}
}
CglibProxy.java
package com.wz.proxy.cglib;
import java.lang.reflect.Method;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import com.wz.aspect.MyAspect;
/*
*CGLIB代理类:代理没有实现接口的类
*1、需要实现MethodInterceptor接口,在接口方法intercept()中调用目标对象的方法,
*并且在调用前增加检查权限的功能,在调用后增加记录日志的功能。
*2、需要提供createProxy()返回 目标对象的代理
*/
public class CglibProxy implements MethodInterceptor{
/*
* 创建代理,即创建目标对象的子类
* @param targrt 目标对象
* @return
*/
public Object createProxy(Object target) {
// 创建一个动态类对象
Enhancer enhancer = new Enhancer();
// 确定需要增强的类,设置其父类
enhancer.setSuperclass(target.getClass());
// 添加回调函数
enhancer.setCallback(this);
// 返回创建的代理类
return enhancer.create();
}
/**
* proxy CGlib根据指定父类生成的代理对象
* method 拦截的方法
* args 拦截方法的参数数组
* methodProxy 方法的代理对象,用于执行父类的方法
*/
@Override
public Object intercept(Object proxy, Method method, Object[] args,
MethodProxy methodProxy) throws Throwable {
// 创建切面类对象
MyAspect myAspect = new MyAspect();
// 前增强
myAspect.check_Permissions();
// 目标方法执行
Object obj = methodProxy.invokeSuper(proxy, args);
// 后增强
myAspect.log();
return obj;
}
}
首先创建了一个动态类对象Enhancer,它是CGLIB的核心类;然后调用了Enhancer 类的setSuperclass()方法来确定目标对象;接下来调用了setCallback()方法添加回调函数,其中的this代表的就是代理类CglibProxy本身;最后通过return语句将创建的代理类对象返回。intercept()方法会 在程序执行目标方法时被调用,方法运行时将会执行切面类中的增强方法。
CglibTest.java
package com.wz.proxy.cglib;
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();
}
}
输出结果如下:
4.基于代理类的AOP实现
到了这里,读者对Spring中的两种代理模式已经有了一定的了解。可是有的人就有疑问?我们这里不是Spring AOP代理方式吗?为什么要将JDK动态代理和CGLIB代理呢?实际上,Spring中的AOP代理默认就是使用JDK动态代理的方式来实现的。在Spring 中,使用ProxyFactoryBean是创建AOP代理的最基本方式。接下来就来展示一下。
ProxyFactoryBean是FactoryBean接口的实现类,FactoryBean 负责实例化-一个 Bean,ProxyFactoryBean负责为其他Bean创建代理实例。在Spring中,使用ProxyFactoryBean是创建AOP代理的基本方式。
在讲解具体的代理类之前,我们需要先了解一下Spring 的通知类型。Spring 中的通知按照
在目标类方法的连接点位置,可以分为以下5种类型。
●org.aopalliance intercept.MethodInterceptor (环绕通知)
在目标方法执行前后实施增强,可以应用于日志、事务管理等功能。
●org.springframework aop.MethodBeforeAdvice (前置通知)
在目标方法执行前实施增强,可以应用于权限管理等功能。
●org.springframework. aop AfterReturningAdvice (后置通知)
在目标方法执行后实施增强,可以应用于关闭流、上传文件、删除临时文件等功能。
●org.springframework.aop.ThrowsAdvice (异常通知)
在方法抛出异常后实施增强,可以应用于处理异常记录日志等功能。
●org.springframework.aop.IntroductionInterceptor (引介通知)
在目标类中添加一些新的方法和属性,可以应用于修改老版本程序(增强类)。
在上述创建的Web项目中,创建com.wz.factorybean的包,导入框架包。
MyAspect.java
package com.wz.factorybean;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
/*
* 使用ProxyFactoryBean创建AOP代理
* 切面类MyAspect:由 于实现环绕通知需要实现MethodInterceptor接口,所以需要实现invoke()方法。
* 在invoke()方法中执行目标方法。为了演示效果,在目标方法的前后分别执行了检查权限和记录日志的方法。
* 这两个方法就是增强的方法,即通知。
*
*/
public class MyAspect implements MethodInterceptor{
@Override
public Object invoke(MethodInvocation mi) throws Throwable {
// TODO Auto-generated method stub
check_Permissions();
//执行目标方法
Object obj=mi.proceed();
log();
return obj;
}
public void check_Permissions(){
System.out.println("ProxyFactoryBeanTest模拟检查权限");
}
public void log(){
System.out.println("ProxyFstoryBeanTet模拟记录日志");
}
}
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.3.xsd">
<!-- 1 目标类 -->
<bean id="userDao" class="com.wz.proxy.jdk.UserDaoImpl" />
<!-- 2 切面类 -->
<bean id="myAspect" class="com.wz.factorybean.MyAspect" />
<!-- 3 使用Spring代理工厂定义一个名称为userDaoProxy的代理对象 -->
<bean id="userDaoProxy"
class="org.springframework.aop.framework.ProxyFactoryBean">
<!-- 3.1 指定代理实现的接口-->
<property name="proxyInterfaces"
value="com.wz.proxy.jdk.UserDao" />
<!-- 3.2 指定目标对象 -->
<property name="target" ref="userDao" />
<!-- 3.3 指定切面,织入环绕通知 -->
<property name="interceptorNames" value="myAspect" />
<!-- 3.4 指定代理方式,true:使用cglib,false(默认):使用jdk动态代理 -->
<property name="proxyTargetClass" value="true" />
</bean>
</beans>
xml文件中的3.4是选择AOP代理的方式,是可以随意选择的。
ProxyFactoryBean.java
package com.wz.factorybean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.wz.proxy.jdk.UserDao;
//测试类
public class ProxyFactoryBeanTest {
public static void main(String args[]){
String xmlPath="com/wz/factorybean/applicationContext.xml";
ApplicationContext applicationContext=new ClassPathXmlApplicationContext(xmlPath);
//从spring容器获得代理对象
UserDao userdao =(UserDao) applicationContext.getBean("userDaoProxy");
//执行方法
userdao.addUser();
userdao.deleteUser();
}
}
输出结果如下:
总结:上述的Spring Aop的底层技术使用了动态代理。AOP核心调用过程,通过调用AOP代理类,开始一个个调用后面的(前置) 通知/拦截器链条,完成之后在调用目标方法,最后回来的时候接着调用(后置、结束)通知/拦截器链条。jdk动态代理、cglib动态代理。
AOP面向切面编程,实际上就是通过预编译或者动态代理技术在不修改源代码的情况下给原来的程序统-添加功能的一 -种技术。我们看几个关键词:
第一个是“动态代理技术”, 这个就是Spring Aop实现底层技术;
第二个”不修改源代码”, 这个就是Aop最关键的地方,也就是我们平时所说的非入侵性;
第三个"添加功能”,不改变原有的源代码,为程序添加功能。
其实我们在使用AOP框架的时候,很大一部分是因为它的非侵入性和添加功能的优点。通过上述的代码学习理解我们知道一个动态代理需要目标类,代理类,通知,切面类等。在什么情况下才用哪种代理方式,是需要经验的,只有多敲代码,多理解编程思维,才能游刃有余的编写代码。希望我的这篇文章对你有帮助!