Spring框架自学之路——AOP

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_15096707/article/details/79602676

目录

AOP

介绍

  AOP,即面向切面编程,简单来说其思想指的是在不修改原有代码的情况下扩展新的功能。在传统OOP思想下,我们扩展一个类的功能,可能采取的方式为纵向继承,即定义父类,编写新功能,通过继承的形式在子类中使用父类的方法扩展功能。这种方式下,存在着类与类之间的耦合,多个类需要扩展多个新的功能,都需要新增新的代码到原有类中。
  而AOP思想则表示采用“在程序运行期间将某段代码,动态的切入到某个类的指定方法的指定位置”的方式,即我们不需要对原有类的功能进行修改,而是在程序运行过程中,将新增的功能加入都被扩展的类型的指定方法的指定位置。

AOP底层原理

  AOP,面向切面编程,底层使用动态代理方式实现。针对实现了接口的类的横向扩展,采用的是JDK动态代理进行实现。在JDK动态代理中,为对原有类(实现了某一接口的类)进行横向扩展,内部创建了实现了同一接口的类代理对象,具有与原有类对象相同的功能,且进行了增强。对于未实现接口的类,想实现横向的扩展,则需要使用cglib动态代理实现,内部创建原有类的子类的代理对象,即在子类中调用父类的方法完成增强。
  详细的分析还有待我深入的研究(20180320)!!!

核心概念

  1. 连接点(Joinpoint):被拦截到的点。在Spring中只支持方法类型的连接点,因此这些点在Spring中指的是方法。简单来说,我们可以认为是可以被增强功能的方法。
  2. 切入点(Pointcut):对连接点进行拦截的定义。在Spring中通过表达式(后续将进行说明)形式来定义所匹配的类下的方法,以此定义切入点。
  3. 通知/增强(Advice):所谓通知指的就是指拦截到连接点之后要执行的代码,通知分为前置通知(before 在方法之前执行)、后置(after-returning 在方法之后执行)、异常(after-throwing 在方法出现异常时执行)、最终(after 在后置之后执行,无论方法是否执行成功)、环绕(around 在方法之前和之后执行)通知五类。
  4. 目标对象(Target):代理的类或是要被增强的类。
  5. 织入(Weaving):将目标增强的过程,或者是是通知应用到目标的过程。
  6. 代理(Proxy):一个类进行织入后将产生一个结果类,称为代理类。
  7. 切面(Aspect):是切入点和通知的结合。

基于AspectJ的Spring AOP操作

AOP操作准备

  (1)导入jar包,除包括Spring基础jar包外,还需要导入AOP相关的jar包,此外为了方便测试还需引入junit相关的jar包,包括如下:

  Spring基础jar包:
1. spring-beans
2. spring-context
3. spring-core
4. spring-expression
5. commons-logging-1.2.jar
6. log4j-1.2.17.jar
  Spring AOP相关的jar包:
1. aopalliance-1.0.jar
2. aspectjweaver-1.8.7.jar
3. spring-aop
4. spring-aspects
  junit相关的jar包:
1. junit-4.12.jar
2. hamcrest-core-1.3.jar

  (2)创建Spring核心配置文件,并导入AOP的约束。这里我将该核心配置文件命名为aop.xml。内容如下:

<?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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

</beans>

XML方式

案例:在User类的add方法执行前后加入日志记录功能。
(1)创建需要被增强的类,User.java,如下:

package com.wm103.aop;

/**
 * Created by DreamBoy on 2018/3/18.
 */
public class User {
    public void add() {
        System.out.println("Running User Add Method.");
    }
}

(2)创建用于增强其他类的类,这里以日志类为例,即目的是为在User对象add方法调用前后,写入日志。LogHandler.java,如下:

package com.wm103.aop;

/**
 * Created by DreamBoy on 2018/3/18.
 */
public class LogHandler {
    public void before() {
        System.out.println("Start Write Log.");
    }

    public void after() {
        System.out.println("End Write Log.");
    }
}

(3)在Spring核心配置文件(这里为aop.xml)中配置,实现类的增强。如下:

<!-- 1. 配置对象 -->
<bean id="user" class="com.wm103.aop.User"></bean>
<bean id="logHandler" class="com.wm103.aop.LogHandler"></bean>

<!-- 2. AOP配置 -->
<aop:config>
    <!-- 2.2 配置切入点 -->
    <aop:pointcut id="pointcut" expression="execution(* com.wm103.aop.User.*(..))"/>

    <!-- 2.1 配置切面 -->
    <aop:aspect ref="logHandler">
        <!-- 2.3 针对切入点,配置通知/增强 -->
        <aop:before method="before" pointcut-ref="pointcut"/>
        <aop:after method="after" pointcut-ref="pointcut"/>
    </aop:aspect>
</aop:config>

(4)创建测试类,TestAop.java,如下:

package com.wm103.aop;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * Created by DreamBoy on 2018/3/18.
 */
public class TestAop {
    @Test
    public void runUser() {
        ApplicationContext context = new ClassPathXmlApplicationContext("aop.xml");
        User user = (User) context.getBean("user");
        user.add();
    }
}

定义切点

使用表达式配置切入点:
  在上述例子中为实现AOP操作,对切入点进行定义,定义如下:

<aop:pointcut id="pointcut" expression="execution(* com.wm103.aop.User.*(..))"/>

  在expression属性中,我们通过使用execution属性对方法进行了定义,即规定哪些方法为切入点。该表达式的语法如下:

execution(<修饰符模式>?<返回类型模式><方法名模式>(<参数模式>)<异常模式>?)  除了返回类型模式、方法名模式和参数模式外,其它项都是可选的。

举例几个主要使用:
1. execution(public * *(..)):匹配所有类的public方法;
2. execution(* com.wm103.dao.*(..)):匹配指定包(这里指com.wm103.dao)下的所有类的方法,不包含子包;
3. execution(* com.wm103.dao..*(..)):匹配指定包以及其下子孙包下的所有类的方法,值得注意的是,“..”出现在类名中时,后面必须跟“*”,表示包、子孙包下的所有类
4. execution(* com.wm103.dao.UserDaoImpl.*(..)):匹配指定类下的所有方法;
5. execution(* com.wm103.dao.UserDao+.*(..)):匹配实现指定接口(这里指com.wm103.dao.UserDao接口)的所有类的方法;
6. execution(* save*(..)):匹配所有指定前缀(这里指的是save)开头的方法;
7. execution(* *.*(..)):匹配所有类的所有方法。

案例扩展

案例:在上述案例中,对User类的add方法进行补充,增加记录方法运行前的时间和运行后的时间以及花费的时间。
(1)创建用于增强其他类的类,这里以时间类为例,即目的是为在User对象add方法调用前后,写入运行时间。TimeHandler.java,如下:

package com.wm103.aop;

import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * Created by DreamBoy on 2018/3/18.
 */
public class TimeHandler {
    private long startTime = 0;
    private long endTime = 0;

    public void before() {
        startTime = new Date().getTime();
        System.out.println("Start Time: " + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(startTime) + ".");
    }

    public void after() {
        endTime = new Date().getTime();
        System.out.println("End Time: " + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(endTime) + ".");
        long costTime = endTime - startTime;
        System.out.println("Total Cost Time: " + (costTime / 1000.0) + "s.");
    }
}

(2)对上述Spring核心配置文件,进行进一步补充。如下:
配置对象

<bean id="timeHandler" class="com.wm103.aop.TimeHandler"></bean>

配置切面

<aop:aspect ref="timeHandler">
    <aop:before method="before" pointcut-ref="pointcut"/>
    <aop:after method="after" pointcut-ref="pointcut"/>
</aop:aspect>

(3)运行测试类TestAop,运行结果如下:

Start Write Log.
Start Time: 2018-03-21 00:01:35.
Running User Add Method.
End Time: 2018-03-21 00:01:35.
Total Cost Time: 0.075s.
End Write Log.

  增强的顺序默认按照xml中配置的顺序运行。如果需要将TimeHandler增强先执行的话,那么可以调整LogHandlerTimeHandler的顺序。或者在aop:aspect标签中使用order属性(按从小到大的顺序执行)。如下:

<!-- 2.1 配置切面 -->
<aop:aspect ref="logHandler" order="2">
    <!-- 2.3 针对切入点,配置通知/增强 -->
    <aop:before method="before" pointcut-ref="pointcut"/>
    <aop:after method="after" pointcut-ref="pointcut"/>
</aop:aspect>

<aop:aspect ref="timeHandler" order="1">
    <aop:before method="before" pointcut-ref="pointcut"/>
    <aop:after method="after" pointcut-ref="pointcut"/>
</aop:aspect>

  运行结果如下:

Start Time: 2018-03-21 00:08:29.
Start Write Log.
Running User Add Method.
End Write Log.
End Time: 2018-03-21 00:08:29.
Total Cost Time: 0.082s.
TestExp Run Method.

(4)环绕通知实现
  在User.java中添加del方法,如下:

public void del() {
    System.out.println("Running User Del Method.");
}

  为实现环绕通知,在LogHandler.java中添加如下方法:

public void around(ProceedingJoinPoint joinPoint) {
    try {
        before();
        joinPoint.proceed();
        after();
    } catch (Throwable throwable) {
        throwable.printStackTrace();
    }
}

  其中joinPoint.proceed();表示调用被增强类的方法。
  在Spring核心配置文件中,单独为User类的del方法定义新的切入点,如下:

<aop:pointcut id="pointcut3" expression="execution(* com.wm103.aop.User.del(..))"/>

  配置切面,针对切入点实现环绕通知,如下:

<aop:aspect ref="logHandler">
    <aop:around method="around" pointcut-ref="pointcut3"/>
</aop:aspect>

  以上操作即简单实现了环绕通知。我们可以在被增强的类的方法前后执行所需功能的程序代码,以达到增强类方法的目的。测试结果如下:

Start Write Log.
Running User Del Method.
End Write Log.

注解方式

案例:使用注解方式实现前置通知。如下:
(1)创建User.java,内容如下:

package com.wm103.aopanno;

/**
 * Created by DreamBoy on 2018/3/18.
 */
public class User {
    public void add() {
        System.out.println("Running User Add Method.");
    }
}

(2)创建LogHandler.java,这里以日志为例,如下:

package com.wm103.aopanno;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

/**
 * Created by DreamBoy on 2018/3/18.
 */
public class LogHandler {
    public void before() {
        System.out.println("Start Write Log.");
    }

    public void after() {
        System.out.println("End Write Log.");
    }
}

(3)定义Spring核心配置文件,这里命名为 aopanno.xml,并开启AOP注解扫描,配置对象,内容如下:

<?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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- 1. 开启AOP注解扫描 -->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>

    <!-- 2. 配置对象 -->
    <bean id="user" class="com.wm103.aopanno.User"></bean>
    <bean id="logHandler" class="com.wm103.aopanno.LogHandler"></bean>
</beans>

(4)开启AOP注解扫描后,我们还需要在增强类上使用注解@Aspect,在增强方法上使用注解定义切入点,以及设置通知类型。修改LogHandler.java如下:

package com.wm103.aopanno;

import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

/**
 * Created by DreamBoy on 2018/3/18.
 */
@Aspect
public class LogHandler {
    @Before(value="execution(* com.wm103.aopanno.User.*(..))")
    public void before() {
        System.out.println("Start Write Log.");
    }

    @After(value="execution(* com.wm103.aopanno.User.*(..))")
    public void after() {
        System.out.println("End Write Log.");
    }
}

(5)创建测试类TestAop.java进行测试,内容如下:

package com.wm103.aopanno;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * Created by DreamBoy on 2018/3/18.
 */
public class TestAop {
    @Test
    public void runUser() {
        ApplicationContext context = new ClassPathXmlApplicationContext("aopanno.xml");
        User user = (User) context.getBean("user");
        user.add();
    }
}

(6)总结
  本案例采用注解的方式简单的实现了类的增强。除了前置通知和最终通知,@AspectJ同样也提供了对应的其他通知类型的注解形式,如下:

@Before 前置通知
@AfterReturning 后置通知
@Around 环绕通知
@AfterThrowing 异常通知
@After 最终通知(不管是否异常,该通知都会被执行)
阅读更多
相关热词
换一批

没有更多推荐了,返回首页