上一篇文章自己实现Spring AOP(一)环境搭建及知识准备,我搭建好了项目也写了一个例子,接下来我就要实现这个例子中的AOP功能。
在Spring中如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP,如果目标对象没有实现接口,必须采用CGLib(Code Generation Library)方式,下面我就先用JDK的动态代理实现一下。
在实现AOP功能前,先准备好要测试的相关类
准备好目标对象相关类和接口
UserDao接口
package edu.jyu.dao;
public interface UserDao {
public void add(String user);
public String getUser(String id);
}
UserDaoImpl类,实现了UserDao接口
package edu.jyu.dao;
public class UserDaoImpl implements UserDao {
@Override
public void add(String user) {
System.out.println("add " + user);
}
@Override
public String getUser(String id) {
System.out.println("getUser " + id);
return id + ":Jason";
}
}
还是以前置通知为例,我先仿造Spring弄个前置通知接口MethodBeforeAdvice
,要自定义前置通知就必须实现它,它里面有个before
方法,就是在目标方法执行前执行的
package edu.jyu.aop;
import java.lang.reflect.Method;
/**
* 前置通知接口
*
* @author Jason
*/
public interface MethodBeforeAdvice {
/**
*
* @param method
* 目标方法
* @param args
* 目标方法所需的参数
* @param target
* 目标对象
*/
void before(Method method, Object[] args, Object target);
}
然后自定义一个前置通知类MyBeforeAdvice
,它实现了MethodBeforeAdvice
接口
package edu.jyu.advice;
import java.lang.reflect.Method;
import edu.jyu.aop.MethodBeforeAdvice;
/**
* 自定义前置通知类
*
* @author Jason
*/
public class MyBeforeAdvice implements MethodBeforeAdvice {
@Override
public void before(Method method, Object[] args, Object target) {
System.out.println("前置通知");
}
}
还记得上一章我用的那个例子嘛,配置生成代理对象时需要指定的class
为
org.springframework.aop.framework.ProxyFactoryBean
类
id="userDaoProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
所以我也来定义一个ProxyFactoryBean
类来专门生成的代理对象,现在还没有任何方法
package edu.jyu.aop;
/**
* 用于生产代理对象的类
* @author Jason
*/
public class ProxyFactoryBean {
}
实现AOP相关的类和接口都准备好了,现在要在applicationContext.xml
文件中配置生成代理对象了
<beans>
<bean name="userDao" class="edu.jyu.dao.UserDaoImpl">bean>
<bean name="beforeAdvice" class="edu.jyu.advice.MyBeforeAdvice">bean>
<bean name="userDaoProxy" class="edu.jyu.aop.ProxyFactoryBean">
<property name="target" ref="userDao" />
<property name="proxyInterface" value="edu.jyu.dao.UserDao" />
<property name="interceptor" ref="beforeAdvice" />
bean>
beans>
这个配置文件和上一章的例子中的很像,比较多的区别在于配置生成代理对象,在配置织入目标通知那里,Spring中的name
是interceptorNames
,我的是
interceptor
,为了简单,我只打算让一个目标对象只能有一个通知去增强。
那么根据这个配置文件ProxyFactoryBean
文件就应该修改成如下
package edu.jyu.aop;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* 用于生产代理对象的类
*
* @author Jason
*/
public class ProxyFactoryBean {
// 目标对象
private Object target;
// 通知
private Object interceptor;
// 代理实现的接口
private String proxyInterface;
// 提供setter让容器注入属性
public void setTarget(Object target) {
this.target = target;
}
public void setInterceptor(Object interceptor) {
this.interceptor = interceptor;
}
public void setProxyInterface(String proxyInterface) {
this.proxyInterface = proxyInterface;
}
}
现在创建对象的类edu.jyu.core.ClassPathXmlApplicationContext
就需要进行修改了,因为它创建对象时得分两种情况,一种是创建一般对象,另外一种是创建代理对象。我们只需要修改它的createBeanByConfig
方法
/**
* 根据bean的配置信息创建bean对象
*
* @param bean
* @return
*/
private Object createBeanByConfig(Bean bean) {
// 根据bean信息创建对象
Class clazz = null;
Object beanObj = null;
try {
clazz = Class.forName(bean.getClassName());
// 创建bean对象
beanObj = clazz.newInstance();
// 获取bean对象中的property配置
List properties = bean.getProperties();
// 遍历bean对象中的property配置,并将对应的value或者ref注入到bean对象中
for (Property prop : properties) {
Map params = new HashMap<>();
if (prop.getValue() != null) {
params.put(prop.getName(), prop.getValue());
// 将value值注入到bean对象中
BeanUtils.populate(beanObj, params);
} else if (prop.getRef() != null) {
Object ref = context.get(prop.getRef());
// 如果依赖对象还未被加载则递归创建依赖的对象
if (ref == null) {
ref = createBeanByConfig(config.get(prop.getRef()));
}
params.put(prop.getName(), ref);
// 将ref对象注入bean对象中
BeanUtils.populate(beanObj, params);
}
}
// 说明是要创建代理对象
if (clazz.equals(ProxyFactoryBean.class)) {
ProxyFactoryBean factoryBean = (ProxyFactoryBean) beanObj;
// 创建代理对象
beanObj = factoryBean.createProxy();
}
} catch (Exception e1) {
e1.printStackTrace();
throw new RuntimeException("创建" + bean.getClassName() + "对象失败");
}
return beanObj;
}
也没改什么代码,只是新增了下面几句代码
// 说明是要创建代理对象
if (clazz.equals(ProxyFactoryBean.class)) {
ProxyFactoryBean factoryBean = (ProxyFactoryBean) beanObj;
// 创建代理对象
beanObj = factoryBean.createProxy();
}
就是判断一下创建好的对象的类是否为ProxyFactoryBean
,如果是的话,就将这个对象强转成ProxyFactoryBean
类型对象,然后调用它的createProxy()
方法来创建一个代理对象,并将这个代理对象作为最终结果beanObj
。
此时ProxyFactoryBean
还没有createProxy()
方法,所以现在就来创建这个方法并且实现它要完成的功能。代码如下
/**
* 创建代理对象
*
* @return
*/
public Object createProxy() {
// 判断有没有指定proxyInterface,没有指定就用CGLib方式
if (proxyInterface == null || proxyInterface.trim().length() == 0)
return createCGLibProxy();
// 使用JDK中的代理
return createJDKProxy();
}
这个方法很简单,就是根据有没有指定proxyInterface
来判断使用哪种动态代理方式去生成代理对象,没有接口的那就用CGLib,有的话就用JDK中的代理。
那现在用CGLib创建代理对象的createCGLibProxy()
方法和用JDK创建代理对象的createJDKProxy()
方法都还没有,我们要先创建这两个方法,createCGLibProxy()
方法留到下一章再实现,现在先来实现一下createJDKProxy()
方法
/**
* JDK方式创建代理对象
*
* @return
*/
private Object createJDKProxy() {
Class> clazz = null;
try {
clazz = Class.forName(proxyInterface);// 实现的接口
} catch (ClassNotFoundException e) {
e.printStackTrace();
throw new RuntimeException(proxyInterface + "找不到,请注意填写正确");
}
// JDK方式生成的代理对象
Object proxyInstance = Proxy.newProxyInstance(clazz.getClassLoader(), new Class[] { clazz },
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null;
// 要先判断interceptor是哪种通知类型,以决定执行目标方法的位置
// 判断interceptor是否为前置通知类型
if (interceptor instanceof MethodBeforeAdvice) {
MethodBeforeAdvice advice = (MethodBeforeAdvice) interceptor;
// 在目标方法执行前执行前置通知代码
advice.before(method, args, target);
// 执行目标方法
result = method.invoke(target, args);
}
return result;
}
});
return proxyInstance;
}
使用JDK创建代理对象的方法也不难,主要关注在InvocationHandler
那里,我先要判断一下interceptor
是属于哪种通知类型,因为不同的通知类型在于目标方法的执行顺序上有所不同,比如说前置通知在目标方法前执行,后置通知在目标方法后执行,现在的实现其实是很有问题的,比如说我新加了一个后置通知,那么我就要在加一条判断分支判断interceptor
是否为后置通知。这个问题我会在下一章我解决。
我再把ProxyFactoryBean
整个类的代码贴上来吧
package edu.jyu.aop;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* 用于生产代理对象的类
*
* @author Jason
*/
public class ProxyFactoryBean {
// 目标对象
private Object target;
// 通知
private Object interceptor;
// 代理实现的接口
private String proxyInterface;
// 提供setter让容器注入属性
public void setTarget(Object target) {
this.target = target;
}
public void setInterceptor(Object interceptor) {
this.interceptor = interceptor;
}
public void setProxyInterface(String proxyInterface) {
this.proxyInterface = proxyInterface;
}
/**
* 创建代理对象
*
* @return
*/
public Object createProxy() {
// 判断有没有指定proxyInterface,没有指定就用CGLib方式
if (proxyInterface == null || proxyInterface.trim().length() == 0)
return createCGLibProxy();
// 使用JDK中的代理
return createJDKProxy();
}
/**
* JDK方式创建代理对象
*
* @return
*/
private Object createJDKProxy() {
Class> clazz = null;
try {
clazz = Class.forName(proxyInterface);// 实现的接口
} catch (ClassNotFoundException e) {
e.printStackTrace();
throw new RuntimeException(proxyInterface + "找不到,请注意填写正确");
}
// JDK方式生成的代理对象
Object proxyInstance = Proxy.newProxyInstance(clazz.getClassLoader(), new Class[] { clazz },
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null;
// 要先判断interceptor是哪种通知类型,以决定执行目标方法的位置
// 判断interceptor是否为前置通知类型
if (interceptor instanceof MethodBeforeAdvice) {
MethodBeforeAdvice advice = (MethodBeforeAdvice) interceptor;
// 在目标方法执行前执行前置通知代码
advice.before(method, args, target);
// 执行目标方法
result = method.invoke(target, args);
}
return result;
}
});
return proxyInstance;
}
/**
* CGLib方式创建代理对象
*
* @return
*/
private Object createCGLibProxy() {
return null;
}
}
到此,JDK方式实现AOP就已经能行了,现在就可以写个测试类测试一下
package edu.jyu.aop;
import org.junit.Test;
import edu.jyu.core.BeanFactory;
import edu.jyu.core.ClassPathXmlApplicationContext;
import edu.jyu.dao.UserDao;
public class TestProxy {
@Test
public void testJDKProxy(){
BeanFactory ac = new ClassPathXmlApplicationContext("/applicationContext.xml");
UserDao userDao = (UserDao) ac.getBean("userDaoProxy");
System.out.println(userDao.getClass());
userDao.add("Jason");
String user = userDao.getUser("132");
System.out.println(user);
}
}
输出结果
class com.sun.proxy.$Proxy4
前置通知
add Jason
前置通知
getUser 132
132:Jason
第一行输出可以看到确实是JDK生成的代理对象,然后再执行userDao
的add
和getUser
方法前也执行了前置通知的方法,最后的getUser
的结果也没有错
现在,整个项目就已经完成一半了,另外一半就是增加CGLib方式创建代理对象还有优化一下架构。
JSpring AOP项目已经上传到Github上
https://github.com/HuangFromJYU/JSpring-AOP