学习代理反射

最近从网上学习了动态代理,写篇博客来加深一下理解,学习了https://www.zhihu.com/question/20794107 这篇文章
从静态代理开始说起
假设现在项目经理有一个需求:在项目现有所有类的方法前后打印日志。
这个可以通过静态代理来实现
1.为现有的每一个类都编写一个对应的代理类,并且让它实现和目标类相同的接口(假设都有)
2.在创建代理对象时,通过构造器塞入一个目标对象,然后在代理对象的方法内部调用目标对象同名方法,并在调用前后打印日志。也就是说,代理对象 = 增强代码 + 目标对象(原对象)。有了代理对象后,就不用原对象了
接口

public interface UserService {
    //新增用户,为了简化不体现参数
    void addUser(); //接口方法不能体现{},即body(类体)
    //修改用户,为了简化不体现参数
    void editUser();
}

实现类

public class UserServiceImpl implements UserService {
    @Override
    public void addUser() {
        System.out.println("新增一个用户");
    }

    @Override
    public void editUser() {
        System.out.println("对用户进行修改");
    }

代理类

public class UserServiceProxy implements UserService{
    private UserService realSubject;
    @Override
    public void addUser() {
        System.out.println("代理类UserServiceProxy addUser方法的调用前执行...");
        realSubject.addUser();
        System.out.println("代理类UserServiceProxy addUser方法的调用后执行...");

    }

    @Override
    public void editUser() {
        System.out.println("代理类UserServiceProxy editUser方法的调用前执行...");
        realSubject.editUser();
        System.out.println("代理类UserServiceProxy editUser方法的调用后执行...");
    }

    public UserServiceProxy() {
    }

    public UserServiceProxy(UserService realSubject) {
        this.realSubject = realSubject;
    }
}

但是我们会发现静态代理有他的缺陷,一个是要为每一个方法编写他的代理类,工作量太大,另外后续再接口和实现类中如果添加了方法,也要在代理中添加,太麻烦,于是我们就要想办法来解决

我们知道通过反射能够通过类的class对象,创建出对应的实例,那我们能否不写代理类,而是通过代理类的class对象直接创建代理实例呢,这时候有人可能就会提出疑问了,如果不写代理类,哪里来的class对象呢,那些构造器,方法去哪里找呢?
实际上,代理类和目标类理应实现同一组接口。之所以实现相同接口,是为了尽可能保证代理对象的内部结构和目标对象一致,这样我们对代理对象的操作最终都可以转移到目标对象身上,代理对象只需专注于增强代码的编写。所以我们可以从接口那里获取到本来应该由代理类提供的信息,而构造方面则可以通过java.lang.reflect.InvocationHandler接口和 java.lang.reflect.Proxy类互相配合来实现
先说说Proxy,Proxy有个静态方法:getProxyClass(ClassLoader, interfaces),只要你给它传入类加载器和一组接口,它就给你返回对应接口的代理Class对象。简单来说就是在获得接口的属性的同时,可以通过反射实例化为对象了.
图片说明
再来说说InvocationHandler,根据代理Class的构造器创建对象时,需要传入InvocationHandler。每次调用代理对象的方法,最终都会调用InvocationHandler的invoke()方法:
图片说明

 class ProxyTest {
    public static void main(String[] args) throws Throwable {
        //目标对象
        CalculatorImpl target = new CalculatorImpl();
        //传入目标对象
        //目的:1.根据它实现的接口生成代理对象 2.代理对象调用目标对象方法
        Calculator calculatorProxy = (Calculator) getProxy(target);
        calculatorProxy.add(1, 2);
        calculatorProxy.subtract(2, 1);
    }

    //用来返回代理对象的方法
    private static Object getProxy(final Object target) throws Exception {
        //参数1:随便找个类加载器给它, 参数2:目标对象实现的接口,让代理对象实现相同接口
        //得到的是代理对象的class
        Class proxyClazz = Proxy.getProxyClass(target.getClass().getClassLoader(), target.getClass().getInterfaces());
        //传入InvocationHandler,得到有参构造器
        Constructor constructor = proxyClazz.getConstructor(InvocationHandler.class);
        //通过构造器反射获得代理对象
        Object proxy = constructor.newInstance(new InvocationHandler() {
            //InvocationHandler接口的invoke方法每次调用代理对象方法,最终都会执行invoke
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println(method.getName() + "方法开始执行...");
                //通过反射执行目标对象方法,两个参数一个是目标对象,一个是本来方法的参数,来应对重构参数不一样的同名方法
                Object result = method.invoke(target, args);
                System.out.println(result);
                System.out.println(method.getName() + "方法执行结束...");
                //返回方法执行结果
                return result;
            }
        });
        //返回代理对象
        return proxy;
    }
}

不过实际编程中,一般不用getProxyClass(),而是使用Proxy类的另一个静态方法:Proxy.newProxyInstance(),直接返回代理实例,连中间得到代理Class对象的过程都帮你隐藏.
最后来回顾一下学到的
图片说明
最后动态代理生成的代理对象,最终都可以用接口接收,和目标对象一起形成了多态,可以随意切换展示不同的功能。但是切换的同时,与用实现类接受相比只能使用该接口定义的方法。

全部评论

相关推荐

从输入URL到页面加载发生了什么:总体来说分为以下几个过程: 1.DNS解析 2.TCP连接 3.发送HTTP请求 4.服务器处理请求并返回HTTP报文 5.浏览器解析渲染页面 6.连接结束。简述了一下各个过程的输入输出作用:以下是对从输入 URL 到页面加载各过程的输入、输出或作用的一句话描述:DNS 解析: 输入:用户在浏览器地址栏输入的域名(如 www.example.com)。输出:对应的 IP 地址(如 192.168.1.1)。作用:将易于记忆的域名转换为计算机能够识别和用于网络通信的 IP 地址,以便浏览器与目标服务器建立连接。TCP 连接: 输入:浏览器获得的服务器...
明天不下雨了:参考一下我的说法: 关键要讲出输入网址后涉及的每一个网络协议的工作原理和作用: 涉及到的网络协议: HTTP/HTTPS协议->DNS协议->TCP协议->IP协议->ARP协议 面试参考回答: 第一次访问(本地没有缓存时): 一般我们在浏览器地址栏输入的是一个域名。 浏览器会先解析 URL、解析出域名、资源路径、端口等信息、然后构造 HTTP 请求报文。浏览器新开一个网络线程发起HTTP请求(应用层) 接着进行域名解析、将域名解析为 IP 地址 浏览器会先检查本地缓存(包括浏览器 DNS 缓存、操作系统缓存等)是否已解析过该域名 如果没有、则向本地 DNS 服务器请求解析; 本地服务器查不到会向更上层的 DNS 服务器(根域名服务器->顶级域名服务器->权威域名服务器询问)递归查询 最终返回该域名对应的 IP 地址。(应用层DNS协议)DNS 协议的作用: 将域名转换为 IP 地址。 由于 HTTP 是基于 TCP 传输的、所以在发送 HTTP 请求前、需要进行三次握手、在客户端发送第一次握手的时候、( 浏览器向服务器发送一个SYN(同步)报文、其中包含客户端的初始序列号。TCP头部设置SYN标志位、并指定客户端端口 同时填上目标端口和源端口的信息。源端口是浏览器随机生成的、目标端口要看是 HTTP 还是 HTTPS、如果是 HTTP 默认目标端口是 80、如果是 HTTPS 默认是 443。(传输层) 然后到网络层:涉及到(IP协议) 会将TCP报文封装成IP数据包、添加IP头部,包含源IP地址(浏览器)和目标IP地址(服务器)。IP 协议的作用: 提供无连接的、不可靠的数据包传输服务。 然后到数据链路层、会通过 ARP 协议、获取目标的路由器的 MAC 地址、然后会加上 MAC 头、填上目标 MAC 地址和源 MAC 地址。 然后到物理层之后、直接把数据包、转发给路由器、路由器再通过下一跳、最终找到目标服务器、然后目标服务器收到客户的 SYN 报文后,会响应第二次握手。 当双方都完成三次握手后、如果是 HTTP 协议、客户端就会将 HTTP 请求就会发送给目标服务器。如果是 HTTPS 协议、客户端还要和服务端进行 TLS 四次握手之后、客户端才会将 HTTP 报文发送给目标服务器。 目标服务器收到 HTTP 请求消息后、就返回 HTTP 响应消息、浏览器会对响应消息进行解析渲染、呈现给用户
点赞 评论 收藏
分享
评论
1
收藏
分享

创作者周榜

更多
牛客网
牛客企业服务