JAVA提高八:动态代理技术
对于动态代理,学过AOP的应该都不会陌生,因为代理是实现AOP功能的核心和关键技术。那么今天我们将开始动态代理的学习:
一、引出动态代理
生活中代理应该是很常见的,比如你可以通过代理商去买电脑,也可以直接找厂商买电脑,最终都是买到了电脑。程序中也一样存在代理的情况,比如要为已经存在的多个具有相同接口的目标类的各个方法增加一些系统功能,例如:异常处理、日志、计算方法耗时等等,那么我们会怎么做呢?
1.会编写一个与目标类拥有相同接口的代理类,代理类的每个方法调用目标类的相同方法,然后在调用方法前后加上系统功能所需要的代码。
2.如果采用工厂模式或者配置文件的方式进行管理,则不需要修改客户端程序,在配置文件中配置是使用目标类、还是代理类,这样以后很容易切换。
样例如下:
public class X{
public void sayHello(){
syso:say hello;
}
}
现在我要在这个方法之前添加一个时间,方法之后添加一个时间,计算这个方法执行的时间一共是多少.如果我没有得到sayHello源码,那么我怎么做呢?写一个代理:
public class XProxy
{
private X x;
public void sayHello
{
startTime:
x.syHello();
endTime;
}
}
说明:上面的是伪代码。
二、创建动态代理类
现在试想一下,上面只是代理了一个目标类,如果多个目标类,那么是不是要创建N多个代理类呢?那样不是代码太不灵活且笨重了。当然不会。
java虚拟机可以在运行期间动态生成类,这种类是以字节码的形式生成出来的。这种动态生成的类往往呢就是代理类。即动态代理类。
JVM生成的动态代理类必须满足一定的条件,这就是必须实现一个或多个接口。所以JVM生成的动态代理只能用作具有相同接口的目标类的代理。(动态生成的类不是代理,我们只是吧这个类当成代理来用。)
Proxy动态代理的API:
两个参数应该很容易理解:
第一个参数:我们知道任何一个字节码都是需要通过类加载器来加载的,那么这个动态生成的字节码也不例外,需要给它一个类加载器。
第二个参数:就是动态代理类生成,必须满足的一个条件,需要实现一个或者多个接口,否则这个生成的类字节码中就没有方法了,没有方法就失去了其功能意义。
下面我们动手来创建一个动态的代理类,大体思路为:
1.创建实现Collection接口的动态类和查看其名称,分析Proxy.getProxyClass方法的各个参数
2.编码列出动态类中的所有构造方法和参数签名
3.编码列出动态类中的所有方法和参数签名
4.创建动态类的实例对象:1)用反射获取构造方法 2)编写一个最简单的invocationHandle类 3)调用构造方法创建动态类的实例对象,并将编写的InvocationHandle类的实例对象传递进去 4)打印创建对象和调用对象的没有返回的方法和getClass方法,演示调用其他有返回值方法报告了异常。
5)将创建动态类的实例对象的代理写成匿名内部类方式,简化代码。
样例分步实现如下:
(1)首先我们来完成前面3步:
package study.javaenhance; import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Collection; public class ProxyTest
{
public static void main(String[] args)
{
Class clazzProxy1 = Proxy.getProxyClass(Collection.class.getClassLoader(),Collection.class);
System.out.println(clazzProxy1.getName()); //上面输出的为一个类,那么一个类肯定有其构造方法和方法,下面我们来列出来 System.out.println("----------begin constructors list----------");
//1.列出构造方法
Constructor[] constructors = clazzProxy1.getConstructors();
for (Constructor constructor : constructors)
{
String name = constructor.getName();
StringBuilder sBuilder = new StringBuilder(name);
sBuilder.append('(');
Class[] clazzParams = constructor.getParameterTypes();
for (Class clazzParam : clazzParams) {
sBuilder.append(clazzParam.getName()).append(',');
}
if(clazzParams!=null && clazzParams.length != 0)
sBuilder.deleteCharAt(sBuilder.length()-1);
sBuilder.append(')');
System.out.println(sBuilder.toString());
} //2.列出这个类字节码中的所有方法
System.out.println("----------begin methods list----------");
Method[] methods = clazzProxy1.getMethods();
for(Method method : methods){
String name = method.getName();
StringBuilder sBuilder = new StringBuilder(name);
sBuilder.append('(');
Class[] clazzParams = method.getParameterTypes();
for(Class clazzParam : clazzParams){
sBuilder.append(clazzParam.getName()).append(',');
}
if(clazzParams!=null && clazzParams.length != 0)
sBuilder.deleteCharAt(sBuilder.length()-1);
sBuilder.append(')');
System.out.println(sBuilder.toString());
} } }
输出结果如下:
$Proxy0
----------begin constructors list----------
$Proxy0(java.lang.reflect.InvocationHandler)
----------begin methods list----------
add(java.lang.Object)
hashCode()
clear()
equals(java.lang.Object)
toString()
contains(java.lang.Object)
isEmpty()
addAll(java.util.Collection)
iterator()
size()
toArray([Ljava.lang.Object;)
toArray()
remove(java.lang.Object)
containsAll(java.util.Collection)
removeAll(java.util.Collection)
retainAll(java.util.Collection)
isProxyClass(java.lang.Class)
getProxyClass(java.lang.ClassLoader,[Ljava.lang.Class;)
getInvocationHandler(java.lang.Object)
newProxyInstance(java.lang.ClassLoader,[Ljava.lang.Class;,java.lang.reflect.InvocationHandler)
wait()
wait(long,int)
wait(long)
getClass()
notify()
notifyAll()
可以看到所有的方法均是来自Collection 和父类Object中的方法,符合我们的预期结果。接下来我们进入第四步和第五步的实现:
首先,我们来创建这个动态代理类的实例。那么直接clazzProxy1.newInstance();可不可以呢?显然是不可以的嘛.我们刚刚说了,动态生成的这个代理类只有一个构造方法,有没有无参构造方法呢?没有啊.所以,创建 一个参数的构造方法.参数类型是java.lang.reflect.InvocationHandler。
下面我们来实现:
1.定义一个上述接口的实例:
package study.javaenhance; import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method; public class MyInvocationHandler implements InvocationHandler { @Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
// TODO Auto-generated method stub
return null;
} }
2.调用实现创建实例动态类:
//3.创建实例对象
System.out.println("----------begin create instance object----------");
Constructor constructor = clazzProxy1.getConstructor(InvocationHandler.class);
MyInvocationHandler myInvocationHandler = new MyInvocationHandler();
Collection collection = (Collection) constructor.newInstance(myInvocationHandler);
System.out.println(collection);
collection.clear();
//collection.size(); //报错,异常会发生,产生异常的原因在于其返回值为int类型,但是每一次调用一个方法都会调用到invoke方法,我们此时的invoke返回的为null,所以是没有办法转换为int类型的。
3.我们采用匿名内部类的方式优化:
//3.1 采用匿名内部类方式进行创建
Collection collection2 = (Collection) constructor.newInstance(new InvocationHandler()
{ @Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
// TODO Auto-generated method stub
return null;
} });
System.out.println(collection2);
collection2.clear();
//collection2.size();
4.继续优化:思考如果我们每次都想上面方式去创建动态的代理类实在有点重复,那么这个是JDK的Proxy类中提供了简单的方法直接去创建动态代理类,方式如下:
//3.3 采用Proxy 中提供的简单方法创建
Collection collection3 = (Collection)Proxy.newProxyInstance(Collection.class.getClassLoader(),
new Class[]{Collection.class},
new InvocationHandler()
{
private ArrayList target = new ArrayList();
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
return method.invoke(target, args);
} });
//System.out.println(collection3);
collection3.add("abc");
collection3.add("def");
collection3.add("hij");
System.out.println(collection3.size());
三、动态代理的原理简单分析
上面我们创建了动态代理类,下面我们分析下代理的原理:
下面在来看一个问题:
动态代理的工作原理图:
对上面的这个图,我们简单来说说:客户端动态生成代理类,然后调用代理类的方法,代理类内部调用handler.invoke()方法,在invoke中呢,我们又指向的目标类.这样就实现了代理了.我客户端调用代理的什么方法,invoke就只向目标类的同一个方法.而在指定目标类方法的前后呢,我们还可以做其他的操作,比如记录日志.图中用圈圈出来的部分就是代理类自己实现的功能了.这就是代理类的原理.
我们来做最后一步,将上面的动态生成的代理类,编写可生成代理和插入通告的通用方法:
test代码:
package study.javaenhance; import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Collection; public class ProxyTest
{
public static void main(String[] args) throws Exception
{
Class clazzProxy1 = Proxy.getProxyClass(Collection.class.getClassLoader(),Collection.class);
System.out.println(clazzProxy1.getName()); //上面输出的为一个类,那么一个类肯定有其构造方法和方法,下面我们来列出来 System.out.println("----------begin constructors list----------");
//1.列出构造方法
Constructor[] constructors = clazzProxy1.getConstructors();
for (Constructor constructor : constructors)
{
String name = constructor.getName();
StringBuilder sBuilder = new StringBuilder(name);
sBuilder.append('(');
Class[] clazzParams = constructor.getParameterTypes();
for (Class clazzParam : clazzParams) {
sBuilder.append(clazzParam.getName()).append(',');
}
if(clazzParams!=null && clazzParams.length != 0)
sBuilder.deleteCharAt(sBuilder.length()-1);
sBuilder.append(')');
System.out.println(sBuilder.toString());
} //2.列出这个类字节码中的所有方法
System.out.println("----------begin methods list----------");
Method[] methods = clazzProxy1.getMethods();
for(Method method : methods){
String name = method.getName();
StringBuilder sBuilder = new StringBuilder(name);
sBuilder.append('(');
Class[] clazzParams = method.getParameterTypes();
for(Class clazzParam : clazzParams){
sBuilder.append(clazzParam.getName()).append(',');
}
if(clazzParams!=null && clazzParams.length != 0)
sBuilder.deleteCharAt(sBuilder.length()-1);
sBuilder.append(')');
System.out.println(sBuilder.toString());
} //3.创建实例对象
System.out.println("----------begin create instance object----------");
Constructor constructor = clazzProxy1.getConstructor(InvocationHandler.class);
MyInvocationHandler myInvocationHandler = new MyInvocationHandler();
Collection collection = (Collection) constructor.newInstance(myInvocationHandler);
System.out.println(collection);
collection.clear();
//collection.size(); //报错,异常会发生,产生异常的原因在于其返回值为int类型,但是每一次调用一个方法都会调用到invoke方法,我们此时的invoke返回的为null,所以是没有办法转换为int类型的。 //3.1 采用匿名内部类方式进行创建
Collection collection2 = (Collection) constructor.newInstance(new InvocationHandler()
{ @Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
// TODO Auto-generated method stub
return null;
} });
System.out.println(collection2);
collection2.clear();
//collection2.size(); //3.3 采用Proxy 中提供的简单方法创建
Collection collection3 = (Collection)Proxy.newProxyInstance(Collection.class.getClassLoader(),
new Class[]{Collection.class},
new InvocationHandler()
{
private ArrayList target = new ArrayList();
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
return method.invoke(target, args);
} });
//System.out.println(collection3);
collection3.add("abc");
collection3.add("def");
collection3.add("hij");
System.out.println(collection3.size());
System.out.println(collection3.getClass().getName());//这个返回的是 System.out.println("----------begin create instance object 抽化----------");
//3.4抽出动态代理让目标对象和切面对象都是传入进去的 final ArrayList target = new ArrayList();
Collection collection4 = (Collection)getProxy(target,new MyAdvice());
collection4.add("test1");
collection4.add("test2");
System.out.println(collection4.size());
} private static Object getProxy(final Object target,final Advice advice) {
Object object = Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new InvocationHandler()
{
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
advice.beforeMethod(method);
Object retValue = method.invoke(target, args);
advice.afterMethod(method);
return retValue;
} });
return object;
} }
四、实现类似Spring的可配置的AOP框架
首先,我们要完成的要求如下:
我们来模拟Spring的工厂模式读取从配置文件传递过来的类。如果这个类是一个普通类则直接返回。如果是一个代理类,则创建代理对象,返回代理类。
具体的理论知识可以看上面的图片
首先创建一个BeanFactory.java类。这是一个Bean工厂,用于读取配置文件中的类
package study.javaenhance.aopframework; import java.io.IOException;
import java.io.InputStream;
import java.util.Properties; import study.javaenhance.Advice; public class BeanFactory
{
private Properties properties = new Properties(); public BeanFactory(InputStream inStream)
{
try
{
properties.load(inStream);
}
catch (IOException e)
{
e.printStackTrace();
}
finally
{
if(inStream != null)
{
try
{
inStream.close();
}
catch (IOException e)
{
e.printStackTrace();
}
}
}
} public Object getBean(String name)
{
String className = properties.getProperty(name);
Object bean = null;
try
{
Class clazz = Class.forName(className);
bean = clazz.newInstance();
if(bean instanceof ProxyFactoryBean)
{
ProxyFactoryBean proxyBean = (ProxyFactoryBean) bean;
Advice advice = (Advice)Class.forName(properties.getProperty(name + ".advice")).newInstance();
Object target = Class.forName(properties.getProperty(name + ".target")).newInstance();
proxyBean.setAdvice(advice);
proxyBean.setTarget(target);
Object proxy = proxyBean.getProxy();
return proxy;
}
}
catch (Exception e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
return bean;
}
}
package study.javaenhance.aopframework; import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy; import study.javaenhance.Advice; public class ProxyFactoryBean
{ private Object target; private Advice advice; public Object getTarget() {
return target;
} public void setTarget(Object target) {
this.target = target;
} public Advice getAdvice() {
return advice;
} public void setAdvice(Advice advice) {
this.advice = advice;
} public Object getProxy()
{
Object object = Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),
new InvocationHandler()
{ @Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
advice.beforeMethod(method);
Object retValue = method.invoke(target, args);
advice.afterMethod(method);
return retValue;
} });
return object;
} }
接下来创建一个config.Properties文件.config.Properties
xxx=java.util.ArrayList
#xxx=study.javaenhance.aopframework.ProxyFactoryBean
xxx.advice=study.javaenhance.MyAdvice
xxx.target=java.util.ArrayList
最后建立测试类:
package study.javaenhance.aopframework; import java.io.InputStream;
import java.util.Collection; public class AopFrameworkTest
{
public static void main(String[] args)
{
InputStream ips = AopFrameworkTest.class.getResourceAsStream("config.properties");
Object bean = new BeanFactory(ips).getBean("xxx");
System.out.println(bean.getClass().getName());
((Collection)bean).clear();
} }
测试OK
总结:整个java增强的视频学习完成了,一共记住了多少我也不知道.但知道了很多内在的知识,如果有时间的话,或者说过一段时间可以拿出来问下,提供自己的技能。
参考资料:
张孝祥老师java 增强视频
JAVA提高八:动态代理技术的更多相关文章
- java中的动态代理机制
java中的动态代理机制 在java的动态代理机制中,有两个重要的类或接口,一个是 InvocationHandler(Interface).另一个则是 Proxy(Class),这一个类和接口是实现 ...
- 代理模式 & Java原生动态代理技术 & CGLib动态代理技术
第一部分.代理模式 代理模式是常用的java设计模式,他的特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息.过滤消息.把消息转发给委托类,以及事后处理消息等.代理类与委托类之间通常 ...
- Java中动态代理技术生成的类与原始类的区别 (转)
用动态代理的时候,对它新生成的类长什么样子感到好奇.有幸通过一些资料消除了心里的疑惑. 平时工作使用的Spring框架里面有一个AOP(面向切面)的机制,只知道它是把类重新生成了一遍,在切面上加上了后 ...
- Java中动态代理技术生成的类与原始类的区别
用动态代理的时候,对它新生成的类长什么样子感到好奇.有幸通过一些资料消除了心里的疑惑. 平时工作使用的Spring框架里面有一个AOP(面向切面)的机制,只知道它是把类重新生成了一遍,在切面上加上了后 ...
- Java基础---Java---基础加强---类加载器、委托机制、AOP、 动态代理技术、让动态生成的类成为目标类的代理、实现Spring可配置的AOP框架
类加载器 Java虚拟机中可以安装多个类加载器,系统默认三个主要类加载器,每个类负责加载特定位置的类:BootStrap,ExtClassLoader,AppClassLoader 类加载器也是Jav ...
- Java中动态代理技术生成的类与原始类的区别 (good)
用动态代理的时候,对它新生成的类长什么样子感到好奇.有幸通过一些资料消除了心里的疑惑. 平时工作使用的Spring框架里面有一个AOP(面向切面)的机制,只知道它是把类重新生成了一遍,在切面上加上了后 ...
- 杨晓峰-Java核心技术-6 动态代理 反射 MD
目录 第6讲 | 动态代理是基于什么原理? 典型回答 考点分析 知识扩展 反射机制及其演进 动态代理 精选留言 Markdown版本笔记 我的GitHub首页 我的博客 我的微信 我的邮箱 MyAnd ...
- 一文读懂Java中的动态代理
从代理模式说起 回顾前文: 设计模式系列之代理模式(Proxy Pattern) 要读懂动态代理,应从代理模式说起.而实现代理模式,常见有下面两种实现: (1) 代理类关联目标对象,实现目标对象实现的 ...
- 使用Java中的动态代理实现数据库连接池
2002 年 12 月 05 日 作者通过使用JAVA中的动态代理实现数据库连接池,使使用者可以以普通的jdbc连接的使用习惯来使用连接池. 数据库连接池在编写应用服务是经常需要用到的模块,太过频繁的 ...
- Java学习笔记--动态代理
动态代理 1.JDK动态代理 JDK1.3之后,Java提供了动态代理的技术,允许开发者在运行期创建接口的代理实例.JDK的动态代理主要涉及到java.lang.reflect包中的两个类:Proxy ...
随机推荐
- 201521123072《Java程序设计》第1周学习总结
201521123072<Java程序设计>第1周学习总结 标签(空格分隔): JAVA学习 1,本周学习总结 了解了JDK,JRE,JVM,学会使用cmd中一些简单的命令 了解了Java ...
- 201521123092《java程序设计》第12周学习总结
1. 本周学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结多流与文件相关内容. 2. 书面作业 将Student对象(属性:int id, String name,int age,doubl ...
- <c:forEach>+<c:if>
<c:forEach>:用来做循环<c:if>:相当于if语句用于判断执行,如果表达式的值为 true 则执行其主体内容. <c:forEach var="每个 ...
- Day3 文件操作和函数
一 文件操作 打开文件,得到文件句柄并赋值给一个变量 通过句柄对文件进行操作 关闭文件 1.1打开文件读取内容 print(open("sounds","r", ...
- Codeforces Round #436 (Div. 2) E. Fire
http://codeforces.com/contest/864/problem/E 题意: 有一堆物品,每个物品有3个属性,需要的时间,失效的时间(一开始)和价值.只能一件一件的选择物品(即在选择 ...
- 我是如何利用Hadoop做大规模日志压缩的
背景 刚毕业那几年有幸进入了当时非常热门的某社交网站,在数据平台部从事大数据开发相关的工作.从日志收集.存储.数据仓库建设.数据统计.数据展示都接触了一遍,比较早的赶上了大数据热这波浪潮.虽然今天的人 ...
- popOver 弹出框简单使用
1.仿QQ弹出框 1.1用到的知识点 1.1.1如何调整弹出框的大小(这里弹出的也是控制器) 这里已经有讲解过http://blog.csdn.net/iostiannan/article/detai ...
- textarea文本域值中含有大量\t\n问题
最近在发现了一个问题,很是头疼,textarea值中有大量的制表符,尝试了很多办法,最终找到了解决办法,希望能帮到同样有此困扰的你. <textarea> <c:out value= ...
- Symbol
ES5 的对象属性名都是字符串,这容易造成属性名的冲突.比如,你使用了一个他人提供的对象,但又想为这个对象添加新的方法(mixin 模式),新方法的名字就有可能与现有方法产生冲突.如果有一种机制,保证 ...
- ThinkPHP中:检查Session是否过期
1.创建Session public function index(){ $sess_time=time(); session('name','andy'); session('time_stamp' ...