前言

AOP是一种与语言无关的程序思想、编程范式。项目业务逻辑中,将通用的模块以水平切割的方式进行分离统一处理,常用于日志、权限控制、异常处理等业务中。

编程范式主要有以下几类

  • AOP(Aspect Oriented Programming)面向切面编程
  • OOP(Object Oriented Programming)面向对象编程
  • POP(procedure oriented programming)面向过程编程
  • FP(Functional Programming)面向函数编程

引入pom依赖

项目根目录 pom.xml 添加依赖 spring-boot-starter-aop

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>

aop注解

  • @Aspect: 切面,由通知和切入点共同组成,这个注解标注在类上表示为一个切面。
  • @Joinpoint: 连接点,被AOP拦截的类或者方法,在前置通知中有介绍使用@Joinpoint获取类名、方法、请求参数。
  • Advice: 通知的几种类型
  • @Before: 前置通知,在某切入点@Pointcut之前的通知
  • @After: 后置通知,在某切入点@Pointcut之后的通知无论成功或者异常。
  • @AfterReturning: 返回后通知,方法执行return之后,可以对返回的数据做加工处理。
  • @Around: 环绕通知,在方法的调用前、后执行。
  • @AfterThrowing: 抛出异常通知,程序出错跑出异常会执行该通知方法。
  • @Pointcut: 切入点,从哪里开始。例如从某个包开始或者某个包下的某个类等。

实现日志分割功能

目录 aspect下 新建 HttpAspect.java类,在收到请求之后先记录请求的相关参数日志信息,请求成功完成之后打印响应信息,请求处理报错打印报错日志信息。

HttpAspect.java

package com.itaofly.aspect.aspect;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes; import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map; /**
* @desctiption:
* @author: yinghuaYang
* @date: 2018/12/22
*/ @Aspect
@Component
public class HttpAspect {
// 打印日志模块
private final Logger logger = LoggerFactory.getLogger(HttpAspect.class);
// 下面慢慢介绍...

添加切入点

定义切入的入口在哪里,封装一个公共的方法实现复用

HttpAspect.java

/**
* 定义一个公共的方法
* 拦截UserController下面的所有方法
* 拦截UserController下面的userList方法里的任何参数(..表示拦截任何参数)
* 写法: @Pointcut("execution(public * com.itaofly.aspect.controller.UserController.*(..))")
*/
@Pointcut("execution(public * com.itaofly.aspect.controller.UserController.*(..))")
public void verify() {}

前置通知

拦截方法之前的一段业务逻辑,获取请求的一些信息,其中用到了Gson处理对象转json输出

HttpAspect.java

@Before("verify()")
public void doBefore(JoinPoint joinPoint) {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest(); Map<String,Object> map = new HashMap<>();
// 获取请求的url
map.put("url",request.getRequestURL());
// 获取请求的方式
map.put("method",request.getMethod());
// 获取请求的ip地址
map.put("ip",request.getRemoteAddr());
// 获取类名
map.put("className",joinPoint.getSignature().getDeclaringTypeName());
// 获取类方法
map.put("classMethod",joinPoint.getSignature().getName());
// 请求参数
map.put("args",joinPoint.getArgs()); // 输出格式化后的json字符串
Gson gson = new GsonBuilder().setPrettyPrinting().create(); logger.info("request: {}",gson.toJson(map)); }

后置通知

拦截方法之后的一段业务逻辑

HttpAspect.java

@After("verify()")
public void doAfter() {
logger.info("doAfter!");
}

环绕通知

环绕通知是在方法的前后的一段逻辑操作,可以修改目标方法的返回值,第一个参数是org.aspectj.lang.ProceedingJoinPoint类型,注意这里要调用执行目标方法proceed()获取值返回,不然会造成空指针异常。在环绕通知里面也可以捕获错误返回。

HttpAspect.java

@Around("verify()")
public Object doAround(ProceedingJoinPoint proceedingJoinPoint) {
try {
Object obj = proceedingJoinPoint.proceed();
System.out.println("方法环绕..., 结果是 : {}" + obj);
logger.info("doAround...1"); return obj;
}catch (Throwable e) {
logger.info("Throwable ..."); return null;
}
}

返回后通知

在切入点完成之后的返回通知,此时就不会抛出异常通知,除非返回后通知的业务逻辑报错。

HttpAspect.java

/**
* 获取响应返回值
* @param obj
*/
@AfterReturning(returning = "obj",pointcut = "verify()")
public void doAfterReturning(Object obj) {
// 会打印出一个对象,想打印出具体内容需要在定义模型处加上toString()
//logger.info("response: {}",obj); logger.info("response: {}",obj.toString());
}

异常通知

抛出异常后的通知,此时返回后通知@AfterReturning就不会执行。

HttpAspect.java

@AfterThrowing(pointcut = "verify()")
public void doAfterThrowing() {
logger.error("doAfterThrowing: {}"," 异常情况!!!!");
}

一段段伪代码读懂执行顺序

try {
// @Before 执行前通知 // 执行目标方法 // @Around 执行环绕通知 成功走finall,失败走catch
} finally {
// @After 执行后置通知 // @AfterReturning 执行返回后通知
} catch(e) {
// @AfterThrowing 抛出异常通知
}

测试正常异常两种情况

测试之前先对controller/UserController.java文件的getUserList 方法增加了exception`参数

/**
* 根据所有用户
* @param exception
* @return
*/
@RequestMapping(value = "/list/{exception}")
public List<User> getUserList(@PathVariable("exception") Boolean exception) {
if (exception) {
throw new Error("throw error!");
} return repository.findAll();
}
  • 测试正常情况
curl 127.0.0.1:8080/user/list/false

正常情况返回值如下所示:

  • 测试异常情况
curl 127.0.0.1:8080/user/list/true

异常情况返回值如下所示:

通过以上两种情况测试可以看到环绕通知在正常、异常两种情况都可以执行到

完整示例代码

-- HttpAspect.java --
package com.itaofly.aspect.aspect;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes; import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map; /**
* @desctiption:
* @author: yinghuaYang
* @date: 2018/12/22
*/ @Aspect
@Component
public class HttpAspect {
// 打印日志模块
private final Logger logger = LoggerFactory.getLogger(HttpAspect.class); /**
* 定义一个公共的方法
* 拦截UserController下面的所有方法
* 拦截UserController下面的userList方法里的任何参数(..表示拦截任何参数)
* 写法: @Pointcut("execution(public * com.itaofly.aspect.controller.UserController.*(..))")
*/
@Pointcut("execution(public * com.itaofly.aspect.controller.UserController.*(..))")
public void verify() {} @Before("verify()")
public void doBefore(JoinPoint joinPoint) {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest(); Map<String,Object> map = new HashMap<>();
// 获取请求的url
map.put("url",request.getRequestURL());
// 获取请求的方式
map.put("method",request.getMethod());
// 获取请求的ip地址
map.put("ip",request.getRemoteAddr());
// 获取类名
map.put("className",joinPoint.getSignature().getDeclaringTypeName());
// 获取类方法
map.put("classMethod",joinPoint.getSignature().getName());
// 请求参数
map.put("args",joinPoint.getArgs()); // 输出格式化后的json字符串
Gson gson = new GsonBuilder().setPrettyPrinting().create(); logger.info("request: {}",gson.toJson(map)); } @Around("verify()")
public Object doAround(ProceedingJoinPoint proceedingJoinPoint) {
try {
Object obj = proceedingJoinPoint.proceed();
System.out.println("方法环绕..., 结果是 : {}" + obj);
logger.info("doAround...1"); return obj;
}catch (Throwable e) {
logger.info("Throwable ..."); return null;
}
} @After("verify()")
public void doAfter() {
logger.info("doAfter!");
} /**
* 获取响应返回值
* @param obj
*/
@AfterReturning(returning = "obj",pointcut = "verify()")
public void doAfterReturning(Object obj) {
// 会打印出一个对象,想打印出具体内容需要在定义模型处加上toString()
//logger.info("response: {}",obj); logger.info("response: {}",obj.toString());
} @AfterThrowing(pointcut = "verify()")
public void doAfterThrowing() {
logger.error("doAfterThrowing: {}"," 异常情况!!!!");
} }
-- UserController.java --
package com.itaofly.aspect.controller;

import com.itaofly.aspect.model.User;
import com.itaofly.aspect.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*; import java.util.List;
import java.util.Optional; /**
* @desctiption:
* @author: yinghuaYang
* @date: 2018/12/22
*/ @RestController
@RequestMapping("/user")
public class UserController { @Autowired
private UserRepository repository; /**
* 新增用户
* @param params
* @return
*/
@PostMapping(value = "/add")
public User addUesr(@RequestBody User params) {
User user = new User();
user.setUserName(params.getUserName());
user.setUserAge(params.getUserAge()); return repository.save(user);
} /**
* 根据所有用户
* @param exception
* @return
*/
@GetMapping(value = "/list/{exception}")
public List<User> getUserList(@PathVariable("exception") Boolean exception) {
if (exception) {
throw new Error("throw error!");
} return repository.findAll();
} /**
* 根据Id查询用户
* @param id
* @return
*/
@GetMapping("/{id}")
public Optional<User> getUserById(@PathVariable("id") Integer id) {
return repository.findById(id);
} /**
* 根据userName获取用户信息
* @param userName
* @return
*/
@GetMapping("/userName")
public List<User> getUserListByName(@RequestParam(name = "userName",defaultValue = "") String userName) {
return repository.findByUserName(userName);
} /**
* 更新用户信息
* @param id
* @param userName
* @param userAge
* @return
*/
@PutMapping("/{id}")
public User updateUser(@PathVariable("id") Integer id,
@PathVariable("userName") String userName,
@PathVariable("userAge") Integer userAge){
User user = new User();
user.setId(id);
user.setUserAge(userAge);
user.setUserName(userName); return repository.save(user); } @DeleteMapping("/{id}")
public void deleteUser(@PathVariable("id") Integer id) {
repository.deleteById(id);
} }
-- User.java --
package com.itaofly.aspect.model;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id; /**
* @desctiption:
* @author: yinghuaYang
* @date: 2018/12/22
*/ @Entity
public class User {
@Id
@GeneratedValue
private Integer id; private String userName; private Integer userAge; public Integer getId() {
return id;
} public void setId(Integer id) {
this.id = id;
} public String getUserName() {
return userName;
} public void setUserName(String userName) {
this.userName = userName;
} public Integer getUserAge() {
return userAge;
} public void setUserAge(Integer userAge) {
this.userAge = userAge;
} @Override
public String toString() {
return "User{" +
"id=" + id +
", userName='" + userName + '\'' +
", userAge=" + userAge +
'}';
}
}
-- UserReponsitory.java --
package com.itaofly.aspect.repository;

import com.itaofly.aspect.model.User;
import org.springframework.data.jpa.repository.JpaRepository; import java.util.List; /**
* @desctiption:
* @author: yinghuaYang
* @date: 2018/12/22
*/ public interface UserRepository extends JpaRepository<User,Integer> { /**
* 通过用户名查询
* @param userName
* @return
*/
List<User> findByUserName(String userName); }

我的新博客地址链接:https://www.itaofly.com

Spring Boot之AOP面向切面编程-实战篇的更多相关文章

  1. Spring Boot2(六):使用Spring Boot整合AOP面向切面编程

    一.前言 众所周知,spring最核心的两个功能是aop和ioc,即面向切面和控制反转.本文会讲一讲SpringBoot如何使用AOP实现面向切面的过程原理. 二.何为aop ​ aop全称Aspec ...

  2. Spring(3):AOP面向切面编程

    一,AOP介绍 AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术.AOP是OOP的延续,是软件开 ...

  3. 【Spring 核心】AOP 面向切面编程

    一.什么是面向切面编程? 二.通过切点来选择连接点 三.使用注解创建切面 四.在XML中声明切面 五.注入AspectJ切面

  4. Spring注解式AOP面向切面编程.

    1.AOP指在程序运行期间动态的将某段代码切入到指定方法指定位置进行运行的编程方式.aop底层是动态代理. package com.bie.config; import org.aspectj.lan ...

  5. Spring:AOP面向切面编程

    AOP主要实现的目的是针对业务处理过程中的切面进行提取,它所面对的是处理过程中的某个步骤或阶段,以获得逻辑过程中各部分之间低耦合性的隔离效果. AOP是软件开发思想阶段性的产物,我们比较熟悉面向过程O ...

  6. 浅谈Spring AOP 面向切面编程 最通俗易懂的画图理解AOP、AOP通知执行顺序~

    简介 我们都知道,Spring 框架作为后端主流框架之一,最有特点的三部分就是IOC控制反转.依赖注入.以及AOP切面.当然AOP作为一个Spring 的重要组成模块,当然IOC是不依赖于Spring ...

  7. 详细解读 Spring AOP 面向切面编程(二)

    本文是<详细解读 Spring AOP 面向切面编程(一)>的续集. 在上篇中,我们从写死代码,到使用代理:从编程式 Spring AOP 到声明式 Spring AOP.一切都朝着简单实 ...

  8. Spring 08: AOP面向切面编程 + 手写AOP框架

    核心解读 AOP:Aspect Oriented Programming,面向切面编程 核心1:将公共的,通用的,重复的代码单独开发,在需要时反织回去 核心2:面向接口编程,即设置接口类型的变量,传入 ...

  9. 基于SpringBoot AOP面向切面编程实现Redis分布式锁

    基于SpringBoot AOP面向切面编程实现Redis分布式锁 基于SpringBoot AOP面向切面编程实现Redis分布式锁 基于SpringBoot AOP面向切面编程实现Redis分布式 ...

随机推荐

  1. 【转载】checkbox复选框的一些深入研究与理解

    转载来自:原创文章,转载请注明来自张鑫旭-鑫空间-鑫生活[http://www.zhangxinxu.com] 一.一开始的唠叨最近忙于开发,自淫于项目的一步步完工,心浮躁了.舍近而求远,兵家之大忌. ...

  2. Python内置函数之all()

    all()函数返回值不是True就是False. 它只能传入一个参数,而且参数必须是可迭代对象,换句话说,参数不是元组就是列表(通常情况下). all()中的可迭代对象所有元素值为True或者不包含元 ...

  3. Windows Server 2008 R2入门之用户管理

    一.用户账户概述: ”用户”是计算机的使用者在计算机系统中的身份映射,不同的用户身份拥有不同的权限,每个用户包含一个名称和一个密码: 在Windows中,每个用户帐户有一个唯一的安全标识符(Secur ...

  4. 我的第四个程序 java实现加减乘除

    import java.util.Scanner; public class Test { public static void main(String [] args) { Scanner sc = ...

  5. DBCP与C3P0数据库连接池

    数据库连接池是做什么的? 学过计算机网络的都知道,在一个内部局域网中.大部分用的都是私有地址,要想和外部 打交道,必须要有相应的合法外部地址相相应.然而内部用户数量巨大.一台机子一个外部IP 是不现实 ...

  6. hdu 4587(割点的应用)

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=4587 思路:题目的意思很简单,就是删除任意2个节点以及关联的边,求图的最大连通分量数.我们知道删除割点 ...

  7. Linux虚拟机安装完centos后环境配置

    linux下面安装软件 yum install rpm -ivh 编译安装 三部曲:./configure make make install 卸载 rpm -e 安装方法 1)通过yum安装软件 需 ...

  8. JS中的加号+运算符详解

    加号+运算符 在 JavaScript 中,加法的规则其实很简单,只有两种情况: 把数字和数字相加 把字符串和字符串相加 所有其他类型的值都会被自动转换成这两种类型的值. 为了能够弄明白这种隐式转换是 ...

  9. 【BZOJ1925】[Sdoi2010]地精部落 组合数+DP

    [BZOJ1925][Sdoi2010]地精部落 Description 传说很久以前,大地上居住着一种神秘的生物:地精. 地精喜欢住在连绵不绝的山脉中.具体地说,一座长度为 N 的山脉 H可分 为从 ...

  10. 《从零开始学Swift》学习笔记(Day5)——我所知道的标识符和关键字

    Swift 2.0学习笔记(Day5)——我所知道的标识符和关键字   原创文章,欢迎转载.转载请注明:关东升的博客 好多计算机语言都有标识符和关键字,一直没有好好的总结,就是这样的用着,现在小小的整 ...