示例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的結果如下: