springMVC AOP for Controller with Annotation assistent

15 篇文章 0 订阅

前提

情景:记录用户操作日志

项目基础框架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才会执行到对异常的捕获方法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值