自己实现Spring AOP(二)JDK代理实现AOP

#前言
上一篇文章自己实现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("前置通知");
	}

}

#ProxyFactoryBean
还记得上一章我用的那个例子嘛,配置生成代理对象时需要指定的class
org.springframework.aop.framework.ProxyFactoryBean

<bean id="userDaoProxy" class="org.springframework.aop.framework.ProxyFactoryBean">

所以我也来定义一个ProxyFactoryBean类来专门生成的代理对象,现在还没有任何方法

package edu.jyu.aop;
/**
 * 用于生产代理对象的类
* @author Jason
 */
public class ProxyFactoryBean {

}

#配置
实现AOP相关的类和接口都准备好了,现在要在applicationContext.xml文件中配置生成代理对象了

<?xml version="1.0" encoding="UTF-8"?>
<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中的nameinterceptorNames,我的是
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<Property> properties = bean.getProperties();
		// 遍历bean对象中的property配置,并将对应的value或者ref注入到bean对象中
		for (Property prop : properties) {
			Map<String, Object> 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生成的代理对象,然后再执行userDaoaddgetUser方法前也执行了前置通知的方法,最后的getUser的结果也没有错


现在,整个项目就已经完成一半了,另外一半就是增加CGLib方式创建代理对象还有优化一下架构。

JSpring AOP项目已经上传到Github上
https://github.com/HuangFromJYU/JSpring-AOP

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值