SpringBoot AOP & Interceptor Demo

本文介绍 Spring 中的 AOP 概念与应用实践,包括如何通过注解和 XML 方式配置 AOP,定义切面、切入点及不同类型的增强。同时提供了一个完整的日志记录切面示例,并解释了 Spring MVC 中的拦截器配置。

AOP

spring支持

// springboot 配置方式
@Configuration
@EnableAspectJAutoProxy
public class AppConfig {
}


// XML 配置方式
<aop:aspectj-autoproxy/>

定义切面

@Component
@Aspect
public class MyTest {
}

概念

  1. joinPoint(连接点):可执行方法就是连接点。
  2. pointCut(切入点):用来描述连接点的一个形容词,可以确定哪些连接点是需要使用advice的。
  3. advice(通知,有人翻译成“增强”,个人觉得通知更形象):before advice:在 join point 前被执行的 advice. 虽然 before advice 是在 join point 前被执行, 但是它并不能够阻止 join point 的执行, 除非发生了异常;after return advice:在一个 join point 正常返回后执行的 advice;after throwing advice:当一个 join point 抛出异常后执行的 advice;after(final) advice:无论一个join point是正常退出还是发生了异常, 都会被执行的 advice;around advice:在 join point 前和 joint point 退出后都执行的 advice. 这个是最常用的 advice。

完整示例


@Aspect //定义一个切面
@Component // 系统启动自动注入spring容器
@Slf4j  // 日志支持
public class LogRecordAspect {
private static final Logger logger = LoggerFactory.getLogger(LogRecordAspect.class);

    // 定义切点Pointcut
    // public : 指方法类型,只有public的方法才会拦截
    // *      : 指返回类型,*表示所有返回类型都拦截
    // org.jeecg.modules.*.*.*Controller.*(..))
    // 包路径-----------------类名--------方法及其参数
    // PS. 一个点代表下一层目录,两个点代表下层所有子目录
    @Pointcut("execution(public * org.jeecg.modules.*.*.*Controller.*(..))")
    public void excudeService() {
    }

    @Around("excudeService()")
    public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
        RequestAttributes ra = RequestContextHolder.getRequestAttributes();
        ServletRequestAttributes sra = (ServletRequestAttributes) ra;
        HttpServletRequest request = sra.getRequest();

        String url = request.getRequestURL().toString();
        String method = request.getMethod();
        String uri = request.getRequestURI();
        String queryString = request.getQueryString();
        logger.info("请求开始, 各个参数, url: {}, method: {}, uri: {}, params: {}", url, method, uri, queryString);

        // result的值就是被拦截方法的返回值,pjp就是被拦截的方法,pjp.proceed()就是调用被拦截方法并返回了Object类型的result
        Object result = pjp.proceed();

        // 在方法拦截之后添加自定义业务逻辑
        logger.info("请求结束,controller的返回值是 " + result);
        return result;
    }
}

@Pointcut executions 表达式

execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern)throws-pattern?) 

括号中各个pattern分别表示:

  • 修饰符匹配(modifier-pattern?)表示方法描述符,public private protected,可省略
  • 返回值匹配(ret-type-pattern)可以为*表示任何返回值,全路径的类名等
  • 类路径匹配(declaring-type-pattern?)
  • 方法名匹配(name-pattern)可以指定方法名 或者 *代表所有, set* 代表以set开头的所有方法
  • 参数匹配((param-pattern))可以指定具体的参数类型,多个参数间用“,”隔开,各个参数也可以用“*”来表示匹配任意类型的参数,如(String)表示匹配一个String参数的方法;(*,String) 表示匹配有两个参数的方法,第一个参数可以是任意类型,而第二个参数是String类型;可以用(..)表示零个或多个任意参数
  • 异常类型匹配(throws-pattern?)
  • 其中后面跟着“?”的是可选项

表达式是一个由两个空格组成的一段字符串,第一段是方法修饰符,第二段是返回类型,第三段是包名+方法名+方法参数

举例说明:
随意公共方法的运行:
execution(public * *(..))
不论什么一个以“set”開始的方法的运行:
execution(* set*(..))
AccountService 接口的随意方法的运行:
execution(* com.xyz.service.AccountService.*(..))
定义在service包里的随意方法的运行:
execution(* com.xyz.service.*.*(..))
定义在service包和全部子包里的随意类的随意方法的运行:
execution(* com.xyz.service..*.*(..))
定义在pointcutexp包和全部子包里的JoinPointObjP2类的随意方法的运行:
execution(* com.test.spring.aop.pointcutexp..JoinPointObjP2.*(..))")

***> 最靠近(..)的为方法名,靠近.*(..))的为类名或者接口名,如上例的JoinPointObjP2.*(..))

 SpringMVC Interceptor

代码示例

public class WxDictAspect implements HandlerInterceptor {
	
	/**
	 * Logger for this class
	 */
	private static final Logger logger = Logger.getLogger(WxDictAspect.class);
	
   /**
	 * 进入handler之前(Controller)
     * 返回false,则不会进入handler,也不会触发后面两个事件
	 */
	@Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {
		System.err.print("preHandle");
		return true;
	}

    /**
	 * handler(Controller) return 之后 & preHandle返回了true
     * ModelAndView 是controller的返回值,controller返回类型是ModelAndView 才能在这接到
	 */
	@Override
	@ResponseBody
	public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
			ModelAndView modelAndView) throws Exception {
        // 修改handler返回的数据
		Result<?> res = (Result<?>) modelAndView.getModel().get("result");
		res.setMessage("(キ`゚Д゚´)!!");
		System.err.print("postHandle");
	}

    /**
	 * postHandle处理完 & preHandle返回了true
     * 多用来回收资源或者异常处理(参数列表最后一个是异常)
	 */
	@Override
	public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
			throws Exception {
		if (null != ex) {			
			logger.error(ex.getMessage());
		}
		
	}
}

spring-mvc.xml 配置

    <mvc:interceptors> 
       <mvc:interceptor>
		    <mvc:mapping path="/testController.do*" />
			<mvc:exclude-mapping path="/websocket.do"/>
			<mvc:exclude-mapping path="/websocket/sockjs.do"/>
			<bean class="com.myproject.wx.enhance.aspect.WxDictAspect"></bean>		
		</mvc:interceptor>
	</mvc:interceptors>

SpringMvc架构 aop 在controller层失效

因为Spring的Bean扫描和Spring-MVC的Bean扫描是分开的, 两者的Bean位于两个不同的Application, 而且Spring-MVC的Bean扫描要早于Spring的Bean扫描, 所以当Controller Bean生成完成后, 再执行Spring的Bean扫描,Spring会发现要被AOP代理的Controller Bean已经在容器中存在, 配置AOP就无效了.

同样这样的情况也存在于数据库事务中, 如果Service的Bean扫描配置在spring-mvc.xml中, 而数据库事务管理器配置在application.xml中, 会导致数据库事务失效, 原理一样.

所以这里 ,我们需要把AOP放置在Controller扫描配置的文件中.

评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值