Spring AOP 简介
AOP 即 Aspect Oriented Program 面向切面编程
首先,在面向切面编程的思想里面,把功能分为核心业务功能,和周边功能。
所谓的核心业务,比如登陆,增加数据,删除数据都叫核心业务
所谓的周边功能,比如性能统计,日志,事务管理等等
周边功能在 Spring 的面向切面编程AOP思想里,即被定义为切面
在面向切面编程AOP的思想里面,核心业务功能和切面功能分别独立进行开发,然后把切面功能和核心业务功能 “编织” 在一起,这就叫AOP
AOP 的目的
AOP能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。
AOP 当中的概念:
切入点(Pointcut)
在哪些类,哪些方法上切入(where)
通知(Advice)
在方法执行的什么实际(when:方法前/方法后/方法前后)做什么(what:增强的功能)
切面(Aspect)
切面 = 切入点 + 通知,通俗点就是:在什么时机,什么地方,做什么增强!
织入(Weaving)
把切面加入到对象,并创建出代理对象的过程。(由 Spring 来完成)
AOP 的一个思想:让关注点代码与业务代码分离!
来自 https://www.jianshu.com/p/994027425b44
一、自定义注解
方法上的注解
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface OpLog {
String name() default "";
XX value();
}
参数上的注解
@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface OpLogParam {
String name() default "";
}
二、切面类
@Aspect
@Component
@Slf4j
public class SystemLogAspect {
@Autowired
private XXXDao xx;
@Autowired
private SessionService sessionService;
/**
* 切入点
*/
@Pointcut("execution (* com.xx.xx.controller.*.*(..))")
public void xxxControllerAspect() {
throw new UnsupportedOperationException();
}
/**
* 配置后置返回通知,使用在方法aspect()上注册的切入点
* @param joinPoint JoinPoint
* @param rvt BaseResponse
*/
@AfterReturning(value = "xxxControllerAspect()",returning = "rvt")
public void afterReturn(JoinPoint joinPoint, BaseResponse<Object> rvt){
try {
SysOperateLog sysOperateLog = joinPoint(joinPoint);
if (!BaseResponse.DEFAULT_CODE.equals(rvt.getCode())) {
xx.setDes("输入: " + xx.getDes() + ",输出非200: " + rvt.getMessage());
}
xxxDao.insert(xxx);
} catch (Exception e) {
log.error("AOP操作异常: {}",e);
}
}
@AfterThrowing(value = "xxControllerAspect()",throwing = "ex")
public void afterThrowing(JoinPoint joinPoint,Throwable ex) {
XX xx = joinPoint(joinPoint);
xx.setDes("输入: "+xxx.getDes()+",输出异常: "+ex.getMessage());
xxxDao.insert(sysOperateLog);
}
private XXX joinPoint(JoinPoint joinPoint) {
ServletRequestAttributes sra = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
//获取IP
String ip = getIp(sra);
//获取用户名
String userName = sessionService.getUserName();
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
//获取方法上的注解
OpLog opLog = getType(method);
//给对象赋值
SysOperateLog sysOperateLog = new SysOperateLog();
if (opLog!=null){
//获取方法参数上的注解
String des = getDes(method, joinPoint);
xxx.setType(opLog.value().toString());
xxx.setDes(des);
xxx.setIp(ip);
xxx.setUsername(userName);
}
return sysOperateLog;
}
/**
* 获取IP
* @param sra
* @return
*/
private String getIp(ServletRequestAttributes sra){
String ip = "" ;
if(sra != null){
HttpServletRequest request=sra.getRequest();
ip = IPUtil.getIPAddress(request) ;
}
return ip;
}
/**
* 方法参数处理
* @param method
* @return
*/
private OpLog getType(Method method){
//获取方法上的注解
return method.getDeclaredAnnotation(OpLog.class);
}
/**
* 参数注解处理
* @param method
* @param joinPoint
* @return
*/
private String getDes(Method method,JoinPoint joinPoint) {
Annotation[][] parameterAnnotations= method.getParameterAnnotations();
//获取所有参数注解返回的是一个二维数组Annotation[][],每个参数上可能有多个注解,是一个一维数组,
//再根据Object[] args= joinPoint.getArgs();拿到所有的参数,根据指定的下标即可拿到对象的值
Object[] args= joinPoint.getArgs();
for (Annotation[] parameterAnnotation: parameterAnnotations) {
int paramIndex= ArrayUtils.indexOf(parameterAnnotations, parameterAnnotation);
for (Annotation annotation: parameterAnnotation) {
if (annotation instanceof OpLogParam){
String name = ((OpLogParam) annotation).name();
Object paramValue = args[paramIndex];
//判断是否是MultipartFile类型
Class<?> aClass = paramValue.getClass();
if (MultipartFile.class.isAssignableFrom(aClass)){
MultipartFile multipartFile = (MultipartFile) paramValue;
return name+":"+multipartFile.getOriginalFilename();
}
return name+":"+paramValue;
}
}
}
return null;
}
}
IPUtil类
public class IPUtil {
private static final String UNKNOWN = "unknown";
private IPUtil(){}
public static String getIPAddress(HttpServletRequest request) {
String ip = null;
//X-Forwarded-For:Squid 服务代理
String ipAddresses = request.getHeader("X-Forwarded-For");
if (ipAddresses == null || ipAddresses.length() == 0 || UNKNOWN.equalsIgnoreCase(ipAddresses)) {
//Proxy-Client-IP:apache 服务代理
ipAddresses = request.getHeader("Proxy-Client-IP");
}
if (ipAddresses == null || ipAddresses.length() == 0 || UNKNOWN.equalsIgnoreCase(ipAddresses)) {
//WL-Proxy-Client-IP:weblogic 服务代理
ipAddresses = request.getHeader("WL-Proxy-Client-IP");
}
if (ipAddresses == null || ipAddresses.length() == 0 || UNKNOWN.equalsIgnoreCase(ipAddresses)) {
//HTTP_CLIENT_IP:有些代理服务器
ipAddresses = request.getHeader("HTTP_CLIENT_IP");
}
if (ipAddresses == null || ipAddresses.length() == 0 || UNKNOWN.equalsIgnoreCase(ipAddresses)) {
//X-Real-IP:nginx服务代理
ipAddresses = request.getHeader("X-Real-IP");
}
//有些网络通过多层代理,那么获取到的ip就会有多个,一般都是通过逗号(,)分割开来,并且第一个ip为客户端的真实IP
if (ipAddresses != null && ipAddresses.length() != 0) {
ip = ipAddresses.split(",")[0];
}
//还是不能获取到,最后再通过request.getRemoteAddr();获取
if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ipAddresses)) {
ip = request.getRemoteAddr();
}
return ip != null && ip.equals("0:0:0:0:0:0:0:1")?"127.0.0.1":ip;
}
}
controller层
@PostMapping(value = "/insert")
@OpLog(name = " ",value = XX.XX)
public void insert(@OpLogParam(name = "设备类型信息") @RequestBody XX xx) {
//获取service层代码
}