从以下几个方面浅谈一下java的代理机制。如有不足,欢迎留言交流。
为什么使用代理
类比一:
假如你刚毕业,要租一个房子,有两种方式。一种就是自己去找房源,找房东,这样无疑时间成本是很高的。第二种方式就是找一个房产中介,你只需要提供你的需求和租房的规格和条件。中介就会推荐你心仪的房子。这里的中介就是代理人,房源就是目标对象,你可以直接找房源,也可以通过中介找房源。
类比二:
记者要采访一个儿童。首先需要经过他父母的同意,他的父母就记者提出的问题能进行甄选,那些可以回答,那些问题拒绝回答。这里父母就是代理人,儿童就是目标对象。通过代理可以增强或者控制儿童这个目标对象。
总结: 生活中这样代理的例子还有很多,通过以上的两个简单的例子可以看到,在生活中使用代理往往可以节省时间和成本,使资源能有效的利用。而在程序中使用代理也不例外。以Java的动态代理为例,它的优势就是实现无侵入式的代码扩展,也就是在不修改源码的情况下实现方法的增强,在方法的前后你可以自定义你的实现。在后面的篇幅中会用代码作以说明。
java中代理类型:
Java的代理类主要有静态代理和动态代理
静态代理:
由程序员创建或者由第三方工具生成,再进行编译;在程序运行之前代理类的.class文件已经存在,这种代理的方式需要代理的对象和目标对象实现一样的接口。
优点: 可以在不修改目标对象的前提下扩展目标对象的功能
缺点:
- 冗余, 由于代理对象实现与目标对象一致的接口,会产生过多的代理理
- 不易维护,耦合度太高,一旦接口增加方法,目标对象和代理对象都要进行修改
已更新用户的信息的为例:
1.创建接口
package cn.chen.proxy;
public interface UserDao {
void update();
}
2.目标对象,实现接口
public class UserDaoImpl implements UserDao {
@Override
public void update() {
System.out.println("更新信息");
}
}
3.创建的代理对象,必须实现接口
public class ProxyUserDao implements UserDao{
private UserDao userDao;
public ProxyUserDao(UserDao userDao){
this.userDao = userDao;
}
/**
* 对原有的方法进行增强
*/
@Override
public void update() {
System.out.println("核对你的信息");
userDao.update();
System.out.println("信息更新成功");
}
}
4.测试
public class ProxyUserTest {
public static void main(String[] args) {
// 目标对象
UserDao userDao = new UserDaoImpl();
// 代理对象
ProxyUserDao proxyUserDao = new ProxyUserDao(userDao);
System.out.println(proxyUserDao.getClass().getName());
proxyUserDao.update();
}
}
运行结果:
cn.chen.proxy.ProxyUserDao
核对你的信息
更新用户
信息更新成功
动态代理:
在程序运行时通过反射机制动态生成
优点:动态代理对象不需要实现与目标对象一致的接口,但要求目标对象必须实现接口,否则不能使用动态代理。
缺点: 使用动态代理的对象必须实现一个或多个接口。在代理类没有接口的情况下,可以使用cglib实现动态代理,达到代理类的无侵入。
静态代理和动态代理的主要区别:
- 静态代理在编译时就已经实现,编译完成后的代理类是一个实际的class文件。
- 动态代理是在运行时动态生成的,即编译完成后没有实际的class文件,是在运行时动态生成类字节码,并加载到jvm中。
- 静态的代理类需要实现与目标对象一致的接口,耦合度较高,而动态的代理类则不用实现与目标对象一致的接口。
动态代理的实现:
java的动态代理利用了JDK Apl,动态地在内存中构建代理对象,从而实现对目标对象的代理功能。
在jdk中生成代理对象主要涉及的类有:
-
java.lang.reflect.Proxy 主要方法为:
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException 这个方法会给与我们来生成一个代理对象,含有三个参数 clasLoader: 类加载器 interface绑定的接口,也就是把代理对象绑定到那些接口下,可以是多个接口 invocationHander 绑定对象的逻辑实现
-
java.lang.reflect InvocationHander,主要方法为:
Object invoke(Object proxy, Method method, Object[] args) 这个方法也有三个参数 proxy 代理的对象 method 当前的方法 args 运行参数
还是以用户的信息更新为例:
1.创建接口
package cn.chen.proxy;
public interface UserDao {
void update();
}
2.目标对象,实现接口
public class UserDaoImpl implements UserDao {
@Override
public void update() {
System.out.println("更新信息");
}
}
3.生成代理对象
public class DynamicProxyUser implements InvocationHandler {
// 被代理的对象
private Object object;
public DynamicProxyUser(){}
public DynamicProxyUser(Object object) {
this.object = object;
}
/**
* 处理代理对象的逻辑,所有被代理对象的方法都会在invoke中执行
* @param proxy 代理的对象
* @param method 当前方法
* @param args 方法运行参数
* @return 方法调用结果
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("核对你的信息");
// 执行目标方法
Object target = method.invoke(object,args);
System.out.println("更新成功");
return target;
}
}
4.测试:
public class DynamicProxyTest {
public static void main(String[] args) {
// 目标对象
UserDao userDao = new UserDaoImpl();
DynamicProxyUser dynamicProxyUser = new DynamicProxyUser(userDao);
ClassLoader loader = userDao.getClass().getClassLoader();
// 调用Proxy的newProxyInstance()方法生成最终的代理对象
UserDao proxy = (UserDao) Proxy.newProxyInstance(loader,new Class[]{UserDao.class},dynamicProxyUser);
System.out.println(proxy.getClass().getName());
proxy.update();
}
}
运行结果:
com.sun.proxy.$Proxy0
核对你的信息
更新信息
更新成功
当然,除了java的代理类型外,比较流行的还有CGLB Javassit,ASM 等。
java代理的应用场景:
- 切面编程
- 加事物,加权限
- 加日志
- Rpc框架的使用