Java反射机制(四):动态代理
一、静态代理
在开始去学习反射实现的动态代理前,我们先需要了解代理设计模式,那何为代理呢?
代理模式: 为其他对象提供一种代理,以控制对这个对象的访问。
先看一张代理模式的结构图:
简单的理解代理设计: 一个操作接口有两个子类,其中一个真实主题的实现类,另一个是代理类,代理实现类要完成比真实主题实现类更多的内容,而且本身还需要处理一些与具体业务有关的程序代码。
静态代理示例:
- package org.chen.yuan.prox;
- interface Subject
- {
- public String say(String name, int age);
- }
- class RealSubject implements Subject
- {
- @Override
- public String say(String name, int age)
- {
- return "姓名: " + name + ", 年龄: " + age;
- }
- }
- class ProxSubject implements Subject
- {
- private Subject sub = null;
- public ProxSubject(Subject sub)
- {
- this.sub = sub;
- }
- @Override
- public String say(String name, int age)
- {
- this.preRequest(); // 在调用真实角色操作之前所附加的操作
- String info = sub.say(name, age); // 真实角色所完成的事情
- this.postRequest(); // 在真实角色操作之后所附加的操作
- return info;
- }
- // 自定义的方法,在真实方法执行之前调用的方法
- private void preRequest()
- {
- System.out.println("pre request");
- }
- // 自定义的方法,在真实方法执行之后调用的方法
- private void postRequest()
- {
- System.out.println("post request");
- }
- }
- public class DynaProxyDemo
- {
- public static void main(String[] args)
- {
- Subject sub = new ProxSubject(new RealSubject());
- String info = sub.say("沉缘", 25);
- System.out.println(info);
- }
- }
输出:
pre request
post request
姓名: 沉缘, 年龄: 25
说明:
在实现代理角色对象时,最重要的一点是要有一个真实对象的引用。通过这个引用在代理对象中去调用真实对象的方法,并且可以自定义一些其他的操作,比如本例子中的preRequest()和postRequest(),他们分别在之前和之后被调用。
大家看代码后可以发现,这里并没有用到反射的知识。而且对于上面的例子来说,真实角色对象是事先必须已经存在的,并且必须是作为代理对象的内部属性。
如果这样的话,有一个真实角色那就必须在代理角色中有一个他的引用,如果在不知道真实角色的情况下又需要怎么办?这就需要"动态代理"来解决了。
二、 动态代理(以下内容摘自博文http://blog.csdn.net/a396901990/article/details/26015977)
静态代理中,一个代理类只能为一个接口服务,那么如果现在有很多个接口的话,则肯定要写很多的代理类了,而且,所有的代理操作除了调用的方法不一样之外,其他的操作都一样,此时,肯定有很多重复的代码。解决这一问题最好的做法是可以通过一个代理类完成全部的代理功能或者说去动态的生成这个代理类,那么此时就必须使用动态代理完成。
动态代理实现所需要的API:
Java动态代理类位于java.lang.reflect包下,主要有以下一个接口和一个类:
1. InvocationHandler接口: 该接口中仅有一个方法
public object invoke(Object proxy, Method method, Object[] args)
在实际使用时,proxy一般是指代理类,method是被代理的方法,args为该方法的参数数组。这个抽象的invoke方法在代理类中动态实现。
2. Proxy类: 该类即为动态代理类,这里只介绍一下newProxyInstance()这个方法
static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)
ClassLoader loader: 类加载器
Class<?>[] interfaces: 得到全部的接口
InvocationHandler h: 得到InvocationHandler接口的子类实例
这个方法是最主要的方法,它会返回代理类的一个实例,返回后的代理类可以当做被代理类使用。
如果你要想得到一个加载器的对象,则肯定使用Class类完成。
实现动态代理需4步骤:
1)创建一个实现接口InvocationHandler的类,它必须实现invoke方法。
2) 通过Proxy的静态方法newProxyInstance创建一个代理
3) 创建被代理的类以及接口
4) 通过代理调用方法
下面看这个例子具体说明如何通过上面的4个步骤来建立一个动态代理:
步骤1和步骤2合并写在一个类中,命名为DynamicProxy
- public class DynamicProxy implements InvocationHandler {
- // 需要被代理类的引用
- private Object object;
- // 通过构造方法传入引用
- public DynamicProxy(Object object) {
- this.object = object;
- }
- // 定义一个工厂类,去生成动态代理
- public Object getProxy() {
- // 通过Proxy类的newProxyInstance方法动态的生成一个动态代理,并返回它
- return Proxy.newProxyInstance(object.getClass().getClassLoader(), object
- .getClass().getInterfaces(), this);
- }
- // 重写的invoke方法,这里处理真正的方法调用
- @Override
- public Object invoke(Object obj, Method method, Object[] args)
- throws Throwable {
- beforeDoing();
- Object invoke = method.invoke(object, args);
- afterDoing();
- return invoke;
- }
- public void beforeDoing() {
- System.out.println("before ............");
- }
- public void afterDoing() {
- System.out.println("after ............."+"\n");
- }
- }
该类实现了InvocationHandler接口,并且自定义了一个getProxy()方法去调用Proxy类的newProxyInstance()去生成一个动态代理。
步骤3:创建被代理的类以及接口
- //真实角色对象,继承自抽象角色,重写定义的方法。
- public class RealSubject implements Subject1,Subject2{
- //Subject1接口中的方法
- @Override
- public void request() {
- System.out.println("this is real subject");
- }
- //Subject1接口中的方法
- @Override
- public void ask() {
- System.out.println("this is real ask");
- }
- //Subject2接口中的方法
- @Override
- public void request2() {
- System.out.println("this is real subject2");
- }
- }
这个类就是我们需要被代理的类,他继承了两个接口分别是Subject1,Subject2
- interface Subject1 {
- public void request();
- public void ask();
- }
- interface Subject2 {
- public void request2();
- }
4.通过代理调用方法
接下来在main方法中通过动态生成的代理来调用方法
- public static void main(String[] args) {
- //需要被代理的类
- RealSubject realSubject = new RealSubject();
- //用于创建动态代理的类,将被代理类的引用传递进去
- DynamicProxy dynamicProxy = new DynamicProxy(realSubject);
- //通过getProxy方法动态的获取代理类,转换成需要调用的接口类型后调用方法
- Subject1 s1 = (Subject1) dynamicProxy.getProxy();
- s1.request();
- s1.ask();
- //通过getProxy方法动态的获取代理类,转换成需要调用的接口类型后调用方法
- Subject2 s2 = (Subject2) dynamicProxy.getProxy();
- s2.request2();
- }
输出:
before ............
this is real subject
after .............
before ............
this is real ask
after .............
before ............
this is real subject2
after .............
简单介绍动态代理内部实现原理:
例子看完了,肯定有如下疑问:
动态代理在哪里应用了反射机制?仅仅通过一个InvocationHandler接口和一个Proxy类的newProxyInstance方法是如何动态的生成代理?
下面就来简单的分析一下InvocationHandler,和Proxy的newProxyInstance方法是如何在运行时动态的生成代理的:
先看newProxyInstance是如何定义的:
static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)
这里需要传入3个参数。先看第二个参数,传入一个接口类型的Class数组。
上面例子中传入的参数是object.getClass().getInterfaces()
object是被代理对象,这个参数就是通过反射拿到被代理对象的所有接口
在上面例子中就是我们定义的Subject1,Subject2接口了
有了接口数组,就可以通过类似下面的代码使用反射拿到接口中的所有方法:
- for (interface infce : interfaces[]) {
- Method[] methods = infce.getMethods();
- for (Method m : method) {
- m.getName();
- }
- }
在正常情况下,知道了被代理的接口和接口里面的方法就可以去生成代理类了。
大概就是下面这种的一个简单的实现:一个很固定的套路,只要知道实现接口和方法就仿照写出。
- public class ProxySubject implements Subject{
- private RealSubject realSubject;
- @Override
- public void request() {
- realSubject.request();
- }
- }
动态代理还会在代理的方法中做一些其他的操作,如添加日志,时间,权限等操作。这时候就要靠InvocationHandler接口中的invoke方法。看看例子中如何实现的。
- @Override
- public Object invoke(Object obj, Method method, Object[] args) throws Throwable {
- beforeDoing();
- Object invoke = method.invoke(object, args);
- afterDoing();
- return invoke;
- }
这些代码是我们自定义的,需要实现什么操作就写在里面。
这段代码存在于Invocationhandler对象中,这个对象会在调用Proxy的newProxyInstance的方法中传递进去。
这时候可以通过反射知道被调用方法的名字等信息,之后还是通过字符串的形式拼接处类似下面的动态代理类
- public class ProxySubject implements Subject{
- private RealSubject realSubject;
- @Override
- public void request() {
- Methond md = Subject.getMethod("methodName");
- handler.invoke(this, md);
- }
- }
这个大概就是根据传递的接口对象和InvocationHandler结合后应该生成的代理类。但现在的问题是如何去动态的生成上面这样的代理类。
答案是使用字符串拼接的方式。
从看上面的代码可以看出,除了接口和调用方法不同其他都相同。而且我们已经通过反射获得了方法和接口名字,这样就可以按着这个“套路”去用字符串拼接成这样的一类。
大概就是下面这种代码:
- String source = "package com.gxy.proxy;" + rt
- + "public class "+ClassName+"implements "+InterfaceName+ rt
- + "{" + rt
- + "private "+ ClassName + ClassName.toLowerCase()+" ; " + rt
- + "@Override"
- + "public Void "+InterfaceName+ "()" + rt + " {"
- + "Method md = "+InterfaceName+".getMethod("+ methodName+");" +rt
- + "hander.invoke(this, md);" + rt
- + "}" + rt
- + "}";
用反射生成的出来类名,接口名,方法名去动态的创建这样一个类的字符串。
之后就特定的方法去将这个字符串生成成类。在用反射把这个类取出来。这样就有了这个“动态”生成的代理类了。
Java反射机制(四):动态代理的更多相关文章
- Java反射机制以及动态代理
Java反射机制以及动态代理 Java反射机制 含义与功能 Java的反射(reflection)机制是指在程序的运行状态中,可以构造任意一个类的对象,可以了解任意一个对象所属的类,可以了解任意一个类 ...
- java反射机制与动态代理
在学习HadoopRPC时.用到了函数调用.函数调用都是採用的java的反射机制和动态代理来实现的,所以如今回想下java的反射和动态代理的相关知识. 一.反射 JAVA反射机制定义: JAVA反射机 ...
- Java反射机制可以动态修改实例中final修饰的成员变量吗?
问题:Java反射机制可以动态修改实例中final修饰的成员变量吗? 回答是分两种情况的. 1. 当final修饰的成员变量在定义的时候就初始化了值,那么java反射机制就已经不能动态修改它的值了. ...
- 【java】java反射机制,动态获取对象的属性和对应的参数值,并属性按照字典序排序,Field.setAccessible()方法的说明【可用于微信支付 签名生成】
方法1:通过get()方法获取属性值 package com.sxd.test.controller; public class FirstCa{ private Integer num; priva ...
- Java的反射机制和动态代理
介绍Java注解的时候,多次提到了Java的反射API.与javax.lang.model不同的是,通过反射API可以获取程序在运行时刻的内部结构.反射API中提供的动态代理也是非常强大的功能,可以原 ...
- java.lang.Class<T> -- 反射机制及动态代理
Interface : Person package java_.lang_.component.bean; public interface Person { String area = " ...
- 【Java基础】java中的反射机制与动态代理
一.java中的反射机制 java反射的官方定义:在运行状态下,可以获取任意一个类的所有属性和方法,并且可通过某类任意一对象实例调用该类的所有方法.这种动态获取类的信息及动态调用类中方法的功能称为ja ...
- java反射机制与动态加载类
什么是java反射机制? 1.当程序运行时,允许改变程序结构或变量类型,这种语言称为动态语言.我们认为java并不是动态语言,但是它却有一个非常突出的动态相关机制,俗称:反射. IT行业里这么说,没有 ...
- Java中的反射机制和动态代理
一.反射概述 反射机制指的是Java在运行时候有一种自观的能力,能够了解自身的情况为下一步做准备,其想表达的意思就是:在运行状态中,对于任意一个类,都能够获取到这个类的所有属性和方法:对于任意一个对象 ...
- java反射中的动态代理机制(有实例)
在学习Spring的时候,我们知道Spring主要有两大思想,一个是IoC,另一个就是AOP,对于IoC,依赖注入就不用多说了,而对于Spring的核心AOP来说,我们不但要知道怎么通过AOP来满足的 ...
随机推荐
- JavaScript的注意事项
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- 陈云川的OPENLDAP系列
前言 本 来,我应该准备一篇精彩的演说辞,从LDAP应用的方方面面讲起,细数LDAP在各种场合应用的成功案例,大肆渲染LDAP应用的辉煌前景,指出有多少机 构和组织的关键业务是建立在LDAP的基础上的 ...
- 清除SQL数据库文本字段中的回车、换行符的方法
清除SQL数据库中文本字段的回车.换行符的方法 清除回车符: update tableName set columnName = rtrim(ltrim(replace(columnName ,cha ...
- laravel-admin 安装(总结)
https://www.jianshu.com/p/844b05e4c45a laravel-admin 是一个可以快速帮你构建后台管理的工具,它提供的页面组件和表单元素等功能,能帮助你使用很少的代码 ...
- Django 点滴
1.views 中可用 render 传递参数 def home(request): info_dict = {'site': u'震撼学习', 'content': u'各种IT技术'} #Tuto ...
- js将canvas保存成图片并下载
<canvas id="canvas" width="400" height="400"></canvas> < ...
- concurrent模块
concurrent包 concurrent.futrues模块 3.2版本引入 异步并行任务模块,提供一个高级的异步可执行的便利接口. 提供了两个池执行器 ThreadPoolExecutor异步调 ...
- JasperStudio 输出pdf 出错。
发表于 2008-09-23 09:35:15 楼主net.sf.jasperreports.engine.JRException: Error retrieving field value from ...
- NSArray 查询数组中的对象
1.NSString 对象 NSArray *array =@["123", @"234" , @"345"]; NSPredicate ...
- 【转】Sprague-Grundy函数
http://www.cnitblog.com/weiweibbs/articles/42735.html 上一期的文章里我们仔细研究了Nim游戏,并且了解了找出必胜策略的方法.但如果把Nim的规则略 ...