这里其实讲的是应用如何在Spring Framework如何应用AOP。在看本篇之前,倡议先看代理模式-AOP绪论,代理模式-AOP绪论是本篇的基础理论,不看也行,本篇讲的也就是讲根本应用。

AOP 简论

AOP: Aspect oriented programming 面向切面编程,是OOP(面向对象编程)的扩大。
如果说OOP是解决面向过程中遇到的一些问题(比方代码复用性不强等)的话,那么AOP解决的就是通用代码反复编写的问题。
这么说可能有点形象,咱们向事实的场景拉一拉,咱们的办法通常是一步一步执行的,不同的办法有不同的调用机会。

但总有一些办法仿佛是在所有的代码流程外面都须要,比方检测是否登录,记录执行工夫等。然而咱们总不想做反复的事件,就是在每个代码中写一遍,咱们的欲望是写一次,而后标定哪些代码在哪里须要执行呢? AOP解决的就是这类问题。那么这样做的话,咱们首先就要表明哪里是须要反复执行的,也就是注入点,用AOP的术语就是切入点(pointcut), 第二个就是申明在切入点在满足哪些机会时注入,比方能够抉择一个办法在执行时注入,在办法产生异样时注入。像Spring Proxy并不反对多种多样的连接点,百分之九十九的状况是在办法执行的时候注入。这个工夫咱们用术语称之为连接点(joinPoint)。第三个就是注入的机会,是在办法前还是办法后,还是办法前后都来一遍。这个概念咱们个别称之为倡议(advice)。

pointCut+advice能够形容一段代码在什么机会注入(jointPoint),注入到哪里(pointCut),而被注入的代码咱们用切面(Aspect)这个概念来形容,这也就是面向切面编程 (Aspect oriented programmin)。

认真想一下,下面讲的AOP中的倡议,是不是跟动静代理中的在不批改原来类的根底上加强一个类有点像呢? 没错AOP是借助于动静代理来实现的,如果你不懂什么是动静代理,请参看这篇文章:

  • 代理模式-AOP绪论

其实不懂动静代理,也能看懂本篇。本篇次要介绍的是AOP在Spring中是如何应用的,AOP是一种理念,Spring实现了AOP。只不过了解动静代理对Spring实现的AOP会了解更深而已。

筹备工作

在欢迎光临Spring时代(一) 上柱国IOC列传的依赖咱们还要接着用,同时咱们还要再补充以下依赖:

<dependency>            <groupId>org.springframework</groupId>            <artifactId>spring-aop</artifactId>            <version>5.2.8.RELEASE</version>        </dependency><dependency>            <groupId>org.aspectj</groupId>            <artifactId>aspectjweaver</artifactId>            <version>1.9.5</version></dependency>

不会maven想下jar包的,在欢迎光临Spring时代(一) 上柱国IOC列传中曾经介绍了下载jar包的办法,这里不再反复介绍了。当初Spring Framework的轻量级曾经体现进去了,依赖很少,大抵上只须要七个jar包就能帮忙咱们优雅的治理对象之间的依赖关系和AOP了。

AOP在Spring中的实现简介

Java中办法依附于类,而要应用Spring提供的AOP也就须要将须要加强的类退出到IOC容器中,而在欢迎光临Spring时代(一) 上柱国IOC列传咱们曾经介绍过了,IOC容器有两种模式,一种是基于配置文件的,一种是基于注解的。咱们首先来介绍基于配置文件的。在Spring Framework中咱们个别称倡议为告诉,在办法执行前执行,咱们称之为前置告诉,在办法执行后执行,咱们称之为后置告诉,在办法执行之前和在办法执行之后都执行咱们称之为盘绕告诉,在办法产生异样的时候执行咱们称之为异样告诉。

在AOP中咱们介绍了三个概念:

  • 切点(pointCut)形容在哪些办法上执行
  • 连接点(joinPoint) 形容了注入机会
  • 切面(Aspect) 形容的是被注入代码。

留神这三个概念,咱们在用Spring Framework提供的AOP的时候经常会碰到。

如何让一个一般类中的办法称为一个切面呢? 那必定要让Spring Framework辨识到这个办法跟别的办法不一样,Spring Framework提供了以下接口:

  • org.springframework.aop.MethodBeforeAdvice
前置告诉 在办法执行之前执行
  • org.springframework.aop.AfterReturningAdvice
后置告诉 在办法执行之后执行
  • org.aopalliance.intercept.MethodInterceptor
盘绕告诉 拦挡指标办法的调用,即调用指标办法的整个过程,即能够做到办法执行之前执行、办法执行之后执行、办法产生了异样之后执行
  • org.springframework.aop.ThrowsAdvice
异样告诉 在办法产生了异样之后执行
实现它们,并将它们纳入到IOC容器的管辖,再筹备切点,连接点。咱们就能做到AOP。

咱们下面讲切点是说要加强哪些办法,那办法在Java中应该怎么形容呢? 咱们用全类名+办法名? 这样写起来多麻烦啊!这也就是execution表达式入世的起因,简略点,再简略点

execution表达式简介

格局通常如下:

execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?)
  • modifiers-pattern?: 指定办法的修饰符(public private protected),反对通配符,该局部能够省略
  • ret-type-pattern: 办法的返回值类型,反对通配符,可应用*来拦挡所有的返回类型
  • declaring-type-pattern?: 该办法所属的类,即全类名+办法名。也能够用*拦挡所有办法
  • name-pattern: 指定要匹配的办法名,反对通配符,能够应用*通配符来匹配所有办法。
  • param-pattern: 指定办法申明中的形参列表,反对两个通配符,即 * 和..,其中*代表一个任意类型的参数,而..表零个或多个任意类型的参数。例如,() 匹配一个不承受任何参数的办法,而(..) 匹配一个承受任意数量参数的办法,( * )匹配了一个承受一个任何类型的参数的办法,(*,String)匹配了一个承受两个参数的办法,其中第一个参数是任意类型,第二个参数必须是String类型。
  • throws-pattern:指定办法申明抛出的异样,反对通配符,该局部能够省略。

基于配置文件的AOP

第一种配置文件的AOP形式

前置告诉、后置告诉、盘绕告诉

首先咱们实现MethodBeforeAdvice:

public class LogBefore implements MethodBeforeAdvice{    @Override    public void before(Method method, Object[] args, Object o) throws Throwable {        System.out.println("在办法之前执行");    }}

而后将LogBefore 退出到IOC容器中,形式有很多种,可参看欢迎光临Spring时代(一) 上柱国IOC列传,这里选取是在配置文件里配置:

<bean id = "logBefore" class="org.example.aop.LogBefore"></bean> <bean id = "landlord" class = "org.example.aop.Landlord">  </bean>

而后筹备切点,并将切点和切面分割起来,如上文所说,Spring Framework并没有给咱们提供多少连接点,咱们抉择的就是在办法执行这个机会,之前、之后,还是盘绕。

<!-- 这个配置是开启AOP用的,记得肯定要加-->  <aop:aspectj-autoproxy/><aop:config>        <aop:pointcut id = "pointCut" expression = "execution(public void org.example.aop.Landlord.rentHouse())"/>        <aop:advisor advice-ref="logBefore" pointcut-ref="pointCut"></aop:advisor></aop:config>  

所以咱们的切点就是位于org.example.aop包下的Landlord的 返回值类型为void的rentHouse办法。 <aop:advisor>用于关联切点和切面。advice-ref指向切面的id,pointcut-ref指向的是切点的id。
咱们筹备一下代码测试一下:

public interface IRentHouse {    /**     * 实现该接口的类将可能对外提供房子     */    void rentHouse();}public class Landlord implements IRentHouse {    @Override    public void rentHouse() {        System.out.println(" 我向你提供房子..... ");    }} private static void testLogAop() {        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");        Landlord landlord = applicationContext.getBean("landlord", Landlord.class);        landlord.rentHouse();    }

运行后果:
我放在配置文件中的不是org.example.aop.Landlord,为什么我当初取不进去了啊。因为你此时要加强Landlord中的rentHouse办法,凑巧你实现了接口,那我就用JDK的动静代理给你加强啊。你现再在把配置文件中的AOP配置去掉,就会发现能胜利运行了,或者 咱们当初换种接值形式,向容器中索取的是IRentHouse类型的Bean就不会出错了, 代码如下:

  private static void testLogAop() {        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");        IRentHouse landlord = applicationContext.getBean("landlord", IRentHouse.class);        landlord.rentHouse();    }

这个例子是我特意筹备的例子,让须要加强的类实现了一个接口,为的就是与代理模式-AOP绪论这篇文章造成映照,如果看过这篇文章的同学,就会晓得动静代理还有另一种模式是基于Cglib库来做的,这种加强就是在运行时创立须要加强类的子类,所以此时你将下面的Landlord不实现IRentHouse接口,取Landlord类型的就能够了。这其中就有向上转型、向下转型的知识点。能够认真领会一下。

后置告诉和盘绕告诉基本上就是照葫芦画瓢而已,跟前置告诉相似。后置告诉咱们实现AfterReturningAdvice接口,盘绕告诉咱们实现MethodInterceptor接口:

public class LogAfter implements AfterReturningAdvice {    @Override    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {        System.out.println("这是后置告诉...............");    }}public class LogAround implements MethodInterceptor {    @Override    public Object invoke(MethodInvocation invocation) throws Throwable {        System.out.println("这是盘绕告诉,在办法执行之前执行.........");        Object result = null;        try {         //  invocation 控制目标办法的执行,正文这段,则指标办法不会执行。         result = invocation.proceed();        }catch (Exception ex){            System.out.println("这是盘绕告诉,在办法执行之后执行.........,能够当做异样告诉");            // ex.printStackTrace();        }        System.out.println("这是盘绕告诉,在办法执行之后执行.........");        return result;    }}

在配置文件中配置:

 <bean id = "logAfter" class="org.example.aop.LogAfter"> </bean> <bean id = "logAround" class = "org.example.aop.LogAround"> </bean><aop:config>        <aop:pointcut id = "pointCut" expression = "execution(public void org.example.aop.Landlord.rentHouse())"/>        <aop:advisor advice-ref="logAround" pointcut-ref="pointCut"></aop:advisor>    </aop:config>    <aop:config>        <aop:pointcut id = "pointCut" expression = "execution(public void org.example.aop.Landlord.rentHouse())"/>        <aop:advisor advice-ref="logException" pointcut-ref="pointCut"></aop:advisor>    </aop:config>    <aop:config>        <aop:pointcut id = "pointCut" expression = "execution(public void org.example.aop.Landlord.rentHouse())"/>        <aop:advisor advice-ref="logBefore" pointcut-ref="pointCut"></aop:advisor>    </aop:config>    <aop:config>        <aop:pointcut id = "pointCut" expression = " execution(public void org.example.aop.Landlord.rentHouse())"/>        <aop:advisor advice-ref="logAfter" pointcut-ref="pointCut"></aop:advisor>    </aop:config>

测试代码:

private static void testLogAop() {        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");        Landlord landlord = applicationContext.getBean("landlord", Landlord.class);        landlord.rentHouse();    }

运行后果:

异样告诉

异样告诉跟其余接口的不一样之处,就在于这是一个空接口:

那么在办法在执行过程中,产生了异样,Spring该怎么回调这个办法呢? 别着急咱们先看正文:

问题解决了,咱们照正文要求的来:

public class LogException implements ThrowsAdvice {    public void afterThrowing(Method method, Object[] args, Object target, Exception ex){        System.out.println(method.getName()+"办法产生了异样");    }}

而后再度测试,还是下面的测试代码,在指标代码中请做出一个Exception的子类,我做的是除零异样:

第二种配置文件的AOP形式

下面实现的各种告诉都是基于接口的,如果你不想实现接口,Spring也能将一个一般的类变成告诉类,写到这里有想到了动静代理,想必还是基于动静代理的第二种模式来做的。咱们首先筹备一个告诉类:

@Componentpublic class LogSchema {    public void before(JoinPoint joinPoint) {        System.out.println("z办法调用执行之前执行");    }    public void invoke(ProceedingJoinPoint joinPoint) throws Throwable {        System.out.println("z盘绕告诉的前置告诉........");        Object result = null;        try {            // 管制办法的应用            result = joinPoint.proceed();            System.out.println("z打印办法的返回值..........." + result);        } catch (Exception e) {            System.out.println("z盘绕告诉的异样告诉........");        }        System.out.println("z盘绕告诉的后置告诉........");    }    public void after(JoinPoint joinPoint, Object returningValue) {        System.out.println("z办法的返回值是:" + returningValue);        System.out.println("z办法调用执行之后执行");    }    public void afterThrowing(JoinPoint joinPoint, ArithmeticException ex) {        System.out.println("z这是异样告诉的告诉");    }}

而后将这个类在配置文件变成告诉类:

<aop:config>        <aop:pointcut id="schema" expression="execution(public void org.example.aop.Landlord.rentHouse(int ))"/>        <aop:aspect ref="logSchema">            <aop:around method = "invoke" pointcut-ref="schema" arg-names="joinPoint"/>            <aop:before method = "before" pointcut-ref="schema"  arg-names="joinPoint"/>            <aop:after-returning method="after"  returning="returningValue" arg-names="joinPoint,returningValue" pointcut-ref="schema"/>            <aop:after-throwing method="afterThrowing" pointcut-ref="schema" arg-names="joinPoint,ex" throwing="ex"/>        </aop:aspect>    </aop:config>

测试代码:

基于注解的AOP

接下来咱们介绍基于注解模式的Spring AOP,对于注解其实也有几种不同的写法,次要区别在于取参数的形式不同。一种是在注解中绑定参数,一种是用JoinPoint对象获取参数。两种咱们都介绍,在应用注解的形式开发AOP之前,咱们首先要Spring对AOP的反对,如果你是配置文件请在配置文件中加上: <aop:aspectj-autoproxy/>,如果是配置类请在配置类上加上@EnableAspectJAutoProxy注解。不然Spring就认为你不想用AOP。

基于@Pointcut的写法

取指标办法参数的第一种模式

@Aspect// 该注解将该类纳入IOC容器@Component public class LogAspectAnnotation {    /**     * Pointcut注解用于定义切点,能够被其余办法援用。     * 相当于配置文件的: <aop:pointcut id = "pointCut" expression = "execution(public void    &org.example.aop.Landlord.rentHouse())"/>     * Pointcut有两个属性一个是value用于指定execution表达式,argNames用于匹配参数取参数。     * 记得语法是跟在配置文件中不同的是写完execution表达式,用&&args写参数名,多的用逗号隔开,在argNames中写对应的参数名     * 而后在切点的办法中也要写,用于绑定参数。     * @param i     * @return     */    @Pointcut(value = "execution(public void org.example.aop.Landlord.rentHouse(int)) && args(i)",argNames = "i")    public int  pointCut(int i){        return i;    }    @Before(value = "pointCut(i)",argNames = "i")    public void before(int i){        System.out.println("办法调用执行之前执行");    }}

取指标办法参数的第二种模式

@Pointcut(value = "execution(public void org.example.aop.Landlord.rentHouse(int))")    public void  pointCut(){    }    @Before(value = "pointCut()")    public void before(JoinPoint joinPoint){        for (Object arg : joinPoint.getArgs()) {            System.out.println(arg);        }        System.out.println("办法调用执行之前执行");    }

JoinPoint 能够获取指标类的所有元数据,咱们来大抵看一下JoinPoint类:

其实还能够这么写

// value 就间接相当于切点@Before(value = "execution(public void org.example.aop.Landlord.rentHouse(int))")    public void before(JoinPoint joinPoint){        // 这里是打印切点的参数        for (Object arg : joinPoint.getArgs()) {            System.out.println(arg);        }        System.out.println("办法调用执行之前执行");    }

后置告诉、盘绕告诉、异样告诉

@Aspect@Componentpublic class LogAspectAnnotation {    /**     * Pointcut注解用于定义切点,能够被其余办法援用。     * 相当于配置文件的: <aop:pointcut id = "pointCut" expression = "execution(public void org.example.aop.Landlord.rentHouse())"/>     * Pointcut有两个属性一个是value用于指定execution表达式,argNames用于匹配参数取参数。     * 记得语法是跟在配置文件中不同的是写完execution表达式,用&&args写参数名,多的用逗号隔开,在argNames中写对应的参数名     * 而后在切点的办法中也要写,用于绑定参数。     *     * @param     * @return     */    @Pointcut(value = "execution(public void org.example.aop.Landlord.rentHouse(int))")    public void pointCut() {    }    @Before(value = "pointCut()")    public void before(JoinPoint joinPoint) {        System.out.println("办法调用执行之前执行");    }    /**     * 绝对于前置告诉,AfterReturning中多了一个属性就是returning,用于获取指标办法的返回值。     * returning中的值要和后置告诉的参数名保持一致     *     * @param joinPoint     * @param returningValue     */    @AfterReturning(value = "execution(public void org.example.aop.Landlord.rentHouse(int))", returning = "returningValue")    public void after(JoinPoint joinPoint, Object returningValue) {        System.out.println("办法的返回值是:" + returningValue);        System.out.println("办法调用执行之后执行");    }    /**     * 盘绕告诉用ProceedingJoinPoint来管制办法的执行     *     * @param joinPoint     * @throws Throwable     */    @Around("execution(public void org.example.aop.Landlord.rentHouse(int))")    public void around(ProceedingJoinPoint joinPoint) throws Throwable {        System.out.println("盘绕告诉的前置告诉........");        Object result = null;        try {            // 管制办法的应用            result = joinPoint.proceed();            System.out.println("打印办法的返回值..........." + result);        } catch (Exception e) {            System.out.println("盘绕告诉的异样告诉........");        }        System.out.println("盘绕告诉的后置告诉........");    }    /**     * throwing属性会将产生异样时的对象传递给办法的参数,所以throwing的属性值和参数名要保持一致     * 产生了办法中的异样就会触发异样告诉,以后办法就是ArithmeticException时触发     *     * @param joinPoint     * @param ex     */    @AfterThrowing(value = "execution(public void org.example.aop.Landlord.rentHouse(int))", throwing = "ex")    public void afterThrowing(JoinPoint joinPoint, ArithmeticException ex) {        System.out.println("这是异样告诉的告诉");        ex.printStackTrace();    }}

还是下面的测试代码,我在Landlord的rentHouse办法中做了一个1除以0操作,咱们来看一下测试后果:

总结一下

之前我学Spring Framework的时候就是看的颜群老师在B站公布的视频,这个教程也的确不错,过后学的时候心里还存在些疑难,比方AOP的实现,IOC到底做了什么,打算当前有工夫将这些疑难全副解决等。到当初这些问题算上之前的三篇博客大多都得以解决,这也算是从新梳理了一下本人对Spring Framework的意识,因为我还是心愿本人的常识成零碎一点,不是零零碎碎的。心愿对大家学习Spring Framework有所帮忙。

参考资料

  • 如何用通俗易懂形式解释面向(切面)AOP编程 和AOP的实现形式?
  • Java进阶常识23 Spring execution 切入点表达式
  • Spring视频教程 颜群