前提
情景:记录用户操作日志
项目基础框架L: springMVC
实现:希望使用注解的方式,在其中定义操作类型,方便书写
开始
1. 开启注解方式使用AOP:
在spring的配置文件中添加配置开启(注意,是那个在web.xml中dispacherServlet属性里面配置的配置文件,至于为什么?自己思考:),提示:springMVC是spring的子容器)
<aop:aspectj-autoproxy proxy-target-class="true"/>
2. 创建自己的注解 以及 类型
类型:
package com.jfqqqq.common.enums;
public enum LogOperationEnum {
UNEXCEPTED("未设置",-1),
LOGIN("登录",1),LOGOUT("登出",2)
;
private String value;
private Integer code;
LogOperationEnum(String value, Integer code) {
this.value = value;
this.code = code;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public static String codeOf(Integer code) {
LogOperationEnum[] operationEnums = LogOperationEnum.values();
for (LogOperationEnum operationEnum : operationEnums) {
if (operationEnum.getCode().equals(code)) {
return operationEnum.getValue();
}
}
return null;
}
}
注解(其中的values是做一些特殊处理用的,比如一个controlle的接口可能会有多种操作,那就传递复数到这里,然后在切面里做判断,执行相应处理,并不晚上,仅供娱乐 ):
package com.jfqqqq.common.annotation;
import com.jfqqqq.common.enums.LogOperationEnum;
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Inherited
@Retention(value = RetentionPolicy.RUNTIME)
public @interface LogFlag {
LogOperationEnum value() default LogOperationEnum.UNEXCEPTED;
LogOperationEnum[] values() default {};
}
3. 编写切面
package com.jfqqq.test.client.cms.web.controller;
import com.jfqqq.test.client.cms.log.LogRecoder;
import com.jfqqq.test.common.Content;
import com.jfqqq.test.pojo.User;
import com.jfqqq.test.common.annotation.LogFlag;
import com.jfqqq.test.common.enums.LogOperationEnum;
import com.jfqqq.test.pojo.User;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.lang.reflect.Method;
@Aspect
@Component
public class ControllerProxy {
@Autowired
private LogRecoder logRecoder;
@Pointcut("execution(* com.jfqqq.test.client.cms.web.controller.*Controller.*(..))")
private void controllerMethodAspect() {
}
/**
* 方法开始执行
*/
@Before("controllerMethodAspect()")
public void doBefore() {
}
/**
* 方法结束执行
*/
@After("controllerMethodAspect()")
public void after() {
}
/**
* 方法结束执行后的操作
*/
@AfterReturning("controllerMethodAspect()")
public void doAfterReturnning() {
}
/**
* 方法有异常时的操作
*/
@AfterThrowing("controllerMethodAspect()")
public void doAfterThrow() {
//这一段代码在controller出现异常时会被调用
//就算在@Around修饰的方法里面对 pjp.proceed() 进行了try catch,也是不受影响的
//而且,是先执行本方法,然后再进入catch中
}
/**
* 方法执行
*
* 在around中对joinPoint.proceed();进行了try catch后,如果不把异常向上抛出,
* controllerAdvice就会因无法察觉异常而不会被执行。
* 也就是说,controllerAdvice在我定义的controllerAOP的外层
* @param pjp
* @return
* @throws Throwable
*/
@Around("controllerMethodAspect()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
boolean shouldRecord = false;//是否需要记录日志
boolean beenRecorded = false;//是否已经被记录过日志
Signature signature = null;
MethodSignature methodSignature = null;
Method method = null;
LogFlag annotation = null;
RequestAttributes requestAttributes = null;
HttpServletRequest request = null;
HttpSession session = null;
User user = null;
Object[] args = null;
// pjpThreadLocal.set(pjp);
try {
// ProceedingJoinPoint pjp = pjpThreadLocal.get();
if (pjp != null) {
signature = pjp.getSignature();
if (signature != null && signature instanceof MethodSignature) {
methodSignature = (MethodSignature) signature;
method = methodSignature.getMethod();//(signature.getName());
annotation = method.getAnnotation(LogFlag.class);
if (annotation != null) {
shouldRecord = true;//不等于null就说明需要记录日志
requestAttributes = RequestContextHolder.getRequestAttributes();
if (requestAttributes != null) {
if ((request = ((ServletRequestAttributes) requestAttributes).getRequest()) != null
&& (session = request.getSession()) != null
&& (user = (User) session.getAttribute(Content.SESSION_USER)) != null) {
args = pjp.getArgs();
LogOperationEnum logOperationEnum = wrappeAttribute(annotation, args);
logRecoder.commit(logOperationEnum, user);
beenRecorded = true;//记录过了
}
}
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
Object object = pjp.proceed();
if (shouldRecord && !beenRecorded) {
try {
// ProceedingJoinPoint pjp = pjpThreadLocal.get();
if (session != null && (user = (User) session.getAttribute(Content.SESSION_USER)) != null) {
LogOperationEnum logOperationEnum = wrappeAttribute(annotation, args);
logRecoder.commit(logOperationEnum, user);
}
} catch (Exception e) {
e.printStackTrace();
}
}
return object;
}
private LogOperationEnum wrappeAttribute(LogFlag annotation, Object[] args) {
LogOperationEnum[] values = annotation.values();
if (values != null && values.length > 0) {
for (LogOperationEnum value : values) {
if (value == LogOperationEnum.ADD_BLACK) {//证明是要做黑名单的特殊处理
User arg = getUser(args);
if (arg != null) {
if (0 == arg.getWhiteOrBlock()) {//白 : 0 ; 黑 : 1
return LogOperationEnum.REMOVE_BLACK;
} else {
return LogOperationEnum.ADD_BLACK;
}
}
}
}
}
return annotation.value();
}
private static User getUser(Object[] args) {
for (Object arg : args) {
if (arg instanceof User) {
return (User) arg;
}
}
return null;
}
}
4. controller使用注解
@LogFlag(LogOperationEnum.LOGIN)
@PostMapping("/login")
public Message realNameAuthentication(HttpServletRequest request) {
//...
}
@LogFlag(values = {LogOperationEnum.ADD_BLACK, LogOperationEnum.REMOVE_BLACK})
@RequestMapping(value = "/edit")
@ResponseBody
public Integer edit(User user) {
if(user.type == true){
// 激活
}else{
// 禁用
}
return user.type;
}
5. 完成
------------------------------------------------ 升级版 -------------------------------------------------------------------
around1
doBefore...
service.service
afterThrowing...
doAfter...
around2
扩展
controller AOP是对controller的包裹,它的逻辑在controllerAdivce里边,因此,controller的方法执行出现异常后,会先被aop捕获到,然后aop将异常跑出去,advice才会执行到对异常的捕获方法。