飞道的博客

江的福浅谈动态代理模式->反射机制->Spirng的Aop

289人阅读  评论(0)

当我看到反射机制视频的弹幕时,有一句诗萦绕在我的心头。
”同是天涯沦落人,相逢何必曾相识。“
碰到了很多正在学习Spirng框架时,被动态代理模式打回原形,重新修炼反射机制的难兄难弟。
害,学习就是这么个过程,查缺补漏,劝诸君毋妄自菲薄,同时也得戒骄戒躁。
下面开始正题。就简单说一说这一段的挑战Aop之旅。
Aspect Oriented Programming的缩写,意为:面向切面编程,
面向切面?这是什么鬼东西。不理解我们就先把它放一放。

先来说一说

代理模式

代理模式提供了间接对目标对象进行访问的方式;即通过代理对象访问目标对象.这样做的好处是:可以在目标对象实现的功能上,增加额外的功能补充,既扩展目标对象的功能,又不改动原来的代码,分为静态代理模式和动态代理模式还有cglib代理。

什么意思呢?
前段时间很火的电视剧《安家》,不知道大家看过没有,是一个关于房产中介的剧,而当今的房产买卖,就是一个典型的代理模式。两个对象需求互补,需要一个中间商作为粘合剂,解决双方问题。
也许有的人就说了,我就是喜欢直接一点,不想让房产中介拿那份佣金,所以呢?
如果你是卖家,你得花时间找买家,得腾时间带买家看房,得招待,得拟定合同,得干很多很多的事情,而房产中介,则会帮你省去很多麻烦。即在目标对象实现的功能(卖房)上,添加额外的功能补充(扩展目标对象的功能)。
再来从代码的角度分析,如果你想为卖家添加功能,就得修改卖家类的代码,但是这是编译的大忌,毕竟我们的每一段编译,都是一环套一环的,一步错,步步错,所以为了减少风险,我们定义一个代理类,既扩展了功能,又避免了修改原生代码,何乐而不为呢。

怎么代理呢?或者说怎么完成一个静态代理。
这个问题,我们还是得站在中介的角度思考。
中介需要什么? 首当其冲的自然就是房源,你只有手握大把的房源买家才有选择的机会。
所以代理类首先需要目标对象。


public class host implements rent {
    public void rent() {
        System.out.println("我是房主,我要卖房,你快给钱");
    }
}

然后就要实现自己的功能了


public class proxy implements rent {
    host host ;
    public proxy(host host){
        this.host=host;
    }
    public void rent() {
        host.rent();
        System.out.println("我是房产中介,我有房子,我要收佣金");
        look();
        getMoney();
    }
    public void look(){
        System.out.println("我带租客看房了");
    }

    public void getMoney(){
        System.out.println("我收了佣金了");
    }
}

买家通过中介对接卖家,就可完成买房流程。


public class client {
    public static void main(String[] args) {
        host host =new host();
        proxy proxy =new proxy(host);
        proxy.rent();
    }
}

但我们会发现,静态代理中,代理对象和目标对象,都要实现同一个接口,这就导致了虽然代理类可以扩展目标对象的功能,但是如果有很多代理类时,一旦我们需要扩展接口的功能,就意味着需要同时管理目标对象及代理对象。很难管理,而动态代理就解决了这一烦恼。

这是为什么呢?
动态代理的主要特点就是能够在程序运行时JVM才为被代理对象生成代理对象。
可以这样理解,卖房的人要卖房就得去当地的中介门店,中介作为代理,买卖房屋,这就是静态代理。
而动态代理是直接和中介总部进行交互,你要卖房,总部直接专门你分配一个中介,给你提供服务。也就是说,只有你需要帮助(即程序运行时),总部(jvm)才会为卖家(被代理对象)生成中介(代理对象)。
静态代理:服务于一类。
动态代理:天生是因你一人(对象)而存在。
以上我对它的理解。
那jvm是怎样为自动生成代理对象的呢?
这自然也是需要一个代理类,不过这是一个代理总部,即代理工厂。
代理工厂解决目标对象问题分为两步,第一步首先生成代理类实例,第二步就是处理目标对象的问题(即处理实例)。


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

public class proxyFactory implements InvocationHandler {
    private  Object target;

    public void setTarget(Object target) {
        this.target = target;
    }
    //生成代理类
    public Object getProxy(){
        return Proxy.newProxyInstance(this.getClass().getClassLoader(), target.getClass().getInterfaces(), this);

    }
    //处理实例,并返回结果
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            log(method.getName());
            Object result =method.invoke(target, args);
            return result;
    }
    public  void log(String msg){
        System.out.println("我要"+msg);
    }
}


public class test {
    public static void main(String[] args) {
        //真实对象
        userServiceImpl userService =new userServiceImpl();
        proxyFactory proxyFactory =new proxyFactory();
        //设置要找的代理类
        proxy.setTarget(userService);
        //动态代理类实例
        userService realproxy= (userService) proxyFactory.getProxy();
        realproxy.add();
    }
}

一脸懵?
(哇,前面还在能力范围之内)

   private  Object target;

    public void setTarget(Object target) {
        this.target = target;
    }

怎么到这里----Proxy.newProxyInstance(this.getClass().getClassLoader(), target.getClass().getInterfaces(), this);还有这些

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            log(method.getName());
            Object result =method.invoke(target, args);
            return result;
    }

后面就感觉学了个假的Java?先别急。
其实大致还是能看懂一些的,比如说:
1、 proxyFactory 需要实现 InvocationHandler,重写invoke方法。
2、生成代理类的时候,返回的对象是调用了Proxy的newProxyInstance方法。
3、有一个setTarget方法,我们能想到,这肯定就是传入了目标对象。
也就是说我们现在的难点在于:
1、InvocationHandler接口及其invoke方法。
2、Proxy及newProxyInstance方法。
3、最后的客户,为什么没有实现invoke方法,就能实现目标对象的方法调用。

首先,不要认为代理工厂就是生产代理实例(即代理对象中介)的,生成代理实例的是Proxy,可以将它理解为一台生产机器。
好,那么我们就带着这些问题,进行下面的探索。

实现InvocationHandler接口,就是实现了一个调用处理器,见名知意,就是当代理类对象调用器被代理类的方法时,这个处理器就会处理方法。
也就是说每一个动态代理类的调用处理程序都必须实现InvocationHandler接口,并且每个代理类的实例都关联到了实现该接口的动态代理类调用处理程序中,当我们通过动态代理对象调用一个方法时候,这个方法的调用就会被自动转发到实现InvocationHandler接口类的invoke方法来调用。
我把它(InvocationHandler接口)理解为代理工厂中的另外一台机器,而invoke就是必须对它进行的配置。通过动态代理对象调用一个方法时,即代理对象要完成目标对象的方法时,会用这台机器,去完成。并且invoke是不需要显式调用的。因为生产的代理对象就会自己去找实现了InvocationHandler接口,即拥有调用处理器的类。

来看看invoke中的参数:
method就是是需要调用的方法(比如说上面提到卖家的卖房rent方法),即需要执行的方法;args是方法的参数;还有一个proxy,咦?看一下

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            log(method.getName());
            Object result =method.invoke(target, args);
            return result;
    }`

是不是很奇怪,这里虽然传入了proxy参数,但是在invoke方法中并没有用到。
看看JDK文档中Proxy的说明,如下:

A method invocation on a proxy instance through one of its proxy interfaces will be dispatched to the invoke method of the instance's invocation handler, passing the proxy instance,a java.lang.reflect.Method object identifying the method that was invoked, and an array of type Object containing the arguments.

翻译:

通过代理实例的一个代理接口对代理实例的方法调用,将被分派到实例的调用处理程序的invoke方法,传递代理实例、标识被调用方法的java.lang.reflect.method对象和包含参数的对象类型数组。

是不是又到了感慨汉字看不懂的时候了?
通过查阅一些网文,我知道了以下几点:
1、proxy这个参数,在动态代理这一过程中,是没有什么实际作用的。
2、 可以使用反射获取代理对象的信息(也就是proxy.getClass().getName())。
3、可以将代理对象返回以进行连续调用,这就是proxy存在的目的,因为this并不是代理对象。
关于二三点具体的大家可以看InvocationHandler中invoke方法中的第一个参数proxy的用途
因为它与本文没有什么直接联系,也就不过多赘述。

再来看看之前提到创建代理对象的那台主力机器Proxy及newProxyInstance方法。要聊它的话我们就不得不先聊一下标题的第二大块儿内容反射机制了,看完这个再看他是怎么去生产一个代理对象的。

反射机制

为什么要谈反射机制?
还记得为什么静态代理模式需升级为动态管理模式吗?
对,因为它的动态性,因为我们需要jvm帮我们动态生成代理对象。
正是反射机制,赋予了Java动态性。使得其成为动态语言。何为动态语言?
动态语言:运行时可以改变其结构的语言。
非动态语言:运行时不可以改变其结构的语言。
先来粗浅地了解一下类的加载过程吧。
每个编写的".java"拓展名类文件都存储着需要执行的程序逻辑,这些".java"文件经过Java编译器编译成拓展名为".class"的文件,".class"文件中保存着Java代码经转换后的虚拟机指令,当需要使用某个类时,虚拟机将会加载它的".class"文件,并创建对应的class对象,将class文件加载到虚拟机的内存,这个过程称为类加载。
如果大家想要详究的话建议大家可以看看这篇深入理解Java类加载器(ClassLoader)超级详细~
但是我觉得关于动态代理的初步理解,我们知道这个过程以及classloader的四种类型就已经够了,即.java通过编译器编译成.class,".class"文件中保存着Java代码经转换的虚拟机指令,需要使用某个类时,虚拟机加载”.class“文件,创建对应的Class对象,将class文件加载到虚拟机的内存中。
而反射关键点就在于就是那个Class对象。
关于反射,大家可以看看这一篇,
Java反射技术详解
反射的威力体会到了吗?
获取类的信息,并能调用类的构造方法,以及调用类的私有方法。
看到这句话了吗?

我们可以先拿到Class对象再对这个类其进行实例的创建,这就是动态创建过程。
了解了这些之后,我们再回来看Proxy的newProxyInstance方法,

    public static Object newProxyInstance(ClassLoader loader, 
                                            Class<?>[] interfaces, 
                                            InvocationHandler h)

 Proxy.newProxyInstance(this.getClass().getClassLoader(), target.getClass().getInterfaces(), this);

loader:一个classloader对象,定义了由哪个classloader对象对生成的代理类进行加载
interfaces:一个interface对象数组,表示我们将要给我们的代理对象提供一组什么样的接口,如果我们提供了这样一个接口对象数组,那么也就是声明了代理类实现了这些接口,代理类就可以调用接口中声明的所有方法。
h:一个InvocationHandler对象,表示的是当动态代理对象调用方法的时候会关联到哪一个InvocationHandler。
这样来看是不是还挺好理解的?
我们告诉 Proxy.newProxyInstance生成的代理类需要通过哪个类加载器加载,再告诉他代理类需要实现哪些接口(即可调用这些接口的所有方法),再者就是之前说的那个调用处理器对象了,代理对象调用方法时通过调用处理器进行处理。
Proxy.newProxyInstance的秘密大家可以通过这一篇了解到,newProxyInstance方法内部其实除了安全检查之外,就是通过构造器创建实例的方法来来创建代理对象的。

不知不觉间是不是那三个难点都已经被解决了?

有一点需要注意的是我们需要将get到的那个代理对象强转成接口型。因为这种动态代理模式是jdk自带的是基于接口实现的。 动态代理的对象实现了newIntance里面所有的接口,只要有实现就能转成里面的任意一种类型。还有就是因为在newProxyInstance传入的是接口,也就是说代理类与被代理类类似兄弟关系,不能相互转换,只能用共同的接口。

那么我们的动态代理及反射机制就聊到这里,
开始我们的压轴戏;

Spring的AOP

经历过前面的折磨已经身心俱疲了吧,但这也是为我们之后的学习打基础,
aop就是动态代理模式的一种实现,为了能让我们的编译更加便捷。
这里只说一说关于aop的一些操作:
首先需要对配置文件进行约束

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">


service接口,serviceImpl是service接口的实现类,也就是目标对象,现在我们需要扩充这个serviceImpl的功能。即log和afterlog该怎么办呢?
当然,我们需要将这些类全部注入容器。

还需要注意的是:我们需要aop开启自动代理。

第一种方式(点切入):


import org.springframework.aop.AfterReturningAdvice;

import java.lang.reflect.Method;

public class afterLog implements AfterReturningAdvice {
    public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable {
        System.out.println("返回结果为"+o+method.getName()+o1.getClass().getName()+"after log");
    }
}


import org.springframework.aop.MethodBeforeAdvice;

import java.lang.reflect.Method;

public class Log implements MethodBeforeAdvice {

    public void before(Method method, Object[] objects, Object target) throws Throwable {
        System.out.println(target.getClass().getName()+method.getName()+"log");
    }
}

要切入serviceImpl 的功能需要根据需要分别继承 MethodBeforeAdvice 和AfterReturningAdvice
然后在beans中进行相关配置

<aop:config>
<!--        <aop:pointcut id="poincut" expression="execution(* com.pang.service.serviceImpl.*(..))"/>-->
<!--        <aop:advisor advice-ref="log" pointcut-ref="poincut"/>-->
<!--        <aop:advisor advice-ref="afterlog" pointcut-ref="poincut"/>-->

    </aop:config>

先定义切入点,再将功能切入。

第二种方式(面切入):


public class diy {
    public void befor(){
        System.out.println("========之前==========");
    }
    public void after(){
        System.out.println("========之后===========");
    }
}

beans配置:


<aop:config>
     <aop:aspect ref="diy">
         <aop:pointcut id="pointcut" expression="execution(* com.pang.service.serviceImpl.*(..))"/>
         <aop:before method="befor" pointcut-ref="pointcut"/>
         <aop:after method="after" pointcut-ref="pointcut"/>
     </aop:aspect>
</aop:config>

一前一后加入diy(面)中的两个方法,

第三种方法(注解):
不需对beans进行多余配置,只需将类(组件)导入容器即可。


import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

@Aspect
public class annotationDiy {
    @Before("execution(* com.pang.service.serviceImpl.*(..))")
    public void before(){
        System.out.println("之前====--=-=-=-=-=-=");
    }
    @After("execution(* com.pang.service.serviceImpl.*(..))")
    public void after(){
        System.out.println("之后+++)))+))");
    }
    @Around("execution(* com.pang.service.serviceImpl.*(..))")
    public void aroud(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("sdadadaasdadadadad");
        Object proceed = joinPoint.proceed();
        System.out.println("uouououououououououo");
    }
}

结果

sdadadaasdadadadad
之前====--=-=-=-=-=-=
增加
uouououououououououo
之后+++)))+))

再总结一下吧,我们之所以要聊代理模式,是因为代理模式的初衷就是不改动原生代码的情况下,对目标对象的功能进行扩展,aop就是Spring对其进行的一个优化,一个内部实现,不需要我们再建工厂,不需要我们去手动传入目标对象,手动启动机器建代理对象。就能够自动的完成对目标对象功能的扩充。
以上就是对于前几天的学习的一个总结,希望大家都能”但行好事,莫问前程“。


转载:https://blog.csdn.net/weixin_45276914/article/details/105447281
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场