Java代理模式和切面编程

作者: SpaceCat | 来源:发表于2019-04-01 12:33 被阅读4次

    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编程的一种补充。

    参考资料

    相关文章

      网友评论

        本文标题:Java代理模式和切面编程

        本文链接:https://www.haomeiwen.com/subject/fctvbqtx.html