Java代理模式和切面编程

1、代理模式

即Proxy Pattern,23种java常用设计模式之一。代理模式的定义:对其他对象提供一种代理以控制对这个对象的访问。

1.1 介绍

代理模式的主要作用是为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不想或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。
代理模式的思想是为了提供额外的处理或者不同的操作而在实际对象与调用者之间插入一个代理对象。这些额外的操作通常需要与实际对象进行通信。

1.2 场景举例

假设有一组对象都实现同一个接口,实现同样的方法,但这组对象中有一部分对象需要有单独的方法,传统的笨办法是在每一个应用端都加上这个单独的方法,但是代码重用性低,耦合性高。如果用代理的方法则很好的解决了这个问题。

1.3 涉及到的角色

代理模式是给指定对象提供代理对象。由代理对象来控制具体对象的引用。代理模式涉及到的角色如下:

  • 抽象主题角色
    声明了代理主题和真实主题的公共接口,使任何需要真实主题的地方都能用代理主题代替。
  • 代理主题角色
    含有真实主题的引用,从而可以在任何时候操作真实主题,代理主题功过提供和真实主题相同的接口,使它可以随时代替真实主题。代理主题通过持有真实主题的引用,不但可以控制真实主题的创建或删除,可以在真实主题被调用前进行拦截,或在调用后进行某些操作。
  • 真实代理对象
    定义了代理角色所代表的具体对象。

1.4 分类

按照代理创建的时期代理类可以分成两种:

  • 静态代理
    由程序员创建或特定工具自动生成源代码,再对其编译。在程序运行前,代理类的.class文件就已经存在 了。
  • 动态代理
    在程序运行时,运用反射机制动态创建而成。

1.4.1 静态代理的例子

接下来,介绍一个代理模式使用的例子。在该例中,通过使用代理模式,没有改动Person类的代码,实现了给Person类中方法添加日志的功能。一方面,如果有多个和Person类似的实现了PersonInterface的类,都可以通过该代理类实现日志功能。另一方面,也实现了业务代码的解耦,Person类专注于业务代码的实现,可维护性和可读性都更好。具体如下。
PersonInterface.java:

package com.aop;

/**
 * Created by chengxia on 2019/3/30.
 */
public interface PersonInterface {
    public void run();
    public void eat();
}

Person.java:

package com.aop;

/**
 * Created by chengxia on 2019/3/30.
 */
public class Person implements PersonInterface {
    public Person(String name) {
        this.name = name;
    }

    private String name;

    public void run()
    {
        System.out.println(name + ": " + "i am running...");
    }

    public void eat()
    {
        System.out.println(name + ": " + "i am eating...");
    }

}

PersonInterfaceProxy.java:

package com.aop;

/**
 * Created by chengxia on 2019/4/1.
 */
public class PersonInterfaceProxy implements PersonInterface {

    PersonInterface p;

    public PersonInterfaceProxy(PersonInterface p) {
        this.p = p;
    }

    @Override
    public void eat() {
        System.out.println("Log: begin to eat.");
        p.eat();
        System.out.println("Log: eat over.");
    }

    @Override
    public void run() {
        System.out.println("Log: begin to run.");
        p.run();
        System.out.println("Log: run over.");
    }
}

TestProxy.java:

package com.aop;

/**
 * Created by chengxia on 2019/3/30.
 */
public class TestProxy {
    public static void main(String []args){
        //新建一个person类
        PersonInterface p = new Person("Kobe");
        //新建一个person代理
        PersonInterfaceProxy pProxy = new PersonInterfaceProxy(p);
        pProxy.eat();
        pProxy.run();
    }
}

运行结果:

Log: begin to eat.
Kobe: i am eating...
Log: eat over.
Log: begin to run.
Kobe: i am running...
Log: run over.

Process finished with exit code 0

1.4.2 动态代理介绍

由静态代理的代码可以看到静态代理只能对一个接口进行服务,如果项目中有很多个接口,那么肯定会产生过多的代理。这时候就需要用到动态代理,由一个代理类完成所有的代理功能。
动态代理类的字节码在程序运行时由Java反射机制动态生成,无需程序员手工编写它的源代码。动态代理类不仅简化了编程工作,而且提高了软件系统的可扩展性,因为Java。反射机制可以生成任意类型的动态代理类。
JDK动态代理中包含一个InvocationHandler接口和一个Proxy类。
(1) invocationHandler接口

public interface InvocationHandler {     
    public Object invoke(Object proxy,Method method,Object[] args) throws Throwable; 
}

其中:
Object porxy: 是被代理的对象
Method method: 要调用的方法
Object[] args: 要调用的方法的参数

(2)Proxy类
Proxy类是专门完成代理的操作类,可以通过此类为一个或多个接口动态地生成实现类,此类提供了如下的操作方法:

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

其中:
ClassLoader loader: 是类加载器(在java中主要有三种类加载器:Booststrap ClassLoader:此加载器采用C++编写,一般开发中是看不到的;Extendsion ClassLoader:用来进行扩展类的加载,一般对应的是jrelibext目录中的类; AppClassLoader:(默认)加载classpath指定的类,是最常使用的是一种加载器。)
Class<?>[] interfaces: 得到全部接口;
InvocationHandler h: 得到InvocationHandler接口的子类实例;

1.4.3 动态代理例子

下面是个动态代理的例子,原始的接口和类用的还是上面例子中的。
DynamicProxyInnvocationHandler.java:

package com.aop;

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

/**
 * Created by chengxia on 2019/4/1.
 */
public class DynamicProxyInnvocationHandler implements InvocationHandler {
    private Object target;

    public DynamicProxyInnvocationHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object result = null;
        //预处理逻辑
        System.out.println("Before executing " + method.getName());
        result = method.invoke(target, args);
        //事后处理逻辑
        System.out.println("After executing " + method.getName());
        return result;
    }
}

TestDynamicProxy.java:

package com.aop;

import java.lang.reflect.Proxy;

/**
 * Created by chengxia on 2019/3/30.
 */
public class TestDynamicProxy {
    public static void main(String []args){
        // 产生一个被代理对象,一个person类
        PersonInterface p = new Person("Kobe");
        //
        // 将被代理对象交给InvocationHandler
        DynamicProxyInnvocationHandler dpi = new DynamicProxyInnvocationHandler(p);

        // 根据被代理对象产生一个代理
        PersonInterface pProxied = (PersonInterface) Proxy.newProxyInstance(p.getClass().getClassLoader(), p.getClass().getInterfaces(), dpi);

        // 执行被代理对象的方法
        pProxied.run();
        pProxied.eat();
    }
}

运行结果如下:

Before executing run
Kobe: i am running...
After executing run
Before executing eat
Kobe: i am eating...
After executing eat

Process finished with exit code 0

2、切面编程介绍

上面提到的动态代理,在java中最常见的应用之一就是切面编程。
Aspect Oriented Programming(AOP),面向切面编程,是一个比较热门的话题。AOP主要实现的目的是针对业务处理过程中的切面进行提取,它所面对的是处理过程中的某个步骤或阶段,以获得逻辑过程中各部分之间低耦合性的隔离效果。

2.1 举例介绍

比如我们最常见的就是日志记录了,举个例子,我们现在提供一个查询学生信息的服务,但是我们希望记录有谁进行了这个查询。如果按照传统的OOP的实现的话,那我们实现了一个查询学生信息的服务接口(StudentInfoService)和其实现类 (StudentInfoServiceImpl.java),同时为了要进行记录的话,那我们在实现类(StudentInfoServiceImpl.java)中要添加其实现记录的过程。这样的话,假如我们要实现的服务有多个呢?那就要在每个实现的类都添加这些记录过程。这样做的话就会有点繁琐,而且每个实现类都与记录服务日志的行为紧耦合,违反了面向对象的规则。
那么怎样才能把记录服务的行为与业务处理过程中分离出来呢?看起来好像就是查询学生的服务自己在进行,但却是背后日志记录对这些行为进行记录,并且查询学生的服务不知道存在这些记录过程,这就是我们要讨论AOP的目的所在。AOP的编程,好像就是把我们在某个方面的功能提出来与一批对象进行隔离,这样与一批对象之间降低了耦合性,可以就某个功能进行编程。

2.2 实例:通过切面编程添加日志功能

通常,Java语言中的切面编程是通过动态代理来实现的。
接下来举一个通过切面编程给一个类添加日志功能的例子。如下是一个Person类,定义:
PersonInterface.java:

package com.aop;

/**
 * Created by chengxia on 2019/3/30.
 */
public interface PersonInterface {
    public void run();
    public void eat();
}

Person.java:

package com.aop;

/**
 * Created by chengxia on 2019/3/30.
 */
public class Person implements PersonInterface {
    public Person(String name) {
        this.name = name;
    }

    private String name;

    public void run()
    {
        System.out.println(name + ": " + "i am running...");
    }

    public void eat()
    {
        System.out.println(name + ": " + "i am eating...");
    }

}

接下来,通过切面编程,给该类添加日志功能。
首先,编写日志记录的代码实现:
LoggingInerface.java:

package com.aop;

import java.lang.reflect.Method;

/**
 * Created by chengxia on 2019/3/30.
 */
public interface LoggingInerface {
    public void log(Method m);
}

Logging.java:

package com.aop;

import java.lang.reflect.Method;

/**
 * Created by chengxia on 2019/3/30.
 */
public class Logging implements LoggingInerface {
    public void log(Method m){
        System.out.println("Log: " + m.getName() + "Method excuted.");
    }
}

接下来,通过java的动态代理,实现一个生成带日志记录功能Person实例的工厂方法:
PersonFactory.java:

package com.aop;

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

/**
 * Created by chengxia on 2019/3/30.
 */
public class PersonFactory {
    //接受目标和建议,产生任意类(只要该类有接口)的代理类,拦截所有的方法访问
    public static Object getPerson(final Object obj,final LoggingInerface log)
    {
        Object proxy = Proxy.newProxyInstance(PersonFactory.class.getClassLoader(), obj.getClass().getInterfaces(), new InvocationHandler()
        {
            public Object invoke(Object arg0, Method m, Object[] arg2)
                    throws Throwable
            {
                log.log(m);
                Object value = m.invoke(obj, arg2);
                return value;
            }
        });

        return proxy;
    }
}

到这里,切面程序就完成了,接下来,写一个类测试:
TestAOP.java:

package com.aop;

/**
 * Created by chengxia on 2019/3/30.
 */
public class TestAOP {
    public static void main(String []args){
        //包含日志记录的类
        LoggingInerface log = new Logging();
        PersonInterface p = (PersonInterface) PersonFactory.getPerson(new Person("Tom"), log);
        p.eat();
        p.eat();
        p.run();
        p.run();
    }
}

运行结果如下:

Log: eatMethod excuted.
Tom: i am eating...
Log: eatMethod excuted.
Tom: i am eating...
Log: runMethod excuted.
Tom: i am running...
Log: runMethod excuted.
Tom: i am running...

Process finished with exit code 0

这时候,如果我们新建一个类,也实现了PersonInterface接口:
People.java:

package com.aop;

/**
 * Created by chengxia on 2019/3/30.
 */
public class People implements PersonInterface {
    public People(String name) {
        this.name = name;
    }

    private String name;

    public void run()
    {
        System.out.println(name + ": " + "i am running...");
    }

    public void eat()
    {
        System.out.println(name + ": " + "i am eating...");
    }

}

这个类也可以通过前面的动态代理,获得日志功能。下面是一个测试例子:
TestAOP.java:

package com.aop;

/**
 * Created by chengxia on 2019/3/30.
 */
public class TestAOP {
    public static void main(String []args){
        //包含日志记录的类
        LoggingInerface log = new Logging();
        PersonInterface p = (PersonInterface) PersonFactory.getPerson(new People("Chairman"), log);
        p.eat();
        p.eat();
        p.run();
        p.run();
    }
}

运行结果:

Log: eatMethod excuted.
Chairman: i am eating...
Log: eatMethod excuted.
Chairman: i am eating...
Log: runMethod excuted.
Chairman: i am running...
Log: runMethod excuted.
Chairman: i am running...

Process finished with exit code 0

甚至这里,我们再新建一个Animal类,定义如下:
AnimalInterface.java:

package com.aop;

/**
 * Created by chengxia on 2019/3/30.
 */
public interface AnimalInterface {
    public void jump();
    public void fly();
}

Animal.java:

package com.aop;

/**
 * Created by chengxia on 2019/3/30.
 */
public class Animal implements AnimalInterface {
    public Animal(String name) {
        this.name = name;
    }

    private String name;

    public void jump()
    {
        System.out.println(name + ": " + "jumping...");
    }

    public void fly()
    {
        System.out.println(name + ": " + "flying...");
    }

}

这个类,也可以通过上面的动态代理切面获得日志功能,下面是要给测试例子:

package com.aop;

/**
 * Created by chengxia on 2019/3/30.
 */
public class TestAOP {
    public static void main(String []args){
        //包含日志记录的类
        LoggingInerface log = new Logging();
        AnimalInterface a = (AnimalInterface) PersonFactory.getPerson(new Animal("Paopao"), log);
        a.fly();
        a.jump();
    }
}

运行结果如下:

Log: flyMethod excuted.
Paopao: flying...
Log: jumpMethod excuted.
Paopao: jumping...

Process finished with exit code 0

可见,切面还是很强大的。它可以实现业务代码和技术日志代码的分离,使代码的结构、可读性、可维护性等都更好。

面向切面在英文中的单词是Aspect Oriented Programming(AOP),在spring框架中叫aop,它是可以通过预编译方式和运行期动态代理实现在不修改源代码的情况下给程序动态统一添加功能的一种技术。它是一种新的方法论,它是对传统OOP编程的一种补充。

参考资料

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容