Spring框架自学之路——AOP

2023-05-16

目录

  • 目录
  • AOP
    • 介绍
    • AOP底层原理
    • 核心概念
  • 基于AspectJ的Spring AOP操作
    • AOP操作准备
    • XML方式
    • 定义切点
    • 案例扩展
    • 注解方式

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 最终通知(不管是否异常,该通知都会被执行)
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Spring框架自学之路——AOP 的相关文章

随机推荐

  • Linux简介及系统安装

    目录 目录Linux简介 Linux的应用Linux与Windows的不同字符界面的优势 Linux系统安装 VMware简介虚拟机的新建与设置注意点Linux的系统分区安装Linux系统Linux系统的安装日志 Linux简介 Linux
  • Linux常用命令

    目录 目录命令基本格式文件处理命令 目录或文件处理Linux中常见目录链接命令 搜索命令 文件搜索命令命令搜索命令字符串搜索命令find命令与grep命令的区别 帮助命令压缩与解压缩命令关机和重启命令其他常用命令 挂载命令用户登录查看和用户
  • 对php-fpm的理解重述

    目录 目录CGI的出现FastCGIphp fpm摘录 FPM的请求处理流程nginx转发请求给FPM 参考 CGI的出现 早期的Web服务器只能处理HTML等静态文件 xff0c 随着PHP等动态语言的出现 xff0c Web Serve
  • Shell编程——基础入门

    目录 目录Shell概述 Shell是什么Shell的分类 脚本执行方式 echo输出命令第一个脚本 Bash的基本功能 命令别名与快捷键历史命令 history命令历史命令的调用命令与文件补全 输入输出重定向 输出重定向输入重定向 多命令
  • Linux命令之<cp命令>

    cp命令用来复制文件或者目录 xff0c 是Linux系统中最常用的命令之一 一般情况下 xff0c shell会设置一个别名 xff0c 在命令行下复制文件时 xff0c 如果目标文件已经存在 xff0c 就会询问是否覆盖 xff0c 不
  • PHPer的进阶探索之旅

    目录 目录前言魔术常量PHP常量详解define和const的区别PHP中的闭包PSR 规范PHP命令空间Web性能优化与HTTP2 前言 作为一个PHPer xff0c 一直认为Java与PHP都 差不多 xff0c 尽管身边的人会说大型
  • Linux下的VIM编辑器

    目录 目录VIM编辑器的概述VIM编辑器的操作模式vim命令的使用操作模式的命令 底行模式的常用指令命令模式的常用指令VIM键位指令图 VIM编辑器的概述 VIM其实VI编辑器的升级版本 VIM相对于VI xff0c 增强的功能 xff1a
  • Linux中的磁盘管理

    目录 目录磁盘管理命令向服务器添加硬盘磁盘分区 分区模式之MBR分区分区模式之GPT分区 分区操作 Linux中的MBR分区Linux中的GPT分区 分区格式化操作挂载分区Linux中的swap分区 磁盘管理命令 xff08 1 xff09
  • Linux中的用户管理

    目录 目录概述关于用户组的配置文件关于用户的配置文件用户和用户组的基本命令 用户组操作用户操作 用户和用户组的进阶命令 主要组与附属组 其他命令 概述 使用操作系统的人 xff0c 我们称之为用户 在Linux中允许多个不同用户同时登录同一
  • Linux中的计划任务Crontab

    目录 目录介绍安装并检查Crontab服务入门栗子Crontab的基本组成 用户任务调度 crontab命令的使用及相关文件Crontab的任务配置格式 系统任务调度系统级任务调度与用户级任务调度 注意事项参考 介绍 Crontab是什么
  • MySQL易忘常用的知识点

    目录 目录前言WHEREDISTINCTGROUP BYSELECT语句完整的执行顺序NOT NULLNULL和default默认值MySQL中文字节问题关于MYSQL Incorrect string value参考 前言 这里介绍MyS
  • MySQL深度掌握之路

    目录 目录前言知识点 前言 知识点 MySQL要求AUTO INCREMENT列必须有某种索引 xff0c 如果没有索引 xff0c 那么表的定义就是不合法的 任何一个PRIMARY KEY列必须是NOT NULL的 如果定义没有设置为NO
  • Linux中Web环境的搭建LNMP

    目录 目录环境搭建准备 为Linux创建后续使用的用户XShell的上传下载上传安装包 Web服务器的搭建 目录的创建php fpm编译安装 安装GCC安装libxml2和libxml2 devel包安装BZip2包安装libcurl包安装
  • 微信第三方小程序授权开发之旅

    目录 目录开发准备开发流程注意事项 开发准备 授权方AppId第三方AppId 开发流程 注意事项 开发过程中 xff0c 处于测试阶段 xff08 未全网发布 xff09 xff0c 需要在微信第三方平台中设置授权测试公众号列表 xff0
  • Linux中的网络管理——计算机网络基础

    目录 目录OSI七层网络模型TCPIP协议四层模型OSI模型与TCPIP模型的比较IP地址 IP地址分类子网掩码 端口DNS DNS服务域名DNS查询 DNS查询过程DNS查询类型 网关 OSI七层网络模型 ISO OSI七层模型 需要说明
  • 在MySQL中使用MD5加密【入门体验】

    什么是MD5 xff1f MD5信息摘要算法 英语 MD5 Message Digest Agorithm xff0c 一种被广泛使用的密码散列函数 xff0c 可以产生出一个128位 16字节 的散列值 hash value xff0c
  • Linux中Web环境memcached和redis安装(FOR PHP)

    目录 目录前言php memcached扩展安装 服务器端安装客户端安装设置memcached开机自启脚本 创建脚本设置服务服务命令 php redis扩展安装 redis安装服务命令开放redis端口redis手动安装 修改redis配置
  • Linux中Web环境安装php的扩展

    目录 文章目录 目录前言安装imagick扩展安装ImageMagick安装php扩展imagick 安装PDO扩展安装PDO组件安装PDO MYSQL组件 前言 继上文 Linux中Web环境memcached和redis安装 xff08
  • Maven项目构建工具的入门使用

    目录 目录前言Maven的介绍及环境搭建创建Maven项目Maven的常用命令自动创建目录骨架Maven中的坐标和仓库 坐标仓库 关于仓库的介绍更改本地仓库的位置 Maven的生命周期和插件 生命周期插件 pom xml常见元素介绍Mave
  • Spring框架自学之路——AOP

    目录 目录AOP 介绍AOP底层原理核心概念 基于AspectJ的Spring AOP操作 AOP操作准备XML方式定义切点案例扩展注解方式 AOP 介绍 AOP xff0c 即面向切面编程 xff0c 简单来说其思想指的是在不修改原有代码