动态代理理解
二话不说直接上代码!
注意哦!我们需要一个接口和该接口的实现类!
public static void main(String[] args) {
//1param: 固定值: 告诉虚拟机用哪个字节码加载器加载内存中创建出的字节码文件
//2param: 告诉虚拟机内存中正在被创建的字节码文件中应该有哪些方法
//3param: 告诉虚拟机正在被创建的字节码上的各个方法如何处理, InvocationHandler h:得到InvocationHandler接口的子类实例
ICar(接口) car=(ICar)Proxy.newProxyInstance(TestCar.class.getClassLoader(), GoogleCar.class.getInterfaces(),new InvocationHandler() {
//method:代表正在执行的方法
//args:代表正在执行的方法中的参数
//Object:代表方法执行完毕之后的返回值
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//经过测试得知:method代表当前正在执行的每个方法
//System.out.println(method.getName());
//执行当前的方法
//method.invoke(new GoogleCar(), args);
//代表每个方法执行完毕之后返回对象
Object obj=null;
if(method.getName().equalsIgnoreCase("start")){
System.out.println("检查天气是否良好");
//打印args中的内容
System.out.println(Arrays.toString(args));
obj=method.invoke(new GoogleCar(), args);
System.out.println("检查路况是否拥堵");
}else{
obj=method.invoke(new GoogleCar(), args);
}
return obj;
}
});
String cc=car.start(1,4);
System.out.println(cc);
car.run();
car.stop();
}
一、什么是ClassLoader?
大家都知道,当我们写好一个Java程序之后,不是管是CS还是BS应用,都是由若干个.class文件组织而成的一个完整的Java应用程序,当程序在运行时,即会调用该程序的一个入口函数来调用系统的相关功能,而这些功能都被封装在不同的class文件当中,所以经常要从这个class文件中要调用另外一个class文件中的方法,如果另外一个文件不存在的,则会引发系统异常。而程序在启动的时候,并不会一次性加载程序所要用的所有class文件,而是根据程序的需要,通过Java的类加载机制(ClassLoader)来动态加载某个class文件到内存当中的,从而只有class文件被载入到了内存之后,才能被其它class所引用。所以ClassLoader就是用来动态加载class文件到内存当中用的。
//获取String类的加载器
ClassLoader classLoader = String.class.getClassLoader();
System.out.println(classLoader);
//由于String.class ,int.class等字节码文件需要频繁的被加载内存,速度必须快,底层用其他语言来实现c c++
//获取ext(extendtion)包下的某个类的字节码加载器 ExtClassLoader:扩展类加载器
ClassLoader classLoader2 = sun.net.spi.nameservice.dns.DNSNameService.class.getClassLoader();
System.out.println(classLoader2);
//应用类:程序员实现的所有的类都属于应用类
//获取应用类加载器 AppClassLoader
ClassLoader classLoader3 = TestClassLoader.class.getClassLoader();
System.out.println(classLoader3);
}
}
// 获取谷歌Car googleCar.class字节码文件上所有的接口,googlecar其上可能实现了多个接口
Class[] clazz = GoogleCar.class.getInterfaces();
//由于当前案例中谷歌Car仅实现了一个接口ICar
//以下代码相当于获取到了ICar.class字节码对象
Class cla=clazz[0];
//获取ICar.class字节码对象上所有的方法
Method[] mds = cla.getMethods();
for (Method method : mds) {
System.out.println(method.getName());
原有类本身的方法装饰不了!
例子:使用Filter处理乱码问题
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
//将request对象转换为HttpServletRequest
final HttpServletRequest req=(HttpServletRequest)request;
//让JDK在内存中生成代理对象:增强了req对象上的getParameter(name);API
HttpServletRequest myReq=(HttpServletRequest)Proxy.newProxyInstance(EncodingFilter.class.getClassLoader(), req.getClass().getInterfaces(), new InvocationHandler() {
<? req.getClass().getInterfaces()告诉了这个代理类里面应该有那些方法?> <?怎么知道这个method里面就一定有getparamter呢?下面的Method又不是一个数组> <?在浏览器向服务器发送请求之后, 服务器接受到请求, 在调用service方法处理请求之前, 会创建Request对象, 并把所有的请求信息( 请求行、请求头、请求实体 ) 全部封装到Request对象中?> <?//经过测试得知:method代表当前正在执行的每个方法?> <?怎么知道request当前就在执行getParameter?> <?这是重新写了要增强强的方法?> <? method是方法对象 obj是该方法对象所在的类对象实例 args是方法参数?>
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
<?这个method是方法调用时才走invoke()?这一段重载方法在未调用之前都可以不看>
Object obj=null;
if(method.getName().equalsIgnoreCase("getParameter")){
//获取本次请求方式
String md=req.getMethod();
if("get".equalsIgnoreCase(md)){
<?原本在jsp页面就规定了传输方式?>
//get方式的请求
//调用req对象上的getParameter方法
String v=(String)method.invoke(req, args);
//转码
String vv=new String(v.getBytes("iso-8859-1"),"utf-8");
return vv;
<?返回到哪?> <?返回给转码的接受体,return 后的部分都不用看了?>
}else{
//post方式的请求
req.setCharacterEncoding("utf-8");
obj=method.invoke(req, args);<?调用getparamters?>
}
}else{
obj=method.invoke(req, args);
}
return obj;
}
});
//打印代理对象哈希码
System.out.println(myReq.hashCode());
//将代理对象放行
chain.doFilter(myReq, response);
<?将包装后的myreq返回给服务器?>
}
难点:理解各个参数的含义及不要忘记需不需要将封装的类返回,或者注意调用原先的函数的返回类型,这一部分对应的是obj参数的返回.(对于新手来说,我一直觉得无法理解底层的是怎么实现了,学某个知识点时,只是记住它的步骤和所使用的类或接口的方法的含义.后来想开了,照葫芦画瓢何尝不是学习呢?还学得如此艰难!底层原理我肯定会去学的啊!)
我之前一直纠结于程序的顺序执行思维,所以一直没有走出那个圈子,在哪里绕啊绕的.其实只要把那个需要增强的Proxy.newProxyInstance写好,就不需要管了.因为在调用你需要的某个接口里的函数时,invoke方法里就会给你判断,是不是你需要增强的那个函数!当然了,这一部分时你预先就要写好的!
动态代理的优点在于弥补了装饰者模式在接口类方法很多的时候的包装类需要重载很多函数.
动态代理仅仅需要在你需要那个接口里某个函数时往invoke方法里走一下,判断一下当下你调用的函数是不是你需要的函数,然后在原方法的基础上补充你想要的内容!
总而言之!
Proxy和重载后的invoke方法起了一个对接口内的方法的筛选然后再使用的过程!
动态体现在哪?
通过类加载器把你需要的函数从接口的实现类里面加载(复制)并增添你需要的功能到包装类里面!这是一个动态的过程,你需要它就加载,判断的方法是你需要的方法他就加载.
加载过程在jvm里实现,通常为了加载速度,一般采用c/c++来写这部分代码!
还没有评论,来说两句吧...