AOP的全称是Aspect-Oriented Programming,即面向切面编程(也称面向方面编程)。它是面向对象编程(OOP)的一种补充,目前已成为一种比较成熟的编程方式。
在学习使用AOP之前,我们先了解一下AOP的专业术语。见下表:
术语 | 解释 |
---|---|
Aspect(切面) | 封装的用于横向插入系统功能(如事务、日志等)的类。该类要被Spring容器识别为切面,需要在配置文件通过<bean>元素指定。 |
Joinpoint(连接点) | 在程序执行过程中的某个阶段点。在AOP中,连接点就是指方法的调用。 |
Pointcut(切入点) | 切面与程序流程的交叉点,即那些需要处理的连接点,如下图所示。 |
Advice(通知 / 增强处理) | AOP框架在特定的切入点执行的增强处理,即在定义好的切入点处所要执行的程序代码。 |
Target Object(目标对象) | 指所有被通知的对象,也称为被增强对象。 |
Proxy(代理) | 将通知应用到目标对象之后,被动态创建的对象。 |
Weaving(织入) | 将切面代码插入到目标对象上,从而生成代理对象的过程。 |
切面、连接点和切入点
Spring AOP使用了两种代理机制:一种是基于JDK的动态代理,一种是基于CGLIB的动态代理。
1.JDK动态代理
JDK动态代理是通过java.lang.reflect.Proxy 类来实现的,我们可以调用Proxy类的newProxyInstance()方法来创建代理对象。
我们通过案例来演示Spring中JDK动态代理的实现过程。
(1).打开Eclipse,在SpringTest项目的src目录下,创建一个com.example.jdk包,包中创建用户管理接口UserDao接口。
package com.example.jdk;
public interface UserDao {
public void addUser();
public void deleteUser();
}
(2).打开Eclipse,在SpringTest项目的src目录下,在com.example.jdk包,创建UserDao接口的实现类UserDaoImpl类。
package com.example.jdk;
public class UserDaoImpl implements UserDao{
@Override
public void addUser() {
// TODO Auto-generated method stub
System.out.println("添加用户");
}
@Override
public void deleteUser() {
// TODO Auto-generated method stub
System.out.println("删除用户");
}
}
(3).打开Eclipse,在SpringTest项目的src目录下,在com.example.jdk包,创建切面类MyAspect类。
package com.example.jdk;
public class MyAspect {
public void startAffair(){
System.out.println("开始事务");
}
public void stopAffair(){
System.out.println("停止事务");
}
}
(4).打开Eclipse,在SpringTest项目的src目录下,在com.example.jdk包,创建代理类JdkProxy类,该类需要实现InvocationHandler接口,并编写代理方法。在代理方法中,需要通过Proxy类实现动态代理类。
JdkProxy实现了接口中invoke()方法,动态代理类所调用的方法都会由该方法处理。newProxyInstance()方法中包含3个参数,第1个参数是当前类的类加载器,第2个参数表示的是被代理对象实现的所有接口,第3个参数this代表的是代理类JdkProxy本身。
package com.example.jdk;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.text.SimpleDateFormat;
import java.util.Date;
public class JdkProxy implements InvocationHandler{
private UserDao userDao;
//创建代理方法
public Object createProxy(UserDao userDao){
this.userDao = userDao;
//类加载器
ClassLoader classLoader = JdkProxy.class.getClassLoader();
//被代理对象实现所有接口
Class[] classes = userDao.getClass().getInterfaces();
//使用代理类进行增强,返回代理后的对象
return Proxy.newProxyInstance(classLoader, classes, this);
}
/*
*proxy 被代理后的对象
*method 将要被执行的方法信息(反射)
*args 执行方法时需要的参数
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// TODO Auto-generated method stub
MyAspect myAspect = new MyAspect();
Date date = new Date();
SimpleDateFormat f = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
System.out.println(f.format(date));
myAspect.startAffair();
Object object = method.invoke(userDao, args);
myAspect.stopAffair();
System.out.println(f.format(date));
return object;
}
}
(5).打开Eclipse,在SpringTest项目的src目录下,在com.example.jdk包,创建测试类JdkTest类。
package com.example.jdk;
public class JdkTest {
public static void main(String[] args) {
//创建代理对象
JdkProxy jdkProxy = new JdkProxy();
//创建目标对象
UserDao userDao = new UserDaoImpl();
//从代理对象中获取增强后的目标对象
UserDao user = (UserDao) jdkProxy.createProxy(userDao);
user.addUser();
System.out.println("--------------------");
user.delUser();
}
}
运行结果:
2.CGLIB代理
通过上面的学习可知,JDK的动态代理用起来非常简单,但它有局限,使用动态代理的对象必须实现一个或多个接口。如果想代理没有实现接口的类,那么可以使用CGLIB代理。
CGLIB(Code Generation Library)是一个高性能开源的代码生成包,它采用非常底层的字节码技术,对指定的目标类生成一个子类,并对子类进行增强。在Spring的核心包中已经集成了CGLIB所需要的包,不需要另外导入JAR包。
我们通过案例来演示CGLIB代理的实现过程。
(1).打开Eclipse,在SpringTest项目的src目录下,创建一个com.example.cglib包,包中创建User类,不需要实现任何接口。
package com.example.cglib;
public class UserDao {
public void addUser(){
System.out.println("添加用户");
}
public void delUser(){
System.out.println("删除用户");
}
}
(2).打开Eclipse,在SpringTest项目的src目录下,在com.example.cglib包,创建切面类MyAspect类。
package com.example.cglib;
public class MyAspect {
public void startAffair(){
System.out.println("开始事务");
}
public void stopAffair(){
System.out.println("停止事务");
}
}
(3).打开Eclipse,在SpringTest项目的src目录下,在com.example.cglib包,创建代理类CglibProxy类,该类需要实现MethodInterceptor接口,并实现接口中的intercept()方法。
package com.example.cglib;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.Date;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
public class CglibProxy implements MethodInterceptor{
//代理方法
public Object createProxy(Object target){
//创建动态类对象
Enhancer enhancer = new Enhancer();
//确定需要增强的类,设置其父类
enhancer.setSuperclass(target.getClass());
//添加回调函数
enhancer.setCallback(this);
//返回创建的代理类
return enhancer.create();
}
/*
* arg0: CGLIB根据指定父类生成的代理的对象
* arg1: 拦截的方法
* arg2:拦截方法的参数数组
* arg3: 方法的代理类,用于执行父类的方法
*/
@Override
public Object intercept(Object arg0, Method arg1, Object[] arg2, MethodProxy arg3) throws Throwable {
//创建切面类对象
MyAspect myAspect = new MyAspect();
Date date = new Date();
SimpleDateFormat f = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
System.out.println(f.format(date));
myAspect.startAffair();
Object object = arg3.invokeSuper(arg0, arg2);
myAspect.stopAffair();
System.out.println(f.format(date));
return object;
}
}
(4).打开Eclipse,在SpringTest项目的src目录下,在com.example.cglib包,创建测试类CglibTest类。、
package com.example.cglib;
public class CglibTest {
public static void main(String[] args) {
//创建代理对象
CglibProxy cglibProxy = new CglibProxy();
//创建目标对象
UserDao userDao = new UserDao();
//获取增强后的目标对象
UserDao user = (UserDao) cglibProxy.createProxy(userDao);
user.addUser();
System.out.println("--------------------");
user.delUser();
}
}
运行结果:
通过以上的学习,我们对Spring中的两种代理模式有了一定的了解。Spring中的AOP代理默认是使用JDK动态代理的方式,而使用ProxyFactoryBean是创建AOP代理的最基本方式。
3.基于代理类的AOP实现
首先,我们需要了解一下Spring的通知类型, Spring按照通知在目标类方法的连接点位置,可以分为5种类型。
3.1 Spring的通知类型
org.springframework.aop.MethodBeforeAdvice(前置通知)
在目标方法执行前实施增强,可以应用于权限管理等功能。
org.springframework.aop.AfterReturningAdvice(后置通知)
在目标方法执行后实施增强,可以应用于关闭流、上传文件、删除临时文件等功能。
org.aopalliance.intercept.MethodInterceptor(环绕通知)
在目标方法执行前后实施增强,可以应用于日志、事务管理等功能。
org.springframework.aop.ThrowsAdvice(异常抛出通知)
在方法抛出异常后实施增强,可以应用于处理异常记录日志等功能。
org.springframework.aop.IntroductionInterceptor(引介通知)
在目标类中添加一些新的方法和属性,可以应用于修改老版本程序。
3.2 ProxyFactoryBean
ProxyFactoryBean是FactoryBean接口的实现类,FactoryBean负责实例化Bean,ProxyFactoryBean负责为其他Bean创建代理实例。
属性名称 | 描述 |
---|---|
target | 代理的目标对象。 |
proxyInterfaces | 代理要实现的接口。 |
proxyTargetClass | 是否对类进行代理(而不是对接口进行代理)。设置为true时,使用cglib代理。 |
interceptorNames | 需要织入目标的Advice。 |
singleton | :返回的代理对象是否为单实例,默认为true(单实例)。 |
optimize | 当设置为true时 ,强制使用cglib代理。 |
3.3 代理类的AOP实现
我们通过一个典型的环绕通知案例,演示使用ProxyFactoryBean创建AOP代理的过程。
(1).打开Eclipse,在SpringTest项目的src目录下,创建一个com.example.proxyfactorybean包,包中创建切面类MyAspect类。
package com.example.proxyfactorybean;
import java.text.SimpleDateFormat;
import java.util.Date;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
public class MyAspect implements MethodInterceptor{
public void startAffair(){
System.out.println("开始事务");
}
public void stopAffair(){
System.out.println("停止事务");
}
@Override
public Object invoke(MethodInvocation arg0) throws Throwable {
Date date = new Date();
SimpleDateFormat f = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
System.out.println(f.format(date));
startAffair();
Object object = arg0.proceed();
stopAffair();
System.out.println(f.format(date));
return object;
}
}
(2).打开Eclipse,在SpringTest项目的src目录的com.example.proxyfactorybean包,创建Spring的配置文件applicationContext.xml。
目标类直接使用JDK动态代理中的UserDaoImpl类,不再此包中编写。
<?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.xsd">
<!-- 目标类 -->
<bean id="userDao" class="com.example.jdk.UserDaoImpl"></bean>
<!-- 切面类 -->
<bean id="myAspect" class="com.example.proxyfactorybean.MyAspect"></bean>
<!-- 代理对象 -->
<bean id="MyProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<!-- 代理实现的接口 -->
<property name="proxyInterfaces" value="com.example.jdk.UserDao"></property>
<!-- 指定目标对象 -->
<property name="target" ref="userDao"></property>
<!-- 指定切面,植入环绕通知 -->
<property name="interceptorNames" value="myAspect"></property>
<!-- 指定代理方式 -->
<property name="proxyTargetClass" value="true"></property>
</bean>
</beans>
(5).打开Eclipse,在SpringTest项目的src目录下,在com.example.proxyfactorybean包,创建测试类ProxyFactoryBeanTest类。
package com.example.proxyfactorybean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.example.jdk.UserDao;
public class ProxyFactoryBeanTest {
public static void main(String[] args) {
ApplicationContext ac =
new ClassPathXmlApplicationContext("com/example/proxyfactorybean/applicationContext.xml");
UserDao userDao = (UserDao) ac.getBean("MyProxy");
userDao.addUser();
System.out.println("---------------------");
userDao.delUser();
}
}
运行结果:
其他的通知可以修改相应的代码来实现,这里不再实现,大家自行修改。
这就是Spring中AOP的动态代理,若有错漏,欢迎指正,希望大家一起学习进步!!!!
如果转载以及CV操作,请务必注明出处,谢谢!