在上一篇《关于日志打印的几点建议以及非最佳实践》的末尾提到了日志打印更为高级的一种方式——利用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. 201521123072《java程序设计》第三周学习总结

    201521123072<java程序设计>第三周学习总结 标签: java学习 1. 本周学习总结 2. 书面作业 1,代码阅读 public class Test1 { private ...

  2. 201521123025《java程序设计》第11周学习总结

    1. 本周学习总结 2. 书面作业 Q1.互斥访问与同步访问 完成题集4-4(互斥访问)与4-5(同步访问) 1.1 除了使用synchronized修饰方法实现互斥同步访问,还有什么办法实现互斥同步 ...

  3. 201521123025<<java程序设计>>第9周学习总结

    1. 本周学习总结 2.书面作业 Q1.常用异常 题目5-1 1.1 截图你的提交结果(出现学号) 1.2 自己以前编写的代码中经常出现什么异常.需要捕获吗(为什么)?应如何避免? 经常出现Array ...

  4. 201521123119《Java程序设计》第12周学习总结

    1. 本周学习总结 Q1.1 以你喜欢的方式(思维导图或其他)归纳总结多流与文件相关内容. 2. 书面作业 将Student对象(属性:int id, String name,int age,doub ...

  5. 用Beautifulsoup 来爬取贴吧图片

    import urllib.request import bs4 import re import os url="https://tieba.baidu.com/p/1988291937? ...

  6. Spring-java-模板设计模式

    1,模板设计模式指的是将相应的模板方法提取出来在专门的位置定义,然后把相同调用过程操作,通过模板来实现对于模板设计模式而言,一般有两种实现方式 1)基于继承的实现 2)基于组合的实现 Spring的J ...

  7. JavaWeb学习之JDBC API中常用的接口和类

    JDBC API中包含四个常用的接口和一个类分别是: 1.Connection接口 2.Statement接口 3.PreparedStatement接口 4.ResultSet接口 5.Driver ...

  8. JavaScript中DOM

    概念 什么是DOM 1. 什么是 DOM DOM 的全称是document object model 它的基本思想是将结构化文佳例如HTML xml解析成一系列的节点.就像一颗树一样. 所有的节点和最 ...

  9. Excel 若某列包含某个字符则特定列显示特定字符

    今天在写Excel , 有很多重复的数据, 在想 如果 可以像Java  一样 筛选就好了 这样的效果 if ("adj".equals(sheet1.A1)){ sheet1.B ...

  10. 这是一名Java学者关于学习方向的建议

    无可厚非,编程是一门艺术.但是辉煌的背后必须是一段辛苦的奋斗过程,而过程的引导方向就是最重要的一环.Java语言可谓是引领了编程的潮流,你会是怎样去学的呢? 这是一名Java学者的学习方向的建议 注: ...