在之前的系列博客中介绍了Spring的核心——IoC,接下来介绍的面向切面编程就是Spring最为重要的功能之一,在数据库事务中切面编程被广泛使用。应该说,Spring AOP的原理是Spring技术中最难理解的一部分,因此为了更好的理解AOP的底层动态代理,接下来会通过一个简单的实例了解整个Spring AOP的一个简单执行过程。
在之前一直使用的Spring_Demo项目中新建一个名为“AOPExampleGame”的模块(module),并在其lib文件夹下导入如下jar包,并将jar包Add as Library。
在com.ccff.spring.game.pojo包下创建名为“Role”的POJO类,具体代码如下所示:
package com.ccff.spring.game.pojo;
public class Role {
private Long roleId;
private String roleName;
private String roleNote;
public Role() {
}
public Role(Long roleId, String roleName, String roleNote) {
this.roleId = roleId;
this.roleName = roleName;
this.roleNote = roleNote;
}
public Long getRoleId() {
return roleId;
}
public void setRoleId(Long roleId) {
this.roleId = roleId;
}
public String getRoleName() {
return roleName;
}
public void setRoleName(String roleName) {
this.roleName = roleName;
}
public String getRoleNote() {
return roleNote;
}
public void setRoleNote(String roleNote) {
this.roleNote = roleNote;
}
@Override
public String toString() {
return "Role{" +
"roleId=" + roleId +
", roleName='" + roleName + '\'' +
", roleNote='" + roleNote + '\'' +
'}';
}
}
在com.ccff.spring.game.interceptor包下创建名为“Interceptor”的接口。这是一个拦截器接口,同时在该包下对它创建名为“RoleServiceImpl”的一个实现类。如果对Spring AOP有一定了解的话,一定会发现该接口的定义和Spring AOP定义的消息是如此的接近。
Interceptor接口代码如下所示:
package com.ccff.spring.game.interceptor;
public interface Interceptor {
void before(Object object);
void after(Object object);
void afterReturning(Object object);
void afterThrowing(Object object);
}
RoleServiceImpl实现类代码如下所示:
package com.ccff.spring.game.interceptor;
public class RoleInterceptor implements Interceptor {
@Override
public void before(Object object) {
System.out.println("before方法执行,准备打印角色信息!");
}
@Override
public void after(Object object) {
System.out.println("after方法执行,已经完成角色信息的打印处理!");
}
@Override
public void afterReturning(Object object) {
System.out.println("afterReturning方法执行,刚刚完成打印功能,一切正常!");
}
@Override
public void afterThrowing(Object object) {
System.out.println("afterThrowing方法执行,打印功能执行异常,请查看角色对象是否为空");
}
}
此时,要求该实例生成的所有对象都使用一个代理工厂类去生成对应的对象。因此,在com.ccff.spring.game.factory包下创建名为“ProxyBeanFactory”的代理工厂类,具体代码如下所示:
package com.ccff.spring.game.factory;
import com.ccff.spring.game.interceptor.Interceptor;
public class ProxyBeanFactory {
public static <T> T getBean(T object, Interceptor interceptor) {
return (T) ProxyBeanUtil.getBean(object,interceptor);
}
}
通过上面的代码可知,通过调用代理工厂类ProxyBeanFactory 的getBean方法生成对应的对象,其实底层实际调用的是代理工厂工具类ProxyBeanUtil中的getBean方法。而无论在生成任何对象时,在生成的过程中都需要传递Interceptor接口参数。
当调用代理工厂工具类ProxyBeanUtil中的getBean方法后,存在如下约定(这里不讨论ogj对象为空或者拦截器interceptor为空的情况,因为这些并不具备很大的讨论价值,只需要很简单的判断就可以了)。
当一个对象通过代理工厂工具类ProxyBeanUtil中的getBean方法后,存在如下约定:
第一,Bean必须是一个实现了某一个接口的对象。
第二,最先会执行拦截器的before方法。
第三,其次执行Bean的方法(通过反射的形式)。
第四,执行Bean方法时,无论是否产生异常,都会执行after方法。
第五,执行Bean方法时,如果不产生异常,则执行afterRunning方法,如果产生异常,则执行afterThrowing方法。
这个约定实际已经十分接近Spring AOP对我们的约定,所以这个约定十分重要,其流程图如下所示:
代理工厂工具类ProxyBeanUtil是基于JDK动态代理模式的,动态代理是理解Spring AOP的基础。在com.ccff.spring.game.factory包下创建名为“ProxyBeanUtil”的类,并实现InvocationHandler接口用于动态代理。具体代码如下所示:
package com.ccff.spring.game.factory;
import com.ccff.spring.game.interceptor.Interceptor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class ProxyBeanUtil implements InvocationHandler {
private Object object; //声明代理对象
private Interceptor interceptor; //声明创建的拦截器
/**
* 获取动态代理对象。
* 使用当前类,作为代理方法,此时被代理对象执行方法的时候,会进入当前类的invoke方法里。
* @param object:被代理的对象
* @param interceptor:拦截器
* @return 动态代理对象
*/
public static Object getBean(Object object,Interceptor interceptor){
ProxyBeanUtil proxyBeanUtil = new ProxyBeanUtil();
//保存被代理的对象
proxyBeanUtil.object = object;
//保存拦截器
proxyBeanUtil.interceptor = interceptor;
//生成代理对象返回,并绑定代理方法
return Proxy.newProxyInstance(object.getClass().getClassLoader(),object.getClass().getInterfaces(),proxyBeanUtil);
}
/**
* 代理方法
* @param proxy:代理对象
* @param method:当前调用的方法
* @param args:参数
* @return 方法返回
* @throws Throwable 异常
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object retObj = null;
//是否产生异常
boolean exceptionFlag = false;
//before方法
interceptor.before(object);
try {
//反射原有方法
retObj = method.invoke(object,args);
}catch (Exception ex){
exceptionFlag = true;
}finally {
//after方法
interceptor.after(object);
}
if (exceptionFlag){
//afterThrowing方法
interceptor.afterThrowing(object);
}else {
//afterReturning方法
interceptor.afterReturning(object);
}
return retObj;
}
}
通过上面的代码可知,使用JDK的动态代理。首先,通过getBean方法保存了被代理的对象(object)、拦截器(interceptor),为之后的调用奠定了基础。
然后,生成了JDK动态代理对象(proxy),同时绑定了ProxyBeanUtil返回的对象作为其代理类,这样当代理对象调用方法的时候,就会进入到ProxyBeanUtil的invoke方法中。
在invoke方法中实现了拦截器方法,其中设置了异常标志位(exceptionFlag),通过这个标志位就能判断反射原有对象方法的时候是否发生了异常。但是,由于动态代理和反射的代码会比较抽象,更多的时候大部分的框架只会告诉你流程图和具体的流程方法的配置,Spring框架就是这样的。
通过上面的两小节已经得到了拦截器接口和Bean的获取方式,接下来在该小节定义该实例的业务逻辑——打印角色信息。
首先,在com.ccff.spring.game.service包下创建名为“RoleService”的业务服务接口,具体代码如下所示:
package com.ccff.spring.game.service;
import com.ccff.spring.game.pojo.Role;
public interface RoleService {
void printRole(Role role);
}
然后,在该包下创建该接口的实现类RoleServiceImpl,具体代码如下:
package com.ccff.spring.game.service;
import com.ccff.spring.game.pojo.Role;
public class RoleServiceImpl implements RoleService {
@Override
public void printRole(Role role){
System.out.println("【打印角色对象的信息】\n"+role.toString());
}
}
在com.ccff.spring.game.test包下创建名为“TestGame”的测试类,具体代码如下:
package com.ccff.spring.game.test;
import com.ccff.spring.game.factory.ProxyBeanFactory;
import com.ccff.spring.game.interceptor.Interceptor;
import com.ccff.spring.game.interceptor.RoleInterceptor;
import com.ccff.spring.game.pojo.Role;
import com.ccff.spring.game.service.RoleService;
import com.ccff.spring.game.service.RoleServiceImpl;
import org.junit.Test;
public class TestGame {
@Test
public void test(){
RoleService roleService = new RoleServiceImpl();
Interceptor interceptor = new RoleInterceptor();
RoleService proxy = ProxyBeanFactory.getBean(roleService,interceptor);
Role role = new Role(1L,"role-name-1","role_note-1");
proxy.printRole(role);
System.out.println("===============测试afterThrowing方法========================");
role = null;
proxy.printRole(role);
}
}
通过上面的代码可知,为了测试afterThrowing方法,因此将角色对象role设置为空,这样便能是得原有的打印方法发生异常。运行该测试类中的测试方法test,查看输出在控制台上的日志信息如下: