Java动态代理总结

a) 什么是Proxy Design Pattern?

在说Java动态代理之前,先复习下代理模式(Proxy Pattern)。如下类图所示:

一旦有了代理,它将代替被代理类去参与程序的业务逻辑。代理和被代理都实现了同样的接口,并且代理类会hold一个被代理类,这样当在代理上调用业务方法的时候,代理可以把真正的核心逻辑仍然让被代理类去完成。

 

b) Java的动态代理(Dynamic Proxy) 是怎么回事?

简单说动态代理是Java提供的一种机制,它是在运行的时候基于一个或多个接口(Interface)创造出实现该接口或多个接口的代理类实例。值得再加强一提的是,Dynamic Proxy是基于接口 的,它能为单个或者多个接口创建一个代理类实例。这个机制主要有两个类参与实现:

    - java.lang.reflect.Proxy

    - java.lang.reflect.InvocationHandler

 

1. java.lang.reflect.Proxy

Proxy是一个实现了Serializable的具体类,它里面的方法主要就是用来根据必要的条件创建出指定接口或者是多个接口的代理类。下面就是其提供的常用的public方法:

private final static Class[] constructorParams =
	{ InvocationHandler.class };
public static Object newProxyInstance(ClassLoader loader,
					  Class<?>[] interfaces,
					  InvocationHandler h)
	throws IllegalArgumentException
    {
	if (h == null) {
	    throw new NullPointerException();
	}

	/*
	 * Look up or generate the designated proxy class.
	 */
	Class cl = getProxyClass(loader, interfaces);

	/*
	 * Invoke its constructor with the designated invocation handler.
	 */
	try {
	    Constructor cons = cl.getConstructor(constructorParams);
	    return (Object) cons.newInstance(new Object[] { h });
	} catch (NoSuchMethodException e) {
	    throw new InternalError(e.toString());
	} catch (IllegalAccessException e) {
	    throw new InternalError(e.toString());
	} catch (InstantiationException e) {
	    throw new InternalError(e.toString());
	} catch (InvocationTargetException e) {
	    throw new InternalError(e.toString());
	}
    }

 

从这个方法的参数可以知道,要成功创建一个Proxy Instance,要具备以下条件:

1) ClassLoader ,一般使用当前对象的就可以了
2) Class<?>[] ,接口数组,这就是生成的代理类最后实现的接口
3) InvocationHandler ,配备一个实现了这个接口的类实例是一定 需要的,当在生成好的代理类实例上调用接口中的方法时,这些方法调用会被派发到这个InvocationHandler中去,下面将分作一节详细说它。

 

2. java.lang.reflect.InvocationHandler

这是一个接口,里面只有一个方法如下:

public Object invoke(Object proxy, Method method, Object[] args)
	throws Throwable;

  上面说到了,当在动态产生好的代理类实例上调用接口方法时,这个方法调用会被转到这个invoke方法里面来,就连Object中的hashCode, equal还有toString方法也躲不过,对它们的调用会被通通转到invoke里面来。这样在实际使用中,我们让InvocationHandler的实现类hold住一个被代理的类,然后就可以在invoke的里面对被代理的类方法进行所谓的拦截了,即使在被代理类的业务方法调用前或后另作一些其他的工作了。

 

这里结合最开始的类图给出代码举例。

首先是Client

public class Client {
    public static void main(String[] args) {
        Subject concreteSubject = new ConcreteSubject();
        Class[] interfaceNeedsImp = new Class[] {Subject.class};
        InvocationHandler handler = new SubjectProxy(concreteSubject);
        ClassLoader cl = Subject.class.getClassLoader();

        //Here the type casting you must use the interface
        //if using the concrete class, shxt will happen cause
        //the returned instance is the instance of the generated proxy class
        Subject proxySubject = (Subject) java.lang.reflect.Proxy.newProxyInstance(cl,
                interfaceNeedsImp,
                handler);

        proxySubject.greet();
        System.out.println();
        proxySubject.request();
    }
}

 值得一提的是注意转型那里,要用接口来转,用具体类转要出错。

 

Subject接口

//弄了两个方法表明是业务方法
public interface Subject {
    Subject request();
    void greet();
}
 

ConcreteSubject的代码

public class ConcreteSubject implements Subject {
    public void greet() {
        System.out.println("    greet() of ConcreteSubject invoked");
    }
    public Subject request() {
        System.out.println("    request() of ConcreteSubject invoked");
        return this;
    }
}

 

SubjectProxy的代码

 

public class SubjectProxy implements InvocationHandler {
    private Subject aConcreteSubject;
    public SubjectProxy(Subject target) {
        aConcreteSubject = target;
    }
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        System.out.println("Proxy prints this before " + method.getName()
                                      + ".() of ConcreteSubject");
        Object o = method.invoke(aConcreteSubject, args);
        if (o instanceof Subject) {
            System.out.println("Proxy prints this after concrete " + method.getName()
                                      + ".() of ConcreteSubject");
            return proxy;
        }
        System.out.println("Proxy prints this after concrete" + method.getName()
                                      + ".() of ConcreteSubject");
        return o;
    }
}

 这里这个类和类图中传统的Proxy Pattern有出入了,这个SubjectProxy没有去实现Subject接口,而是实现了InvocationHandler。在Client类中,Proxy会把SubjectProxy和它需要实现的Subject做绑定的,也就是下面几句Client的代码,小小的重复贴一下之前在Client中的几行关键代码如下:

Subject proxySubject = (Subject) java.lang.reflect.Proxy.newProxyInstance(cl,
                interfaceNeedsImp, //一群等待被实现的"接口"同志们
                handler);

 这差不多就是Java的动态代理机制了。下面是程序的输出,一目了然,ConcreteSubject的方法都被拦截了:

 

 

Proxy prints this before greet.() of ConcreteSubject
    greet() of ConcreteSubject invoked
Proxy prints this after concretegreet.() of ConcreteSubject

Proxy prints this before request.() of ConcreteSubject
    request() of ConcreteSubject invoked
Proxy prints this after concrete request.() of ConcreteSubject

 

额外需要注意的:

 

 

1. 网上有好多人在讨论InvocationHandler中invoke方法的第一个参数proxy怎么用?

答案是有时候接口方法会把当前接口引用作为返回值返回,参考上面例子中的request()业务方法。它返回了Subject引用,如果在代理中拦截到该方法不做特别处理的话,返回出去的就是ConcerteSubject的引用,若想继续使用SubjectProxy到底,就可以判断一下返回参数proxy。所以在上面例子里的SubjectProxy中有下面的代码段:

if (o instanceof Subject) {
            System.out.println("Proxy prints this after concrete " + method.getName()
                                        + ".() of ConcreteSubject");
            return proxy;
        }

 

2. java.lang.reflect.Proxy 的源代码写得挺精彩。它使用到了loaderToCache的一个WeakHashMap,使程序在做缓存的同时不会泄漏内存,值得学习。

 

3. 当代理需要实现多个接口,并且多个接口中有些方法duplicate了,那顺序会起决定作用,在Class[]中最前面的那个接口会被认为是方法的提供者!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java动态代理和反射是Java语言中的两个重要概念。 反射机制允许程序在执行期间获取任何类的内部信息,并能直接操作任意对象的内部属性和方法。通过反射,我们可以在运行时动态地获取类的结构信息,例如类的字段、方法、构造函数等。反射机制在一些框架和工具中被广泛使用,例如Spring框架的依赖注入和AOP(面向切面编程)。 动态代理是一种设计模式,它允许在运行时创建一个代理对象,该代理对象可以替代原始对象执行相同的操作。动态代理通常用于在不修改原始类的情况下,为原始类添加额外的功能或行为。在Java中,动态代理是通过反射机制实现的。 Java动态代理是通过Proxy类和InvocationHandler接口实现的。Proxy类用于创建代理对象,InvocationHandler接口定义了代理对象的方法调用处理逻辑。通过传入不同的InvocationHandler实现类,可以在运行时生成不同的代理类。 总结起来,反射机制允许程序在运行时获取类的结构信息并操作对象的属性和方法,而动态代理则是通过反射机制在运行时生成代理对象,实现对原始对象的代理操作。这两个概念在Java中都具有重要的应用价值。\[1\]\[2\]\[3\] #### 引用[.reference_title] - *1* [Java学习路线:day28 反射](https://blog.csdn.net/m0_46153949/article/details/106192238)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* *3* [java中的动态代理和反射](https://blog.csdn.net/xunbaobao123/article/details/115180522)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值