从代理机制到Spring AOP

这篇文章准备从Java的代理机制讲到Spring的AOP。php

1.代理模式

代理模式是很常见的一种设计模式,代理一词拆开来看就是代为受理,那显然是要涉及到请求被代理的委托方,提供代理的代理方,以及想要经过代理来实际联系委托方的客户三个角色。举个生活中很常见的例子,各路的明星都会有个本身的经纪人来替本身打点各类各样的事情,这种场景下,明星自己是委托方,经纪人是代理方,明星把本身安排演出、出席见面会的时间安排权利委托给经纪人,这样当各个商家做为客户想要请明星来代言时,就只能经过经纪人来进行。这样明星自己不用暴露身份,而经济人也能够在沟通中告知商家明星出席活动时要吃什么饭,作什么车的一些要求,省去了明星本身操心这些鸡毛蒜皮小事儿。另外一方面,当经纪人也能够给多个明星提供服务,这样商家只接触一个经纪人,能够联系到不一样的明星,找个适合本身公司的人选。
经过上面的例子,代理模式的优势就显而易见了:java

优势一:能够隐藏委托类的实现;程序员

优势二:能够实现客户与委托类间的解耦,在不修改委托类代码的状况下可以作一些额外的处理。spring

2.字节码与代理模式

Java程序员都应该知道,Java经过Java编译器将.java源文件编译成.class字节码文件,这种.class文件是二进制文件,内容是只有JVM虚拟机可以识别的机器码,JVM虚拟机读取字节码文件,取出二进制数据,加载到内存中,解析.class文件内的信息,生成对应的Class对象,进而使Class对象建立类的具体实例来进行调用实现具体的功能。express

上图说明了Java加载字节码的流程,可是Java的强大在于不只仅能够加载在编译期生成好的字节码,还能够在运行期系统中,遵循Java编译系统组织.class文件的格式和结构,生成相应的二进制数据,而后再把这个二进制数据加载转换成对应的类,这样,就完成了在代码中,动态建立一个类的能力了,以下图流程。编程

下面举一个动态生成类的实例,经过Javassist实现,Javassist是一个开源的分析、编辑和建立Java字节码的类库,咱们可使用Javasisst工具在运行时动态建立字节码并加载类,以下代码:设计模式

/** * Created by zhoujunfu on 2018/9/6. */
public class JavassistDemo {
    
    public static void main(String[] args) {
        makeNewClass();
    }
    
    public static Class<?> makeNewClass() {
        try {
            // 获取ClassPool
            ClassPool pool = ClassPool.getDefault();
            // 建立Student类
            CtClass ctClass = pool.makeClass("com.fufu.aop.Student");
            // 建立Student类成员变量name
            CtField name = new CtField(pool.get("java.lang.String"), "name", ctClass);
            // 设置name为私有
            name.setModifiers(Modifier.PRIVATE);
            // 将name写入class
            ctClass.addField(name, CtField.Initializer.constant("")); //写入class文件
            //增长set方法,名字为"setName"
            ctClass.addMethod(CtNewMethod.setter("setName", name));
            //增长get方法,名字为getname
            ctClass.addMethod(CtNewMethod.getter("getName", name));
            // 添加无参的构造体
            CtConstructor cons = new CtConstructor(new CtClass[] {}, ctClass);
            cons.setBody("{name = \"Brant\";}"); //至关于public Sclass(){this.name = "brant";}
            ctClass.addConstructor(cons);
            // 添加有参的构造体
            cons = new CtConstructor(new CtClass[] {pool.get("java.lang.String")}, ctClass);
            cons.setBody("{$0.name = $1;}");  //第一个传入的形参$1,第二个传入的形参$2,至关于public Sclass(String s){this.name = s;}
            ctClass.addConstructor(cons);

            //反射调用新建立的类
            Class<?> aClass =  ctClass .toClass();
            Object student = aClass.newInstance();
            Method getter = null;
            getter = student.getClass().getMethod("getName");
            System.out.println(getter.invoke(student));

        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}
复制代码

介绍静态和动态加载字节码的两种方式,是为了引出下面关于两种代理方式的介绍,代理机制经过代理类建立的时间不一样分为了静态代理和动态代理:
静态代理:代理类在编译阶段生成,程序运行前就已经存在,那么这种代理方式被成为静态代理,这种状况下的代理类一般都是咱们在Java代码中定义的。
动态代理:代理类在程序运行时建立,也就是说,这种状况下,代理类并非在Java代码中定义的,而是在运行时根据咱们在Java代码中的“指示”动态生成的。bash

目前,静态代理主要有AspectJ静态代理、JDK静态代理技术、而动态代理有JDK动态代理、Cglib动态代理技术,而Spring Aop是整合使用了JDK动态代理和Cglib动态代理两种技术,下面咱们结合实例一步一步介绍全部的概念。app

3.静态代理

3.1 AspectJ静态代理

对于AspectJ,咱们只会进行简单的了解,为后续理解打下基础,如今只须要知道下面这一句定义:框架

AspectJ是一个Java实现的面向切面的框架,它扩展了Java语言。AspectJ有自定义的语法,因此它有一个专门的编译器用来生成遵照Java字节编码规范的Class文件。

注意上面定义中的“专门的编译器”这个描述,能够看出AspectJ是典型的静态代理技术,由于是在编译时期就生成了代理类,而使用AspectJ也确定须要指定特定的编译器,下面咱们用AspectJ来实现上面的明星和经纪人的模型。

首先在maven工程中引入AspectJ依赖:

<dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjrt</artifactId>
      <version>1.8.9</version>
    </dependency>
    <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjtools</artifactId>
      <version>1.8.9</version>
    </dependency>
复制代码

而后在idea中将javac编译器改成acj编译器来支持AspectJ语法:

将明星的表演抽象成一个ShowService接口,包括了唱歌、跳舞的功能

public interface ShowService {
    // 歌唱表演
    void sing(String songName);
    // 舞蹈表演
    void dance();
}

复制代码

明星类实现了ShowService接口:

package com.fufu.aop;

/**
 * Created by zhoujunfu on 2018/9/6.
 * 明星类
 */
public class Star implements ShowService{
    private String name;

    @Override
    public void sing(String songName) {
        System.out.println(this.name + " sing a song: " + songName);
    }

    @Override
    public void dance() {
        System.out.println(this.name + "dance");
    }

    public Star(String name) {
        this.name = name;
    }

    public Star() {
    }

    public static void main(String[] args) {
        Star star = new Star("Eminem");
        star.sing("Mockingbird");
    }
}
复制代码

用AspectJ语法实现一个代理AgentAspectJ:

package com.fufu.aop;

/**
 * Created by zhoujunfu on 2018/9/7.
 */
public aspect AgentAspectJ {

    /**
     * 定义切点
     */
    pointcut sleepPointCut():call(* Star.sing(..));

    /**
     * 定义切点
     */
    pointcut eatPointCut():call(* Star.eat(..));

    /**
     * 定义前置通知
     *
     * before(参数):链接点函数{
     *     函数体
     * }
     */
    before():sleepPointCut(){
        getMoney();
    }

    /**
     * 定义后置通知
     * after(参数):链接点函数{
     *     函数体
     * }
     */
    after():sleepPointCut(){
        writeReceipt();
    }

    private void getMoney() {
        System.out.println("get money");
    }

    private void writeReceipt() {
        System.out.println("write receipt");
    }
}
复制代码

建立一个Star并运行方法:

public static void main(String[] args) {
        Star star = new Star("Eminem");
        star.sing("Mockingbird");
    }
复制代码

输出:

get money
Eminem sing a song: Mockingbird
write receipt
复制代码

能够看到Star的sing()方法先后输出了咱们在AgentAspectJ中定义的前置通知和后置通知,因此是AspectJ在编译期间,根据AgentAspectJ代码中定义的代码,生成了加强的Star类,而咱们实际调用时,就会实现代理类的功能。具体的AspectJ语法咱们不深究,只须要知道pointcut是定义代理要代理的切入点,这里是定义了两个pointcut,分别是Star类的sing()方法和dance()方法。而before()和after()分别能够定义具体在切入点先后须要的额外操做。

总结一下,AspctJ就是用特定的编译器和语法,对类实现编译期加强,实现静态代理技术,下面咱们看JDK静态代理。

3.2 JDK静态代理

一般状况下, JDK静态代理更多的是一种设计模式,JDK静态代理的代理类和委托类会实现同一接口或是派生自相同的父类,代理模式的基本类图入下:

咱们接着经过把上面的明星和经纪人的例子写成代码来实现一个JDK静态代理模式。

经纪人类也实现了ShowService接口,持有了一个明星对象来提供真正的表演,并在各项表演的先后加入了经纪人须要处理的事情,如收钱、开发票等:

package com.fufu.aop;

/** * Created by zhoujunfu on 2018/9/6. * 经纪人 */
public class Agent implements ShowService{

    private Star star;

    public Agent(Star star) {
        this.star = star;
    }

    private void getMoney() {
        System.out.println("get money");
    }

    private void writeReceipt() {
        System.out.println("write receipt");
    }
    @Override
    public void sing(String songName) {
        // 唱歌开始前收钱
        getMoney();
        // 明星开始唱歌
        star.sing(songName);
        // 唱歌结束后开发票
        writeReceipt();
    }

    @Override
    public void dance() {
        // 跳舞开始前收钱
        getMoney();
        // 明星开始跳舞
        star.dance();
        // 跳舞结束后开发票
        writeReceipt();
    }
}
复制代码

经过经纪人来请明星表演:

public static void main(String[] args) {
        Agent agent = new Agent(new Star("Eminem"));
        agent.sing("Mockingbird");
 }
复制代码

输出:

get money
Eminem sing a song: Mockingbird
write receipt
复制代码

以上就是一个典型的静态代理的实例,很简单可是也能说明问题,咱们来看看静态代理的优缺点:

优势: 业务类能够只关注自身逻辑,能够重用,经过代理类来增长通用的逻辑处理。

缺点: 1.代理对象的一个接口只服务于一种类型的对象,若是要代理的类不少,势必要为每个类都进行代理,静态代理在程序规模稍大时就没法胜任了。

2.若是接口增长一个方法,除了全部实现类须要实现这个方法外,全部代理类也须要实现此方法。增长了代码维护的复杂度

另外,若是要按照上述的方法使用代理模式,那么真实角色(委托类)必须是事先已经存在的,并将其做为代理对象的内部属性。可是实际使用时,一个真实角色必须对应一个代理角色,若是大量使用会致使类的急剧膨胀;此外,若是事先并不知道真实角色(委托类),该如何使用代理呢?这些问题能够经过Java的动态代理类来解决。

4.动态代理

动态代理类的源码是在程序运行期间由JVM根据反射等机制动态的生成,因此不存在代理类的字节码文件。代理类和委托类的关系是在程序运行时肯定。

4.1 动态代理思路

想弄明白动态代理类实现的思路是什么,咱们还需用从静态代理的存在的问题入手,由于毕竟动态代理是为了解决静态代理存在问题而出现的,回过头来看静态代理的问题:

  1. 类膨胀: 每一个代理类都是一个须要程序员编写的具体类,不现实。
  2. 方法级代理:代理类和实现类都实现相同接口,致使代理类每一个方法都须要进行代理,你有几个方法我就要有几个,编码复杂,没法维护。

动态代理如何解决:

  1. 第一个问题很容易回答,相似使用Javasisst的例子,在代码中动态的建立代理类的字节码,而后获取到代理类对象。
  2. 第二问题就要引出InvocationHandler了,为了构造出具备通用性和简单性的代理类,能够将全部的触发真实角色动做交给一个触发的管理器,让这个管理器统一地管理触发。这种管理器就是InvocationHandler。静态代理中,代理类无非是在先后加入特定逻辑后,调用对应的实现类的方法,sleep()对应sleep(),run()对应run(),而在Java中,方法Method也是一个对象,因此,动态代理类能够将对本身的全部调用做为Method对象都交给InvocationHandler处理,InvocationHandler根据是什么Method调用具体实现类的不一样方法,InvocationHandler负责增长代理逻辑和调用具体的实现类的方法。

也就是说,动态代理类仍是和实现类实现相同的接口,可是动态代理类是根据实现类实现的接口动态生成,不须要使用者关心,另外动态代理类的全部方法调用,统一交给InvocationHandler,不用处理实现类每一个接口的每一个方法。

在这种模式之中:代理Proxy和RealSubject应该实现相同的功能,这一点至关重要。(我这里说的功能,能够理解为某个类的public方法)

在面向对象的编程之中,若是咱们想要约定Proxy和RealSubject能够实现相同的功能,有两种方式:

a.一个比较直观的方式,就是定义一个功能接口,而后让Proxy 和RealSubject来实现这个接口。
b.还有比较隐晦的方式,就是经过继承。由于若是Proxy继承自RealSubject,这样Proxy则拥有了RealSubject的功能,Proxy还能够经过重写RealSubject中的方法,来实现多态。

其中JDK中提供的建立动态代理的机制,是以a这种思路设计的,而cglib则是以b思路设计的。

4.1 JDK动态代理(经过接口)

先来看一个具体的例子,仍是以上边明星和经纪人的模型为例,这样方便对比理解:

将明星的表演抽象成一个ShowService接口,包括了唱歌、跳舞的功能:

package com.fufu.aop;

/** * Created by zhoujunfu on 2018/9/6. */
public interface ShowService {
    // 歌唱表演
    void sing(String songName);
    // 舞蹈表演
    void dance();
}
复制代码

明星类实现了ShowService接口:

package com.fufu.aop;

/** * Created by zhoujunfu on 2018/9/6. * 明星类 */
public class Star implements ShowService{
    private String name;

    @Override
    public void sing(String songName) {
        System.out.println(this.name + " sing a song: " + songName);
    }

    @Override
    public void dance() {
        System.out.println(this.name + "dance");
    }

    public Star(String name) {
        this.name = name;
    }

    public Star() {
    }
}
复制代码

实现一个代理类的请求处理器,处理对具体类的全部方法的调用:

package com.fufu.aop;

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

/** * Created by zhoujunfu on 2018/9/7. */
public class InvocationHandlerImpl implements InvocationHandler {

    ShowService target;

    public InvocationHandlerImpl(ShowService target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 表演开始前收钱
        getMoney();
        // 明星开始唱歌
        Object invoke = method.invoke(target, args);
        // 表演结束后开发票
        writeReceipt();

        return invoke;
    }

    private void getMoney() {
        System.out.println("get money");
    }

    private void writeReceipt() {
        System.out.println("write receipt");
    }
}
复制代码

经过JDK动态代理机制实现一个动态代理:

package com.fufu.aop;

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

/**
 * Created by zhoujunfu on 2018/9/7.
 */
public class JDKProxyDemo {

    public static void main(String[] args) {
        // 1.建立被代理的具体类
        Star star = new Star("Eminem");
        // 2.获取对应的ClassLoader
        ClassLoader classLoader = star.getClass().getClassLoader();
        // 3.获取被代理对象实现的全部接口
        Class[] interfaces = star.getClass().getInterfaces();
        // 4.设置请求处理器,处理全部方法调用
        InvocationHandler invocationHandler = new InvocationHandlerImpl(star);

        /**
         * 5.根据上面提供的信息,建立代理对象 在这个过程当中,
         *   a.JDK会经过根据传入的参数信息动态地在内存中建立和.class文件等同的字节码
         *   b.而后根据相应的字节码转换成对应的class,
         *   c.而后调用newInstance()建立实例
         */
        Object o = Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);
        ShowService showService = (ShowService)o;
        showService.sing("Mockingbird");
    }
}
复制代码

咱们从代理的建立入手,看看JDK的动态代理都作了什么:

Object o = Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);
复制代码
  1. Proxy.newProxyInstance()获取Star类的全部接口列表(第二个参数:interfaces)
  2. 肯定要生成的代理类的类名,默认为:com.sun.proxy.$ProxyXXXX
  3. 根据须要实现的接口信息,在代码中动态建立该Proxy类的字节码;
  4. 将对应的字节码转换为对应的class对象;
  5. 建立InvocationHandler实例handler,用来处理Proxy全部方法调用
  6. Proxy的class对象以建立的handler对象为参数(第三个参数:invocationHandler),实例化一个Proxy对象

而对于InvocationHandler,咱们须要实现下列的invoke方法:

public Object invoke(Object proxy, Method method, Object[] args) 
复制代码

在调用代理对象中的每个方法时,在代码内部,都是直接调用了InvocationHandler的invoke方法,而invoke方法根据代理类传递给本身的method参数来区分是什么方法。

能够看出,Proxy.newProxyInstance()方法生成的对象也是实现了ShowService接口的,因此能够在代码中将其强制转换为ShowService来使用,和静态代理到达了一样的效果。咱们能够用下面代码把生成的代理类的字节码保存到磁盘里,而后反编译看看JDK生成的动态代理类的结构。

package com.fufu.aop;

import sun.misc.ProxyGenerator;

import java.io.FileOutputStream;
import java.io.IOException;

/** * Created by zhoujunfu on 2018/9/7. */
public class ProxyUtils {

    public static void main(String[] args) {
        Star star = new Star("Eminem");
        generateClassFile(star.getClass(), "StarProxy");
    }

    public static void generateClassFile(Class clazz, String proxyName) {

        //根据类信息和提供的代理类名称,生成字节码
        byte[] classFile = ProxyGenerator.generateProxyClass(proxyName, clazz.getInterfaces());
        String paths = clazz.getResource(".").getPath();
        System.out.println(paths);
        FileOutputStream out = null;

        try {
            //保留到硬盘中
            out = new FileOutputStream(paths + proxyName + ".class");
            out.write(classFile);
            out.flush();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                out.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
复制代码

反编译StarPoxy.class文件后获得:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

import com.fufu.aop.ShowService;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

// 动态代理类StarPoxy实现了ShowService接口
public final class StarProxy extends Proxy implements ShowService {
    // 加载接口中定义的全部方法
    private static Method m1;
    private static Method m3;
    private static Method m4;
    private static Method m2;
    private static Method m0;

    //构造函数接入InvocationHandler,也就是持有了InvocationHandler对象h
    public StarProxy(InvocationHandler var1) throws {
        super(var1);
    }

    public final boolean equals(Object var1) throws {
        try {
            return ((Boolean)super.h.invoke(this, m1, new Object[]{var1})).booleanValue();
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    // 自动生成的sing()方法,实际调用InvocationHandler对象h的invoke方法,传入m3参数对象表明sing()方法
    public final void sing(String var1) throws {
        try {
            super.h.invoke(this, m3, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }
    
    //同理生成dance()方法
    public final void dance() throws {
        try {
            super.h.invoke(this, m4, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final String toString() throws {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final int hashCode() throws {
        try {
            return ((Integer)super.h.invoke(this, m0, (Object[])null)).intValue();
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    // 加载接口中定义的全部方法
    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[]{Class.forName("java.lang.Object")});
            m3 = Class.forName("com.fufu.aop.ShowService").getMethod("sing", new Class[]{Class.forName("java.lang.String")});
            m4 = Class.forName("com.fufu.aop.ShowService").getMethod("dance", new Class[0]);
            m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
            m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}
复制代码

经过上面反编译后的代码能够看出,JDK生成的动态代理类实现和具体类相同的接口,并持有InvocationHandler对象(InvocationHandler对象又持有具体类),调用动态代理类中方法,会触发传入InvocationHandler的invoke()方法,经过method参数,来区分调用的是什么具体的方法,具体以下图所示:

4.2 CGLIB动态代理(经过继承)

JDK中提供的生成动态代理类的机制有个鲜明的特色是:

某个类必须有实现的接口,而生成的代理类也只能代理某个类接口定义的方法,好比:若是上面例子的Star实现了继承自ShowService接口的方法外,另外实现了方法play(),则在产生的动态代理类中不会有这个方法了!更极端的状况是:若是某个类没有实现接口,那么这个类就不能用JDK产生动态代理了!

幸亏咱们有cglib,“CGLIB(Code Generation Library),是一个强大的,高性能,高质量的Code生成类库,它能够在运行期扩展Java类与实现Java接口。”

cglib 建立某个类A的动态代理类的模式是:

1.查找A上的全部非final 的public类型的方法定义;
2.将这些方法的定义转换成字节码;
3.将组成的字节码转换成相应的代理的class对象;
4.实现 MethodInterceptor接口,用来处理对代理类上全部方法的请求(这个接口和JDK动态代理InvocationHandler的功能和角色是同样的)

有了上边JDK动态代理的例子,cglib的理解起来就简单了,仍是先以实例说明,ShowService接口和Star类都复用以前的不变:

实现 MethodInterceptor接口:

package com.fufu.aop;

import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/** * Created by zhoujunfu on 2018/9/7. */
public class MethodInterceptorImpl implements MethodInterceptor {

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        // 表演开始前收钱
        getMoney();
        // 明星开始唱歌
        Object invoke = methodProxy.invokeSuper(o, objects);
        // 表演结束后开发票
        writeReceipt();

        return invoke;
    }

    private void getMoney() {
        System.out.println("get money");
    }

    private void writeReceipt() {
        System.out.println("write receipt");
    }
}
复制代码

建立动态代理:

package com.fufu.aop;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;

/** * Created by zhoujunfu on 2018/9/7. */
public class CglibProxyDemo {

    public static void main(String[] args) {
        Star star = new Star("Eminem");

        MethodInterceptor methodInterceptor = new MethodInterceptorImpl();

        //cglib 中增强器,用来建立动态代理
        Enhancer enhancer = new Enhancer();
        //设置要建立动态代理的类
        enhancer.setSuperclass(star.getClass());
        // 设置回调,这里至关因而对于代理类上全部方法的调用,都会调用CallBack,而Callback则须要实行intercept()方法进行拦截
        enhancer.setCallback(methodInterceptor);

        ShowService showService = (ShowService) enhancer.create();
        showService.sing("Mockingbird");
    }
}
复制代码

经过以上实例能够看出,Cglib经过继承实现动态代理,具体类不须要实现特定的接口,并且代理类能够调用具体类的非接口方法,更加灵活。

5.Spring AOP

5.1 概念

AOP的具体概念就再也不说了,网上一搜一大把,这篇文章主要介绍Spring AOP低层使用的代理技术,由于平时在使用Spring AOP时,不少人都是copy配置,对上面介绍的这些技术概念并不清楚。

Spring AOP采用的是动态代理,在运行期间对业务方法进行加强,因此不会生成新类,对于动态代理技术,Spring AOP提供了对JDK动态代理的支持以及CGLib的支持,然而何时用哪一种代理呢?

一、若是目标对象实现了接口,默认状况下会采用JDK的动态代理实现AOP

二、若是目标对象实现了接口,能够强制使用CGLIB实现AOP

三、若是目标对象没有实现了接口,必须采用CGLIB库,spring会自动在JDK动态代理和CGLIB之间转换

目前来看,Spring貌似和AspectJ没半毛钱关系,那为何在许多应用了Spring AOP的项目中都出现了@AspectJ的注解呢?Spring是应用的动态代理,怎么会还和AspectJ有关系呢,缘由是Spring AOP基于注解配置的状况下,须要依赖于AspectJ包的标准注解,可是不须要额外的编译以及AspectJ的织入器,而基于XML配置不须要,因此Spring AOP只是复用了AspectJ的注解,并无其余依赖AspectJ的地方。

当Spring须要使用@AspectJ注解支持时,须要在Spring配置文件中以下配置:

<aop:aspectj-autoproxy/>
复制代码

而关于第二点强制使用CGLIB,能够经过在Spring的配置文件以下配置实现:

<aop:aspectj-autoproxy proxy-target-class="true"/>
复制代码

proxy-target-class属性值决定是基于接口的仍是基于类的代理被建立。若是proxy-target-class 属性值被设置为true,那么基于类的代理将起做用(这时须要cglib库)。若是proxy-target-class属值被设置为false或者这个属性被省略,那么标准的JDK 基于接口的代理。

因此,虽然使用了Aspect的Annotation,可是并无使用它的编译器和织入器。其实现原理是JDK动态代理或Cglib,在运行时生成代理类。

已经写了这么多了,下面再贴两个Spring AOP的demo代码吧,分别是基于XML和注解的:

5.2 基于XML

切面类:

package com.fufu.spring.aop;

import org.springframework.stereotype.Component;

/** * Created by zhoujunfu on 2018/9/7. * 基于XML的Spring AOP */
@Component
public class AgentAdvisorXML {

    public void getMoney() {
        System.out.println("get money");
    }

    public void writeReceipt() {
        System.out.println("write receipt");
    }
}

复制代码

配置文件:

<?xml version="1.0" encoding="UTF-8"?>
<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" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

    <bean id="star" class="com.fufu.proxy.Star">
        <property name="name" value="Eminem"/>
    </bean>

    <bean id="agentAdvisorXML" class="com.fufu.spring.aop.AgentAdvisorXML"/>
    
     <!--Spring基于Xml的切面-->
     <aop:config>
         <!-- 定义切点函数 -->
         <aop:pointcut id="singPointCut" expression="execution(* com.fufu.proxy.Star.sing(..))"/>
         <!-- 定义切面 order 定义优先级,值越小优先级越大-->
         <aop:aspect ref="agentAdvisorXML" order="0">
             <!--前置通知-->
             <aop:before method="getMoney" pointcut-ref="singPointCut"/>
             <!--后置通知-->
             <aop:after method="writeReceipt" pointcut-ref="singPointCut"/>
         </aop:aspect>
     </aop:config>

</beans>
复制代码

测试类:

package com.fufu.spring.aop;

import com.fufu.proxy.ShowService;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/** * Created by zhoujunfu on 2018/9/7. */
public class Main {

    public static void main(String[] args) {
        ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-aop.xml");

        Object star = applicationContext.getBean("star");

        ShowService showService = (ShowService)star;
        showService.sing("Mockingbird");
    }
}
复制代码

5.3 基于注解

切面类:

package com.fufu.spring.aop;

import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

/** * Created by zhoujunfu on 2018/9/7. * 基于注解的Spring AOP */
@Aspect
@Component
public class AgentAdvisor {

    @Before(value = "execution(* com.fufu.proxy.ShowService.sing(..))")
    public void getMoney() {
        System.out.println("get money");
    }

    @After(value = "execution(* com.fufu.proxy.ShowService.sing(..))")
    public void writeReceipt() {
        System.out.println("write receipt");
    }
}

复制代码

配置文件:

<?xml version="1.0" encoding="UTF-8"?>
<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"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">


    <context:component-scan base-package="com.fufu.proxy, com.fufu.spring.aop"/>

    <aop:aspectj-autoproxy  proxy-target-class="true"/>

</beans>
复制代码

测试类:

package com.fufu.spring.aop;

import com.fufu.proxy.ShowService;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * Created by zhoujunfu on 2018/9/7.
 */
public class Main {

    public static void main(String[] args) {
        ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-aop-annotation.xml");

        Object star = applicationContext.getBean("star");

        ShowService showService = (ShowService)star;
        showService.sing("Mockingbird");
    }
}
复制代码

6.总结

以上内容,虽然比较浅显易懂,可是能够对Java代理机制和Spring AOP会有一个全面的理解,若有错误,欢迎指正。

相关文章
相关标签/搜索