Java webSpring

【Spring】09 - Spring 中的 AOP

2020-10-18  本文已影响0人  itlu

1. AOP 概念

  1. AOP: 全称是 Aspect Oriented Programming 即: 面向切面编程。

  2. 面向切面的程序设计(Aspect-oriented programming,AOP,又译作面向方面的程序设计剖面导向程序设计)是计算机科学中的一种程序设计思想,旨在将横切关注点与业务主体进行进一步分离,以提高程序代码的模块化程度。通过在现有代码基础上增加额外的通知(Advice)机制,能够对被声明为“切点(Pointcut)”的代码块进行统一管理与装饰,如“对所有方法名以‘set*’开头的方法添加后台日志”。该思想使得开发人员能够将与代码核心业务逻辑关系不那么密切的功能(如日志功能)添加至程序中,同时又不降低业务代码的可读性。面向切面的程序设计思想也是面向切面软件开发的基础。

  3. 面向切面的程序设计将代码逻辑切分为不同的模块(即关注点(Concern),一段特定的逻辑功能)。几乎所有的编程思想都涉及代码功能的分类,将各个关注点封装成独立的抽象模块(如函数、过程、模块、类以及方法等),后者又可供进一步实现、封装和重写。部分关注点“横切”程序代码中的数个模块,即在多个模块中都有出现,它们即被称作“横切关注点(Cross-cutting concerns, Horizontal concerns)”。

  4. 日志功能即是横切关注点的一个典型案例,因为日志功能往往横跨系统中的每个业务模块,即“横切”所有有日志需求的类及方法体。而对于一个信用卡应用程序来说,存款、取款、帐单管理是它的核心关注点,日志和持久化将成为横切整个对象结构的横切关注点。

  5. 总结 : 简单的说它就是把我们程序重复的代码抽取出来,在需要执行的时候,使用动态代理的技术,在不修改源码的基础上,对我们的已有方法进行增强。

2. AOP 的作用及优势

2.1 作用:

  1. 在程序运行期间,不修改源码对已有方法进行增强。

2.2 优势:

  1. 减少重复代码;
  2. 提高开发效率;
  3. 维护方便;

3. AOP 的实现方式

  1. 使用动态代理技术。

4. Spring 中的 AOP

4.1Spring 中的术语

  1. Joinpoint(连接点) : 业务层接口中所有的方法 。所谓连接点是指那些被拦截到的点。在 spring 中,这些点指的是方法,因为 spring 只支持方法类型的连接点。

  2. Pointcut(切入点) : 被代理对象增强的方法。所谓切入点是指我们要对哪些 Joinpoint 进行拦截的定义。

  3. Advice (通知/增强):所谓通知是指拦截到 Joinpoint 之后所要做的事情就是通知。通知的类型: 前置通知,后置通知,异常通知,最终通知,环绕通知。

    `Advice` (通知/增强)
  4. Introduction (引介): 引介是一种特殊的通知在不修改类代码的前提下, Introduction 可以在运行期为类动态地添加一些方法或 Field。

  5. Target (目标对象): 代理的目标对象。

  6. Weaving (织入): 是指把增强应用到目标对象来创建新的代理对象的过程。spring 采用动态代理织入,而 AspectJ 采用编译期织入和类装载期织入。

  1. Proxy(代理) : 一个类被 AOP 织入增强后,就产生一个结果代理类。

  2. Aspect (切面): 是切入点和通知(引介)的结合。

术语简单图示

4.2 学习 spring 中的 AOP 要明确的事

开发阶段(我们做的)

  1. 编写核心业务代码(开发主线):大部分程序员来做,要求熟悉业务需求。

  2. 把公用代码抽取出来,制作成通知。(开发阶段最后再做):AOP 编程人员来做。

  3. 在配置文件中,声明切入点与通知间的关系,即切面。: AOP 编程人员来做。

运行阶段(Spring框架完成的)

  1. Spring 框架监控切入点方法的执行。一旦监控到切入点方法被运行,使用代理机制,动态创建目标对象的代理对象,根据通知类别,在代理对象的对应位置,将通知对应的功能织入,完成完整的代码逻辑运行。

4.3 基于XMLAOP入门配置

  1. 创建一个新的项目,使用Maven但是不需要选择骨架。
  1. 导入依赖 :
    <packaging>jar</packaging>

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.0.5.RELEASE</version>
        </dependency>

     <!-- 检查切入点表达式的jar -->
        <!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.8.7</version>
        </dependency>

    </dependencies>
  1. 创建 AccountService 接口 及实现类
    public interface AccountService {

    /**
     * 保存账户信息
     */
    void saveAccount();

    /**
     * 更新账户信息
     *
     * @param i
     */
    void updateAccount(int i);

    /**
     * 删除账户信息
     *
     * @return
     */
    int deleteAccount();
}
  public class AccountServiceImpl implements AccountService {


    public void saveAccount() {
        System.out.println("saveAccount 方法执行了...");
    }

    public void updateAccount(int i) {
        System.out.println("updateAccount 更新方法执行了...");
    }

    public int deleteAccount() {
        return 1;
    }
}
  1. 创建Logger 类 :
public class Logger {

    public void printLog() {
        System.out.println(" 记录日志 ...  ");
    }
}
  1. 创建 bean.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">
</beans>

4.4 SringAOP 配置步骤 :

  1. 将通知Bean交给spring来管理。

  2. 使用aop:config标签开始AOP配置。

  3. 使用aop:aspect标签表明配置切面。

  id 属性 :为切面提供一个唯一标识。
  ref属性:是置顶通知类bean的id。
  1. aop:aspect标签的内部使用对应标签来配置通知的类型。现在的示例是让printLog方法在切入点方法执行之前,所以是前置通知。
  aop:before:表示配置前置通知
  method:用于指定Logger类中那个方法是前置通知
  pointcut属性:用于指定切入点表达式,该表达式的含义是指对业务层中哪些方法进行增强。关键字:execution(表达式);
切入点表达式的写法:访问权限修饰符 返回值 包名...类名.方法名(参数列表);
切入点表达式示例:public void com.lyp.service.impl.AccountServiceImpl.saveAccount();
  1. 配置代码 :
<?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">

      <bean id="accountService" class="com.lyp.service.impl.AccountServiceImpl"/>

        <bean id="logger" class="com.lyp.logger.Logger"/>

        <aop:config>
           <aop:aspect id="loggerAspect" ref="logger">
                <aop:before method="printLog" pointcut="execution(public void com.lyp.service.impl.AccountServiceImpl.saveAccount())"/>
           </aop:aspect>
        </aop:config>
</beans>
  1. 测试 :
public class SpringAOPTest {

    public static void main(String[] args) {
        ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        AccountService as = ac.getBean("accountService", AccountService.class);
        as.saveAccount();
    }
}

4.5 切入点表达式

  1. 全通配写法 :* * ..*.*(..)

  2. 返回值可以使用通配符 *表示任意返回值。

  3. 包名可以使用通配符表示任意包,但是有几级包,就需要写几个 * 中间使用 . 分隔。

  4. 包名还可以使用 .. 表示当前包及其子包;

  5. 类名和方法名都可以使用 * 来实现通配;

  6. 参数列表 : 可以直接写数据类型 。基本数据类型直接写名称 。引用数据类型需要写 包名.类名;

  7. 参数列表:可以使用 * 表示任意类型。但是必须有参数。

  8. 参数列表 : 可以使用 .. 表示有无参数都可。

  9. 实际开发中切入点表达式的通常写法: 切到业务层实现类下的所有方法 * com.lyp. service.impl.*.*(..)

  10. 注意: 切入点表达式需要 aspectjweaver依赖的支持。

4.6 demo地址

  1. Spring 基于xmlAOP 入门配置 demo地址 : Spring 基于xmlAOP 入门配置 demo地址

5. 四种常用通知的类型

  1. 前置通知:在切入点方法执行之前执行。

  2. 后置通知:在切入点方法正常执行之后执行。

  3. 异常通知:在切入点方法执行产生异常的时候执行。

  4. 最终通知:无论切入点方法是否正常执行他都在最后执行。

5.1 创建一个新的项目

  1. 需上节demo: Spring 基于xmlAOP 入门配置 demo地址

  2. 创建一个 Maven 项目,不使用骨架的方式。

  3. 创建业务层接口和实现类 。

  4. 修改 Logger.java 文件 创建新的方法。

public class Logger {

    public void printLog() {
        System.out.println(" 记录日志 ...  ");
    }

    public void beforePrintLog() {
        System.out.println(" 前置通知 ");
    }

    public void afterReturningPrintLog() {
        System.out.println(" 后置通知 ");
    }

    public void afterThrowingPrintLog() {
        System.out.println(" 异常通知 ");
    }

    public void afterPrintLog() {
        System.out.println(" 最终通知 ");
    }
}
  1. 在bean.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">

      <bean id="accountService" class="com.lyp.service.impl.AccountServiceImpl"/>

        <bean id="logger" class="com.lyp.logger.Logger"/>

        <aop:config>
           <aop:aspect id="loggerAspect" ref="logger">
                <!--<aop:before method="printLog" pointcut="execution(public void com.lyp.service.impl.AccountServiceImpl.saveAccount())"/>-->
                <aop:before method="beforePrintLog" pointcut="execution(* com.lyp.service.impl.*.*(..))"/>
                <aop:after-returning method="afterReturningPrintLog" pointcut="execution(* com.lyp.service.impl.*.*(..))"/>
                <aop:after-throwing method="afterThrowingPrintLog" pointcut="execution(* com.lyp.service.impl.*.*(..))"/>
                <aop:after method="afterPrintLog" pointcut="execution(* com.lyp.service.impl.*.*(..))"/>

           </aop:aspect>
        </aop:config>
</beans>
在bean.xml中配置四种不同的通知类型

5.2 通用化切入点表达式

  1. 在上面的例子中,可以发现切入点在每个标签中都配置类一样的。如何配置通用的切入点表达式?

  2. 使用aop:pointcut标签配置通用切入点表达式:此标签卸载aop:aspect标签内部职能当前切面使用。他还可以卸载aop:aspect标签外边,此时所有切面可以使用。但是写在外边需要写在切面标签的上面。

    id 属性 用于指定切入点表达式的唯一标识。
    expression属性用于指定表达式的内容。
  1. 在aop:*** 四种通知类型的标签中,需要使用 pointcut-ref 属性对通用的切入点表达式进行引用。

  2. 在bean.xml中配置通用的切入点表达式:

配置通用的切入点表达式

6. Spring 中的 环绕通知

  1. 在bean.xml文件中配置环绕通知 :
  <aop:config>
    <aop:pointcut id="pc" expression="execution(* com.lyp.service.impl.*.*(..))"/>
    <aop:aspect id="loggerAspect" ref="logger">
        <aop:around method="aroundPrintLog" pointcut-ref="pc"/>
    </aop:aspect>
   </aop:config>
  1. 在Logger类中添加 环绕通知需要执行的方法 :
public void aroundPrintLog() {
        System.out.println(" 环绕通知  aroundPrintLog 方法执行了...  ");
}
  1. 此时测试出现问题 : 当我们配置了环绕通知之后,切入点方法还没有执行,而通知方法执行了。

  2. 分析:通过对比动态代理中的环绕通知代码。发现动态代理的环绕通知有明确的切入点方法调用,而我们代码中没有。

  3. 解决 : Spring框架为我们提供了一个接口。ProceedingJoinPoint ,该接口有一个proceed方法。此方法相当于明确调用切入点方法。该接口作为环绕通知的方法参数,在程序执行时,spring框架会为我们提供该接口的实现类供我们使用。

  4. Spring 环绕通知: 它是spring框架为我们提供的一种可以在代码中手动控制增强方法何时执行的方式。

  5. 修改Logger 中的aroundPrintLog() 方法如下 :

       /**
     * 环绕通知 
     *  高版本的 aspectjweaver 没有 ProceedingJoinPoint 对象了
     * @param pjp
     * @return
     */
    public Object aroundPrintLog(ProceedingJoinPoint pjp) {
        Object res = null;
        try {
            Object[] args = pjp.getArgs();
            System.out.println(" 环绕通知  aroundPrintLog 方法执行了...  前置通知");
            res = pjp.proceed(args);
            System.out.println(" 环绕通知  aroundPrintLog 方法执行了...  后置通知");
            return res;
        } catch (Throwable t) {
            System.out.println(" 环绕通知  aroundPrintLog 方法执行了...  异常通知");
            throw new RuntimeException(t);
        } finally {
            System.out.println(" 环绕通知  aroundPrintLog 方法执行了...  最终通知");
        }
    }
demo地址: spring基于xml配置四种通知类型

8. Spring 基于注解的AOP配置

8.1 新建一个新的工程

  1. 新建一个Maven 工程不使用骨架的方式。

  2. 导入依赖:续上一个demo spring基于xml配置四种通知类型

  3. 在上一个 demo 的基础上使用 @service 注解将业务层组件交给Spring容器管理。

@Service("accountService")
public class AccountServiceImpl implements AccountService {
}
  1. Logger类 使用 @Component 注解将其交给Spring容器管理。
@Component
public class Logger {
}
  1. 修改bean.xml配置文件内容,增加context名称空间、配置spring创建容器时需要扫描的包、配置spring开启注解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:context="http://www.springframework.org/schema/context"
       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
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

    <!-- 配置spring容器创建容器时需要扫描的包 -->
    <context:component-scan base-package="com.lyp"/>

    <!-- 配置spring开启注解AOP的支持 -->
    <aop:aspectj-autoproxy/>
</beans>
  1. 使用 @Aspect注解标识Logger为一个切面类:
@Component
@Aspect
public class Logger {
}
  1. 在切面Logger类中编写切入点表达式:
   @Pointcut("execution(* com.lyp.service..impl.*.*(..))")
    public void pc() {
    }
  1. 在切面类Logger中使用注解配置四种通知类型 :
  @Before("pc()")
    public void beforePrintLog() {
        System.out.println(" 前置通知 ");
    }

    @AfterReturning("pc()")
    public void afterReturningPrintLog() {
        System.out.println(" 后置通知 ");
    }

    @AfterThrowing("pc()")
    public void afterThrowingPrintLog() {
        System.out.println(" 异常通知 ");
    }

    @After("pc()")
    public void afterPrintLog() {
        System.out.println(" 最终通知 ");
    }
  1. 使用注解 @Around("切入点表达式") 配置环绕通知:
    /**
     * 环绕通知
     * 高版本的 aspectjweaver 没有 ProceedingJoinPoint 对象了
     *
     * @param pjp
     * @return
     */
    @Around("pc()")
    public Object aroundPrintLog(ProceedingJoinPoint pjp) {
        Object res = null;
        try {
            Object[] args = pjp.getArgs();
            System.out.println(" 环绕通知  aroundPrintLog 方法执行了...  前置通知");
            res = pjp.proceed(args);
            System.out.println(" 环绕通知  aroundPrintLog 方法执行了...  后置通知");
            return res;
        } catch (Throwable t) {
            System.out.println(" 环绕通知  aroundPrintLog 方法执行了...  异常通知");
            throw new RuntimeException(t);
        } finally {
            System.out.println(" 环绕通知  aroundPrintLog 方法执行了...  最终通知");
        }
    }

8.2 Demo地址

  1. spring基于注解的AOP配置,通知的四种类型。spring基于注解的AOP配置,通知的四种类型
上一篇下一篇

猜你喜欢

热点阅读