理解动态代理及AOP原理

1.理解的前提知识

在理解动态代理之前,我们得先知道几个概念,否则会像我刚开始学Java一样,不能理解,明明比较简单得事情,为什么要用动态代理来做。

何为耦合

我们要知道,动态代理得主要目的是来降低代码耦合度。那代码耦合是什么意思?

“低耦合”这个词相信大家已经耳熟能详,我们在看spring的书籍、MVC的数据、设计模 式的书籍,无处不提到“低耦合、高内聚”,它已经成为软件设计质量的标准之一。那么什么是低耦合?耦合就是对某元素与其它元素之间的连接、感知和依赖的量 度。这里所说的元素,即可以是功能、对象(类),也可以指系统、子系统、模块。假如一个元素A去连接元素B,或者通过自己的方法可以感知B,或者当B不存 在的时候就不能正常工作,那么就说元素A与元素B耦合。耦合带来的问题是,当元素B发生变更或不存在时,都将影响元素A的正常工作,影响系统的可维护性和 易变更性。同时元素A只能工作于元素B存在的环境中,这也降低了元素A的可复用性。正因为耦合的种种弊端,我们在软件设计的时候努力追求“低耦合”。低耦 合就是要求在我们的软件系统中,某元素不要过度依赖于其它元素。请注意这里的“过度”二字。系统中低耦合不能过度,比如说我们设计一个类可以不与JDK耦 合,这可能吗?除非你不是设计的Java程序。再比如我设计了一个类,它不与我的系统中的任何类发生耦合。如果有这样一个类,那么它必然是低内聚(关于内 聚的问题我随后讨论)。耦合与内聚常常是一个矛盾的两个方面。最佳的方案就是寻找一个合适的中间点。

哪些是耦合呢?

1.元素B是元素A的属性,或者元素A引用了元素B的实例(这包括元素A调用的某个方法,其参数中包含元素B)。

2.元素A调用了元素B的方法。

3.元素A直接或间接成为元素B的子类。

4.元素A是接口B的实现。

在Java里面,因为多态的特性,父类或者接口的引用是可以接受子类对的。 比如:

interface Test{} ,class Test1 implements Test{}, class Test2 implements Test{} ,上面声明一个Test接口,两个类Test1和Test2,分别实现Test接口。 表现耦合低的意思是说,删除调用层与接受层无关,比如:有这样一个方法可以这样设置参数类型 void say(Test test){System.out.println("");} 然后调用时可以这样传参数 Test1 t1 = new Test1(); Test2 t2 = new Test2(): say(t1); 或 say(t2); 这就是降低耦合,say方法只需要接口类型,具体传入哪个对象是无需关注的,因为接口是一种规则,say方法进来的参数一定是实现Test接口类的对象。即使有一天将Test1与Test2两个类删除,Test接口还在,那么say方法就有用,并且随意扩展一个新的类来实现Test接口,这叫做向后兼容

何为硬编码

计算机科学中,只有硬编码(hardcode),以及非硬编码,有人也成为“软编码”。
    硬编码和软编码的区别是:软编码可以在运行时确定,修改;而硬编码是不能够改变的。所有的硬编码和软编码的区别都可以有这个意思扩展开。
    在计算机程序或文本编辑中,硬编码是指将可变变量用一个固定值来代替的方法。用这种方法编译后,如果以后需要更改此变量就非常困难了。大部分程序语言里,可以将一个固定数值定义为一个标记,然后用这个特殊标记来取代变量名称。当标记名称改变时,变量名不变,这样,当重新编译整个程序时,所有变量都不再是固定值,这样就更容易的实现了改变变量的目的。
    尽管通过编辑器的查找替换功能也能实现整个变量名称的替换,但也很有可能出现多换或者少换的情况,而在计算机 程序中,任何小错误的出现都是不可饶恕的。最好的方法是单独为变量名划分空间,来实现这种变化,就如同前面说的那样,将需要改变的变量名暂时用一个定义好 的标记名称来代替就是一种很好的方法。通常情况下,都应该避免使用硬编码方法。    
    java小例子: int a=2,b=2;   
    硬编码:if(a==2) return false;   
    非硬编码 if(a==b) return true;   (就是把数值写成常数而不是变量 )
    一个简单的版本:如求圆的面积 的问题 PI(3.14)   
    那么3.14*r*r 就是硬编码,而PI*r*r 就不是硬编码。

动态代理及AOP

首先,我们从最基本得问题开始讲,开发实际应用软件系统时,肯定会出现相同代码段得情况,那最简单的方法就是复制,粘贴,就实现了功能。如果只看功能的话,确实实现了软件开发。

那问题来了,如果有一天,需要修改这些重复代码段怎么办?要是使用了100处就要改100次,要是1000次,10000次呢?那时候维护代码的人肯定想骂娘。

所以,正常的开发人员,都会将这一段代码定义为一个方法,然后调用。修改的话就只需要修改一次就可以了。

但这时候问题又出现了,就是每一处使用这个方法的代码段和这个特定的方法耦合了,耦合的概念和坏处上面已经说了。所以最理想的是,代码段可以使用这个方法,但是又没有用硬编码的方式调用代码段,使代码耦合。这就是动态代理出现的意义。

下面就是创建代理对象的示例。

public interface Runner {
    //接口
    void info();
    void run();
}
//实现类
public class Wujie extends WechatServer implements Runner {
    @Override
    public void info(){
        System.out.println("我要出去跑步啦!");
    }
    @Override
    public void run(){
        System.out.println("我跑完啦!");
    }
}
//handler,执行代理对象的方法
//实现InvocationHandler接口
public class MyInvokationHandler implements InvocationHandler {
    //需要被代理的对象
    private Object target;
    public void setTarget(Object target){
        this.target = target;
    }
    //执行动态代理对象的所有方法,都会被替换成执行如下的Invoke方法
    //proxy:动态代理对象
    //method:代表正在执行的方法
    //args:代表调用目标方法传入的实参
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //以target作为主调来执行method方法
        Object result = method.invoke(target,args);
        return result;
    }
}
//生成动态代理示例
public class MyProxyFactory {
    public static Object getProxy(Object target)throws Exception{
        //创建MyInvokationHandler对象
        MyInvokationHandler handler = new MyInvokationHandler();
        //设置target对象
        handler.setTarget(target);
        //返回动态代理对象
        return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),handler);
    }
}
//测试类
public class Test {
    public static void main(String[] args) throws Exception{
        Runner runner = new Wujie();
        Runner proxy = (Runner) MyProxyFactory.getProxy(runner);
        proxy.info();
        proxy.run();
    }
}

测试结果,在执行info方法之前和之和,会分别调用Server类里面的BeforeRun,AfterRun方法。不难发现使用动态代理已经实现了我们上面所说的解耦,因为他调用了Server里面的方法,但是没有直接硬编码在测试方法里面,也就是符合解耦的原则。

1.元素B是元素A的属性,或者元素A引用了元素B的实例(这包括元素A调用的某个方法,其参数中包含元素B)。

2.元素A调用了元素B的方法。

3.元素A直接或间接成为元素B的子类。

4.元素A是接口B的实现。

上面最关键的代码就是handler类,实现了Invoke方法,用target主调来执行Method方法,就是回调了target的原有方法。在调用之前先执行了server的beforeRun,之后执行了afterRun方法。

当然,我们不会没事就来个动态代理来写代码,这样没有什么实际意义,通常都是为指定的目标对象来生成动态代理,它的主要用处在框架和底层设计上面。

比如Spring中的AOP代理,它的原理就是基于动态代理,可以使得我们面向切面编程,它的代理包含了目标对象的所有方法。但是它的实现肯定不会那么简单的,具体的实现等以后理解了再写吧

https://blog.csdn.net/hhhuuu2020/article/details/52440279

猜你喜欢

转载自blog.csdn.net/qq_37891064/article/details/81981166