代理模式,为其他对象提供了一种代理以控制对这个对象的访问。代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。通俗的来讲代理模式就是我们生活中常见的中介。
代理类和真实类的公用接口
public interface Subject {
void request();
}
真实类的请求
public class RealSubject implements Subject{
@Override
public void request() {
System.out.println("真实请求");
}
}
代理请求,引入了真实类对象,对方法进行了增强
public class Proxy implements Subject{
private RealSubject realSubject;
@Override
public void request() {
if (realSubject == null) {
realSubject = new RealSubject();
}
realSubject.request();
System.out.println("代理请求");
}
}
主函数测试
public class JavaDemo {
public static void main(String[] args){
Subject proxy = new Proxy();
proxy.request();
}
}
静态代理:由程序员创建或特定工具自动生成源代码,也就是在编译时就已经将接口、被代理类、代理类等确定下来。在程序运行之前,代理类的.class文件就已经生成。
假如一个班的同学要向老师交班费,但是都是通过班长把自己的钱转交给老师。这里,班长代理学生上交班费,班长就是学生的代理。
1.确定创建接口具体行为
首先,我们创建一个Person接口。这个接口就是学生(被代理类),和班长(代理类)的公共接口,他们都有上交班费的行为。这样,学生上交班费就可以让班长来代理执行。
/**
* 创建Person接口
*/
public interface Person {
//上交班费
void giveMoney();
}
2.被代理对象实现接口,完成具体的业务逻辑
Student类实现Person接口。Student可以具体实施上交班费的动作:
public class Student implements Person {
private String name;
public Student(String name) {
this.name = name;
}
@Override
public void giveMoney() {
System.out.println(name + "上交班费50元");
}
}
3.代理类实现接口,完成委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。
StudentsProxy类,这个类也实现了Person接口,但是还另外持有一个学生类对象。由于实现了Peson接口,同时持有一个学生对象,那么他可以代理学生类对象执行上交班费(执行giveMoney()方法)行为。
/**
* 学生代理类,也实现了Person接口,保存一个学生实体,这样既可以代理学生产生行为
* @author Gonjan
*
*/
public class StudentsProxy implements Person{
//被代理的学生
Student stu;
public StudentsProxy(Person stu) {
// 只代理学生对象
if(stu.getClass() == Student.class) {
this.stu = (Student)stu;
}
}
//代理上交班费,调用被代理学生的上交班费行为
public void giveMoney() {
stu.giveMoney();
}
}
4.客户端使用操作与分析
public class StaticProxyTest {
public static void main(String[] args) {
//被代理的学生张三,他的班费上交有代理对象monitor(班长)完成
Person zhangsan = new Student("张三");
//生成代理对象,并将张三传给代理对象
Person monitor = new StudentsProxy(zhangsan);
//班长代理上交班费
monitor.giveMoney();
}
}
这里并没有直接通过张三(被代理对象)来执行上交班费的行为,而是通过班长(代理对象)来代理执行了,这就是代理模式。
代理模式最主要的就是有一个公共接口(Person),一个具体的类(Student),一个代理类(StudentsProxy),代理类持有具体类的实例,代为执行具体类实例方法。上面说到,代理模式就是在访问实际对象时引入一定程度的间接性,因为这种间接性,可以附加多种用途。这里的间接性就是指不直接调用实际对象的方法,那么我们在代理过程中就可以加上一些其他用途。就这个例子来说,加入班长在帮张三上交班费之前想要先反映一下张三最近学习有很大进步,通过代理模式很轻松就能办到:
public class StudentsProxy implements Person{
//被代理的学生
Student stu;
public StudentsProxy(Person stu) {
// 只代理学生对象
if(stu.getClass() == Student.class) {
this.stu = (Student)stu;
}
}
//代理上交班费,调用被代理学生的上交班费行为
public void giveMoney() {
System.out.println("张三最近学习有进步!");
stu.giveMoney();
}
}
只需要在代理类中帮张三上交班费之前,执行其他操作就可以了。这种操作,也是使用代理模式的一个很大的优点.
最直白的就是在Spring中的面向切面编程(AOP),我们能在一个切点之前执行一些操作,在一个切点之后执行一些操作,这个切点就是一个个方法。
JDK动态代理只能对实现了接口的类生成代理,而不能针对类
利用拦截器(拦截器必须实现InvocationHanlder)加上反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。
被代理对象的接口:
//老师的接口
public interface IteachDao
{
void teach();
Integer getTeachersAge(String name);
}
具体代理对象
public class TeachDao implements IteachDao
{
@Override
public void teach() {
System.out.println("老师教课中.....");
}
@Override
public Integer getTeachersAge(String name) {
return 18;
}
}
代理工厂
public class ProxyFactory
{
//维护一个目标对象--Object
private Object object;
//构造器,对target对象进行初始化
public ProxyFactory(Object object)
{
this.object=object;
}
//给目标对象生成一个代理对象
public Object getProxyInstance()
{
// public static Object newProxyInstance(ClassLoader loader,
// Class<?>[] interfaces,
// InvocationHandler h)
//1.ClassLoader loader: 指定当前对象使用的类加载器,获取类加载器的方法固定
//2.Class<?>[] interfaces: 目标对象实现的接口类型,使用泛型方法确认类型
//3.InvocationHandler h: 事情处理,执行目标对象的方法时,会触发事件处理器方法
//会把当前执行的目标对象方法作为参数传入
return Proxy.newProxyInstance(object.getClass().getClassLoader(),
object.getClass().getInterfaces(),
//匿名内部类方式传入
new InvocationHandler() {
@Override
//proxy:是代理对象 ,method是当前被代理对象执行的方法
//args:被执行方法的参数
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("JDK代理开始");
//反射机制调用目标对象的方法--返回本来方法的返回值
System.out.println("当前方法:"+method);
System.out.println("当前方法参数:"+args);
Object invoke = method.invoke(object, args);
//返回方法返回值
return invoke;
}
});
}
}
测试
public class test
{
@Test
public void test()
{
//创建目标对象
IteachDao t=new TeachDao();
//给目标对象创建代理对象
IteachDao proxyInstance=(IteachDao)new ProxyFactory(t).getProxyInstance();
System.out.println("===================下面打印代理对象和获取类型===========================");
System.out.println(proxyInstance);
System.out.println(proxyInstance.getClass());//获取类型
System.out.println("=====================下面获取老师年龄=====================================");
proxyInstance.getTeachersAge("大忽悠");
System.out.println("======================输出老师上课中=======================================");
proxyInstance.teach();
}
}
要了解 Java 动态代理的机制,首先需要了解以下相关的类或接口:
首先让我们来了解一下如何使用 Java 动态代理。具体有如下四步骤:
// InvocationHandlerImpl 实现了 InvocationHandler 接口,并能实现方法调用从代理类到委托类的分派转发
// 其内部通常包含指向委托类实例的引用,用于真正执行分派转发过来的方法调用
InvocationHandler handler = new InvocationHandlerImpl(..);
// 通过 Proxy 为包括 Interface 接口在内的一组接口动态创建代理类的类对象
Class clazz = Proxy.getProxyClass(classLoader, new Class[] { Interface.class, ... });
// 通过反射从生成的类对象获得构造函数对象
Constructor constructor = clazz.getConstructor(new Class[] { InvocationHandler.class });
// 通过构造函数对象创建动态代理类实例
Interface Proxy = (Interface)constructor.newInstance(new Object[] { handler });
我们可以通过下面的方法将其打印到文件里面,一睹真容:
/**
* 创建Person接口
*/
public interface Person {
//上交班费
void giveMoney();
}
public class Student implements Person {
private String name;
public Student(String name) {
this.name = name;
}
@Override
public void giveMoney() {
try {
//假设数钱花了一秒时间
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name + "上交班费50元");
}
}
public class MonitorUtil {
private static ThreadLocal<Long> tl = new ThreadLocal<>();
public static void start() {
tl.set(System.currentTimeMillis());
}
//结束时打印耗时
public static void finish(String methodName) {
long finishTime = System.currentTimeMillis();
System.out.println(methodName + "方法耗时" + (finishTime - tl.get()) + "ms");
}
}
public class StuInvocationHandler<T> implements InvocationHandler {
//invocationHandler持有的被代理对象
T target;
public StuInvocationHandler(T target) {
this.target = target;
}
/**
* proxy:代表动态代理对象
* method:代表正在执行的方法
* args:代表调用目标方法时传入的实参
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("代理执行" +method.getName() + "方法");
//代理过程中插入监测方法,计算该方法耗时
MonitorUtil.start();
Object result = method.invoke(target, args);
MonitorUtil.finish(method.getName());
return result;
}
byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy0",Student.class.getInterfaces());
String path = "G:/javacode/javase/Test/bin/proxy/StuProxy.class";
try(FileOutputStream fos = new FileOutputStream(path)) {
fos.write(classFile);
fos.flush();
System.out.println("代理类class文件写入成功");
} catch (Exception e) {
System.out.println("写文件错误");
}
对这个class文件进行反编译,我们看看jdk为我们生成了什么样的内容:
public final class $Proxy0 extends Proxy implements Person {
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m0;
/**
*注意这里是生成代理类的构造方法,方法参数为InvocationHandler类型,看到这,是不是就有点明白
*为何代理对象调用方法都是执行InvocationHandler中的invoke方法,而InvocationHandler又持有一个
*被代理对象的实例,不禁会想难道是....? 没错,就是你想的那样。
*
*super(paramInvocationHandler),是调用父类Proxy的构造方法。
*父类持有:protected InvocationHandler h;
*Proxy构造方法:
* protected Proxy(InvocationHandler h) {
* Objects.requireNonNull(h);
* this.h = h;
* }
*
*/
public $Proxy0(InvocationHandler paramInvocationHandler)
throws
{
super(paramInvocationHandler);
}
//这个静态块本来是在最后的,我把它拿到前面来,方便描述
static
{
try
{
//看看这儿静态块儿里面有什么,是不是找到了giveMoney方法。请记住giveMoney通过反射得到的名字m3,其他的先不管
m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
m3 = Class.forName("proxy.Person").getMethod("giveMoney", new Class[0]);
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
return;
}
catch (NoSuchMethodException localNoSuchMethodException)
{
throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
}
catch (ClassNotFoundException localClassNotFoundException)
{
throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
}
}
/**
*
*这里调用代理对象的giveMoney方法,直接就调用了InvocationHandler中的invoke方法,并把m3传了进去。
*this.h.invoke(this, m3, null);这里简单,明了。
*来,再想想,代理对象持有一个InvocationHandler对象,InvocationHandler对象持有一个被代理的对象,
*再联系到InvacationHandler中的invoke方法。嗯,就是这样。
*/
public final void giveMoney()
throws
{
try
{
this.h.invoke(this, m3, null);
return;
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
//注意,这里为了节省篇幅,省去了toString,hashCode、equals方法的内容。原理和giveMoney方法一毛一样。
}
jdk为我们的生成了一个叫$Proxy0
(这个名字后面的0是编号,有多个代理类会一次递增)的代理类,这个类文件是放在内存中的,我们在创建代理对象时,就是通过反射获得这个类的构造方法,然后创建的代理实例
。通过对这个生成的代理类源码的查看,我们很容易能看出,动态代理实现的具体过程。
我们可以对InvocationHandler看做一个中介类,中介类持有一个被代理对象,在invoke方法中调用了被代理对象的相应方法。通过聚合方式持有被代理对象的引用,把外部对invoke的调用最终都转为对被代理对象的调用。
代理类调用自己方法时,通过自身持有的中介类对象来调用中介类对象的invoke方法,从而达到代理执行被代理对象的方法。也就是说,动态代理通过中介类实现了具体的代理功能。
总结:生成的代理类:$Proxy0 extends Proxy implements Person
,我们看到代理类继承了Proxy类,所以也就决定了java动态代理只能对接口进行代理
,Java的继承机制注定了这些动态代理类们无法实现对class的动态代理。上面的动态代理的例子,其实就是AOP的一个简单实现了,在目标对象的方法执行之前和执行之后进行了处理,对方法耗时统计。Spring的AOP实现其实也是用了Proxy和InvocationHandler这两个东西的。
InvocationHandler接口是proxy代理实例的调用处理程序实现的一个接口,每一个proxy代理实例都有一个关联的调用处理程序;在代理实例调用方法时,方法调用被编码分派到调用处理程序的invoke方法。
当我们通过动态代理对象调用一个方法时候,这个方法的调用就会被转发到实现InvocationHandler接口类的invoke方法来调用,看如下invoke方法:
/**
* proxy:代理类代理的真实代理对象com.sun.proxy.$Proxy0
* method:我们所要调用某个对象真实的方法的Method对象
* args:指代代理对象方法传递的参数
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
Proxy类就是用来创建一个代理对象的类,它提供了很多方法,但是我们最常用的是newProxyInstance方法。
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
这个方法的作用就是创建一个代理类对象,它接收三个参数,我们来看下几个参数的含义:
动态生成的代理类本身的一些特点
代理类实例的一些特点
美中不足
Proxy只能对interface进行代理,无法实现对class的动态代理。观察动态生成的代理继承关系图可知原因,他们已经有一个固定的父类叫做Proxy,Java语法限定其不能再继承其他的父类
返回的动态代理对象是实现了被代理对象实现的所有接口的对象,因此无法调用被代理对象额外增添的内容,也无法将代理对象强制转换为被代理对象的类型,这样做会报错
CGLIB是一个强大的高性能的代码生成包。
CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法(继承);
利用ASM开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
被代理的对象
public class TeachDao
{
public void teach() {
System.out.println("老师教课中.....");
}
public Integer getTeachersAge(String name) {
return 18;
}
public void show()
{
System.out.println("你好");
}
}
生成和处理相关逻辑的代理对象工厂类
//实现MethodInterceptor接口
public class ProxyFactory implements MethodInterceptor
{
//需要代理的目标对象
private Object target;
ProxyFactory(Object target)
{
this.target=target;
}
//获取代理对象的方法
public Object getProxyInstance()
{
// 通过CGLIB动态代理获取代理对象的过程
Enhancer enhancer=new Enhancer();
// 设置enhancer对象的父类,因为Cglib是针对指定的类生成一个子类,所以需要指定父类
enhancer.setSuperclass(target.getClass());
//设置enhancer的回调对象
enhancer.setCallback(this);
// 创建代理对象
return enhancer.create();
}
/**
* @param o cglib生成的代理对象
* @param method 被代理对象的方法
* @param objects 传入方法的参数
* @param methodProxy 代理的方法
*/
@Override//重写拦截方法
public Object intercept(Object o, Method method, Object[] objects,
MethodProxy methodProxy) throws Throwable {
System.out.println("cglib代理中...");
//执行被代理对象的方法---方式一
System.out.println("======================");
System.out.println("动态代理过程中执行被代理对象的方法");
//这里必须传入被代理的对象,否则会死循环
//因为代理对象方法调用会触发拦截器
Object ret= method.invoke(target, objects);
System.out.println("代理对象的方法的返回值:"+ret);
System.out.println("======================");
return ret;
//方式二
//下面这种写法会造成死循环,因为调用被代理的方法会触发拦截器
//Object invoke = methodProxy.invoke(o, objects);
//因此应该是执行父类的方法--这里必须传入代理对象
//Object invokeSuper = methodProxy.invokeSuper(o, objects);
//return invokeSuper;//返回方法返回值
}
}
测试
public class test
{
@Test
public void test()
{
//获取代理对象
ProxyFactory proxyFactory=new ProxyFactory(new TeachDao());
TeachDao proxyInstance = (TeachDao)proxyFactory.getProxyInstance();
//执行代理对象的方法,会触发intercept方法,从而实现对目标对象的调用
proxyInstance.teach();
proxyInstance.show();
Integer age = proxyInstance.getTeachersAge("大忽悠");
System.out.println(age);
}
}
CGlib给我们提供了方法过滤器(CallbackFilter),CallbackFilte可以明确表明,被代理的类中不同的方法,被哪个拦截器所拦截。
public class MyProxyFilter implements CallbackFilter {
//一上来就获取当前对象的所有方法,包括equals
//toString
//hashCode
//clone
@Override
public int accept(Method method) {
//如果方法名叫show
if(method.getName().equals("show"))
return 0;//让第一个拦截器进行处理
return 1;//让第二个拦截器进行处理
}
}
在工场中新增一个使用了过滤器的实例生成方法
//获取代理对象的方法
public Object getProxyInstance()
{
// 通过CGLIB动态代理获取代理对象的过程
Enhancer enhancer=new Enhancer();
// 设置enhancer对象的父类,因为Cglib是针对指定的类生成一个子类,所以需要指定父类
enhancer.setSuperclass(target.getClass());
//设置enhancer的回调对象
//this拦截器,调用的是重写的Intercept方法
enhancer.setCallbacks(new Callback[]{this, NoOp.INSTANCE});
//设置过滤器
enhancer.setCallbackFilter(new MyProxyFilter());
// 创建代理对象
return enhancer.create();
}
setCallbacks中定义了所使用的拦截器,其中NoOp.INSTANCE是CGlib所提供的实际是一个没有任何操作的拦截器, 他们是有序的,一定要和CallbackFilter里面的顺序一致。上面return返回(0/1)的就是返回的顺序。也就是说如果调用show方法就使用this进行拦截。
测试:
//获取代理对象
ProxyFactory proxyFactory=new ProxyFactory(new TeachDao());
TeachDao proxyInstance = (TeachDao)proxyFactory.getProxyInstance();
System.out.println("使用intercept拦截器进行拦截");
proxyInstance.show();
System.out.println("使用NoOp.INSTANCE,空实现拦截器");
proxyInstance.teach();
CGLib采用底层的字节码技术ASM, 可以为一个类创建子类, 在子类中采用方法拦截的技术拦截所有父类方法的调用, 并织入横切逻辑。
这里拦截的是生成的被代理对象,即子类调用的方法,子类继承了所有父类的方法,变相也是调用父类方法