静态代理
在动态代理之前,先说一下静态代理,静态代理角色分析:
抽象角色 : 一般使用接口或者抽象类来实现;
真实角色 : 被代理的角色;
代理角色 : 代理真实角色 ,代理真实角色后 , 一般会做一些附属的操作 ;
使用者 : 使用代理角色来进行一些操作 ;
抽象角色(租房的操作)
package com.wjh;/*** @author wjh* @date 2021/7/17 15:33* @Package com.wjh*/public interface Rent {void rent();}
真实角色(房东,要把房子租出去)
package com.wjh;/*** @author wjh* @date 2021/7/17 15:34* @Package com.wjh*/public class Host1 implements Rent{@Overridepublic void rent() {System.out.println("我是房东,这里有一间房子,现在租给你了");}}
代理角色(中介,会进行附加操作,比如:收费)
import com.wjh.Host1;import com.wjh.Rent;/*** @author wjh* @date 2021/7/17 15:33* @Package PACKAGE_NAME*/public class ProxyHost1 implements Rent {private Host1 host1;public ProxyHost1(Host1 host1) {this.host1 = host1;}@Overridepublic void rent() {fare();host1.rent();}private void fare(){System.out.println("我是房租中介,进行了收费");}}
顾客(使用中介执行租房的操作)
package com.wjh;
/**
* @author wjh
* @date 2021/7/17 15:34
* @Package com.wjh.service
*/
public class Customer {
public static void main(String[] args) {
ProxyHost1 proxyHost1 =new ProxyHost1(new Host1());
proxyHost1.rent();
}
}
通过上面的代码可以看到,顾客没有直接调用房东的rent方法,而是调用了中介的rent方法,并且中介的rent方法还进行了收费的附加操作,且中介和房东都实现的是Rent接口,重写的rent方法,而顾客调用的是中介的rent方法,没有直接调用房东的rent方法,而是通过中介的rent方法间接的调用房东的rent方法。这样的好处就是房东只需要纯粹的完成“租房出去”这个操作,不需要“找租客”、“谈价钱”…. 这些琐事,而这些琐事只需要交给中介去做就可以了,也就是所谓的附加操作。
通过上面的代码,由于ProxyHost1只能代理Host1,不能代理其它类,这就导致如果有其它房东Host2(也是实现的Rent接口),就没有办法代理Host2了,那就必须再写一个代理类去代理Host2,如此一来,代理类就会越来越多,由此可见,这样的代理是无法应用到项目开发中的,而动态代理就可以很好的解决这一问题。
动态代理
动态代理的角色和静态代理的一样,动态代理的代理类是动态生成的 . 静态代理的代理类是我们提前写好的,动态代理分为两类 : 一类是基于接口动态代理 , 一类是基于类的动态代理。了解一些常用的代理:
JDK动态代理:基于接口的动态代理,被代理的对象至少实现一个接口;
cglib:基于类的动态代理,通过“继承”可以继承父类所有的公开方法,然后可以重写这些方法,在重写时对这些方法增强;
本节说的是jdk动态代理,和jdk动态代理的两个类是InvocationHandler和Proxy,它们都位于java.lang.reflect包下面,动态代理类需要实现InvocationHandler接口,重写其中的invoke方法,对于invoke方法,仅对其三个参数进行简单的介绍(其原理较为复杂):
Object invoke(Object proxy, Method method, Object[] args) throws Throwable
proxy: 指代我们所代理的那个真实对象,也就是所谓的代理对象
method: 指代的是我们所要调用真实对象的某个方法
args: method方法的参数,以数组形式表示
至于Proxy类,只介绍newProxyInstance方法即可:
public static Object newProxyInstance
(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h) throws IllegalArgumentException
loader: 一个ClassLoader对象,定义了由哪个ClassLoader对象来对生成的代理对象进行加载,获取被代理对象的ClassLoader即可。
interfaces: 一个Interface对象的数组,被代理对象所实现的所有接口。
h: 一个InvocationHandler对象,就是实现InvocationHandler接口的类,表示的是当动态代理对象在调用方法的时候,会关联到哪一个InvocationHandler对象上。
package dao;
/**
* @author wjh
* @date 2021/7/17 14:52
* @Package dao
*/
public interface UserDao {
void add(int x);
}
package dao;
/**
* @author wjh
* @date 2021/7/17 14:57
* @Package dao
*/
public class UserDaoMysqlImpl implements UserDao{
@Override
public void add(int x) {
System.out.println("使用MySQL数据库进行add操作");
}
}
package dao;
/**
* @author wjh
* @date 2021/7/17 14:58
* @Package dao
*/
public class UserDaoOracleImpl implements UserDao{
@Override
public void add(int x) {
System.out.println("使用Oracle数据库进行add操作");
}
}
package proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* @author wjh
* @date 2021/7/17 14:53
* @Package proxy
*/
public class MyProxy implements InvocationHandler {
/**
* 被代理的对象,即真实对象,只需要通过某种方式从本类外部获取即可
*/
private Object target;
public MyProxy() {
}
/**
*
* @param target 被代理的对象
* @return 返回代理对象
*
* 我们可以通过Proxy类通过的静态方法newProxyInstance来创建被代理对象(target)的代理对象
* target.getClass().getClassLoader() 获取被代理对象(target)的类加载器
* target.getClass().getInterfaces() 获取被代理对象(target)实现的所有接口
* this 实现InvocationHandler接口的自定义类,这里就是本类
*/
public Object getProxy(Object target) {
this.target = target;
return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
}
/**
*
* @param proxy 代理对象(代理类的实例)
* @param method 被代理对象需要执行执行的方法
* @param args 方法的参数
* @return 返回被代理对象(target)执行method方法的结果
* @throws Throwable 抛出异常
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(proxy.getClass());
System.out.println(method.getName());
System.out.println("第一个参数:"+args[0]);
System.out.println("调用了invoke方法");
return method.invoke(target, args);
}
}
Test类:
public class Test {
public static void main(String[] args) {
UserDao userDao1 = new UserDaoMysqlImpl();
UserDao userDao2 = new UserDaoOracleImpl();
MyProxy myProxy = new MyProxy();
Object proxy = myProxy.getProxy(userDao1);
System.out.println(proxy.getClass());
}
}

可以看到代理对象proxy已经被创建了,通过 Proxy.newProxyInstance 创建的代理对象是在 jvm 运行时动态生成的一个对象,它并不是我们的 InvocationHandler 类型,也不是我们定义的那组接口的类型,而是在运行时动态生成的一个对象,并且命名方式都是这样的形式,以$开头,proxy 为中,最后一个数字表示对象的标号。动态代理代理的是接口,如果需要调用接口的方法,则需要先把创建出来的代理对象强制转换为接口类型,但其实际对象依然是类似“$proxy0”的形式,而且我们不能强转转化为它的实现类的类型。如:
public static void main(String[] args) {
UserDao userDao1 = new UserDaoMysqlImpl();
UserDao userDao2 = new UserDaoOracleImpl();
MyProxy myProxy = new MyProxy();
UserDao proxy1 = (UserDao) myProxy.getProxy(userDao1);
proxy1.add(1314);
}

可以看到调用接口的方法真正的是调用代理类中的invoke方法(原理这里就不做解释啦),而代理类的invoke方法又是使用反射调用真正角色(被代理对象target)的add方法。
既然执行接口的add方法实质是执行代理类的invoke方法,那么我们就可以在invoke方法里面进行其它的附加操作,而执行其它的附加操作就是代理模式存在的一大用处,比如:在invoke里面进行事务处理、打印日志等等这些增强UserDaoMysqlImpl中add方法的操作,无需修改UserDaoMysqlImpl中add方法的代码,而且如果我们还需要创建UserDaoOracleImpl的代理对象,我们只需要把真实对象做为参数给代理类就能够创建它的代理对象,无需重新写一个代理类了,这一就解决的静态代理的不足。
可以使用泛型避免把代理对象强转成接口类型
public <T> T getProxy(Object target) {
this.target = target;
return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
}

