java切面类整合_SpringBoot2.x【五】整合AOP切面编程

SpringBoot2.x【五】整合AOP切面编程

面向方面编程(AOP)通过提供另一种思考程序结构的方式来补充面向对象编程(OOP)。

OOP中模块化的关键单元是类,而在AOP中,模块化单元是方面。

准备工作

首先,使用AOP要在build.gradle中加入依赖

//引入AOP依赖

compile "org.springframework.boot:spring-boot-starter-aop:${springBootVersion}"

然后在application.yml中加入

spring:

aop:

proxy-target-class: true

1.@Pointcut 切入点

定义一个切点

例如我们要在一个方法加上切入点,根据方法的返回的对象,方法名,修饰词来写成一个表达式或者是具体的名字

我们现在来定义一个切点

package com.example.aop;

import org.aspectj.lang.annotation.Aspect;

import org.aspectj.lang.annotation.Pointcut;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import org.springframework.stereotype.Component;

/**

* 类定义为切面类

*/

@Aspect

@Component

public class AopTestController {

private static final Logger logger = LoggerFactory.getLogger(AopTestController.class);

/**

* 定义一个切点

*/

@Pointcut(value = "execution(public String test (..))")

public void cutOffPoint() {

}

}

这里的切点定义的方法是

@GetMapping("hello")

public String test(){

logger.info("欢迎关注Java知音");

return "i love java";

}

如果你想写个切入点在所有返回对象为Area的方法,如下

@Pointcut("execution(public com.example.entity.Area (..))")

等很多写法,也可以直接作用在某些包下

注意:private修饰的无法拦截

2.@Before前置通知

在切入点开始处切入内容

在之前的AopTestController类中加入对test方法的前置通知

@Before("cutOffPoint()")

public void beforeTest(){

logger.info("我在test方法之前执行");

}

这里@Before里的值就是切入点所注解的方法名

5095d983f4af

image

在方法左侧出现的图标跟过去以后就是所要通知的方法

这里就是配置正确了,我们来浏览器调用一下方法

5095d983f4af

image

联想一下,这样的效果可以用在哪里,想像如果要扩展一些代码,在不需要动源代码的基础之上就可以进行拓展,美滋滋

3.@After 后置通知

和前置通知相反,在切入点之后执行

@After("cutOffPoint()")

public void doAfter(){

logger.info("我是在test之后执行的");

}

控制台执行结果

5095d983f4af

image

这里定义一个通知需要重启启动类,而修改通知方法的内容是可以热部署的

4.@Around环绕通知

和前两个写法不同,实现的效果包含了前置和后置通知

当使用环绕通知时,proceed方法必须调用,否则拦截到的方法就不会再执行了

环绕通知=前置+目标方法执行+后置通知,proceed方法就是用于启动目标方法执行的

ThreadLocal startTime = new ThreadLocal<>();

@Around("cutOffPoint()")

public Object doAround(ProceedingJoinPoint pjp){

startTime.set(System.currentTimeMillis());

logger.info("我是环绕通知执行");

Object obj;

try{

obj = pjp.proceed();

logger.info("执行返回值 : " + obj);

logger.info(pjp.getSignature().getName()+"方法执行耗时: " + (System.currentTimeMillis() - startTime.get()));

} catch (Throwable throwable) {

obj=throwable.toString();

}

return obj;

}

执行结果:

5095d983f4af

image

1.环绕通知可以项目做全局异常处理

2.日志记录

3.用来做数据全局缓存

4.全局的事物处理 等

5.@AfterReturning

切入点返回结果之后执行,也就是都前置后置环绕都执行完了,这个就执行了

/**

* 执行完请求可以做的

* @param result

* @throws Throwable

*/

@AfterReturning(returning = "result", pointcut = "cutOffPoint()")

public void doAfterReturning(Object result) throws Throwable {

logger.info("大家好,我是@AfterReturning,他们都秀完了,该我上场了");

}

执行结果

5095d983f4af

image

应用场景可以用来在订单支付完成之后就行二次的结果验证,重要参数的二次校验,防止在方法执行中的时候参数被修改等等

6.@AfterThrowing

这个是在切入执行报错的时候执行的

// 声明错误e时指定的抛错类型法必会抛出指定类型的异常

// 此处将e的类型声明为Throwable,对抛出的异常不加限制

@AfterThrowing(throwing = "e",pointcut = "cutOffPoint()")

public void doAfterReturning(Throwable e) {

logger.info("大家好,我是@AfterThrowing,他们犯的错误,我来背锅");

logger.info("错误信息"+e.getMessage());

}

在其他切入内容中随意整个错误出来,制造一个环境

下面是@AfterThrowing的执行结果

5095d983f4af

image

7.AOP用在全局异常处理

定义切入点拦截ResultBean或者PageResultBean

@Pointcut(value = "execution(public com.example.beans.PageResultBean *(..)))")

public void handlerPageResultBeanMethod() {

}

@Pointcut(value = "execution(public com.example.beans.ResultBean *(..)))")

public void handlerResultBeanMethod() {

}

下面是AopController.java

package com.example.aop;

import com.example.beans.PageResultBean;

import com.example.beans.ResultBean;

import com.example.entity.UnloginException;

import com.example.exception.CheckException;

import org.aspectj.lang.ProceedingJoinPoint;

import org.aspectj.lang.annotation.Around;

import org.aspectj.lang.annotation.Aspect;

import org.aspectj.lang.annotation.Pointcut;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import org.springframework.stereotype.Component;

/**

* 使用@Aspect注解将此类定义为切面类

* 根据晓风轻著的ControllerAOP所修改

* 晓风轻大佬(很大的佬哥了):https://xwjie.github.io/

*/

@Aspect

@Component

public class AopController {

private static final Logger logger = LoggerFactory.getLogger(AopController.class);

ThreadLocal resultBeanThreadLocal = new ThreadLocal<>();

ThreadLocal> pageResultBeanThreadLocal = new ThreadLocal<>();

ThreadLocal start = new ThreadLocal<>();

/**

* 定义一个切点

*/

@Pointcut(value = "execution(public com.example.beans.PageResultBean *(..)))")

public void handlerPageResultBeanMethod() {

}

@Pointcut(value = "execution(public com.example.beans.ResultBean *(..)))")

public void handlerResultBeanMethod() {

}

@Around("handlerPageResultBeanMethod()")

public Object handlerPageResultBeanMethod(ProceedingJoinPoint pjp) {

start.set(System.currentTimeMillis());

try {

pageResultBeanThreadLocal.set((PageResultBean>)pjp.proceed());

logger.info(pjp.getSignature() + " 方法执行耗时:" + (System.currentTimeMillis() - start.get()));

} catch (Throwable e) {

ResultBean> resultBean = handlerException(pjp , e);

pageResultBeanThreadLocal.set(new PageResultBean<>().setMsg(resultBean.getMsg()).setCode(resultBean.getCode()));

}

return pageResultBeanThreadLocal.get();

}

@Around("handlerResultBeanMethod()")

public Object handlerResultBeanMethod(ProceedingJoinPoint pjp) {

start.set(System.currentTimeMillis());

try {

resultBeanThreadLocal.set((ResultBean>)pjp.proceed());

logger.info(pjp.getSignature() + " 方法执行耗时:" + (System.currentTimeMillis() - start.get()));

} catch (Throwable e) {

resultBeanThreadLocal.set(handlerException(pjp , e));

}

return resultBeanThreadLocal.get();

}

/**

* 封装异常信息,注意区分已知异常(自己抛出的)和未知异常

*/

private ResultBean> handlerException(ProceedingJoinPoint pjp, Throwable e) {

ResultBean> result = new PageResultBean();

logger.error(pjp.getSignature() + " error ", e);

// 已知异常

if (e instanceof CheckException) {

result.setMsg(e.getLocalizedMessage());

result.setCode(ResultBean.FAIL);

} else if (e instanceof UnloginException) {

result.setMsg("Unlogin");

result.setCode(ResultBean.NO_LOGIN);

} else {

result.setMsg(e.toString());

result.setCode(ResultBean.FAIL);

}

return result;

}

}

用上面的环绕通知可以对所有返回ResultBean或者PageResultBean的方法进行切入,这样子就不用在业务层去捕捉错误了,只需要去打印自己的info日志

看下面一段代码

@Transactional

@Override

public int insertSelective(Area record) {

record.setAddress("test");

record.setPostalcode(88888);

record.setType(3);

int i=0;

try {

i = areaMapper.insertSelective(record);

}catch (Exception e){

logger.error("AreaServiceImpl insertSelective error:"+e.getMessage());

}

return i;

}

假如上面的插入操作失败出错了?

你认为会回滚吗?

答案是:不会。

为什么?

因为你把错误捕捉了,事物没检测到异常就不会回滚。

那么怎么才能回滚呢?

在catch里加throw new RuntimeException().

可是那么多业务方法每个设计修改的操作都加,代码繁琐,怎么进行处理呢?

在这里用到上面的AOP切入处理,错误不用管,直接抛,抛到控制层进行处理,这样的话,接口调用的时候,出错了,接口不会什么都不返回,而是会返回给你错误代码,以及错误信息,便于开发人员查错

8.以上用的是log4j2的日志处理

先移除springboot自带的log日志处理

在build.gradle中增加

configurations {

providedRuntime

// 去除SpringBoot自带的日志

all*.exclude group: 'org.springframework.boot', module: 'spring-boot-starter-logging'

}

ext {

springBootVersion = '2.0.1.RELEASE'

}

dependencies {

compile "org.springframework.boot:spring-boot-starter-log4j2:${springBootVersion}"

}

然后在application.yml中增加

#显示mysql执行日志

logging:

level:

com:

example:

dao: debug

config: classpath:log4j2-spring.xml

log4j2-spring.xml

之后在你要打印日志的类中增加

private static final Logger logger = LoggerFactory.getLogger(你的类名.class);

public static void main(String[] args) {

logger.error("error级别日志");

logger.warn("warning级别日志");

logger.info("info级别日志");

}

有了日志后就很方便了,在你的方法接收对象时打印下,然后执行了逻辑之后打印下,

出错之后很明确了,就会很少去Debug的,养成多打日志的好习惯,多打印一点info级别的日志,用来在开发环境使用,在上线的时候把打印的最低级别设置为warning,这样你的info级别日志也不会影响到项目的重要Bug的打印

写这个博客的时候我也在同时跑着这个项目,有时候会出现一些错误,例如jar包版本,业务层引用无效,AOP设置不生效等等,也同时在排查解决,如果你遇到了同样的错误,可以去我的GitHub联系我,如小弟有时间或许也能帮到你,谢谢

Github地址:https://github.com/cuifuan

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值