• AOP


    面向切面编程(AOP是Aspect Oriented Program的首字母缩写) ,我们知道,面向对象的特点是继承、多态和封装。而封装就要求将功能分散到不同的对象中去,这在软件设计中往往称为职责分配。实际上也就是说,让不同的类设计不同的方法。这样代码就分散到一个个的类中去了。这样做的好处是降低了代码的复杂程度,使类可重用。
          但是人们也发现,在分散代码的同时,也增加了代码的重复性。什么意思呢?比如说,我们在两个类中,可能都需要在每个方法中做日志。按面向对象的设计方法,我们就必须在两个类的方法中都加入日志的内容。也许他们是完全相同的,但就是因为面向对象的设计让类与类之间无法联系,而不能将这些重复的代码统一起来。
        也许有人会说,那好办啊,我们可以将这段代码写在一个独立的类独立的方法里,然后再在这两个类中调用。但是,这样一来,这两个类跟我们上面提到的独立的类就有耦合了,它的改变会影响这两个类。那么,有没有什么办法,能让我们在需要的时候,随意地加入代码呢?这种在运行时,动态地将代码切入到类的指定方法、指定位置上的编程思想就是面向切面的编程。 
          一般而言,我们管切入到指定类指定方法的代码片段称为切面,而切入到哪些类、哪些方法则叫切入点。有了AOP,我们就可以把几个类共有的代码,抽取到一个切片中,等到需要时再切入对象中去,从而改变其原有的行为。
    这样看来,AOP其实只是OOP的补充而已。OOP从横向上区分出一个个的类来,而AOP则从纵向上向对象中加入特定的代码。有了AOP,OOP变得立体了。如果加上时间维度,AOP使OOP由原来的二维变为三维了,由平面变成立体了。从技术上来说,AOP基本上是通过代理机制实现的。 
         AOP在编程历史上可以说是里程碑式的,对OOP编程是一种十分有益的补充。

    Aspect Oriented Programming  面向切面编程。解耦是程序员编码开发过程中一直追求的。AOP也是为了解耦所诞生。

    具体思想是:定义一个切面,在切面的纵向定义处理方法,处理完成之后,回到横向业务流。

    AOP 在spring框架中被作为核心组成部分之一,的确Spring将AOP发挥到很强大的功能。最常见的就是事务控制。工作之余,对于使用的工具,不免需要了解其所以然。学习了一下,写了些程序帮助理解。

    AOP 主要是利用代理模式的技术来实现的。

    1、静态代理:就是设计模式中的proxy模式

    a、业务接口

    复制代码
    /**
     * 抽象主题角色:声明了真实主题和代理主题的共同接口。
     * 
     * @author yanbin
     * 
     */
    public interface ITalk {
    
        public void talk(String msg);
    
    }
    复制代码

    b、业务实现

    复制代码
    /**
     * 真实主题角色:定义真实的对象。
     * 
     * @author yanbin
     * 
     */
    public class PeopleTalk implements ITalk {
    
        public String username;
        public String age;
    
        public PeopleTalk(String username, String age) {
            this.username = username;
            this.age = age;
        }
    
        public void talk(String msg) {
            System.out.println(msg + "!你好,我是" + username + ",我年龄是" + age);
        }
    
        public String getName() {
            return username;
        }
    
        public void setName(String name) {
            this.username = name;
        }
    
        public String getAge() {
            return age;
        }
    
        public void setAge(String age) {
            this.age = age;
        }
    
    }
    复制代码

    c、代理对象

    复制代码
    /**
     * 代理主题角色:内部包含对真实主题的引用,并且提供和真实主题角色相同的接口。
     * 
     * @author yanbin
     * 
     */
    public class TalkProxy implements ITalk {
    
        private ITalk talker;
    
        public TalkProxy(ITalk talker) {
            // super();
            this.talker = talker;
        }
    
        public void talk(String msg) {
            talker.talk(msg);
        }
    
        public void talk(String msg, String singname) {
            talker.talk(msg);
            sing(singname);
        }
    
        private void sing(String singname) {
            System.out.println("唱歌:" + singname);
        }
    
    }
    复制代码

    d、测试

    复制代码
    /**
     * 代理测试类,使用代理
     *
     * @author yanbin
     * 
     */
    public class ProxyPattern {
    
        public static void main(String[] args) {
            // 不需要执行额外方法的。
            ITalk people = new PeopleTalk("AOP", "18");
            people.talk("No ProXY Test");
            System.out.println("-----------------------------");
    
            // 需要执行额外方法的(切面)
            TalkProxy talker = new TalkProxy(people);
            talker.talk("ProXY Test", "代理");
        }
    
    }
    复制代码

    从这段代码可以看出来,代理模式其实就是AOP的雏形。 上端代码中talk(String msg, String singname)是一个切面。在代理类中的sing(singname)方法是个后置处理方法。

    这样就实现了,其他的辅助方法和业务方法的解耦。业务不需要专门去调用,而是走到talk方法,顺理成章的调用sing方法

    再从这段代码看:1、要实现代理方式,必须要定义接口。2、每个业务类,需要一个代理类。

    2、动态代理:jdk1.5中提供,利用反射。实现InvocationHandler接口。

    业务接口还是必须得,业务接口,业务类同上。

    a、代理类:

    复制代码
    /**
     * 动态代理类
     * 
     * @author yanbin
     * 
     */
    public class DynamicProxy implements InvocationHandler {
    
        /** 需要代理的目标类 */
        private Object target;
    
        /**
         * 写法固定,aop专用:绑定委托对象并返回一个代理类
         * 
         * @param delegate
         * @return
         */
        public Object bind(Object target) {
            this.target = target;
            return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
        }
    
        /**
         * @param Object
         *            target:指被代理的对象。
         * @param Method
         *            method:要调用的方法
         * @param Object
         *            [] args:方法调用时所需要的参数
         */
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            Object result = null;
            // 切面之前执行
            System.out.println("切面之前执行");
            // 执行业务
            result = method.invoke(target, args);
            // 切面之后执行
            System.out.println("切面之后执行");
            return result;
        }
    
    }
    复制代码

    b、测试类

    复制代码
    /**
     * 测试类
     * 
     * @author yanbin
     * 
     */
    public class Test {
    
        public static void main(String[] args) {
            // 绑定代理,这种方式会在所有的方法都加上切面方法
            ITalk iTalk = (ITalk) new DynamicProxy().bind(new PeopleTalk());
            iTalk.talk("业务说明");
        }
    }
    复制代码

    输出结果会是:

    切面之前执行
    people talk业务说法
    切面之后执行

    说明只要在业务调用方法切面之前,是可以动态的加入需要处理的方法。

    从代码来看,如果再建立一个业务模块,也只需要一个代理类。ITalk iTalk = (ITalk) new DynamicProxy().bind(new PeopleTalk());  将业务接口和业务类绑定到动态代理类。

    但是这种方式:还是需要定义接口。

    3、利用cglib

    CGLIB是针对类来实现代理的,他的原理是对指定的目标类生成一个子类,并覆盖其中方法实现增强。采用的是继承的方式。不细说,看使用

    a、业务类

    复制代码
    /**
     * 业务类
     * 
     * @author yanbin
     * 
     */
    public class PeopleTalk {
    
        public void talk(String msg) {
            System.out.println("people talk" + msg);
        }
    
    }
    复制代码

    b、cglib代理类

    复制代码
    /**
     * 使用cglib动态代理
     * 
     * @author yanbin
     * 
     */
    public class CglibProxy implements MethodInterceptor {
    
        private Object target;
    
        /**
         * 创建代理对象
         * 
         * @param target
         * @return
         */
        public Object getInstance(Object target) {
            this.target = target;
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(this.target.getClass());
            // 回调方法
            enhancer.setCallback(this);
            // 创建代理对象
            return enhancer.create();
        }
    
        @Override
        public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
            Object result = null;
            System.out.println("事物开始");
            result = methodProxy.invokeSuper(proxy, args);
            System.out.println("事物结束");
            return result;
        }
    
    }
    复制代码

    c.测试类

    复制代码
    /**
     * 测试类
     * 
     * @author yanbin
     * 
     */
    public class Test {
    
        public static void main(String[] args) {
            PeopleTalk peopleTalk = (PeopleTalk) new CglibProxy().getInstance(new PeopleTalk());
            peopleTalk.talk("业务方法");
            peopleTalk.spreak("业务方法");
        }
    
    }
    复制代码

    最后输出结果:

    事物开始
    people talk业务方法
    事物结束
    事物开始
    spreak chinese业务方法
    事物结束

    本篇介绍通过MethodInterceptor和Enhancer实现一个动态代理。

    一、首先说一下JDK中的动态代理

    JDK中的动态代理是通过反射类Proxy以及InvocationHandler回调接口实现的,不了解的同学请参考我的这篇Blog:Java动态代理详解 http://shensy.iteye.com/blog/1698197 

    但是,JDK中所要进行动态代理的类必须要实现一个接口,也就是说只能对该类所实现接口中定义的方法进行代理,这在实际编程中具有一定的局限性,而且使用反射的效率也并不是很高。

    二、使用CGLib实现

    使用CGLib实现动态代理,完全不受代理类必须实现接口的限制,而且CGLib底层采用ASM字节码生成框架,使用字节码技术生成代理类,比使用Java反射效率要高。唯一需要注意的是,CGLib不能对声明为final的方法进行代理,因为CGLib原理是动态生成被代理类的子类。

    下面,将通过一个实例介绍使用CGLib实现动态代理。

    1、被代理类

    首先,定义一个类,该类没有实现任何接口,包含两个方法。

    Java代码  收藏代码
    1. public class ConcreteClassNoInterface {  
    2.     public String getConcreteMethodA(String str){  
    3.         System.out.println("ConcreteMethod A ... "+str);  
    4.         return str;  
    5.     }  
    6.     public int getConcreteMethodB(int n){  
    7.         System.out.println("ConcreteMethod B ... "+n);  
    8.         return n+10;  
    9.     }  
    10. }  
     

    2、拦截器

    定义一个拦截器。在调用目标方法时,CGLib会回调MethodInterceptor接口方法拦截,来实现你自己的代理逻辑,类似于JDK中的InvocationHandler接口。

    Java代码  收藏代码
    1. public class ConcreteClassInterceptor implements MethodInterceptor{  
    2.     public Object intercept(Object obj, Method method, Object[] arg, MethodProxy proxy) throws Throwable {  
    3.         System.out.println("Before:"+method);    
    4.         Object object=proxy.invokeSuper(obj, arg);  
    5.         System.out.println("After:"+method);   
    6.         return object;  
    7.     }  
    8. }  
     

    参数:Object为由CGLib动态生成的代理类实例,Method为上文中实体类所调用的被代理的方法引用,Object[]为参数值列表,MethodProxy为生成的代理类对方法的代理引用。

    返回:从代理实例的方法调用返回的值。

    其中,proxy.invokeSuper(obj,arg):

    调用代理类实例上的proxy方法的父类方法(即实体类ConcreteClassNoInterface中对应的方法)

    在这个示例中,只在调用被代理类方法前后各打印了一句话,当然实际编程中可以是其它复杂逻辑。

    3、生成动态代理类

    Java代码  收藏代码
    1. Enhancer enhancer=new Enhancer();  
    2. enhancer.setSuperclass(ConcreteClassNoInterface.class);  
    3. enhancer.setCallback(new ConcreteClassInterceptor());  
    4. ConcreteClassNoInterface ccni=(ConcreteClassNoInterface)enhancer.create();  

    这里Enhancer类是CGLib中的一个字节码增强器,它可以方便的对你想要处理的类进行扩展,以后会经常看到它。

    首先将被代理类ConcreteClassNoInterface设置成父类,然后设置拦截器ConcreteClassInterceptor,最后执行enhancer.create()动态生成一个代理类,并从Object强制转型成父类型ConcreteClassNoInterface。

    最后,在代理类上调用方法:

    Java代码  收藏代码
    1. ccni.getConcreteMethodA("shensy");  
    2. ccni.getConcreteMethodB(0);  

    查看控制台输出:

    控制台代码  收藏代码
    1. Before :public java.lang.String generic.cglib.proxy.ConcreteClassNoInterface.getConcreteMethodA(java.lang.String)  
    2. ConcreteMethod A ... shensy  
    3. After :public java.lang.String generic.cglib.proxy.ConcreteClassNoInterface.getConcreteMethodA(java.lang.String)  
    4. Before :public int generic.cglib.proxy.ConcreteClassNoInterface.getConcreteMethodB(int)  
    5. ConcreteMethod B ... 0  
    6. After :public int generic.cglib.proxy.ConcreteClassNoInterface.getConcreteMethodB(int)  

    可以看到,拦截器在调用被代理类方法前后都执行了print操作。

  • 相关阅读:
    个人WPF快速入门笔记 基础样式篇02
    个人WPF快速入门笔记 基础布局篇01
    nginx常用笔记备忘
    【leetcode】1685. Sum of Absolute Differences in a Sorted Array
    【leetcode】1696. Jump Game VI
    【leetcode】1694. Reformat Phone Number
    【leetcode】1684. Count the Number of Consistent Strings
    【leetcode】1695. Maximum Erasure Value
    【leetcode】1671. Minimum Number of Removals to Make Mountain Array
    【leetcode】1689. Partitioning Into Minimum Number Of DeciBinary Numbers
  • 原文地址:https://www.cnblogs.com/flycatnet/p/6865860.html
Copyright © 2020-2023  润新知