選擇spring的AOP還是AspectJ?spring確實有自己的AOP。功能已經基本夠用了,除非你的要在接口上動態代理或者方法攔截精確到getter和setter,一般不使用。
②在使用AOP的時候,你是用xml還是注解的方式(@Aspect)?
1)如果使用xml方式,不需要任何額外的jar包。
2)如果使用@Aspect方式,你就可以在類上直接一個@Aspect就搞定,不用費事在xml里配了。但是這需要額外的jar包( aspectjweaver.jar)。因為spring直接使用AspectJ的注解功能,注意只是使用了它 的注解功能而已。並不是核心功能 !!!
如果要實現基於接口的動態代理使用full aspectJ
如果用full AspectJ。比如說Load-Time Weaving的方式 還 需要額外的jar包 spring-instrument.jar
當然,無論是使用spring aop還是 aspectj都需要aspectjweaver.jar spring-aop.jar這兩個jar包。
1. AOP:Aspect Oriented Programming(面向切面編程)
2. 利用動態代理實現面向切面編程(底層原理是動態代理這你理解的沒錯)
3. Spring實現動態代理配置是有兩種配置文件:
1、xml文件方式;
2、annotation方式(使用AspectJ類庫實現的。)
4. aspectJ類庫,AspectJ是一個專門用來實現動態代理(AOP編程)的類庫,AspectJ是面向切面編程的框架,Spring使用就是這個類庫實現動態代理的
5. aspectj的專業術語:
1、JoinPoint連接點(切入點)
2、PointCut切入點,當需要定義一個切入點時,則需要使用這個
3、Aspect切面
4、Advice切入點的邏輯
5、Target被代理對象
6、Weave織入
@EnableAspectJAutoProxy:
表示開啟AOP代理自動配置,如果配@EnableAspectJAutoProxy表示使用cglib進行代理對象的生成;設置@EnableAspectJAutoProxy(exposeProxy=true)表示通過aop框架暴露該代理對象,aopContext能夠訪問.
從@EnableAspectJAutoProxy的定義可以看得出,它引入AspectJAutoProxyRegister.class對象,該對象是基於注解@EnableAspectJAutoProxy注冊一個AnnotationAwareAspectJAutoProxyCreator,該對象通過調用AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);注冊一個aop代理對象生成器。
SpringBoot為我們提供了一個 spring-boot-starter-aop 自動配置模塊。
spring-boot-starter-aop 自動配置行為由兩部分內容組成:
- 位於 spring-boot-autoconfigure的org.springframework.boot.autoconfigure.aop.AopAutoConfiguration 提供 @Configuration 配置類和相應的配置項。
- spring-boot-starter-aop 模塊自身提供了針對 spring-aop、aspectjrt 和 aspectjweaver 的依賴。
一般情況下,只要項目依賴中加入了 spring-boot-starter-aop,其實就會自動觸發 AOP 的關聯行為,包括構建相應的 AutoProxyCreator,將橫切關注點織入(Weave)相應的目標對象等,不過 AopAutoConfiguration 依然為我們提供了可憐的兩個配置項,用來有限地干預 AOP 相關配置:
- spring.aop.auto=true
- spring.aop.proxy-target-class=false
用戶可以選擇關閉自動的 aop 配置(spring.aop.auto=false),或者啟用針對 class 而不是 interface 級別的 aop 代理(aop proxy)。
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
注:
AOP依賴包后,不需要去做其他配置。AOP的默認配置屬性中,spring.aop.auto屬性默認是開啟的,也就是說只要引入了AOP依賴后,默認已經增加了@EnableAspectJAutoProxy,不需要再在程序主類中增加@EnableAspectJAutoProxy了。
代碼實現:
日志注解:
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * @author zhao * */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Log { String value() default ""; }
定義切面:
package com.could.demo.aop.log.aspect; import com.could.demo.aop.log.entity.Log; import com.could.demo.aop.log.service.LogService; import com.could.demo.aop.log.utils.RequestHolder; import com.could.demo.entity.User; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.AfterThrowing; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import javax.servlet.http.HttpServletRequest; import java.util.Arrays; /** * @author zhao * @date 2019-03-27 */ @Component @Aspect @Slf4j//如果不想每次都寫private final Logger logger = LoggerFactory.getLogger(當前類名.class); 可以用注解@Slf4j; 安裝Lombok插件和配置lombok依賴; public class LogAspect { @Autowired LogService logService; private ThreadLocal<Long> currentTime = new ThreadLocal<>(); /** * 配置切入點,通用的攔截切面 */ @Pointcut("@annotation(com.could.demo.aop.log.Log)") public void logPointcut() { // 該方法無方法體,主要為了讓同類中其他方法使用此切入點 } /** * 配置環繞通知,使用在方法logPointcut()上注冊的切入點 * * @param joinPoint join point for advice */ @Around("logPointcut()") public void logAround(ProceedingJoinPoint joinPoint) throws Throwable { log.info("@Slf4j注解測試");//2020-03-27 17:05:58.787 INFO 9636 --- [nio-8085-exec-2] com.could.demo.aop.log.aspect.LogAspect : @Slf4j注解測試 currentTime.set(System.currentTimeMillis()); joinPoint.proceed(); Log log1 = new Log(1, System.currentTimeMillis() - currentTime.get()); HttpServletRequest request = RequestHolder.getHttpServletRequest(); //getRequestURL() 請求的是不帶參數的完整路徑 :http://localhost:8083/Aop/getUserDetails/45 logService.save(getUsername(), request.getRequestURL().toString(), joinPoint, log1); currentTime.remove(); } /** * 配置異常通知 * * @param joinPoint join point for advice * @param e exception */ @AfterThrowing(pointcut = "logPointcut()", throwing = "e") public void logAfterThrowing(JoinPoint joinPoint, Throwable e) { Log logError = new Log(0,System.currentTimeMillis() - currentTime.get()); currentTime.remove(); logError.setExceptionDetail(Arrays.toString(e.getStackTrace()).getBytes()); HttpServletRequest request = RequestHolder.getHttpServletRequest(); logService.save(getUsername(),request.getRequestURL().toString(),(ProceedingJoinPoint)joinPoint, logError); } /** * 獲取的的當前登錄用戶名 * @return */ public String getUsername() { /** * 獲取Security 中的用戶名 * obj = getUserDetails(); * return new JSONObject(obj).get("username", String.class); */ /** * 獲取session中的用戶名 */ Object user1 = RequestHolder.getHttpServletRequest().getSession().getAttribute("user"); if(user1!=null){ User user = (User)user1 ; return user.getName(); } return ""; } }
全局獲取HttpServletRequest
/** * 獲取 HttpServletRequest */ public class RequestHolder { public static HttpServletRequest getHttpServletRequest() { return ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest(); } }
controller及@Log使用,只需在要存儲日志信息的請求上添加@Log注解即可
package com.could.demo.aop.log.controller; import com.could.demo.aop.log.Log; import com.could.demo.entity.User; import org.springframework.web.bind.annotation.*; import javax.servlet.http.HttpServletRequest; @RestController public class LogController { @Log("獲取用戶詳情") @GetMapping(value = "/getUserDetails/{ss}") public String userDetails(@PathVariable("ss") String ss) { return" success"+ss; } @Log("用戶登錄") @GetMapping(value = "/login") public void login(HttpServletRequest request ) { User user = new User(); user.setName("aop打印日志測試號"); request.getSession().setAttribute("user",user); } @Log("測試") @GetMapping(value = "/test") public String test( ) { return "ok"; } }
dao
package com.could.demo.aop.log.dao; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.could.demo.aop.log.entity.Log; public interface LogDao extends BaseMapper<Log> { }
實體:
package com.could.demo.aop.log.entity; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import lombok.Data; import lombok.NoArgsConstructor; import java.io.Serializable; import java.time.LocalDateTime; import java.util.Date; /** * 系統日志類 */ @Data @TableName("log") @NoArgsConstructor public class Log implements Serializable { private static final long serialVersionUID = 7365297754812218451L; /** * 主鍵 * @TableId中可以決定主鍵的類型,不寫會采取默認值,默認值可以在yml中配置 * AUTO: 數據庫ID自增 * INPUT: 用戶輸入ID * ID_WORKER: 全局唯一ID,Long類型的主鍵 * ID_WORKER_STR: 字符串全局唯一ID * UUID: 全局唯一ID,UUID類型的主鍵 * NONE: 該類型為未設置主鍵類型 */ // @TableId(type = IdType.ID_WORKER) // private Long id; @TableId(type = IdType.ID_WORKER_STR) private String id; /** 操作用戶 */ private String username; /** 描述 */ private String description; /** 方法名 */ private String method; /** 參數 */ private String params; /** 日志類型 * 1 :info * 0:錯誤日志 * */ private Integer logType; /** 請求全路徑(不帶參數)*/ private String requestAddress; /** 請求耗時單位毫秒 */ private Long time; /** 異常詳細 */ private byte[] exceptionDetail; /** 創建日期 */ private Date createTime=new Date(); /** * @param logType 日志類型 * @param time 請求時間 */ public Log(Integer logType, Long time) { this.logType = logType; this.time = time; } }
service
@Async異步處理
package com.could.demo.aop.log.service; import com.baomidou.mybatisplus.extension.service.IService; import com.could.demo.aop.log.entity.Log; import org.aspectj.lang.ProceedingJoinPoint; public interface LogService extends IService<Log> {
@Async
void save(String username, String requestAddress, ProceedingJoinPoint joinPoint, Log log); }
service接口實現
package com.could.demo.aop.log.service.impl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.could.demo.aop.log.dao.LogDao; import com.could.demo.aop.log.entity.Log; import com.could.demo.aop.log.service.LogService; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.lang.reflect.Method; @Service @Transactional public class LogServiceImpl extends ServiceImpl<LogDao, Log> implements LogService { @Override public void save(String username, String requestAddress, ProceedingJoinPoint joinPoint, Log log){ MethodSignature signature = (MethodSignature) joinPoint.getSignature(); /** 獲取激活切面的(類)方法*/ Method method = signature.getMethod(); /** 獲取注解*/ com.could.demo.aop.log.Log aopLog = method.getAnnotation(com.could.demo.aop.log.Log.class); /** 獲取方法完整類路徑名 * signature.getName() 獲取的是方法名 * */ String methodName = joinPoint.getTarget().getClass().getName()+"."+signature.getName()+"()"; StringBuilder params = new StringBuilder("{"); //獲取參數值 Object[] argValues = joinPoint.getArgs(); //獲取參數名稱 String[] argNames = ((MethodSignature)joinPoint.getSignature()).getParameterNames(); if(argValues != null){ for (int i = 0; i < argValues.length; i++) { params.append(" ").append(argNames[i]).append(": ").append(argValues[i]); } } // 獲取注解@Log的描述也就是value() 值 if (log != null) { log.setDescription(aopLog.value()); } assert log != null; //斷言log不能為null否則程序拋出AssertionError,並終止執行。 /** 設置請求路徑 */ log.setRequestAddress(requestAddress); /** 設置請求方法名 */ log.setMethod(methodName); /** 設置當前登錄的用戶名 */ log.setUsername(username); /** 設置當前請求的參數和值*/ log.setParams(params.toString() + " }"); this.save(log); } }
啟動類:
package com.could.demo; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.EnableAspectJAutoProxy; @SpringBootApplication @MapperScan(basePackages = {"com.could.demo.aop.log.dao"}) //掃描DAO @ComponentScan("com.could.demo.aop.log") //@EnableAspectJAutoProxy public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }
http://127.0.0.1:8085/login 或 http://127.0.0.1:8085/test數據庫就會存儲對應的請求日志信息
sql文件:
/* Navicat Premium Data Transfer Source Server : saas-test Source Server Type : MySQL Source Server Version : 50729 Source Host : localhost:3306 Source Schema : plus Target Server Type : MySQL Target Server Version : 50729 File Encoding : 65001 Date: 27/03/2020 19:01:49 */ SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS = 0; -- ---------------------------- -- Table structure for log -- ---------------------------- DROP TABLE IF EXISTS `log`; CREATE TABLE `log` ( `id` varchar(25) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '主鍵', `username` varchar(25) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '操作用戶名', `description` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '描述', `method` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '方法名', `params` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '參數', `log_type` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '日志類型', `request_address` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '請求全路徑(不帶參數的)', `time` bigint(20) NULL DEFAULT NULL COMMENT '請求耗時', `exception_detail` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '異常詳細', `create_time` datetime(0) NULL DEFAULT NULL COMMENT '創建時間', PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of log -- ---------------------------- INSERT INTO `log` VALUES ('1243487804686430210', NULL, '測試', 'com.could.demo.aop.log.controller.LogController.test()', '{ }', '1', 'http://127.0.0.1:8085/test', 8, NULL, '2020-03-27 10:39:30'); INSERT INTO `log` VALUES ('1243489076491608066', 'aop打印日志測試號', '用戶登錄', 'com.could.demo.aop.log.controller.LogController.login()', '{ request: org.apache.catalina.connector.RequestFacade@283e7ec5 }', '1', 'http://127.0.0.1:8085/login', 16, NULL, '2020-03-27 10:44:33'); SET FOREIGN_KEY_CHECKS = 1;
application.yml
# 配置端口 server: port: 8085 spring: # 配置數據源 datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/plus?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=UTC username: root password: 123456 type: com.alibaba.druid.pool.DruidDataSource # mybatis-plus相關配置 mybatis-plus: # xml掃描,多個目錄用逗號或者分號分隔(告訴 Mapper 所對應的 XML 文件位置) # mapper-locations: classpath:mapper/*.xml # 以下配置均有默認值,可以不設置 global-config: db-config: #主鍵類型 AUTO:"數據庫ID自增" INPUT:"用戶輸入ID",ID_WORKER:"全局唯一ID (數字類型唯一ID)", UUID:"全局唯一ID UUID"; id-type: ID_WORKER #字段策略 IGNORED:"忽略判斷" NOT_NULL:"非 NULL 判斷") NOT_EMPTY:"非空判斷" field-strategy: NOT_EMPTY #數據庫類型 db-type: MYSQL configuration: # 是否開啟自動駝峰命名規則映射:從數據庫列名到Java屬性駝峰命名的類似映射 map-underscore-to-camel-case: true # 返回map時true:當查詢數據為空時字段返回為null,false:不加這個查詢數據為空時,字段將被隱藏 call-setters-on-nulls: true # 這個配置會將執行的sql打印出來,在開發或測試的時候可以用 log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
總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 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.2.5.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.could.demo</groupId> <artifactId>demo</artifactId> <version>0.0.1-SNAPSHOT</version> <name>demo</name> <description>學習</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <!--log start--> <!-- Commons Lang,這是Java實用程序類的軟件包,用於Java.lang層次結構中的類--> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.9</version> </dependency> <dependency> <groupId>org.lionsoul</groupId> <artifactId>ip2region</artifactId> <version>1.7.2</version> </dependency> <!--log end--> <!--aop注解--> <!--引入AOP依賴--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <!-- <dependency>--> <!-- <groupId>org.aspectj</groupId>--> <!-- <artifactId>aspectjweaver</artifactId>--> <!-- <version>1.9.2</version>--> <!-- </dependency>--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jdbc</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- mybatisPlus 核心庫 --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.1.0</version> </dependency> <!-- 引入阿里數據庫連接池 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.6</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
持久層框架用的是mybatis-plus