在上一篇《关于日志打印的几点建议以及非最佳实践》的末尾提到了日志打印更为高级的一种方式——利用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初级——入门及简单应用的更多相关文章

  1. Spring AOP初级——入门及简单应用

      在上一篇<关于日志打印的几点建议以及非最佳实践>的末尾提到了日志打印更为高级的一种方式——利用Spring AOP.在打印日志时,通常都会在业务逻辑代码中插入日志打印的语句,这实际上是 ...

  2. Vuex初级入门及简单案例

    1.为什么要使用Vuex? (1)方便所有组件共享信息,方便不同组件共享信息. (2)某个组件需要修改状态和需求.   2.状态有哪些? (1)组件内部定义的data状态(通过组件内部修改) (2)组 ...

  3. [Spring框架]Spring AOP基础入门总结一.

    前言:前面已经有两篇文章讲了Spring IOC/DI 以及 使用xml和注解两种方法开发的案例, 下面就来梳理一下Spring的另一核心AOP. 一, 什么是AOP 在软件业,AOP为Aspect ...

  4. 响应式Web初级入门

    本文来自我的前端博客,原文地址:http://www.hacke2.cn/about-responsive/ 跨终端时代的到来 当你乘坐各种交通工具(公交.地铁.轻轨.火车)时你会发现,人们都个个低下 ...

  5. Linux初级入门(第一次作业)

    Linux初级入门 在本科期间学过一些Linux的简单命令,再次接触Linux不仅巩固了知识还学习到了很多新的东西. 什么是操作系统? 操作系统,英文名称Operating System,简称OS,是 ...

  6. Spring Cloud实战之初级入门(四)— 利用Hystrix实现服务熔断与服务监控

    目录 1.环境介绍 2.服务监控 2.1 加入依赖 2.2 修改配置文件 2.3 修改启动文件 2.4 监控服务 2.5 小结 3. 利用hystrix实现消费服务熔断 3.1 加入服务熔断 3.2 ...

  7. SpringCloud实战之初级入门(二)— 服务注册与服务调用

    目录 1.环境介绍 2.服务提供 2.1 创建工程 2.2 修改配置文件 2.3 修改启动文件 2.5 亲测注意事项 3.服务调用 3.1 创建工程 3.2 修改配置文件 3.3 修改启动文件 3.4 ...

  8. 基于注解的Sping AOP详解

    一.创建基础业务 package com.kang.sping.aop.service; import org.springframework.stereotype.Service; //使用注解@S ...

  9. mui初级入门教程(六)— 模板页面实现原理及多端适配指南

    文章来源:小青年原创发布时间:2016-07-26关键词:mui,webview,template,os,多端适配转载需标注本文原始地址: http://zhaomenghuan.github.io. ...

随机推荐

  1. 201521123066 《java程序设计》第一周学习总结

    本周学习总结 (1)学习了Java的跨平台运行是因为有虚拟机,其特点是具有简单性,结构中立. (2)老师使用了新的作业模式,要学会发现其中的优势并好好学习使用. 书面作业 (1)为什么java程序可以 ...

  2. 201521123006 《java程序设计》 第10周学习总结

    1. 本周学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结异常与多线程相关内容. 2. 书面作业 本次PTA作业题集异常.多线程 1.finally 题目4-2 1.1 截图你的提交结果(出 ...

  3. Java实现3DES加密--及ANSI X9.8 Format标准 PIN PAN获取PIN BlOCK

    1, 采用银联ANSI X9.8标准 PIN xor PAN获取PIN BlOCK 2, 采用3Des进行加密 参考: des和3Des加密算法实现 要点:因为3DES是对称加密算法,key是24位, ...

  4. 深入理解计算机系统(2.5)------C语言中的有符号数和无符号数以及扩展和截断数字

    上一篇博客我们讲解了计算机中整数的表示,包括无符号编码和补码编码,以及它们之间的互相转换,个人觉得那是非常重要的知识要点.这篇博客我们将介绍C语言中的有符号数和无符号数以及扩展和截断数字. 1.C语言 ...

  5. 记一次Linux下给硬盘分区格式化操作

    今天找到一张旧TF卡,2G的,正好拿来练习下建立分区 插上orangepi后,fdisk -l看看,可以看到多了一个新的存储设备 /dev/mmcblk1 用fdisk打开它: fdisk /dev/ ...

  6. 基于CSS UI开源框架汇总

    从16年数据统计就有20几款UI框架出现在市面上,至今为止能统计的框架应该有40款左右了.前端框架都是基于HMTL5.CSS.JS开发的,这里主要给大家聊一下CSS UI开源框架有哪些?以后工作中选择 ...

  7. Check for Palindromes-FCC

    問題: 检查回文字符串 如果给定的字符串是回文,返回true,反之,返回false. 如果一个字符串忽略标点符号.大小写和空格,正着读和反着读一模一样,那么这个字符串就是palindrome(回文). ...

  8. 设计模式学习之“观察者模式” [C#]

    <深入浅出设计模式>学习笔记第二章 需求: 开发一套气象监测应用,如图: 气象站,目前有三种装置,温度.湿度和气压感应装置. WeatherData对象追踪气象站的数据,并更新到布告板,布 ...

  9. bzoj1036 [ZJOI2008]树的统计

    一棵树上有n个节点,编号分别为1到n,每个节点都有一个权值w.我们将以下面的形式来要求你对这棵树完成一些操作: I. CHANGE u t : 把结点u的权值改为t II. QMAX u v: 询问从 ...

  10. 支持向量机SVM(二)

    [转载请注明出处]http://www.cnblogs.com/jerrylead 6 拉格朗日对偶(Lagrange duality) 先抛开上面的二次规划问题,先来看看存在等式约束的极值问题求法, ...