java代理的作用
java当中比较重要的概念就是代理技术的实现,在日常的web开发中我遇到最多的应该是基于Spring的AOP(面向切面编程)的动态代理实现,这种代理的好处显而易见,通过AOP可以将一些非业务逻辑的操作和业务代码完全分离,如 日志统计、性能统计、安全控制、事务处理、异常控制等等,这些方法是多重复的且和业务耦合度较低,通过代理我们可以将它进行单独模块的划出并且进行统一的代码管理。
java代理的两大类
1.静态代理:常见的代理模式,Proxy和Subject,以代码形式编写的代理类,都可以称之为静态代理。
2.动态代理:在运行时借助JDK动态代理、CGLIB等在内存中临时生成动态代理类,因此也被称为运行时增强。Spring AOP为代表。
java代理的代码实现
1.JDK动态代理的实现
此时我们有非常简单的需求:希望能打印出每一个方法操作的耗时时间。代码如下:
接口BaseDao
package com.proxy.dao;
public interface BaseDao<T> {
/**
* 增加操作
* @param t
*/
public void add(T t);
/**
* 查找操作
* @param t
*/
public void get(Integer id);
/**
* 更新操作
* @param t
*/
public void update(T t);
/**
* 删除操作
* @param t
*/
public void delete(Integer id);
}
实体类User
package com.proxy.entity;
import java.io.Serializable;
/**
* user类
* @author wh
*
*/
public class User implements Serializable{
private static final long serialVersionUID = 1L;
private Integer id;
private String userName;
private String password;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
实现类UerDao
package com.proxy.dao;
import com.proxy.entity.User;
public class UserDao implements BaseDao<User> {
@Override
public void add(User t) {
System.out.println("增加的方法=============");
}
@Override
public void get(Integer id) {
System.out.println("查找的方法============");
}
@Override
public void update(User t) {
System.out.println("更新的方法============");
}
@Override
public void delete(Integer id) {
System.out.println("删除的方法============");
}
}
调用处理程序类
UserInvocationHandler
package com.proxy.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class UserInvocationHandler implements InvocationHandler{
private Object subject;
public UserInvocationHandler(Object subject) {
super();
this.subject = subject;
}
/**
* @param proxy 代理对象实例
* @param method 真实对象所调用的方法
* @param args 真实对象执行方法时的参数
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//运行方法之前
long beforeTime = System.currentTimeMillis();
System.out.println(method);
Object obj = method.invoke(subject, args); //代理对象调用方法实际上是调用真实对象的方法
//运行方法之后
long afterTime = System.currentTimeMillis();
System.out.println("耗时时间:" + (afterTime - beforeTime) + "毫秒");
return obj;
}
}
这里需要注意的是,invoke方法中的参数proxy是代理对象的实例而非被代理对象的实例,因此需要传入被代理对象subject来进行方法的反射调用。
测试程序
@Test
public void testJdkProxy() {
BaseDao subjectDao = new UserDao();
BaseDao dao = (BaseDao) Proxy.newProxyInstance(UserDao.class.getClassLoader(), UserDao.class.getInterfaces(), new UserInvocationHandler(subjectDao));
System.out.println(dao.getClass().getName());
dao.add(new User());
}
程序的运行结果如下:
代理对象由Proxy的newProxyInstance方法生成,这个方法要传入的三个参数
loader
- 定义代理类的类加载器
interfaces
- 代理类要实现的接口列表
h
- 指派方法调用的调用处理程序
interfaces可以让代理的类实现接口当中的所有方法。
可以根据打印的结果看出 程序在执行的过程动态的生成了代理类$Proxy4,我们在调用add的方法的时候实际上去调用了被代理对象subjectDao的add方法。关于代理类的实现原理可以参考这篇文章,写的十分详细。
需要注意的一点是:jdk代理的实现必须要实现要依赖接口。
有的时候我们并不想用实现类,而仅仅是想用接口来实现各自的调用方法,RPC就是采用这种模式,于是,代理的方法可以这样改写:
BaseDao dao = (BaseDao) Proxy.newProxyInstance(BaseDao.class.getClassLoader(), new Class[] {BaseDao.class}, new UserInvocationHandler(subjectDao));
2.CGLib的实现方式
相比较于jdk进行动态代理的实现方式,CGLib更加的方便,它不需要被代理类有任何的实现关系。在设计上来说,jdk动态代理生成的代理类是实现了被代理类的接口并且继承了Proxy类,而CGLib生成的代理类则是继承了被代理类,这两种方式还是有比较相似的部分的。
基于上面的UserDao用CGLib进行代理,代码如下:
代理类UserCglibProxy
package com.proxy.proxy;
import java.lang.reflect.Method;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
public class UserCglibProxy implements MethodInterceptor{
private Enhancer enhancer = new Enhancer();
/**
*
* @param cls 需要代理的父类
* @return
*/
public Object getInstance(Class cls) {
enhancer.setSuperclass(cls);
enhancer.setCallback(this);
return enhancer.create();
}
@Override
public Object intercept(Object arg0, Method arg1, Object[] arg2, MethodProxy arg3) throws Throwable {
//运行方法之前
long beforeTime = System.currentTimeMillis();
System.out.println(arg1);
System.out.println(arg3);
System.out.println(arg0.getClass().getName());
Object obj = arg3.invokeSuper(arg0, arg2);
//运行方法之后
long afterTime = System.currentTimeMillis();
System.out.println("耗时时间:" + (afterTime - beforeTime) + "毫秒");
return obj;
}
}
测试程序
@Test
public void testCgLibProxy() {
<span style="white-space:pre"> //将字节码文件的输出位置替换,反编译字节码文件可以查看动态生成的class内容</span>
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D://proxyclass");
UserDao userDao = (UserDao) new UserCglibProxy().getInstance(UserDao.class);
System.out.println(userDao.getClass().getName());
userDao.add(new User());
}
程序的运行结果如下:
根据程序的打印结果可以看出
CGLIB的生成的代理类的类型为UserDao<$$>EnhancerByCGLIB<$$>44494fee
它的默认的命名规则为
被代理class name + "<$$>" + 使用cglib处理的class name + "ByCGLIB" + "<$$>" + key的hashcode
关于CGLib的实现字节码的具体过程可以参考这篇文章
通过对比两种方式 我们可以看出 jdk在进行动态代理的时候几乎时间可以不计,但是CGLIB的耗费时间为23毫秒左右,因为调用方法只有一次,由此可以看出CGLIB在生成代理类的时间远长于jdk动态代理,在以后的文章会具体对这两种方式的执行效率进行比较。