黑马程序员--JAVA代理机制


----------- android培训java培训、java学习型技术博客、期待与您交流! ------------

一、代理

概念:

要为已经存在的多个具有相同接口的目标类的各个方法增加一些系统功能,如异常处理、日志、计算方法的运行时间、事物管理等等。

简单实例:

编写一个与目标类具有相同接口的代理类,代理类的每个方法调用目标类的相同方法,并在调用方法时加上系统功能的代码

class X{//目标类                       
   void sayHello(){                      
syso:Hello;                           
}
}
Xproxy{ //目标类
 void sayHello(){
startTime
X. sayHello();
endTime;
}
}

代理类优点

如果采用工厂模式和配置文件的方式进行管理,则不需要修改客户端程序,在配置文件中配置是使用目标类还是代理类。这样以后很容易切换,如果想要日志功能时,就配置代理类,否则配置目标类,这样,增加系统功能很容易,以后运行一段时间后,又想换掉系统功能也很容易。

import java.lang.reflect.*;  
import java.util.*;  
public class ProxyTest {  
    public static void main(String[] args) throws Exception{  
        //获取代理类Proxy的Class对象,传入的是类加载器和相应的字节码对象  
        Class clazzProxy1 = Proxy.getProxyClass(  
                Collection.class.getClassLoader(), Collection.class);  
        System.out.println(clazzProxy1.getName());//$Proxy0  
        System.out.println("---begin constructor list------");  
        //获取代理类的构造方法,可能含有多个,得到数组  
        Constructor[] constructors = clazzProxy1.getConstructors();  
        //遍历数组,获取每个构造方法  
        for(Constructor constructor : constructors){  
            //先得到构造方法的名字,并装入字符串容器中  
            String name = constructor.getName();  
            StringBuilder sBul = new StringBuilder(name);  
            sBul.append('(');  
            //获取构造方法中的参数类型,并遍历  
            Class[] clazzParams = constructor.getParameterTypes();  
            for(Class clazzParam : clazzParams){  
                sBul.append(clazzParam.getName()).append(',');  
            }  
            //将最后一个逗号去除  
            if(clazzParams != null && clazzParams.length!=0)  
                sBul.deleteCharAt(sBul.length()-1);  
            sBul.append(')');  
            System.out.println(sBul.toString());  
        }  
          
        System.out.println("---begin method list------");  
        //获取代理类的方法,存入数组  
        Method[] methods = clazzProxy1.getMethods();  
        //遍历数组,获取每个方法  
        for(Method method : methods){  
            //先得到方法的名字,并装入字符串容器中  
            String name = method.getName();  
            StringBuilder sBul = new StringBuilder(name);  
            sBul.append('(');  
            //获取方法中的参数类型,并遍历  
            Class[] clazzParams = method.getParameterTypes();  
            for(Class clazzParam : clazzParams){  
                sBul.append(clazzParam.getName()).append(',');  
            }  
            //将最后一个逗号去除  
            if(clazzParams!=null && clazzParams.length!=0)  
                sBul.deleteCharAt(sBul.length()-1);  
            sBul.append(')');  
            System.out.println(sBul.toString());  
        }  
}  

二、动态代理技术

一、概述:

1、要为系统中的各种接口的类增加代理功能,那将需要太多的代理类,这时就不能采用静态代理方式,需用动态代理技术。
2、动态代理类:JVM可在运行时,动态生成类的字节码,这种动态(不是代理,只是拿出来作为代理类)生成的类往往被用作代理类,即动态代理类。
注:JVM生成的动态类必须实现一或多个接口,所以JVM生成的动态代理类只能用作具有相同接口的目标类代理。
3、CGLIB库可以动态生成一个类的子类,一个类的子类也可以作为该类的代理,所以,如果要为一个没有实现接口的类生成动态代理,那么可以使用CGLIB库。
4、代理类各个方法通常除了调用目标相应方法和对外返回目标返回的结果外,还可以再代理方法中的如下位置上加上系统功能代码:
1)在调用目标方法之前
2)在调用目标方法之后
3)在调用目标方法前后
4)在处理目标方法异常的catch块中。

二、分析JVM动态生成的类

1、创建动态类的实例对象:
1)用反射获得构造方法
2)编写一个最简单的InvocationHandler的类
3)调用构造方法创建动态类的实例对象,并将编写的InvocationHandler类的实例对象传进去。
示例:
第一、打印创建的对象和调用对象的无返回值的方法和getClass方法,演示调用其他没有返回值的方法报告的异常
第二、将创建的动态类的实例对象的代理改写成为匿名内部类的形式编写。

package cn.itcast.test3;  
import java.lang.reflect.*;  
import java.util.*;  
public class ProxyTest {  
    public static void main(String[] args) throws Exception{  
//创建动态代理类的三种方式  
        //方式一:通过接口的子类创建对象  
        Collection proxy1 = (Collection)  
                constructor.newInstance(new MyInvocationHandler());  
        System.out.println(proxy1);//null  
        System.out.println(proxy1.toString());//null  
        proxy1.clear();//无异常  
        //proxy1.size();  
          
        //方式二:匿名内部类  
        Collection proxy2 = (Collection)  
                constructor.newInstance(new InvocationHandler(){  
                    public Object invoke(Object proxy, Method method,  
                            Object[] args) throws Throwable {  
                        // TODO Auto-generated method stub  
                        return null;  
                    }  
                });  
          
        //方式三:  
        //通过代理类的newProxyInstance方法直接创建对象  
        Collection proxy3 = (Collection)Proxy.newProxyInstance(  
            //定义代理类的类加载器  
            Collection.class.getClassLoader(),  
            //代理类要实现的接口列表  
            new Class[]{Collection.class},  
            //指派方法调用的调用处理程序  
            new InvocationHandler() {  
                //创建集合,制定一个目标  
                ArrayList target = new ArrayList();  
                public Object invoke(Object proxy, Method method, Object[] args)  
                        throws Throwable {  
                    //测试程序运行时间  
                    long beginTime = System.currentTimeMillis();  
                    //调用目标方法,将其从return抽出来,加入代理所需的代码  
                    Object retVal = method.invoke(target, args);  
                    long endTime = System.currentTimeMillis();  
                    //测试  
                    System.out.println(method.getName() +   
                            " run time of " +   
                            (endTime - beginTime));  
                    return retVal;  
                }  
            }  
            );  
        //通过代理类调用目标方法,每调用一个目标的方法就会执行代理类的方法  
        //当调用一次add方法时,就会找一次InvocationHandler这个参数的invoke方法  
        proxy3.add("sdfd");  
        proxy3.add("shrt");  
        proxy3.add("rtbv");  
        System.out.println(proxy3.size());  
        System.out.println(proxy3.getClass().getName());  
    }  
}  
2、 让JVM创建动态类需要提供的信息
1)生成类中的哪些方法,通过让其实现哪些接口的方式进行告知。
2)产生的类字节码必须有一个关联的类加载器对象
3)生成的类中的方法的代码是怎么样的,也得由我们自己提供,把我们的代码写在一个约定好的子接口对象的方法中,把对象传给它,它调用我们的方法,即相当于插入了我们自己的代码。提供执行代码的对象就是InvocationHandler对象,它是在创建动态类的实例对象的构造方法时传递进去的,在上面的InvocationHandler对象的invoke方法中,加一点代码就可以看到这些代码被调用运行了。

三、分析动态生成的类的内部代码

1、构造方法接受一个InvocationHandler对象,接受此对象的用处:
接受一个handler参数是为了记录它,以便在之后的程序中运用它。
2、实现Collection接口的动态类中的各个方法的代码的解析:
1)InvocationHandler接口中定义的invoke方法接受三个参数的含义:
第一、Client(客户端)程序调用objProxy.add(“avc”)方法时,涉及到了三个参数,分别为:objProxy对象,add方法,”avc”参数。代码如下:
class Proxy${
   add(Object obj){
       return handler.invoke(Object proxy, Method method, Object[] args);
   }
}
第二、其中的Object proxy即为bjProxy对象,Method method对应add方法,Object[] args就是”avc”参数。在使用newProxyInstance的方式创建代理对象实现时,当前正在调用代理对象(Object proxy),调用当前对象的哪个方法(Method method),嗲用此对象方法时传入的参数(Object[] args)。
3、调用代理涉及到三个因素:代理对象,代理对象的哪个方法,以及此方法接受的参数。要执行目标对象,只需要将代理对象作为目标对象即可。
4、对于上面代码中的Object retVal = method.invoke(target,args)的分析:
目标对象target执行完返回一个值为retVal,接着将值作为结果return回去,则代理方法就会收到一个返回值。其中还可以定义一个过滤器,对参数args(即当前对象的方法传入的参数)进行过滤(修改)。
5、对于上面代码中的proxy3.add(“sdfd”)的分析:
1)代理对象调用add方法,传递了sdfd参数。
2)add方法内部会找到InvocationHandler中的invoke方法,将代理对象proxy传进去,把add方法传入,将“sdfd”参数传入代理对象中的handler参数,返回了一个结果,就是给了add方法,add方法继续向外返回给调用的对象proxy3,即最终结果。
其中的handler的invoke方法返回又来自于目标target返回值,从而将此返回值返给Object invoke()方法,即作为handler参数位置上的值返回给add方法。add方法作为最后的结果返回。

四、问题:

1、在上面的方式一的代码中,调用无返回值的方法返回的是null,而调用有返回值方法的时候会报NullPointException异常的原因:
在proxy1.size()方法中,size()会调用Object invoke()方法,而对其覆写时的返回值是null,而size()本身会返回int类型,两者返回的类型不相等,所以就会报错。
而对于proxy3.size()不报错,是因为size()返回值与代理对象中handler参数返回值是一致的,当前目标返回什么,代理就返回什么,所以不会报错。
注意:目标返回值和代理返回值必须是同一类型。
2、为何动态类的实例对象的getClass()方法返回了正确结果,而没调用invoke方法:
因为代理类从Object上继承了许多方法,其中只对三个方法(hansCode、equals和toString)进行开发,委托给handler去自行处理,对于它身上其他方法不会交给代理类去实现,所以对于getClass()方法,还是会有Object本身实现的。即proxy3.getClass(),该是什么结果还是什么结果,并不会交给invoke方法处理。
五、总结分析动态代理类的统计原理和结构:
1、怎样将目标传进去:
1)直接在InvocationHandler实现类中创建目标类的实例对象,可看运行效果和加入日志代码,但是毫无意义。
2)为InvocationHandler实现类注入目标的实例对象,不能采用匿名内部类的形式了。
3)让匿名内的InvocationHandler实现类访问外面的方法中的目标类实例对象的final类型的引用变量。
2、动态代理的工作原理:
1)Client(客户端)调用代理,代理的构造方法接受一个InvocationHandler,client调用代理的各个方法,代理的各个方法请求转发给刚才通过构造方法传入的handler对象,又把各请求分发给目标的相应的方法。就是将handler封装起来,其中this引用了当前的放(发来什么请求就接受哪个方法)。
2)将创建代理的过程改为一种更优雅的方式,eclipse重构出一个getProxy方法绑定接受目标,同时返回代理帝乡,让调用者更懒惰,更方便,调用者甚至不用接触任何代理的API。
3、把系统功能代理模块化,即切面代码也改为通过参数形式提供,怎么把要执行的系统功能代码以参数的形式提供:
1)把要执行的代码装到一个对象的某个方法中,然后把此对象作为参数传递,接收者只要调用这个对象的方法,即等于执行了外接提供的代码。
2)为bind方法增加一个Advice参数

//封装getProxy方法  
package cn.itcast.test3;  
import java.lang.reflect.*;  
import java.util.*;  
public class MyProxy {  
    public static void main(String[] args)throws Exception {  
        //创建目标对象,并进行操作测试  
        final ArrayList target = new ArrayList();  
        Collection proxy = (Collection)getProxy(target,new MyAdvice());  
        proxy.add("sdf");  
        proxy.add("wgcd");  
        proxy.add("hgwe");  
        System.out.println(proxy.size());  
          
    }  
    //作为一个通用的方法,就使用Object  
    //传入一个目标,并传入一个接口,此接口作为通信的契约,才能调用额外的方法  
    private static Object getProxy(final Object target,final Advice advice) {  
        Object proxy = Proxy.newProxyInstance(  
                target.getClass().getClassLoader(),  
                //这里的接口要和target实现相同的接口  
                target.getClass().getInterfaces(),  
                new InvocationHandler() {  
                    public Object invoke(Object proxy, Method method, Object[] args)  
                            throws Throwable {  
                        //通过契约,使用其方法--before和after方法  
                        advice.beforeMethod(method);  
                        Object value = method.invoke(target, args);  
                        advice.afterMethod(method);  
                        return value;  
                    }  
                }  
                );  
        return proxy;  
    }  
}  
//创建实现Advice接口的子类  
package cn.itcast.test3;  
import java.lang.reflect.Method;  
//实现Advice接口中方法的具体内容  
public class MyAdvice implements Advice {  
  
    long beginTime = 0;  
    public void beforeMethod(Method method) {  
        // TODO Auto-generated method stub  
        System.out.println("从这里开始");  
        beginTime = System.currentTimeMillis();   
    }  
    public void afterMethod(Method method) {  
        // TODO Auto-generated method stub  
        long endTime = System.currentTimeMillis();  
        System.out.println("从这里结束");  
        System.out.println(method.getName() + " run time of " + (endTime-beginTime));  
    }  
}  
//创建接口Advice  
import java.lang.reflect.Method;  
/*接口中需要实现四个方法 
 * 调用目标方法之前 
 * 调用目标方法之后 
 * 调用目标方法前后 
 * 在处理目标方法异常的catch块中 
 */  
//这里只列出两个作为示例  
public interface Advice {  
    void beforeMethod(Method method);  
    void afterMethod(Method method);  
}  

三、AOP

简述:AOP,面向方面的编程

1,实现类似Spring的可配置的AOP框架

一、工厂类BeanFactory:
1、工厂类BeanFactory负责创建目标类或代理类的实例对象,并通过配置文件实现切换。
2、getBean方法根据参数字符串返回一个相应的实例对象,如果参数字符串在配置文件中对应的类名不是ProxyFactoryBean,则直接返回该类的实例对象,否则返回该类示例对象的getProxy方法返回的对象。
3、BeanFactory的构造方法接收代表配置文件的输入流对象的配置文件格式如下:
#xxx=java.util.ArrayList
xxx=cn.itcast.test3.aopframework.ProxyFactoryBean
xxx.advice=cn.itcast.test3.MyAdvice
xxx.target=java.util. ArrayList
 
注意:其中的#代表注释当前行。
4、ProxyFactoryBean充当封装成动态的工厂,需为工厂提供的配置参数信息包括:
目标(target)
通告(advice)
5、BeanFactory和ProxyFactoryBean:
1)BeanFactory是一个纯粹的bean工程,就是创建bean即相应的对象的工厂。
2)ProxyfactoryBean是BeanFactory中的一个特殊的Bean,是创建代理的工厂。

2,实现类似spring的可配置的AOP框架的思路:

1、创建BeanFactory类:
1)构造方法:接受一个配置文件,通过Properties对象加载InputStream流对象获得。
2)创建getBean(String name)方法,接收Bean的名字,从上面加载后的对象获得。
3)通过其字节码对象创建实例对象bean。
4)判断bean是否是特殊的Bean即ProxyFactoryBean,如果是,就要创建代理类,并设置目标和通告,分别得到各自的实例对象,并返回代理类实例对象。如果不是在返回普通类的实例对象。
2、创建ProxyFactoryBean(接口),此处用类做测试,其中有一个getProxy方法,用于获得代理类对象。
3、对配置文件进行配置,如上面配置一样。
4、作一个测试类:AopFrameworkTest进行测试。
示例:

//创建BeanFactory类  
package cn.itcast.test3.aopframework;  
import java.io.*;  
import java.util.Properties;  
import cn.itcast.test3.Advice;  
public class BeanFactory {  
    Properties prop = new Properties();  
    //创建对象时需要传入一个配置文件中的数据,所以需要在构造方法中接受一个参数  
    public BeanFactory(InputStream ips) {  
        try {  
            //将配置文件加载进来  
            prop.load(ips);  
        } catch (IOException e) {  
            e.printStackTrace();  
        }  
    }  
    //创建getBean方法,通过配置文件中的名字获取bean对象  
    public Object getBean(String name){  
        //从配置文件中读取类名  
        String className = prop.getProperty(name);  
        Object bean = null;  
        try {  
            //由类的字节码获取对象  
            Class clazz = Class.forName(className);  
            bean = clazz.newInstance();  
        } catch (Exception e) {  
            e.printStackTrace();  
        }   
        //判断bean是特殊的bean即ProxyFactoryBean还是普通的bean  
        if(bean instanceof ProxyFactoryBean){  
            Object proxy = null;  
            try {  
                //是ProxyFactoryBean的话,强转,并获取目标和通告  
                ProxyFactoryBean proxyFactoryBean = (ProxyFactoryBean)bean;  
                //获取advice和target  
                Advice advice = (Advice)Class.forName(prop.getProperty(name + ".advice")).newInstance();  
                Object target = Class.forName(prop.getProperty(name + ".target")).newInstance();  
                //设置目标和通告  
                proxyFactoryBean.setAdvice(advice);  
                proxyFactoryBean.setTarget(target);  
                //通过类ProxyFactoryBean(开发中是作为接口存在)中获得proxy对象  
                proxy = proxyFactoryBean.getProxy();  
            } catch (Exception e) {  
                // TODO Auto-generated catch block  
                e.printStackTrace();  
            }   
            //是ProxyFactoryBean的话,返回proxy对象  
            return proxy;  
        }  
        //否则返回普通bean对象  
        return bean;  
    }  
}  
  
//创建ProxyFactoryBean类  
package cn.itcast.test3.aopframework;  
import java.lang.reflect.*;  
import cn.itcast.test3.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 proxy = Proxy.newProxyInstance(  
                target.getClass().getClassLoader(),  
                //这里的接口要和target实现相同的接口  
                target.getClass().getInterfaces(),  
                new InvocationHandler() {  
                    public Object invoke(Object proxy, Method method, Object[] args)  
                            throws Throwable {  
                        //通过契约,使用其方法--before和after方法  
                        advice.beforeMethod(method);  
                        Object value = method.invoke(target, args);  
                        advice.afterMethod(method);  
                        return value;  
                    }  
                }  
                );  
        return proxy;  
    }  
}  
//创建测试类AopFrameworkTest  
package cn.itcast.test3.aopframework;  
import java.io.InputStream;  
public class AopFramewrorkTest {  
    public static void main(String[] args)throws Exception {  
        //读取配置文件的数据  
        InputStream ips =   
                AopFramewrorkTest.class.getResourceAsStream("config.property");  
        //获取bean对象  
        Object bean = new BeanFactory(ips).getBean("xxx");  
        System.out.println(bean.getClass().getName());  
    }  
}  

----------------------- android培训java培训、java学习型技术博客、期待与您交流! ----------------------

详情请查看:http://edu.csdn.net/heima

以下是对提供的参考资料的总结,按照要求结构化多个要点分条输出: 4G/5G无线网络优化与网规案例分析: NSA站点下终端掉4G问题:部分用户反馈NSA终端频繁掉4G,主要因终端主动发起SCGfail导致。分析显示,在信号较好的环境下,终端可能因节能、过热保护等原因主动释放连接。解决方案建议终端侧进行分析处理,尝试关闭节电开关等。 RSSI算法识别天馈遮挡:通过计算RSSI平均值及差值识别天馈遮挡,差值大于3dB则认定有遮挡。不同设备分组规则不同,如64T和32T。此方法可有效帮助现场人员识别因环境变化引起的网络问题。 5G 160M组网小区CA不生效:某5G站点开启100M+60M CA功能后,测试发现UE无法正常使用CA功能。问题原因在于CA频点集标识配置错误,修正后测试正常。 5G网络优化与策略: CCE映射方式优化:针对诺基亚站点覆盖农村区域,通过优化CCE资源映射方式(交织、非交织),提升RRC连接建立成功率和无线接通率。非交织方式相比交织方式有显著提升。 5G AAU两扇区组网:与三扇区组网相比,AAU两扇区组网在RSRP、SINR、下载速率和上传速率上表现不同,需根据具体场景选择适合的组网方式。 5G语音解决方案:包括沿用4G语音解决方案、EPS Fallback方案和VoNR方案。不同方案适用于不同的5G组网策略,如NSA和SA,并影响语音连续性和网络覆盖。 4G网络优化与资源利用: 4G室分设备利旧:面对4G网络投资压减与资源需求矛盾,提出利旧多维度调优策略,包括资源整合、统筹调配既有资源,以满足新增需求和提质增效。 宏站RRU设备1托N射灯:针对5G深度覆盖需求,研究使用宏站AAU结合1托N射灯方案,快速便捷地开通5G站点,提升深度覆盖能力。 基站与流程管理: 爱立信LTE基站邻区添加流程:未提供具体内容,但通常涉及邻区规划、参数配置、测试验证等步骤,以确保基站间顺畅切换和覆盖连续性。 网络规划与策略: 新高铁跨海大桥覆盖方案试点:虽未提供详细内容,但可推测涉及高铁跨海大桥区域的4G/5G网络覆盖规划,需考虑信号穿透、移动性管理、网络容量等因素。 总结: 提供的参考资料涵盖了4G/5G无线网络优化、网规案例分析、网络优化策略、资源利用、基站管理等多个方面。 通过具体案例分析,展示了无线网络优化中的常见问题及解决方案,如NSA终端掉4G、RSSI识别天馈遮挡、CA不生效等。 强调了5G网络优化与策略的重要性,包括CCE映射方式优化、5G语音解决方案、AAU扇区组网选择等。 提出了4G网络优化与资源利用的策略,如室分设备利旧、宏站RRU设备1托N射灯等。 基站与流程管理方面,提到了爱立信LTE基站邻区添加流程,但未给出具体细节。 新高铁跨海大桥覆盖方案试点展示了特殊场景下的网络规划需求。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值