选择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