在《Spring AOP初级——入门及简单应用》中对AOP作了简要的介绍,以及一些专业术语的解释,同时写了一个简单的Spring AOPdemo。本文将继续探讨Spring AOP在实际场景中的应用。

  对用户操作日志的记录是很常见的一个应用场景,本文选取“用户管理”作为本文Spring AOP的示例。当然,该示例只是对真实场景的模拟,实际的环境一定比该示例更复杂。

  该示例的完整代码路径在https://github.com/yu-linfeng/BlogRepositories/tree/master/repositories/Spring%20AOP%E4%B8%AD%E7%BA%A7%E2%80%94%E2%80%94%E5%BA%94%E7%94%A8%E5%9C%BA%E6%99%AF。本文仅对Spring AOP相关的代码进行讲解。

  在这个示例中首次采用RESTful架构风格,对于以下RESTful API的设计可能并不完美,如果有熟悉、精通RESTful架构风格的朋友希望能够指出我的错误。

  

  使用RESTful的前后端分离架构风格后,我感受到了前所未有的畅快,所以此次示例并没有前端页面的展示,完全使用JUnit进行单元测试包括对HTTP请求的Mock模拟,这部分代码不会进行详细讲解,之后会继续深入JUnit单元测试的一些学习研究。

  数据库只有一张表:

  回到正题,我们回顾下切面由哪两个部分组成: 通知 切点 首先明确我们需要在何时记录日志:

  通知

  切点

  首先明确我们需要在何时记录日志:

  1. 查询所有用户时,并没有参数(此示例没有作分页),只有在返回时才会有数据的返回,所以对查询所有用户的方法采用返回通知(AfterReturning)。

  2. 新增用户时,会带有新增的参数,此时可采用前置通知(Before)。

  3. 修改用户时,也会带有新增的参数,此时同样采用前置通知(Before)。

  4. 删除用户时,通常会带有唯一标识符ID,此时采用前置通知(Before)记录待删除的用户ID。

  在明确了通知类型后,此时我们需要明确切点,也就是在哪个地方记录日志。当然上面实际已经明确了日志记录的位置,但主要是切面表达式的书写。 在有了《Spring AOP初级——入门及简单应用》的基础,相信对日志切面类已经比较熟悉了:

  1. package com.manager.aspect;
  2.  
  3. import org.apache.log4j.Logger;
  4. import org.aspectj.lang.JoinPoint;
  5. import org.aspectj.lang.annotation.*;
  6. import org.springframework.stereotype.Component;
  7.  
  8. import java.util.Arrays;
  9.  
  10. /**
  11. * 日志切面
  12. * Created by Kevin on 2017/10/29.
  13. */
  14. @Aspect
  15. @Component
  16. public class LogAspect {
  17. /**
  18. * 操作日志文件名
  19. */
  20. private static final String OPERATION_LOG_NAME = "operationLog";
  21. private static final String LOG_FORMATTER = "%s.%s - %s";
  22. Logger log = Logger.getLogger(OPERATION_LOG_NAME);
  23. /**
  24. * 对查询方法记录日志的切点
  25. */
  26. @Pointcut("execution(* com.manager..*.*Controller.query*(..))")
  27. public void query(){}
  28.  
  29. /**
  30. * 对新增方法记录日志的切点
  31. */
  32. @Pointcut("execution(* com.manager..*.*Controller.add*(..))")
  33. public void add(){}
  34.  
  35. /**
  36. * 对修改方法记录日志的切点
  37. */
  38. @Pointcut("execution(* com.manager..*.*Controller.update*(..))")
  39. public void update(){}
  40.  
  41. /**
  42. * 对删除方法记录日志的切点
  43. */
  44. @Pointcut("execution(* com.manager..*.*Controller.delete*(..))")
  45. public void delete(){}
  46.  
  47. @AfterReturning(value = "query()", returning = "rvt")
  48. public void queryLog(JoinPoint joinPoint, Object rvt) {
  49. String className = joinPoint.getTarget().getClass().getName();
  50. String methodName = joinPoint.getSignature().getName();
  51. String returnResult = rvt.toString();
  52. log.info(String.format(LOG_FORMATTER, className, methodName, returnResult));
  53. }
  54.  
  55. @Before("add()")
  56. public void addLog(JoinPoint joinPoint) {
  57. String className = joinPoint.getTarget().getClass().getName();
  58. String methodName = joinPoint.getSignature().getName();
  59. Object[] params = joinPoint.getArgs();
  60. log.info(String.format(LOG_FORMATTER, className, methodName, Arrays.toString(params)));
  61. }
  62.  
  63. @Before("update()")
  64. public void updateLog(JoinPoint joinPoint) {
  65. String className = joinPoint.getTarget().getClass().getName();
  66. String methodName = joinPoint.getSignature().getName();
  67. Object[] params = joinPoint.getArgs();
  68. log.info(String.format(LOG_FORMATTER, className, methodName, Arrays.toString(params)));
  69. }
  70.  
  71. @Before("delete()")
  72. public void deleteLog(JoinPoint joinPoint) {
  73. String className = joinPoint.getTarget().getClass().getName();
  74. String methodName = joinPoint.getSignature().getName();
  75. Object[] params = joinPoint.getArgs();
  76. log.info(String.format(LOG_FORMATTER, className, methodName, Arrays.toString(params)));
  77. }
  78. }

  上面的日志切面类中出现了JointPoint类作为参数的情况,这个参数能够传递被通知方法的相信,例如被通知方法所处的类以及方法名等。在第47行中的Object rvt参数就是获取被通知方法的返回值。 上面的切面并没有关注被通知方法的参数,如果要使得切面和被通知方法参数参数关联可以使用以下的方式:

  1. @Pointcut("execution(* com.xxx.demo.Demo.method(int)) && args(arg)")
  2. public void aspectMethod(int arg){}
  3.  
  4. @Before(“aspectMedhot(arg)”)
  5. public void method(int arg) {
  6. //此时arg参数就是被通知方法的参数
  7. }

  本例中最主要的切面部分就完成了。注意在结合Spring时需要在applicationContext.xml中加入以下语句:

  1. <!--启用AspectJ自动代理,其中proxy-target-class为true表示使用CGLib的代理方式,false表示JDK的代理方式,默认false-->
  2. <aop:aspectj-autoproxy />

  示例中关于log4j、pom.xml依赖、JUnit如何结合Spring进行单元测试等等均可可以参考完整代码。特别是JUnit是很值得学习研究的一部分,这部分在将来慢慢我也会不断学习推出新的博客,在这里就只贴出JUnit的代码,感兴趣的可以浏览一下:

  1. package com.manager.user.controller;
  2.  
  3. import com.fasterxml.jackson.databind.ObjectMapper;
  4. import com.manager.user.pojo.User;
  5. import org.junit.Before;
  6. import org.junit.Test;
  7. import org.junit.runner.RunWith;
  8. import org.springframework.beans.factory.annotation.Autowired;
  9. import org.springframework.http.MediaType;
  10. import org.springframework.test.context.ContextConfiguration;
  11. import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
  12. import org.springframework.test.context.web.WebAppConfiguration;
  13. import org.springframework.test.web.servlet.MockMvc;
  14. import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
  15. import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
  16. import org.springframework.test.web.servlet.setup.MockMvcBuilders;
  17. import org.springframework.web.context.WebApplicationContext;
  18.  
  19. import static org.junit.Assert.assertNotNull;
  20.  
  21. /**
  22. * UserController单元测试
  23. * Created by Kevin on 2017/10/26.
  24. */
  25. @RunWith(SpringJUnit4ClassRunner.class)
  26. @ContextConfiguration({"classpath*:applicationContext.xml", "classpath*:spring-servlet.xml"})
  27. @WebAppConfiguration
  28. public class UserControllerTest {
  29. @Autowired
  30. private WebApplicationContext wac;
  31. private MockMvc mvc;
  32.  
  33. @Before
  34. public void initMockHttp() {
  35. this.mvc = MockMvcBuilders.webAppContextSetup(wac).build();
  36. }
  37.  
  38. @Test
  39. public void testQueryUsers() throws Exception {
  40. mvc.perform(MockMvcRequestBuilders.get("/users"))
  41. .andExpect(MockMvcResultMatchers.status().isOk());
  42. }
  43.  
  44. @Test
  45. public void testAddUser() throws Exception {
  46. User user = new User();
  47. user.setName("kevin");
  48. user.setAge(23);
  49. mvc.perform(MockMvcRequestBuilders.post("/users")
  50. .contentType(MediaType.APPLICATION_JSON_UTF8)
  51. .content(new ObjectMapper().writeValueAsString(user)))
  52. .andExpect(MockMvcResultMatchers.status().isOk())
  53. .andExpect(MockMvcResultMatchers.jsonPath("$.name").value("kevin"))
  54. .andExpect(MockMvcResultMatchers.jsonPath("$.age").value(23));
  55. }
  56.  
  57. @Test
  58. public void testQueryUserById() throws Exception {
  59. User user = new User();
  60. user.setId(8);
  61. mvc.perform(MockMvcRequestBuilders.get("/users/" + user.getId()))
  62. .andExpect(MockMvcResultMatchers.status().isOk())
  63. .andExpect(MockMvcResultMatchers.jsonPath("$.name").value("kevin"))
  64. .andExpect(MockMvcResultMatchers.jsonPath("$.age").value(23));
  65.  
  66. }
  67.  
  68. @Test
  69. public void testUpdateUserById() throws Exception {
  70. User user = new User();
  71. user.setId(9);
  72. user.setName("tony");
  73. user.setAge(99);
  74. mvc.perform(MockMvcRequestBuilders.put("/users/" + user.getId())
  75. .contentType(MediaType.APPLICATION_JSON_UTF8)
  76. .content(new ObjectMapper().writeValueAsString(user)))
  77. .andExpect(MockMvcResultMatchers.status().isOk());
  78. }
  79.  
  80. @Test
  81. public void testDeleteUserById() throws Exception {
  82. long id = 10;
  83. mvc.perform(MockMvcRequestBuilders.delete("/users/" + id))
  84. .andExpect(MockMvcResultMatchers.status().isOk());
  85. }
  86. }

  有了初级和中级,接下来必然就是Spring AOP高级——源码实现。

这是一个能给程序员加buff的公众号 

Spring AOP中级——应用场景的更多相关文章

  1. [转]彻底征服 Spring AOP 之 实战篇

    Spring AOP 实战 看了上面这么多的理论知识, 不知道大家有没有觉得枯燥哈. 不过不要急, 俗话说理论是实践的基础, 对 Spring AOP 有了基本的理论认识后, 我们来看一下下面几个具体 ...

  2. 161110、彻底征服 Spring AOP 之 实战篇

    Spring AOP 实战 看了上面这么多的理论知识, 不知道大家有没有觉得枯燥哈. 不过不要急, 俗话说理论是实践的基础, 对 Spring AOP 有了基本的理论认识后, 我们来看一下下面几个具体 ...

  3. Spring技术内幕:Spring AOP的实现原理(二)

    **二.AOP的设计与实现 1.JVM的动态代理特性** 在Spring AOP实现中, 使用的核心技术时动态代理.而这样的动态代理实际上是JDK的一个特性.通过JDK的动态代理特性,能够为随意Jav ...

  4. Spring Aop 应用实例与设计浅析

    0.代码概述 代码说明:第一章中的代码为了突出模块化拆分的必要性,所以db采用了真实操作.下面代码中dao层使用了打印日志模拟插入db的方法,方便所有人运行demo. 1.项目代码地址:https:/ ...

  5. 彻底征服 Spring AOP 之 实战篇

      Spring AOP 实战 看了上面这么多的理论知识, 不知道大家有没有觉得枯燥哈. 不过不要急, 俗话说理论是实践的基础, 对 Spring AOP 有了基本的理论认识后, 我们来看一下下面几个 ...

  6. 关于 Spring AOP (AspectJ) 该知晓的一切

    关联文章: 关于Spring IOC (DI-依赖注入)你需要知道的一切 关于 Spring AOP (AspectJ) 你该知晓的一切 本篇是年后第一篇博文,由于博主用了不少时间在构思这篇博文,加上 ...

  7. 关于 Spring AOP (AspectJ) 你该知晓的一切

    版权声明:本文为CSDN博主「zejian_」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明.原文链接:https://blog.csdn.net/javazej ...

  8. Spring AOP 实战运用

    Spring AOP 实战 看了上面这么多的理论知识, 不知道大家有没有觉得枯燥哈. 不过不要急, 俗话说理论是实践的基础, 对 Spring AOP 有了基本的理论认识后, 我们来看一下下面几个具体 ...

  9. Spring AOP应用场景你还不知道?这篇一定要看!

    回顾一下Spring AOP的知识 为什么会有面向切面编程(AOP)? 我们知道Java是一个面向对象(OOP)的语言,但它有一些弊端,比如当我们需要为多个不具有继承关系的对象引入一个公共行为,例如日 ...

随机推荐

  1. Struts2第十篇【数据校验、代码方式、XML配置方式、错误信息返回样式】

    回顾以前的数据校验 使用一个FormBean对象来封装着web端来过来的数据 维护一个Map集合保存着错误信息-对各个字段进行逻辑判断 //表单提交过来的数据全都是String类型的,birthday ...

  2. 如何手动获取Spring容器中的bean(ApplicationContextAware 接口)

    ApplicationContextAware 接口的作用 先来看下Spring API 中对于 ApplicationContextAware 这个接口的描述:   即是说,当一个类实现了这个接口之 ...

  3. 百度编辑器不能插入html标签解决方法

    找到此方法: me.addInputRule(function (root) { var allowDivTransToP = this.options.allowDivTransToP; var v ...

  4. MapReduce执行过程

    Mapper任务的执行过程: 第一阶段是把输入文件按照一定的标准分片(InputSplit),每个输入片的大小是固定的.默认情况下,输入片(InputSplit)的大小与数据块(Block)的大小是相 ...

  5. BZOJ2748_音量调节_KEY

    [HAOI2012]音量调节 Time Limit: 3 Sec Memory Limit: 128 MB Description 一个吉他手准备参加一场演出.他不喜欢在演出时始终使用同一个音量,所以 ...

  6. CentOS更新源

    1.首先备份/etc/yum.repos.d/CentOS-Base.repo mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS ...

  7. BCB中AnsiString类方法小结

    AnsiString类是BCB中最常见类之一,了解它对以后深入学习BCB大有帮助. 介绍AnsiString类之前,先要介绍一些背景知识.VCL(Visual Component Library 可视 ...

  8. 【重点突破】——Canvas技术绘制随机改变的验证码

    一.引言 本文主要是我在学习Canvas技术绘图时的一个小练习,绘制随机改变的验证码图片,虽然真正的项目里不这么做,但这个练习是一个掌握Canvas技术很好的综合练习.(真正的项目中验证码图片使用服务 ...

  9. 【POJ】2115 C Looooops(扩欧)

    Description A Compiler Mystery: We are given a C-language style for loop of type for (variable = A; ...

  10. vue2组件之select2调用

    目前,项目中使用了纯前端的静态项目+RESTFul接口的模式.为了更好的对数据进行操作,前端使用了vue2的mvvm功能,但是由于不是单页面应用,所以,并没有涉及到其它的如vue-route等功能,也 ...