在上一篇《关于日志打印的几点建议以及非最佳实践》的末尾提到了日志打印更为高级的一种方式——利用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的情况下,通常会在删除方法前用户作权限的校验,例如:

1 public void delete(long id) {
2 authService.checkAccess();
3 //TODO:do something.
4 }

  显然,第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();
}
}

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使用场景。

Spring AOP初级——入门及简单应用的更多相关文章

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

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

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

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

  3. [Spring框架]Spring AOP基础入门总结二:Spring基于AspectJ的AOP的开发.

    前言: 在上一篇中: [Spring框架]Spring AOP基础入门总结一. 中 我们已经知道了一个Spring AOP程序是如何开发的, 在这里呢我们将基于AspectJ来进行AOP 的总结和学习 ...

  4. 基于Spring aop写的一个简单的耗时监控

    前言:毕业后应该有一两年没有好好的更新博客了,回头看看自己这一年,似乎少了太多的沉淀了.让自己做一个爱分享的人,好的知识点拿出来和大家一起分享,一起学习. 背景: 在做项目的时候,大家肯定都遇到对一些 ...

  5. Spring AOP详解及简单应用

    Spring AOP详解   一.前言 在以前的项目中,很少去关注spring aop的具体实现与理论,只是简单了解了一下什么是aop具体怎么用,看到了一篇博文写得还不错,就转载来学习一下,博文地址: ...

  6. Spring AOP 知识点入门

    一.基本知识点 1.AOP概念 AOP(Aspect-Oriented Programming), 即 面向切面编程, 它与 OOP( Object-Oriented Programming, 面向对 ...

  7. Spring Aop的理解和简单实现

    1.AOP概念 所说的面向切面编程其实就是在处理一系列业务逻辑的时候这一系列动作看成一个动作集合.比如连接数据库来说: 加载驱动-----获取class--------获取连接对象-------访问数 ...

  8. 使用Spring AOP 实现日志管理(简单教程)

    有时候,我们在做项目时会遇到这样的需求: 给XXX.java中的所有方法加上指定格式的日志输出. 针对这种指定类.或者指定方法进行共性操作的功能,我们完全可以使用Spring AOP来实现. 本文使用 ...

  9. Spring AOP @AspectJ 入门基础

    需要的类包: 1.一个简单的例子 Waiter接口: package com.yyq.annotation; public interface Waiter { void greetTo(String ...

随机推荐

  1. Linux 云服务器中安装 rinetd 进行转发端口实现

    端口转发映射的程序叫rinetd,直接make编译安装即可. wget http://www.boutell.com/rinetd/http/rinetd.tar.gz&&tar -x ...

  2. mybatis foreach 遍历list中的坑

    将jdbc改写为mybatis时,传入的条件为list使用到的标签是<where> .<choose>.<when>.<if>.<foreach& ...

  3. Android开发 ---SQLite数据库,lock文件,结果集游标,适配器,安全退出,给连接设置下划线,编辑器,投影,ContentValues存储,DbHelper,activity栈

    目录截图: 1.activity_main.xml 主界面效果: <?xml version="1.0" encoding="utf-8"?> &l ...

  4. Shell 变量替换及测试

    声明:$ 后面跟linux可执行命令 一.变量替换                   语法                      说明 ${变量名#匹配规则} 从变量的开头进行规则匹配,将符合最 ...

  5. django 简易版搭建

    1.根目录下创建mysql.cnf文件 [client]database = identimguser = rootpassword = roothost = 127.0.0.1port = 3306 ...

  6. sqlserver查询当前库下,一张表的表名,字段名,字段类型,字段长度

    sqlserver版: 查询当前数据库下所有表名: select * from sys.tables; 查询当前库下,一张表的表名,字段名,字段类型,字段长度: select a.name 表名,b. ...

  7. 【oracle入门】数据完整性约束

    数据的完整性约束是对数据描述的某种约束条件,关系型数据模型中可以有三类完整性约束:实体完整性.参照完整性和用户定义的完整性. 实体完整性Entity Integrity 一个基本关系通过对应显示世界的 ...

  8. java 环形链表实现约瑟夫(Joseph)问题

    约瑟夫问题又名丢手绢问题.相传著名犹太历史学家 Josephus 利用其规则躲过了一场自杀游戏,而后投降了罗马. 问题: 已知n个人(以编号1,2,3...n分别表示)围坐在一张圆桌周围.* 从编号为 ...

  9. s21day07 python笔记

    s21day07 python笔记 一.昨日内容回顾及补充 回顾 补充 将前面所提到的功能,统一改称为方法 二.深浅拷贝 基本格式 v1 = [1,2,3] import copy v2 = copy ...

  10. Python全栈之路----函数----返回值

    函数外部的代码想要获取函数的执行结果,就可以在函数里用return语句,把结果返回. def stu_register(name,age,course='PY',country='CN'): prin ...