Sping AOP初级——入门及简单应用
在上一篇《关于日志打印的几点建议以及非最佳实践》的末尾提到了日志打印更为高级的一种方式——利用Spring AOP。在打印日志时,通常都会在业务逻辑代码中插入日志打印的语句,这实际上是和业务无关的代码,这就带来了较强的侵入性编码。较为理想的编码方式,日志和业务代码应该是分离的。
利用Spring AOP就能很好的实现这种业务分离。AOP并不是Spring所特有的,它的全称是Aspect-Oriented Programming(面向切面编程),切面是一种新的模块化机制,用来描述分散在对象、类或函数中的横切关注点(维基百科)。看不懂定义没关系,要知道它和某种特定的语言无关,和OOP(面向对象编程)类似。但它和OOP并不是相互替代的关系,AOP并不是比OOP更高级,它不是用来替代OOP的,反之它是在某些特定领域用于辅佐OOP的。所以要清楚并不是所有的场景都适用AOP,它有它的适用场景。
那么首先需要知道的是什么是它可以使用的场景。

假设上面的长方形是一个完整的业务,可是我们要在其中添加一行日志代码,这行日志代码就“破坏”了这个完整的业务,就好像是在这个业务中间切了一刀。当然不止是日志能“破坏”、“切断”这个业务,还有事务、权限控制等,都能像一把刀一样切掉这个完整的业务模型,带来碍眼的侵入式编程。日志称之为横切关注点,日志的这个类集中在代码的一个地方叫做切面,之所以强调集中在代码的一个地方,是因为像以前侵入式编程日志这种横切关注点是散落在系统的各个地方。故,侵入式编程中也有横切关注点概念,横切关注点表示散落在程序各个地方的功能;但,切面只有在AOP中才有,那是横切关注点不再侵入式的散落在程序各个地方而是集中起来被模块化。 接着我们一一理解AOP中的术语。
通知(Advice)
在上文我们将横切关注点集中起来管理,它不再散落在程序的各个地方,而是被模块化,称之为切面。那么定义横切关注点在何时工作(这并不完全准确,不仅是何时工作,也包括具体的工作是什么),在某个的调用前还是调用后还是抛出异常时?定义在何时工作以及工作内容称之为通知,Spring中的切面一共提供5种通知的类型:
前置通知(Before)
后置通知(After)
返回通知(After-Running)
异常通知(After-throwing)
环绕通知(Around)
前面4个较为容易理解,例如“前置通知”,我们通常在一个方法的第一句打印出传入的方法参数,此时就可以使用前置通知在方法调用前打印出传入的参数。对于“后置通知”实际是“返回通知”和“异常通知”的并集,返回通知表示程序正确运行返回后执行,异常通知表示程序不正常运行抛出异常时执行,而后置通知则不论程序是否正确运行,一旦离开方法就会执行。
环绕通知最为强大,它包裹了被通知的方法,可同时定义前置通知和后置通知。
切点(Pointcut)
通知定义了何时工作以及工作内容,切点则定义了在何处工作,也就是在哪个方法应用通知。要表达出在哪个方法中运用通知,这需要用到切点表达式。Spring AOP借助AspectJ(另一种AOP实现)的切点表达式来确定通知被应用的位置,虽然是借助但并不支持所有AspectJ的所有切点指示器而仅仅是其一个子集,这其中最为常用的就是execution切点指示器,表示执行。例如:
execution(* com.deo.springaop.Test.test(..))
更多的切点指示器使用时查阅即可。
上面介绍了AOP中最为基本的两个术语,通知和切点。简单总结下,横切关注点集中在了一个地方被模块化称之为切面,通知和切点构成了切面的所有内容——它是什么,在何时和何处完成其功能。
在对AOP作了简要介绍后,接下来简单使用一下Spring AOP。例子源于慕课网的一节课程,对其稍作修改,课程地址http://www.imooc.com/video/15699,如有侵权,联系删除。例子的完整代码放置在https://github.com/yu-linfeng/BlogRepositories/tree/master/repositories/Spring%20AOP%E5%88%9D%E7%BA%A7%E2%80%94%E2%80%94%E7%AE%80%E5%8D%95%E4%BD%BF%E7%94%A8。
我们模拟用户删除的一个删除操作,此操作需要由管理员“admin”才能删除,其余用于不可删除。这是一个权限访问的问题,在不适用AOP的情况下,通常会在删除方法前用户作权限的校验,例如:
public void delete(long id) {
authService.checkAccess();
//TODO:do something.
}
显然,第2行代码是与删除逻辑不相干的代码,也就是说这是典型的侵入式编程。在学习AOP后我们可以通过面向切面编程,将这种散落在程序中的代码剥离出来,使之不与业务逻辑相耦合。
首先创建一个Maven工程,其pom.xml配置的依赖如下所示:
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-test -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>4.3.7.RELEASE</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.7.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-aop -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>4.3.7.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjrt -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.8.10</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.10</version>
</dependency>
</dependencies>
因为Spring AOP使用了AspectJ相关的东西,所以需要引入AspectJ包。接着创建如下图所示的包结构:

配置applicationContext.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: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/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-->
<context:component-scan base-package="com.springdemo.aop"/>
<!--启用AspectJ自动代理,其中proxy-target-class为true表示使用CGLib的代理方式,false表示JDK的代理方式,默认false-->
<aop:aspectj-autoproxy />
</beans>
其实使用xml的配置方式是比较繁琐的,我们可以使用JavaConfig的配置方式,当然也可以使用基于Spring Boot的无配置方式。 接下来先写业务相关的类,也就是ProductService,删除产品的服务类,我们只写业务相关的逻辑,不再加入权限校验。
package com.springdemo.aop.service; import org.springframework.stereotype.Service; @Service
public class ProductService { public void delete(long id) {
System.out.println("delete product");
}
}
当然不再加入权限校验的代码不代表不需要权限的校验,实际上权限校验的逻辑如下:
package com.springdemo.aop.service; import com.springdemo.aop.security.CheckUserHolder;
import org.springframework.stereotype.Component; @Component
public class AuthService {
public void checkAccess() {
String user = CheckUserHolder.get();
if (!"admin".equals(user)) {
throw new RuntimeException("权限不够");
}
}
}
CheckUserHolder类是我们模拟的上下文,用于获取用户名。
package com.springdemo.aop.security;
public class CheckUserHolder {
public static final ThreadLocal<String> holder = new ThreadLocal<String>();
public static String get() {
return holder.get() == null ? "unknow" : holder.get();
}
public static void set(String user) {
holder.set(user);
}
}
最后就是重点,定义一个切面。上面说到,切面由通知和切点组成,现在在权限校验的此例中,我们需要在删除前就判断用户是否有权限,也就是“前置通知”——Before,而我们需要匹配ProductService类中的delete方法,所以切点也就是在delete方法。
package com.springdemo.aop.security; import com.springdemo.aop.service.AuthService;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; @Aspect
@Component
public class SecurityAspect { @Autowired
private AuthService authService; @Pointcut("execution(* com.springdemo.aop.service.ProductService.delete(..))")
public void adminOnly(){} @Before("adminOnly()")
public void check() {
authService.checkAccess();
}
}
@Pointcut是切点的意思,定义了一个切点,我们在后面的通知就能像Before那样使用。更为高级的用法是将切点表达式定义为一个注解,这样我们就能在我们需要通知的方法前加入注解就可以了,这里对这种较为高级的方式不做介绍。 这样我们就完成的代码的边写,接下来是单元测试:
package com.springdemo.aop; import com.springdemo.aop.security.CheckUserHolder;
import com.springdemo.aop.service.ProductService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner; @RunWith(SpringRunner.class)
@ContextConfiguration(locations = {"classpath*:applicationContext.xml"})
public class AopTest { @Autowired
private ProductService productService; /**
* 匿名权限访问校验
*/
@Test(expected = Exception.class) //正确结果应该抛出异常
public void annoDeleteTest() {
CheckUserHolder.set("kevin");
productService.delete(1L);
} /**
* 管理员权限校验
*/
@Test
public void adminDelete() {
CheckUserHolder.set("admin");
productService.delete(1L);
}
}

通过。
本篇Spring AOP初级入门就介绍到这里,下一篇我们将更为完整和真实的模拟常见的Spring AOP使用场景。
这是一个能给程序员加buff的公众号

Sping AOP初级——入门及简单应用的更多相关文章
- Spring AOP初级——入门及简单应用
在上一篇<关于日志打印的几点建议以及非最佳实践>的末尾提到了日志打印更为高级的一种方式——利用Spring AOP.在打印日志时,通常都会在业务逻辑代码中插入日志打印的语句,这实际上是 ...
- Vuex初级入门及简单案例
1.为什么要使用Vuex? (1)方便所有组件共享信息,方便不同组件共享信息. (2)某个组件需要修改状态和需求. 2.状态有哪些? (1)组件内部定义的data状态(通过组件内部修改) (2)组 ...
- [Spring框架]Spring AOP基础入门总结一.
前言:前面已经有两篇文章讲了Spring IOC/DI 以及 使用xml和注解两种方法开发的案例, 下面就来梳理一下Spring的另一核心AOP. 一, 什么是AOP 在软件业,AOP为Aspect ...
- 响应式Web初级入门
本文来自我的前端博客,原文地址:http://www.hacke2.cn/about-responsive/ 跨终端时代的到来 当你乘坐各种交通工具(公交.地铁.轻轨.火车)时你会发现,人们都个个低下 ...
- Linux初级入门(第一次作业)
Linux初级入门 在本科期间学过一些Linux的简单命令,再次接触Linux不仅巩固了知识还学习到了很多新的东西. 什么是操作系统? 操作系统,英文名称Operating System,简称OS,是 ...
- Spring Cloud实战之初级入门(四)— 利用Hystrix实现服务熔断与服务监控
目录 1.环境介绍 2.服务监控 2.1 加入依赖 2.2 修改配置文件 2.3 修改启动文件 2.4 监控服务 2.5 小结 3. 利用hystrix实现消费服务熔断 3.1 加入服务熔断 3.2 ...
- SpringCloud实战之初级入门(二)— 服务注册与服务调用
目录 1.环境介绍 2.服务提供 2.1 创建工程 2.2 修改配置文件 2.3 修改启动文件 2.5 亲测注意事项 3.服务调用 3.1 创建工程 3.2 修改配置文件 3.3 修改启动文件 3.4 ...
- 基于注解的Sping AOP详解
一.创建基础业务 package com.kang.sping.aop.service; import org.springframework.stereotype.Service; //使用注解@S ...
- mui初级入门教程(六)— 模板页面实现原理及多端适配指南
文章来源:小青年原创发布时间:2016-07-26关键词:mui,webview,template,os,多端适配转载需标注本文原始地址: http://zhaomenghuan.github.io. ...
随机推荐
- 201521123063 《Java程序设计》第三周学习总结
1.本周学习总结 初学面向对象,会学习到很多碎片化的概念与知识.尝试学会使用思维导图将这些碎片化的概念.知识组织起来.请使用纸笔或者下面的工具画出本周学习到的知识点.截图或者拍照上传. 2.书面作业 ...
- 201521123044 《Java程序设计》第9周学习总结
1. 本章学习总结 2. 书面作业 本次PTA作业题集异常 1.常用异常题目5-1 1.1 截图你的提交结果(出现学号) 1.2 自己以前编写的代码中经常出现什么异常.需要捕获吗(为什么)?应如何避免 ...
- php数据库操作小要点
保留小数点后两位 $ba = floor(($v[2]/$sum[0][0])*10000); //取整数 $bb = $ba/100; //两位小数 列的值加一可以直接用自身,不用单独查询出来 $s ...
- Java:静态内部类的使用目的、使用限制、与非静态内部类的对比
Java之静态内部类(static class) 在一个类中创建另外一个类,叫做成员内部类.这个成员内部类可以静态的(利用static关键字修饰),也可以是非静态的. 一.静态内部类的使用目的. 在 ...
- LINUX - awk命令之NF和$NF区别 (转)
NF和$NF 区别问答:(转)1.awk中$NF是什么意思?#pwd/usr/local/etc~# echo $PWD | awk -F/ '{print $NF}'etcNF代表:浏览记录的域的个 ...
- Ansible系列(一):基本配置和使用
本文目录:1.1 安装Ansible1.2 配置Ansible 1.2.1 环境配置 1.2.2 SSH互信配置 1.2.3 简单测试1.3 inventory Ansible是一种批量.自动部署工具 ...
- Struts2第十篇【数据校验、代码方式、XML配置方式、错误信息返回样式】
回顾以前的数据校验 使用一个FormBean对象来封装着web端来过来的数据 维护一个Map集合保存着错误信息-对各个字段进行逻辑判断 //表单提交过来的数据全都是String类型的,birthday ...
- session get和load方法对比
get测试代码如下: public class Test { public static void main(String[] args) { // TODO Auto-generated metho ...
- 短视频图像处理 OpenGL ES 实践
2017年,短视频正以其丰富的内容表现力和时间碎片化的特点,快速崛起,而短视频最具可玩性之处就在支持人脸识别的动态贴图和各种不同效果的美颜.滤镜等.那短视频动态贴纸.滤镜.美颜等功能究竟是如何实现的呢 ...
- 使用JavaScript实现ATM取款机
ATM机需求描述如下: 假设一个简单的ATM机的取款过程为: 首先提示用户输入密码(password),假设默认密码为111111,最多只能输入3次, 超过3次则提示用户"密码错误,请取 ...