动态代理

本节目标:

  • 动态代理演示

  • 动态代理原理

什么是动态代理?

使用一个代理将对象包装起来,然后用该代理对象取代原始对象,任何对原始对象的调用都得通过代理。代理对象决定是否以及何时将方法调用转到原始对象上。

Q1:做一个能加减乘除的计算器,用日志记录下计算前后的数据

没有设计思想,只求实现功能:

public class mathCalculatorImpl implements mathCalculator {

    /**
     * 日志在int result之前,后。
     * @param i
     * @param j
     * @return
     */
    @Override
    public int add(int i, int j) {
        System.out.println("日志记录:the method add begin with["+i+","+j+"]");
        int result=i+j;
        System.out.println("日志记录:the method add ends withd:"+result);
        return result;
    }

    @Override
    public int sub(int i, int j) {
        System.out.println("日志记录:the method sub begin with["+i+","+j+"]");
        int result=i-j;
        System.out.println("日志记录:the method sub ends withd:"+result);
        return result;
    }

    @Override
    public int mul(int i, int j) {
        System.out.println("日志记录:the method mul begin with["+i+","+j+"]");
        int result=i*j;
        System.out.println("日志记录:the method mul ends withd:"+result);
        return result;
    }

    @Override
    public int div(int i, int j) {
        System.out.println("日志记录:the method div begin with["+i+","+j+"]");
        int result=i/j;
        System.out.println("日志记录:the method div ends withd:"+result);
        return result;
    }
}

我们发现这样的代码有几个问题:

将日志与业务逻辑代码耦合在一起,不好修改,会使得负责写日志模块和负责业务逻辑处理的人员工作量加大,将不相关的代码耦合在一起,是没有设计思想的。重复代码多,在日后的需求更改之时,得修改所有的模块。

Q2:如何解决这种问题呢?
用动态代理完成日志代理功能

JDK的动态代理

Proxy (reflex  jar包下的):jdk的动态代理:是所有动态代理类的父类,专门用户生成代理类或者是代理对象
public static class<?> getProxyClass(ClassLoader loader,Class<?>..interfaces)
用于生成代理类的Class对象。
public static object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
用于生成代理对象
 
2.InvocationHandler (接口):完成动态代理的整个过程。
public object invoke(Object proxy,Method method,Object[] args)throws Throwable;(动态代理想做什么事就写在动态代理就行了)

改进的计算器

package com.hxh.aop;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;


public class mathCalculatorProxy {
    //动态代理:  目标对象  如何获取代理对象  代理要做什么
    // mathCalculatorImpl是目标对象,通过父类型来声明它的一个变量,mathCalculatorProxy是要创建一个对象
    //的,我们会给这个类提供一个方法用来获取代理对象。你想要调用方法就得有一个对象,有一个对象就
    // 得new一个对象,就得执行一个构造器,再去创建对象。传入一个目标对象,告诉我要代理哪个对象,然后
    // 赋值到target,相当于保存了目标对象。
    private mathCalculator target;
    public mathCalculatorProxy(mathCalculator target){
        this.target=target;
    }
    //一个获取代理对象的方法
    public Object getProxy(){
        //定义代理对象
        Object proxy;
        /**
         * loader: ClassLoader对象。类加载器对象,帮我们加载动态生成的代理类。
         * interfaces:接口们,提供目标对象所有的接口。目的是让代理对象保证与目标对象都有接口中相同的方法。
         * h: InvocationHandler类型的对象
         */
        ClassLoader loader=target.getClass().getClassLoader();

        Class[] interfaces = target.getClass().getInterfaces();
        //new InvocationHandler()匿名内部类,执行newProxyInstance的时候不会执行invoke方法。
        //proxy都还没生成,不能传入invoke方法里(参数)
        proxy= Proxy.newProxyInstance(loader, interfaces, new InvocationHandler() {

            /**invoke:代理对象调用代理方法,会回来调用invoke方法.
             * proxy:代理对象,在invoke方法中一般不会使用
             * method:正在被调用的方法对象
             * args:正在被调用的方法的参数
             */
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                //完成动态代理的整个过程
                //将方法的调用转回到目标对象上。
                //获取方法的名字
                String methodName=method.getName();
                //记录日志,将日志的功能移到这里
                System.out.println("LoggingProxy--> The method"+ methodName+"begin with:"+ Arrays.asList(args));
                Object result=method.invoke(target,args);//目标对象执行目标方法,相当于执行 mathCalculatorImpl中的加减乘除
                System.out.println("LoggingProxy--> The method"+methodName+"ends with:"+result);
                //记录日志
                //通过日志代理来记录日志,完全没必要写在业务逻辑处理(实现类mathCalculatorImpl)的代码里面了
                return result;
            }
        });
        return proxy;
    }
}


Test类:

package com.hxh.aop;

public class Main {
    public static void main(String[] args) {
        //目标对象
        mathCalculator target=new mathCalculatorImpl();

        //获取代理对象
        Object obj=new mathCalculatorProxy(target).getProxy();

        //转回具体的对象

        mathCalculator proxy= (mathCalculator) obj;

        int result=proxy.add(1,1);

        System.out.println("Main Result:"+result);


    }
}

运行截图:
在这里插入图片描述
Q3: 1.代理对象能否转换成目标对象的类型? 2.代理对象调用代理方法,为什么会执行invocationHandler中的invoke方法

模拟底层动态代理类

获取动态代理类的名字

 mathCalculator proxy= (mathCalculator) obj;

        System.out.println(proxy.getClass().getName());

运行结果:

com.sun.proxy.$Proxy0
    Proxy.class中
    protected Proxy(InvocationHandler h){
    	Objects.requireNonNull(h);
    	this.h=h;
    	//将h赋值给了当前对象的h(protect InvocationHandler h;)
    }

也就是说我把我创建好的InvocationHandler传到代理类里面,代理类又从它的构造器里面把h传到了父类里面,这样的话,我们父类里面的h其实就是自己创建的h
在这里插入图片描述

这里面的h就是我们自己创建的h,意味着会执行InvocationHandler里面的invoke方法,在invoke方法里面我们就能够完成整个动态代理的过程。其实并没有使用一开始add的功能。真正的加法是在如下代码中操作的

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                String methodName=method.getName();
                System.out.println("LoggingProxy--> The method"+ methodName+"begin with:"+ Arrays.asList(args));
                Object result=method.invoke(target,args);//目标对象执行目标方法,相当于执行 mathCalculatorImpl中的加减乘除
                System.out.println("LoggingProxy--> The method"+methodName+"ends with:"+result);
                return result;
            }

实现了目标对象的加减乘除移到了上述代码中,在代码的前后进行日志的记录

动态代理类是在运行的时候,动态生成的,动态生成的东西在内存里面,不会存在硬盘里,但是从内存中抓取动态代理太难了,不过有一个更简单的方法可以实现该功能

第二种动态代理的实现方法

public class mathCalculatorProxy2 {
    private mathCalculator target;
    public mathCalculatorProxy2(mathCalculator target) {
        this.target = target;
    }
    public Object getProxy() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        //定义代理对象
        Object proxy;

        //第二种方法:public static class<?> getProxyClass(ClassLoader loader,Class<?>..interfaces)
        //用于生成代理类的Class对象。
        //先获取Proxy的class对象
        //反射机制
        ClassLoader loader=target.getClass().getClassLoader();
        Class []interfaces=target.getClass().getInterfaces();

        Class proxyClass=Proxy.getProxyClass(loader,interfaces);
        //Class 创建对象?  newInstance():依赖无参构造器(代理类没有)  通过Class对象获取到构造器对象,再通过
        //构造器对象创建出具体的对象
        Constructor con=proxyClass.getDeclaredConstructor(InvocationHandler.class);
        proxy=con.newInstance(new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                String methodName=method.getName();
                System.out.println("LoggingProxy--> The method"+ methodName+"begin with:"+ Arrays.asList(args));
                Object result=method.invoke(target,args);
                System.out.println("LoggingProxy--> The method"+methodName+"ends with:"+result);
                return result;
            }
        });


        return proxy;
    }
}

保存生成的动态代理类,将代码加入到Test类中即可

        Properties properties=System.getProperties();
        properties.put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
 
posted @ 2019-05-14 16:01  单线程程序员  阅读(209)  评论(0编辑  收藏  举报