引入
概念
JDK动态代理可以理解成jdk帮我们创建一个代理对象,来帮我们动态管理真实对象中的构造函数,方法等信息.真实的对象只需要负责主要业务逻辑,其余的交给代理类.
生活中的例子
通常,由于身在异乡等各种因素,我们在租房子的时候,往往会通过中介(被委托方)寻找各种房源.一般不会直接接触到房东.
实战
我们先看下静态代理的例子.模拟一下代理的意思.然后再引入动态代理.
静态代理
1.定义一个接口
public interface EmployeeService {
/**
* 保存员工注册信息
* @param username 用户名
* @param password 密码
*/
void save(String username,String password);
}
2.实现该接口,创建一个真实类,只做业务代码.
/**
* 房东 -- 真实对象
*/
public class EmployeeServiceImpl implements EmployeeService{
public void save(String username, String password) {
System.out.println("保存用户信息,用户名:"+username+"密码"+password);
}
}
3.创建一个代理类.
/**
* 代理类 -- 中介
*/
public class EmployeeServiceProxy implements EmployeeService{
//引入房东
private EmployeeService target;
public void setTarget(EmployeeService target) {
this.target = target;
}
private MyTransactionManager tx;
public void setTx(MyTransactionManager tx) {
this.tx = tx;
}
public void save(String username, String password) {
try {
//System.out.println("开启事务");
tx.begin();
target.save(username, password);
//System.out.println("提交事务");
tx.commit();
}catch (Exception e){
//System.out.println("回滚事务");
tx.rollback();
}
}
}
4.创建一个事务类
/**
* 事务类
*/
public class MyTransactionManager{
public void begin() {
System.out.println("开启事务");
}
public void commit() {
System.out.println("提交事务");
}
public void rollback() {
System.out.println("回滚事务");
}
}
5.配置文件
<bean id="tx" class="com.mycode.util.staticproxy.MyTransactionManager"/>
<!--代理对象引用真实对象-->
<bean id="employeeService" class="com.mycode.util.staticproxy.EmployeeServiceProxy">
<property name="target">
<bean class="com.mycode.util.staticproxy.EmployeeServiceImpl"/>
</property>
<property name="tx" ref="tx"/>
</bean>
6.最后测试,执行下述代码:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:application.xml")
public class staticProxyTest {
//按照类型找 此时类型为:com.mycode.util.staticproxy.EmployeeService
@Autowired
private EmployeeService employeeService;
@Test
public void test(){
employeeService.save("1","2");
}
}
我们来理一下上面代码.
首先创建了一个接口并定义save()方法;之后用真实对象"EmployeeServiceImpl"实现该接口,并在该真实对象中只实现业务处理,其余不做处理;接着定义了一个代理类,这个代理类不仅去调用真实对象的业务代码(类似与中介帮你推荐房子,是主要工作),而且还简单模拟了一下事务操作(这里可以想象成中介帮你推荐房子前后的准备工作与结束工作,以及你拒绝他房源之后的工作).
之后在配置文件里配置代理类,并注入事务管理和真实对象,交给Spring容器管理.
在测试类中,注入EmployeeService接口,我们知道@Autowired注解会按照类型去寻找,如果存在多个,再按照注入的名字去查找,这里将EmployeeServiceImpl真实对象藏在了代理对象EmployeeServiceProxy中,所以无论怎么找,只会找到代理类.执行测试代码:
开启事务
保存用户信息,用户名:1密码2
提交事务
小结:通过静态代理的例子,我们大致了解了何为代理,真实对象和代理对象都实现了同一个接口,真实对象只处理业务代码,而代理对象在处理真实对象业务逻辑之外,也支持事务等处理.但是静态代理劣势也很明显,就是后面如果我们要想拥有班级的代理,房贷的代理,车贷的代理的话,我们要创建无数个代理对象,很是不妥.
JDK动态代理
首先我们看一下JDK动态代理中比较重要的方法.第一个参数为类加载器,第二个参数为真实对象实现的接口,第三个为需要代理对象做的事情.这个方法就是帮我们创建代理对象并返回代理对象.
Proxy.newProxyInstance(
ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h
);
看一下第三个参数的源码.第一个参数为代理类对象,第二个参数为真实对象的方法,第三个为第二个参数传入的参数数组.
public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
1.定义一个(班级)接口
public interface ClassRoom {
/**
* 班级中,可以吩咐学生去做某件事情
* @param studentName 学生名
* @param doSomething 具体某件事情
*/
void doWork(String studentName,String doSomething);
}
2.写一个真实对象(班主任)并实现上述接口.
/**
* 班主任 -- 真实对象
* 只管主业务
*/
public class ClassRoomOwner implements ClassRoom{
public void doWork(String studentName, String doSomething) {
//只处理主业务
System.out.println("让"+studentName+"去"+doSomething);
}
}
3.事务代码我们继续沿用静态代理中的MyTransactionManager类.
我们一步一步来看.在测试类中调用java.lang.reflect.Proxy.newProxyInstance()方法
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationDynamic.xml")
public class dynamicProxyTest {
@Autowired
private ClassRoomInvocationHandler classRoomInvocationHandler;
@Test
public void test(){
ClassRoom classRoom =(ClassRoom) Proxy.newProxyInstance(
loader,
interfaces,
h
);
classRoom.doWork("xx","扫地");
}
}
我们先填一下第三个参数,InvocationHandler为接口,这里我们写一个实现该接口的处理类.
public class ClassRoomInvocationHandler implements InvocationHandler{
//事务
private MyTransactionManager tx;
public void setTx(MyTransactionManager tx) {
this.tx = tx;
}
//真实对象
private Object object;
public void setObject(Object object) {
this.object = object;
}
public Object getObject() {
return object;
}
/**
* 返回一个真实对象
* @param proxy 真实对象的代理
* @param method 创建真实对象的method
* @param args method传参需要的参数
* @return 真实对象
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//事务处理
Object returnObj=null;
try {
tx.begin();
returnObj = method.invoke(object, args);
tx.commit();
}catch (Exception e){
e.printStackTrace();
e.getMessage();
tx.rollback();
}
//返回真实对象
return returnObj;
}
}
我们再填一下第二个参数,参数为真实对象实现的接口,第一个为类加载器,加载我们业务代码的是AppClassLoader.这两个参数可以直接从真实对象的Class文件中取.所以最终部分测试代码如下:
@Autowired
private ClassRoomInvocationHandler classRoomInvocationHandler;
@Test
public void test(){
ClassRoom classRoom =(ClassRoom) Proxy.newProxyInstance(
classRoomInvocationHandler.getObject().getClass().getClassLoader(),
classRoomInvocationHandler.getObject().getClass().getInterfaces(),
classRoomInvocationHandler
);
classRoom.doWork("xx","扫地");
}
我们最终创建了一个和真实类实现同一个接口的代理类,这里之所以能强转,就是因为代理类和真实类实现的是同一个接口,用接口类强转也体现了java的多态.执行之后我们发现,代理类真的帮我们在做了事情.
分配任务
让xx去扫地
任务进行
这里可能就有人问了,代理类是怎么创建的啊,之后调doWork()方法,代理类又是怎么去调用的,别急,这是咱们下面要说的.
实现原理
我们先看下Proxy.newProxyInstance()的源码
public class Proxy implements java.io.Serializable {
private static final Class<?>[] constructorParams =
{ InvocationHandler.class };
protected InvocationHandler h;
@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
//省略
/*
* 查找并生成代理类Class对象
*/
Class<?> cl = getProxyClass0(loader, intfs);
/*
* 创建代理类Class的有参构造器,参数为InvocationHandler类型
*/
final Constructor<?> cons = cl.getConstructor(constructorParams);
/*
* 通过有参构造器创建代理类对象并返回
*/
return cons.newInstance(new Object[]{h});
//省略
}
为了方便理解,我这边把具体生成的代理类给贴出来,先执行下述代码:
public static void main(String[] args) {
try {
generateClassFile(ClassRoom.class,"ClassRoomProxy");
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 将代理类对象以class形式输出到本地磁盘
* @param targetClass 代理类实现的接口
* @param proxyName 代理对象文件前缀
* @throws Exception
*/
private static void generateClassFile(Class<?> targetClass,String proxyName) throws Exception{
byte[] classFile = ProxyGenerator.generateProxyClass(proxyName, new Class[]{targetClass});
String path = targetClass.getResource(".").getPath();
System.out.println(path);
FileOutputStream fileOutputStream = new FileOutputStream(path + proxyName + ".class");
fileOutputStream.write(classFile);
fileOutputStream.close();
}
得到的代理类class文件如下:
public final class ClassRoomProxy extends Proxy implements ClassRoom {
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m0;
public ClassRoomProxy(InvocationHandler var1) {
super(var1);
}
static {
m3 = Class.forName("com.mycode.util.dynamicproxy.jdkapi.ClassRoom")
.getMethod("doWork",
Class.forName("java.lang.String"),
Class.forName("java.lang.String"));
}
//省略
public final void doWork(String var1, String var2) {
try {
//super.h是拿取父类的h变量,这里其实是ClassRoomInvocationHandler对象
//然后调用其invoke方法
//this是该代理对象,m3就是ClassRoom接口定义的dowork方法,最后一个传参是dowork方法的传参
super.h.invoke(this, m3, new Object[]{var1, var2});
} catch (RuntimeException | Error var4) {
throw var4;
} catch (Throwable var5) {
throw new UndeclaredThrowableException(var5);
}
}
//省略
}
代理类型继承了Proxy,并也实现了ClassRoom接口.
这个文件要跟着测试类中的classRoom.doWork("xx","扫地");来聊.
虽然我们强转了类型,但其还是个代理类,其实是代理类调用doWork("xx","扫地"),重要注释都已经写在了代理类上.