Spring AOP 的实现方式(以日志管理为例)
Spring AOP 的实现方式(以日志管理为例)
本Markdown编辑器使用[StackEdit][6]修改而来,用它写博客,将会带来全新的体验哦:
-
Find Action Ctrl+Shift+A,在此对话框中输入想操作的英文立即出现想的操作
-
System.out.println输入sout即可
-
for循环N输入N.for即可
-
return n;输入n.return即可
-
离线写博客
-
导入导出Markdown文件
-
丰富的快捷键
AOP的概念
AOP(Aspect Oriented Programming),是面向切面编程的技术。AOP基于IoC基础,是对OOP的有益补充,流行的AOP框架有Sping AOP、AspectJ
AOP技术它利用一种称为“横切”的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为”Aspect”,即切面。所谓”切面”,简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性
相关概念:
1、切面(aspect)
散落在系统各处的通用的业务逻辑代码,如上图中的日志模块,权限模块,事务模块等,切面用来装载pointcut和advice
2、通知(advice)
所谓通知指的就是指拦截到连接点之后要执行的代码,通知分为前置、后置、异常、最终、环绕通知五类
3、连接点(joinpoint)
被拦截到的点,因为Spring只支持方法类型的连接点,所以在Spring中连接点指的就是被拦截到的方法,实际上连接点还可以是字段或者构造器
4、切入点(pointcut)
拦截的方法,连接点拦截后变成切入点
6、目标对象(Target Object)
代理的目标对象,指要织入的对象模块,如上图的模块一、二、三
7、织入(weave)
通过切入点切入,将切面应用到目标对象并导致代理对象创建的过程
8、AOP代理(AOP Proxy)
AOP框架创建的对象,包含通知。在Spring中,AOP代理可以是JDK动态代理或CGLIB代理
五种类型的通知
-
Before advice:在某连接点(JoinPoint)之前执行的通知,但这个通知不能阻止连接点前的执行。
< aop:before> -
After advice:当某连接点退出的时候执行的通知(不论是正常返回还是异常退出)。
< aop:after> -
After returnadvice:在某连接点正常完成后执行的通知,不包括抛出异常的情况。
< aop:after-returning> -
Around advice:包围一个连接点的通知,类似Web中Servlet规范中的Filter的doFilter方法。可以在方法执行的前后实现逻辑,也可以选择不执行方法
< aop:around> -
Afterthrowing advice:在方法抛出异常退出时执行的通知。
< aop:after-throwing>
五种类型的通知
基于xml配置的实现
- spring-mvc.xml
<!-- 使用xml配置aop -->
<!-- 强制使用cglib代理,如果不设置,将默认使用jdk的代理,但是jdk的代理是基于接口的 -->
<aop:config proxy-target-class="true" />
<aop:config>
<!--定义切面-->
<aop:aspect id="logAspect" ref="logInterceptor">
<!-- 定义切入点 (配置在com.gray.user.controller下所有的类在调用之前都会被拦截)-->
<!--
第一个*:表示任意返回值
com.gray.user.controller表是包路径
第二个*:表示包下任意类
第三个*:表示类的任意方法
(..):表示任意参数
-->
<aop:pointcut expression="execution(* com.gray.user.controller.*.*(..))" id="logPointCut"/>
<!--方法执行之前被调用执行的-->
<aop:before method="before" pointcut-ref="logPointCut"/><!--一个切入点的引用-->
<aop:after method="after" pointcut-ref="logPointCut"/><!--一个切入点的引用-->
</aop:aspect>
</aop:config>
- 切入点通知方法
package com.gray.interceptor;
import org.springframework.stereotype.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Component
public class LogInterceptor {
private final Logger logger = LoggerFactory.getLogger(LogInterceptor.class);
public void before(){
logger.info("login start!");
}
public void after(){
logger.info("login end!");
}
}
- 当我们访问配置切入点的controller时就会走到通知方法
基于注解的实现
- spring-mvc.xml
<aop:aspectj-autoproxy proxy-target-class="true">
</aop:aspectj-autoproxy>
- 切入点通知方法
@Aspect
@Component
public class LogInterceptor {
private final Logger logger = LoggerFactory.getLogger(LogInterceptor.class);
@Before(value = "execution(* com.gray.user.controller.*.*(..))")
public void before(){
logger.info("login start!");
}
@After(value = "execution(* com.gray.user.controller.*.*(..))")
public void after(){
logger.info("login end!");
}
}
- 原理同上,只是在xml文件引用入了aop,并没有指定aop要切入哪个方法,而在通知类上注入
@Aspect
(告诉spring这个配置类是aop通知类),通过注解方式实现切点的定义。
基于自定义注解的实现
- spring-mvc.xml
<aop:aspectj-autoproxy proxy-target-class="true">
</aop:aspectj-autoproxy>
- 切入点通知方法
package com.gray.interceptor;
import java.lang.reflect.Method;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import com.gray.annotation.Log;
@Aspect
@Component
public class LogInterceptor {
private final Logger logger = LoggerFactory.getLogger(LogInterceptor.class);
@Pointcut("@annotation(com.gray.annotation.Log)")
public void controllerAspect() {
}
@Before("controllerAspect()")
public void before(JoinPoint joinPoint){
logger.info("login before!");
}
@After("controllerAspect()")
public void After(JoinPoint joinPoint){
logger.info("login After!");
}
}
- 定义好切点和通知方法,需要注入切入方式,并在切入方式后面加上切点方法
补充
- joinPoint参数能获取到访问类及方法名称等一些关键信息
//获得IP
final HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
System.out.println("访问客户端IP地址为:"+request.getRemoteAddr());
//类名
System.out.println("访问类名为:"+joinPoint.getTarget().getClass().getSimpleName());
// 方法名
System.out.println("访问方法名为:"+joinPoint.getSignature().getName());
//参数
StringBuilder param=new StringBuilder();
for (int i = 0; i < joinPoint.getArgs().length; i++) {
param.append("参数").append(i+1).append(":").append(joinPoint.getArgs()[i]);
}
System.out.println("访问方法参数为:"+param.toString());