spring AOP 之五:Spring MVC通过AOP切面编程来拦截controller

示例1:通过包路径及类名规则为应用增加切面

该示例是通过拦截所有com.dxz.web.aop包下的以Controller结尾的所有类的所有方法,在方法执行前后打印和记录日志到数据库。

新建一个springboot项目

1:首先定义maven

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.dxz.auth</groupId>
    <artifactId>auth-demo1</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>auth-demo1</name>
    <description>Demo project for Spring Boot</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.9.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context-support</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc-portlet</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>


</project>

2:在拦截controller之前需要自定义一个注解,该注解是放在需要通过AOP织入系统日志的方法上。

package com.dxz.web.aop;

import java.lang.annotation.*;

@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SystemLog {
    String module()  default "";
    String methods()  default "";
}

3:定义记录日志的切面

package com.dxz.web.aop;

import java.lang.reflect.Method;
import java.util.Date;

import javax.servlet.http.HttpServletRequest;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
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.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

@Component
@Aspect
public class LogAopAction {
    
    // 获取开始时间
    private long BEGIN_TIME;

    // 获取结束时间
    private long END_TIME;

    // 定义本次log实体
    private LogModel logModel = new LogModel();

    @Pointcut("execution(* com.dxz.web.aop.*Controller.*(..))")
    private void controllerMethodAspect() {
    }

    /**
     * 方法开始执行
     */
    @Before("controllerMethodAspect()")
    public void doBefore() {
        BEGIN_TIME = new Date().getTime();
        System.out.println("aop--开始");
    }

    /**
     * 方法结束执行
     */
    @After("controllerMethodAspect()")
    public void after() {
        END_TIME = new Date().getTime();
        System.out.println("aop--结束");
    }

    /**
     * 方法结束执行后的操作
     */
    @AfterReturning("controllerMethodAspect()")
    public void doAfter() {

        if (logModel.getState() == 1 || logModel.getState() == -1) {
            logModel.setActionTime(END_TIME - BEGIN_TIME);
            logModel.setGmtCreate(new Date(BEGIN_TIME));
            System.out.println("aop--将logModel="+logModel +",存入到数据库");
        } else {
            System.out.println(logModel);
            System.out.println("aop-->>>>>>>>不存入到数据库");
        }
    }

    /**
     * 方法有异常时的操作
     */
    @AfterThrowing("controllerMethodAspect()")
    public void doAfterThrow() {
        System.out.println("aop--例外通知-----------------------------------");
    }

    /**
     * 方法执行
     * 
     * @param pjp
     * @return
     * @throws Throwable
     */
    @Around("controllerMethodAspect()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        // 日志实体对象
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
                .getRequest();
        // 获取当前登陆用户信息
        String uid = request.getParameter("uid");
        if (uid == null) {
            logModel.setLoginAccount("—— ——");
        } else {
            logModel.setLoginAccount(uid);
        }

        // 拦截的实体类,就是当前正在执行的controller
        Object target = pjp.getTarget();
        // 拦截的方法名称。当前正在执行的方法
        String methodName = pjp.getSignature().getName();
        // 拦截的方法参数
        Object[] args = pjp.getArgs();
        // 拦截的放参数类型
        Signature sig = pjp.getSignature();
        MethodSignature msig = null;
        if (!(sig instanceof MethodSignature)) {
            throw new IllegalArgumentException("该注解只能用于方法");
        }
        msig = (MethodSignature) sig;
        Class[] parameterTypes = msig.getMethod().getParameterTypes();

        Object object = null;

        Method method = null;
        try {
            method = target.getClass().getMethod(methodName, parameterTypes);
        } catch (NoSuchMethodException e1) {
            // TODO Auto-generated catch block
            e1.printStackTrace();
        } catch (SecurityException e1) {
            // TODO Auto-generated catch block
            e1.printStackTrace();
        }

        if (null != method) {
            // 判断是否包含自定义的注解,说明一下这里的SystemLog就是我自己自定义的注解
            if (method.isAnnotationPresent(SystemLog.class)) {
                SystemLog systemlog = method.getAnnotation(SystemLog.class);
                logModel.setModule(systemlog.module());
                logModel.setMethod(systemlog.methods());
                logModel.setLoginIp(getIp(request));
                logModel.setActionUrl(request.getRequestURI());

                try {
                    object = pjp.proceed();
                    logModel.setDescription("执行成功");
                    logModel.setState((short) 1);
                } catch (Throwable e) {
                    // TODO Auto-generated catch block
                    logModel.setDescription("执行失败");
                    logModel.setState((short) -1);
                }
            } else {// 没有包含注解
                object = pjp.proceed();
                logModel.setDescription("此操作不包含注解");
                logModel.setState((short) 0);
            }
        } else { // 不需要拦截直接执行
            object = pjp.proceed();
            logModel.setDescription("不需要拦截直接执行");
            logModel.setState((short) 0);
        }
        return object;
    }

    /**
     * 获取ip地址
     * 
     * @param request
     * @return
     */
    private String getIp(HttpServletRequest request) {
        if (request.getHeader("x-forwarded-for") == null) {
            return request.getRemoteAddr();
        }
        return request.getHeader("x-forwarded-for");
    }
}

 其中我的LogModel实体类如下:

package com.dxz.web.aop;

import java.util.Date;

public class LogModel {
    
    /**日志id */
    private Integer id;

    /** * 当前操作人id */
    private String loginAccount;

    /**当前操作人ip */
    private String loginIp;

    /**操作请求的链接     */
    private String actionUrl;

    /**执行的模块 */
    private String module;

    /**执行的方法 */
    private String method;

    /**执行操作时间 */
    private Long actionTime;

    /** 描述     */
    private String description;

    /** 执行的时间 */
    private Date gmtCreate;

    /** 该操作状态,1表示成功,-1表示失败! */
    private Short state;
        //set()/get()
}

4:业务controller中增加@SystemLog注解

package com.dxz.web.aop;

import javax.servlet.http.HttpServletRequest;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/user")
public class LoginController {

    /**
     * 登录方法
     * @param request
     * @return
     */
    @RequestMapping(value="/toLogin", method = RequestMethod.GET, produces = {"application/json;charset=UTF-8"})
    @SystemLog(methods="用户管理", module = "用户登录")
    public String toLogin(HttpServletRequest request) {
        System.out.println("biz--登录验证中");
        return "login";
    }
}

5、springboot配置类

package com.dxz;

import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@SpringBootApplication
@EnableAspectJAutoProxy
@ComponentScan
public class AuthDemo1Application {

    public static void main(String[] args) {
        new SpringApplicationBuilder(AuthDemo1Application.class).web(true).run(args);
    }
}

启动springboot后,

通过浏览器访问:http://localhost:8080/user/toLogin?uid=duanxz后的结果如下:

二、@Pointcut("execution(* com.dxz.web.aop.*Controller.*(..))")是“com.dxz.web.aop”包下所有以Controller结尾的类的所有方法,为了验证,我再增加2个controller如下:

package com.dxz.web.aop;

import javax.servlet.http.HttpServletRequest;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/user")
public class LogoutController {

    /**
     * 退出方法
     * @param request
     * @return
     */
    @RequestMapping(value="/logout", method = RequestMethod.GET, produces = {"application/json;charset=UTF-8"})
    @SystemLog(methods="用户管理", module = "用户退出")
    public String logout(HttpServletRequest request) {
        System.out.println("biz--退出逻辑");
        return "logout";
    }
}
package com.dxz.web.aop;

import javax.servlet.http.HttpServletRequest;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/user")
public class TestController2 {

    /**
     * test方法
     * @param request
     * @return
     */
    @RequestMapping(value="/test", method = RequestMethod.GET, produces = {"application/json;charset=UTF-8"})
    @SystemLog(methods="用户管理", module = "用户测试")
    public String test(HttpServletRequest request) {
        System.out.println("biz--test");
        return "test";
    }
}

结果:

 示例2:通过within()指定所有@RestController注解类 + @annotation()指定方法上有@Auth注解

package com.dxz.web.aop.auth;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Order(Ordered.HIGHEST_PRECEDENCE) //优先级,暂定最高级
public @interface Auth {
    boolean login() default false;
}

package com.dxz.web.aop.auth;

import javax.servlet.http.HttpServletRequest;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class AuthAopAction {

    @Before("within(@org.springframework.web.bind.annotation.RestController *) && @annotation(authParam)")
    public void requestLimit(JoinPoint joinPoint, Auth authParam) throws AuthException {
        HttpServletRequest request = null;
        try {
            Object[] args = joinPoint.getArgs();
            for (int i = 0; i < args.length; i++) {
                if (args[i] instanceof HttpServletRequest) {
                    request = (HttpServletRequest) args[i];
                    break;
                }
            }
            if (null == request) {
                System.out.println("auth handler error : target:[{}] no param : HttpServletRequest");
                throw new AuthException("HttpServletRequest is null error.");
            }
            if (null != authParam && authParam.login()) {
                // 登录权限验证开启
                //Object userId = request.getSession().getAttribute("uid");
                Object userId = request.getParameter("uid");
                if ("duanxz".equals(userId)) {
                    System.out.println("账号正确,成功登录");
                } else {
                    System.out.println("账号不正确,需要重新登录");
                    throw new AuthException("NEED_LOGIN");
                }
            }
        } catch (Exception e) {
            System.out.println("auth handler error : exception:{}" + e.getMessage());
            throw e;
        }
    }
}
package com.dxz.web.aop.auth;
public class AuthException extends Exception {

    private static final long serialVersionUID = -1341655401594111052L;

    public AuthException() {
        super();
    }

    public AuthException(String message) {
        super(message);
    }

}

下面的交易controller用于是否登录的权限验证

package com.dxz.web.aop;

import javax.servlet.http.HttpServletRequest;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import com.dxz.web.aop.auth.Auth;


@Controller
@RestController
@RequestMapping("/pay")
public class PayController {

    @Auth(login = true)
    @RequestMapping(value="prePay", method = RequestMethod.GET, produces = {"application/json;charset=UTF-8"})
    public String prePay(HttpServletRequest request) {
        String result = "预交易,uid=" + request.getParameter("uid");
        System.out.println(result);
        return result;
    }
}

浏览器访问:

http://localhost:8080/pay/prePay?uid=duanxz

及   http://localhost:8080/pay/prePay?uid=duanxz2的结果如下:

原文地址:https://www.cnblogs.com/duanxz/p/5226304.html