java学习笔记:动态代理Proxy

java学习笔记:动态代理Proxy

 

 

动态代理Proxy
----
装饰者模式、动态代理:解决一类问题,即:不修改系统源代码的基础上,需要对源代码的某些方法进行增强。

一、装饰者模式
1.谷歌汽车案例
*_JAVA设计了一个标准接口: jdk
public interface ICar{ 
  public void start();
  public void run();
  public void stop();
}
*_Google在研发汽车的同时实现了业界标准ICar  谷歌开发人员写驱动包
public class GoogleCar implement ICar{
    @Override
    public void start() {
        System.out.println("谷歌的汽车启动了");
    }
    @Override
    public void run() {
        System.out.println("谷歌的汽车运行中");
    }
    @Override
    public void stop() {
        System.out.println("谷歌的汽车停止中");
    }
}
当我们要为谷歌汽车的启动方法增强一些功能时,可以考虑采用以下方案:

2.考虑用继承的方式为谷歌汽车增强功能:
public class MyCar extends GoogleCar {
    @Override
    public void start() {
        System.out.println("检查电量是否充足");
        System.out.println("检查天气是否良好");
        System.out.println("检查交通是否拥堵");
        super.start();
    }
}

3.装饰者模式为谷歌汽车增强功能
装饰者模式:
A.自定义一个类,实现和被增强对象上相同的接口,即:GoogleCar实现了什么接口,MyCar这个类也实现什么接口。
B.在MyCar抽取成员属性ICar car;
C.MyCar的构造函数接受ICar类型,赋值给MyCar的内部属性。
D.调用:ICar myCar = new MyCar(new GoogleCar());
public class MyCar implements ICar{
    private ICar car = null;
    public MyCar(ICar car) {
        this.car=car;//本条语句执行后,car属性存放的就是GoogleCar对象。
    }     
    @Override
    public void start() {
        System.out.println("检查电量是否充足");
        System.out.println("检查天气是否良好");
        System.out.println("检查车里是否有人");
        car.start();
        
    }
    @Override
    public void run() {
        car.run();
        
    }
    @Override
    public void stop() {
        car.stop();
    }
}

调用:Icar myCar = new MyCar(new GoogleCar());

装饰者模式的应用场景:对系统中已经存在的类/对象上的某些功能要增强,不能采用直接修改源代码/继承。
装饰者模式的要求:A.被增强的类(GoogleCar)上的方法必须实现接口中的方法。B.增强的类(MyCar)必须实现和GoogleCar相同的接口
装饰者模式的弊端:如果接口中的方法过多,代码繁冗.不易维护。即:如果ICar中方法过多,MyCar类中要写很多代码,而这些代码仅仅是对GoogleCar的简单调用。

 

二、动态代理
4.动态代理方式解决问题
动态代理模式本质:JDK在内存中重新生成了MyCar.class字节码文件,通过字节码文件创造了一个代理对象,代理对象上有我们需要的方法
告诉JDK3个参数:
1_:字节码加载器: 固定写法:当前类.class.getClassLoad();
ClassLoader classLoader = 类名.class.getClassLoader();
System.out.println(classLoader);//sun.misc.Launcher$AppClassLoader
字节码加载器:JVM中专门有一类程序,专业将硬盘上的各个字节码文件通过IO流读取到内存中去。
A.应用类加载器:自己创建的各个java文件生成的字节码。应用类AppClassLoader
B.扩展类加载器:在JVM中只要是ext(extension扩展)下的都属于扩展类,在/lib/ext包下。ExtClassLoader
C.根类类加载器:在java.lang包下的常见类,最快的方式加载到内存,通过根类类加载器,底层由C/C++代码实现的不是java实现的。null
2_:告诉JDK,GoogleCar字节码实现了那些接口
//实例化GoogleCar对象
ICar car = new GoogleCar();
//获取到的就是GoogleCar.class文件对应的对象
Class clazz.car.getClass();
Class[] interfaces = clazz.getInterfaces();
for(Class cla : interfaces){
  System.out.println(cla);
  //通过ICar获取到其上所有方法
  Method[] methods = cla.getMethods();
  for(Method md : methods){
    System.out.println(md);
  }
}
3_:实现InvocationHandler接口对象
Class MyProxy implements InvocationHandler{
  //告诉虚拟机MyCar.java底层各个方法上start()、run()、stop()存放哪些指令
  @Override
  public Object invoke(Object proxy,//代理对象
    Method method,//正在执行的各个方法
    Object[] args//正在执行方法上的参数
  ) throws Throwable{
    System.out.println(method.getName());
    method.invoke(car);//method代表GoogleCar上正在执行的各个方法
    if(method.getName().equals("start")){
      System.out.println("检查天气是否良好");
    }else{
      method.invoke(car);
    }
    return null;
  }
}
    
//以下语句执行完毕之后,JDK在内部生成了一个字节码对象,而且通过这个字节码对象创造一个代理对象
ICar car=(ICar)Proxy.newProxyInstance(
                    TestCar.class.getClassLoader(), //第一个参数:字节码加载器
                    GoogleCar.class.getInterfaces(), //第二个参数:被增强类实现的接口
                    new InvocationHandler() {//第三个参数:实现InvocationHandler接口对象
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
              //System.out.println(method.getName());//start  run  stop
              if(method.getName().equals("start")){
                //System.out.println(Arrays.asList(args)+"  @@@@@@@@@"); args代表当前方法参数
                System.out.println("电量是否OK");
                System.out.println("天气是否OK");
                System.out.println("交通是否OK");
                //本质上在调用GoogleCar字节码上名称start的方法,将参数继续向其传递
                return method.invoke(new GoogleCar(), args);
              }else{
                //本质上在调用GoogleCar字节码上名称不是start的方法
                return method.invoke(new GoogleCar(),args);
                //System.out.println(method.getName());
              }    
            }
        });
String str=car.start("aaaaa","bbbbb");
System.out.println(str+"  %%%%%%%%");
car.run();
car.stop();

5.动态代理详解
1_解决了装饰者的弊端
2_本质:我们需要告诉JDK3个参数:字节码加载器,被增强对象所在的字节码实现的接口,实现了invocationhandler接口对象.JDK内存创建一个对应的字节码,并且通过这个字节码生成一个代理对象,这个代理对象上携带了我们所需要的所有的函数.

Proxy.newProxyInstance
  参数1:loader,类加载器,动态代理类运行时创建。任何类都需要类加载器将其加载到内存。
    一般情况:当前类.class.getClassLoader();
  参数2:Class[] interfaces,代理类需要实现的所有接口
    方式1:目标类实例.getClass().getInterfaces();
      注意:只能获得自己的接口,不能获得父元素的接口
    方式2:new Class[]{UserService.class}
      例如:jdbc驱动-->DriverManager  获得接口Connection
  参数3:InvocationHandler,处理类,接口,必须进行实现类,一般采用匿名内部类
    提供invoke方法,代理类的每一个方法执行时,都将调用一次invoke
      参数31:Object proxy:代理对象
      参数32:Method method:代理对象当前执行的方法的描述对象(反射)
        执行方法名:method.getName()
        执行方法:method.invoke(对象,实际参数)
      参数33:Object[] args:方法实际参数


6.案例动态代理完成性能监控
1_场景描述
实际开发中,都是由项目设计人员约定好各个模块的接口,然后由程序员完成这些接口功能。假如我们的项目中有有一个用户模块,在这个模块下应该有如下功能:
用户的注册/登录/添加/修改/删除/查询一个用户/查询所有用户/按照条件查询部分用户 

设计人员在系统设计阶段,先设计好了以下接口
public interface  IUser{
   public void login();
   public void register();
   public void addUser();
   public void delUser();
   public void uptUser();
   public void findOneUser();    
   public void findAllUser();
   public void findSomelUser();
}

之后由开发人员来实现以上功能,我们正常会建立一个User接口的实现类,并实现各自功能。
代码如下:
puiblic class UserImp implements IUser{
    public void login(){
        System.out.println(“用户的登录功能代码”);
    }
    public void register(){
        System.out.println(“注册用户的功能代码”);
    }
    public void addUser(){
        System.out.println(“添加用户的功能代码”);
    }
    public void delUser(){
        System.out.println(“删除用户的功能代码”);
    }
    public void uptUser(){
        System.out.println(“更新用户的功能代码”);
    }
    public void findOneUser(){
        System.out.println(“查找一个用户的功能代码”);
    }
    public void findAllUser(){
        System.out.println(“查找所有用户的功能代码”);
    }
    public void findSomelUser(){
        System.out.println(“查找部分用户的功能代码”);  
    }

}

功能制作完毕之后,项目经理有一个需求,测试每个方法的执行时间:
要求在每个方法执行完毕之后,要打印如下结果:
方法XX执行了33秒

大家可能需要在每个函数内内部来记录函数的起始结束时间,在去控制台打印当前函数的执行时间。我们发现每个方法前后写相同代码很繁琐,而且测试结束后这些代码就不需要了,此时我们如何实现?

利用动态代理实现方法的性能执行测试

2_原理分析
分析我们利用动态代理创建IUser 接口的实现类,在动态代理中将各个方法的起始和结束时间进行一个测试。这样就可动态的检测各个方法的执行时间了

3_代码实现
IUser uu=new UserImp();
IUser user=(IUser)Proxy.newProxyInstance(TestDynamicProxy.class.getClassLoader(), uu.getClass().getInterfaces(), new InvocationHandler() {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object obj=null;

        long start=System.currentTimeMillis();
        obj=method.invoke(uu,null);
        long end=System.currentTimeMillis();
        System.out.println("方法"+method.getName()+"执行了"+(end-start)+"毫秒");
        return obj;
    }
});

user.addUser();
user.delUser();
user.findAllUser();
user.findOneUser();
user.findSomelUser();
user.login();
user.uptUser();
====

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值