通用日志组件库:【日志记录组件库】
前言:
在一些实际开发中,一些项目需要方法执行后自动记录日志,这时候就需要用到通用日志组件库。
有了这个组件库,用户就只需要添加jar包和在方法上使用注解@log,就可以实现执行这个方法时自动记录日志。
日志可以通过【数据库、文件、ES索引库】输出。
今天我们就来学习如何自己设计一个简单的通用日志组件库!
在设计之前我们需要思考一些问题:
-
数据库字段怎么设计?
-
需要用到哪些技术?
-
怎么实现系统的用户操作日志记录?
-
怎么打成Jar包,用户只需要一个注解就能用?
带着这些问题,我们开始进行通用日志组件的开发~~
一、代码设计:
项目结构:
1.导入依赖:
在设计之前我们需要先导入相应的依赖Jar包。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>2.0.8</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
</dependency>
2.设计用户操作日志表
思考一下用户操作日志表需要有哪些字段?
操作人+操作时间+操作人IP地址+什么操作【哪个类+哪个方法+方法参数+结果+异常信息】+ 哪个系统 + 操作类型(枚举)。
用户操作日志表:
CREATE TABLE sys_operate_log(
id BIGINT PRIMARY KEY,
operator VARCHAR(20) COMMENT '操作人',
operate_time DATETIME COMMENT '操作时间',
operate_ipaddr VARCHAR(15) COMMENT '操作人客户端ip地址',
method_name VARCHAR(200) COMMENT '执行的类名+方法名',
method_param LONGTEXT COMMENT '方法参数',
method_result LONGTEXT COMMENT '返回结果',
exception LONGTEXT COMMENT '执行异常信息',
operate_type VARCHAR(10) COMMENT '操作类型:login、logout、add、update、query、delete'
);
3.设计实体类、Mapper、Service
用户的操作类型有哪些?
登录、登出、增删改查等。
设计枚举类:
public enum OperateType {
LOGIN,
LOGOUT,
ADD,
UPDATE,
DELETE,
QUERY
}
根据字段名写系统操作人实体类:
@Data
public class SysOperateLog {
/**
*
*/
private Long id;
/**
* 操作人
*/
private String operator;
/**
* 操作时间
*/
private LocalDateTime operateTime;
/**
* 操作人客户端ip地址
*/
private String operateIpaddr;
/**
* 执行的类名+方法名
*/
private String methodName;
/**
* 方法参数
*/
private String methodParam;
/**
* 返回结果
*/
private String methodResult;
/**
* 执行异常信息
*/
private String exception;
/**
* 操作类型:login、logout、add、update、query、delete
*/
private String operateType;
}
Mapper接口:
@Mapper
public interface SysOperateLogMapper {
@Insert("insert into sys_operate_log " +
"values(null,#{operator},#{operateTime}" +
",#{operateIpaddr},#{methodName},#{methodParam}," +
"#{methodResult},#{exception}" +
",#{operateType})")
void insertLog(SysOperateLog log);
}
Service接口:
@Service
@Transactional
public class SysOperateLogService {
@Autowired
private SysOperateLogMapper sysOperateLogMapper;
public void insertLog(SysOperateLog log){
sysOperateLogMapper.insertLog(log);
}
}
输出日志的适配器类接口:
public interface LogOutputAdapter {
//该方法将传入的日志信息传递给适配器进行输出。
public void outputLog(SysOperateLog log);
}
4.设计自定义注解
设计好基本的属性后,我们需要设计一个自定义注解用来用于标记日志操作,并指定日志操作的类型。
//指定注解的作用目标,即可以用于类(TYPE)和方法(METHOD)上。
@Target({ElementType.TYPE,ElementType.METHOD})
//指定注解的保留策略,即注解会在运行时保留,可以通过反射机制读取。
@Retention(RetentionPolicy.RUNTIME)
//指定注解会被包含在Java文档中。
@Documented
//指定注解可以被子类继承。
@Inherited
public @interface Log {
/**
* 日志操作类型:login、logout、add、update、query、delete
* @return
*/
public OperateType type() default OperateType.QUERY;
}
拓展:
@Target({ElementType.TYPE,ElementType.METHOD}):指定另一个注解可以应用在类上、方法上。
ElementType.TYPE
:适用于类、接口、枚举等类型的声明ElementType.METHOD
:适用于方法声明ElementType.FIELD
:适用于字段声明ElementType.PARAMETER
:适用于参数声明ElementType.CONSTRUCTOR
:适用于构造函数声明ElementType.LOCAL_VARIABLE
:适用于局部变量声明ElementType.ANNOTATION_TYPE
:适用于注解类型声明ElementType.PACKAGE
:适用于包声明
@Retention(RetentionPolicy.RUNTIME):@Retention
注解本身也是一个元注解,它可以用于注解其他的注解。通过指定 RetentionPolicy.RUNTIME
,我们告诉编译器和Java虚拟机(JVM)在运行时保留注解,并且可以通过反射机制获取注解的信息。
@Documented: 当我们在使用 @Documented
注解时,它会告诉Java编译器将被注解的元素包含在生成的文档中,以供文档生成工具例如JavaDoc使用。
@Inherited:@Inherited
是Java语言中的一个元注解,用于指定一个注解是否可以被子类继承
自定义注解定义好后,方法上添加这个注解后,程序怎么才能对这个方法进行日志记录呢?我那么通过什么技术去实现呢?
这个时候就用到了AOP,对所有方法上添加了@log注解的方式进行代码增强,增强的逻辑就是记录日志。
5.编写AOP代码实现记录日志的功能:
先定义一个分支类,在通知类上完成如下代码:
5.1编写切点表达式:对所有方法上添加了@log注解的方式进行代码增强,增强的逻辑就是记录日志
public LogAdvice(){ System.out.println("111111111111111111111111111111111111111111111");}
/** * 切点表达式:对所有方法上添加了 @Log 注解的方法进行代码增强 */
@Pointcut("@annotation(cn.itcast.common.log.anno.Log)")public void pointCut(){}
5.2编写通知方法,在通知方法中实现日志记录。
第一步:先选择通知类型:@Before @after @around @afterReturning @afterThrowing
选择:@afterReturning @afterThrowing ,记录方法正常返回日志和异常返回日志。
第二步:编写通知方法,并在通知方法中完成日志的记录。
//1.封装日志对象数据
//2.将日志对象写入日志表,在这里我们用到了异步多线程技术。
2.1、创建连接池对象
2.2、向线程池对象中放入记录日志的操作任务对象。
/**
* 记录方法正常返回日志
* @param joinPoint 切点对象
* @param result 方法返回结果
*/
/*定义一个后置通知,表示在方法正常返回后执行的逻辑.
1、pointcut:指定切入点表达式,即要拦截的方法的条件。
2、returning:指定一个形参名,用于接收方法的返回结果。
*/
@AfterReturning(pointcut = "pointCut() ",returning = "result")
//在logSuccess方法中,根据JoinPoint对象和方法的返回结果result,创建一个SysOperateLog日志对象,用于记录方法的执行信息,然后,通过异步的方式将日志对象提交给AsyncManager,并由其创建一个日志操作任务进行处理。
public void logSuccess(JoinPoint joinPoint,Object result){
System.out.println("========================================================");
//1、封装日志对象数据
SysOperateLog log = createLog(joinPoint, result);
//2、将日志对象写入日志表中
/*
异步多线程:
1、创建线程池对象
2、向线程池对象中放入记录日志的操作任务对象
*/
AsyncManager.me().submitAsyncJob(LogFactory.createLogJob(log));
}
/**
* 记录方法正抛出异常日志
* @param joinPoint 切点对象
* @param ex 方法抛出的异常
*/
@AfterThrowing(pointcut = "pointCut()",throwing = "ex")
//在logException方法中,根据JoinPoint对象和抛出的异常ex,可以进行相应的异常日志记录处理。
public void logException(JoinPoint joinPoint,Throwable ex){
//TODO 记录日志
}
处理JoinPoint对象和方法的返回结果result,封装成日志对象数据SysOperateLog。
private SysOperateLog createLog(JoinPoint joinPoint, Object result) {
//创建对象
SysOperateLog sysOperateLog = new SysOperateLog();
//操作时间
sysOperateLog.setOperateTime(LocalDateTime.now());
sysOperateLog.setOperator(null);//TODO ??? 登录人!!
//操作人客户端ip地址 【RequestContextHolder + request】
//通过调用 ServletUtils.getRemoteHost() 方法获取客户端的远程IP地址,并将其赋值给 sysOperateLog`对象的 operateIpaddr 属性。
String remoteHost = ServletUtils.getRemoteHost();
sysOperateLog.setOperateIpaddr(remoteHost);
//执行的类名+方法名 【joinPoint】
String className = joinPoint.getSignature().getDeclaringTypeName();
String methodName = joinPoint.getSignature().getName();
sysOperateLog.setMethodName(className+"."+methodName);//cn.itcast.service.UserService.findById()
//方法参数 【joinPoint】
Object[] args = joinPoint.getArgs();
sysOperateLog.setMethodParam(JSON.toJSONString(args));
//返回结果 【方法形参】
sysOperateLog.setMethodResult(JSON.toJSONString(result));
sysOperateLog.setException(null);
//操作类型:login、logout、add、update、query、delete 【注解属性】
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Log log = AnnotationUtils.findAnnotation(signature.getMethod(), Log.class);
sysOperateLog.setOperateType(log.type().name());//获取注解上的属性值 ==> 获取注解
return sysOperateLog;
}
在实现createLog(JoinPoint joinPoint, Object result)方法的过程中需要用到一个工具类ServletUtils获取客户端的远程IP地址。
/**
* 提供servlet相关对象的api操作方法
*/
public class ServletUtils {
/**
* 获取请求ip地址
* @return
*/
public static String getRemoteHost() {
// 获取当前请求的HttpServletRequest对象
//首先通过 RequestContextHolder.getRequestAttributes() 获取当前请求的 RequestAttributes 对象
RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
//通过 ServletRequestAttributes 将其转换为 HttpServletRequest 对象。
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) attributes;
HttpServletRequest request = requestAttributes.getRequest();
// 从请求头中获取IP地址
//通过获取请求头中的 `x-forwarded-for`、`Proxy-Client-IP`、`WL-Proxy-Client-IP` 等字段,尝试获取客户端的真实IP地址。如果这些字段中都没有获取到有效的IP地址,那么使用 `request.getRemoteAddr()` 获取默认的远程IP地址。
String ip = request.getHeader("x-forwarded-for");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
// 处理IPv6的本地回环地址
return ip.equals("0:0:0:0:0:0:0:1") ? "127.0.0.1" : ip;
}
}
分析:
MethodSignature signature = (MethodSignature) joinPoint.getSignature()
Log log = AnnotationUtils.findAnnotation(signature.getMethod(), Log.class);
sysOperateLog.setOperateType(log.type().name()):
获取了连接点(joinPoint
)的方法签名(MethodSignature
),然后使用AnnotationUtils
来查找方法上的Log
注解,并将注解中定义的操作类型(type
)赋值给sysOperateLog
对象的operateType
属性。
JSON.toJSONString(result): 将一个参数转成JSON字符串,使用前要导入fastjson依赖
String className = joinPoint.getSignature().getDeclaringTypeName():
joinPoint
对象提供了对连接点的访问和操作。
joinPoint.getSignature()
返回连接点的签名对象,可以通过该对象获取连接点的签名信息,包括方法名、参数类型等。
getDeclaringTypeName()
是 Signature
接口的方法之一,用于获取连接点所属类的类名。
总结:
在 getRemoteHost()
方法中,首先通过 RequestContextHolder.getRequestAttributes()
获取当前请求的 RequestAttributes
对象,再通过 ServletRequestAttributes
将其转换为 HttpServletRequest
对象。
接下来,通过获取请求头中的 x-forwarded-for
、Proxy-Client-IP
、WL-Proxy-Client-IP
等字段,尝试获取客户端的真实IP地址。如果这些字段中都没有获取到有效的IP地址,那么使用 request.getRemoteAddr()
获取默认的远程IP地址。
最后,对IPv6的本地回环地址进行处理,将其转换为常见的本地回环地址 127.0.0.1
。
在完成日志对象封装后,我们通过异步的方式将日志对象写入日志表中。
6.通过异步多线程将日志对象写入日志表中
//2、将日志对象写入日志表中
/*
异步多线程:
1、创建线程池对象
2、向线程池对象中放入记录日志的操作任务对象
*/
AsyncManager.me().submitAsyncJob(LogFactory.createLogJob(log));
分析:
在这段代码中,AsyncManager.me()
返回一个异步管理器的实例,通过该实例可以进行异步任务的提交和管理。调用submitAsyncJob(LogFactory.createLogJob(log))
方法来提交一个异步任务,该方法接受一个异步任务对象作为参数。
LogFactory.createLogJob(log)
是一个工厂方法,用于创建一个异步任务对象,该异步任务对象负责处理日志记录的逻辑。它接受一个日志对象log
作为参数,并返回一个对应的异步任务对象。
整体来说,这段代码的作用是将日志对象log
提交给异步任务进行处理。通过异步方式处理日志记录,可以避免日志记录操作对主线程的阻塞,提高系统的并发能力和性能。
/**
* 异步管理器对象:执行异步操作【多线程】
*/
public class AsyncManager {
//该类中定义了一个 ThreadPoolExecutor 类型的成员变量 threadPoolExecutor,用于管理线程池。在构造方法中,创建了一个线程池对象,配置了核心线程数为10,最大线程数为50,线程空闲时间为10秒,使用了大小为100的有界队列。
private final ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10,
50,
10,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(100));
/**
* 添加异步任务
* @param runnable 任务对象
*/
//用于向线程池提交异步任务。它接受一个 Runnable 对象作为参数,表示需要执行的异步任务。在方法内部,调用线程池的 submit() 方法,将任务提交给线程池进行异步执行。
public void submitAsyncJob(Runnable runnable){
this.threadPoolExecutor.submit(runnable);
}
//单例设计模式:饿汉式
// AsyncManager 类是一个单例类,采用饿汉式的单例设计模式实现,即在类加载时就创建了一个静态的、唯一的实例对象。
private AsyncManager(){
}
private static AsyncManager me = new AsyncManager();
//me() 方法是一个静态方法,用于获取 AsyncManager 的唯一实例对象。通过该方法可以在代码的任何地方获取到相同的 AsyncManager实例。
public static AsyncManager me(){
return me;
}
}
我们在创建一个异步任务对象用到了 LogFactory.createLogJob(log)
,这是一个工厂方法。
7.工厂模式
public class LogFactory {
//createLogJob()方法接受一个 SysOperateLog 对象作为参数,用于传递要记录的日志信息。该方法通过创建一个匿名内部类实现了 Runnable 接口,从而创建了一个可以在后台线程中执行的任务对象。
public static Runnable createLogJob(SysOperateLog log){
return new Runnable() {
@Override
//在 run()方法中,实现了具体的记录日志逻辑。
public void run() {
//首先打印了 "开始记录日志" 的信息,表示开始执行记录日志的操作。
System.out.println("开始记录日志");
//TODO 执行记录日志的操作
//获取 SysOperateLogMapper 执行 insert操作!!
//接下来,通过 SpringContextUtil.getBean(LogOutputAdapter.class) 方法获取 用于输出日志的适配器类,LogOutputAdapter 类型的 Bean 对象。
LogOutputAdapter logOutputAdapter = (LogOutputAdapter) SpringContextUtil.getBean(LogOutputAdapter.class);
//然后调用 logOutputAdapter.outputLog(log) 方法,将传入的日志信息传递给适配器进行输出。
logOutputAdapter.outputLog(log);
System.out.println("记录日志结束");
}
};
}
}
我们在这里用到了一个在IOC情况下获取IOC容器对象的一个技术的一个工具类SpringContextUtil,那么这个工具类是怎么获取到IOC对象的呢?
我们这里用到了感知接口ApplicationContextAware。
8.在非IOC情况下获取Ioc容器中的对象
/**
* 在非IOC情况下获取IOC容器中的Bean
* ApplicationContextAware 感知接口:获取IOC容器对象ApplicationContext
*
*/
//表示是一个由 Spring 管理的组件
@Component
//通过实现该接口,可以在 Spring 容器初始化时将应用上下文对象(ApplicationContext)注入到 SpringContextUtil 类中
public class SpringContextUtil implements ApplicationContextAware {
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
SpringContextUtil.applicationContext = applicationContext;
}
/**
* 根据Class类型获取容器中的Bean
* @param var1 Class类型
* @return Bean
* @throws BeansException
*/
public static Object getBean(Class var1) throws BeansException {
return applicationContext.getBean(var1);
}
/**
* 根据Bean的名称来获取容器中Bean
* @param beanName bean的名称
* @return Bean
* @throws BeansException
*/
public static Object getBean(String beanName) throws BeansException {
return applicationContext.getBean(beanName);
}
}
总结:
SpringContextUtil中实现感知接口ApplicationContextAware,
通过静态成员变量 applicationContext
和静态方法 getBean()
,可以实现通过类类型或 Bean 名称来获取容器中的 Bean 对象。
getBean(Class var1)
方法接受一个 Class 类型的参数,表示要获取的 Bean 的类型。它通过调用applicationContext.getBean(var1)
方法来获取对应类型的 Bean 对象,并返回结果。getBean(String beanName)
方法接受一个 Bean 的名称作为参数,表示要获取的 Bean 的名称。它通过调用applicationContext.getBean(beanName)
方法来获取对应名称的 Bean 对象,并返回结果。
通过使用 SpringContextUtil
类,你可以在任何需要获取容器中的 Bean 对象的地方,通过调用 getBean()
方法来获取对应的 Bean 对象。
注意:为了使 SpringContextUtil
类能够被正确地注入 ApplicationContext,需要将该类纳入 Spring 容器的扫描范围,并确保应用上下文初始化时能够识别并注入该类。
这样我们就简单实现了自动记录日志的功能。接下来我们需要再自定义一个注解来开启日志的功能。
9.开启@EnableLog注解
//表示该注解可以应用于类、接口和枚举类型。
@Target(ElementType.TYPE)
//表示该注解的生命周期为运行时,可以在运行时通过反射机制获取注解信息。
@Retention(RetentionPolicy.RUNTIME)
//表示该注解将包含在Java文档中
@Documented
//表示在使用了@EnableLog注解的类中,将会自动导入LogConfig类
@Import(LogConfig.class)
public @interface EnableLog {
}
作用: 当一个类使用了@EnableLog
注解时,将会自动导入LogConfig
类,实现日志功能的配置和启用。
LogConfig配置类:
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
//表示该类是一个配置类,用于定义Bean的配置信息
@Configuration
//指定要扫描的包,将会扫描该包及其子包下的组件,并注册为Bean。
@ComponentScan("cn.itcast.common.log")
//指定要扫描的Mapper接口所在的包,将会将其注册为MyBatis的Mapper。
@MapperScan({"cn.itcast.common.log.mapper"})
//启用AspectJ自动代理,用于支持AOP切面功能
@EnableAspectJAutoProxy
public class LogConfig {
//TODO 其他配置项
}
作用:
LogConfig
类用于配置日志功能,通过注解方式配置了组件扫描、Mapper扫描和AOP切面的启用,实现日志功能的初始化和配置。
二、使用:
1、打包流程:
Maven->install->私服仓库 另外一个项目只需要导这个依赖包就行了。
<dependency>
<groupId>cn.itcast</groupId>
<artifactId>common_log</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
2、在启动类上添加@EbableLog注解和@MapperScan包扫描。
3、在需要记录日志的地方使用@Log注解
三、总结:
1.@Import有啥用?@Enable有啥用?底层原理是啥?有啥区别?
答:
@Import是将 JavaBean 注入到IOC容器中。
@EnableXxxx 用来启用某一个功能的配置。启用某一功能,仅需要加上一个注解即可生效,解耦。
@EnableXxxx底层原理:是基于注解处理器和条件注解机制,通过解析和处理注解以及根据条件来决定是否启用某个功能或模块。其真正核心的是@Import注解,由它去加载它自己对应的配置类,然后启动他的功能。
拓展:
@Import
注解和 @Enable
不同的作用和用途。
- @Import 注解:
@Import
注解是用于导入其他配置类或组件类的注解。- 通过
@Import
注解,可以将其他配置类或组件类引入到当前的配置类中,使其生效。 - 这种方式适用于在配置类中手动导入特定的组件,以扩展配置类的功能或引入特定的实现。
- @Enable 注解:
@Enable
注解是用于启用某个特定的功能或模块。- 通过
@Enable
注解,可以启用 Spring Framework 中的某个功能,例如自动配置、缓存、定时任务等。 - 这种方式适用于启用特定的功能,而不需要显式地导入和配置相关的类。
虽然 @Import
注解可以用于导入其他配置类或组件类,但是它并不会自动启用相关的功能。而 @Enable
注解则专门用于启用特定的功能或模块,它会自动配置相关的类和组件,并提供预定义的功能。
因此,在某些情况下,您可能需要同时使用 @Import
注解和 @Enable
注解。通过 @Import
注解导入特定的配置类或组件类,并使用 @Enable
注解启用相关的功能或模块。
@EnableXxxx常用注解:
@EnableAutoConfiguration
:这是 Spring Boot 中常用的注解,用于自动配置应用程序的配置类。它会根据项目的依赖和配置,自动配置 Spring 应用程序的各种组件和特性。@EnableWebMvc
:这个注解用于启用 Spring MVC 框架的功能,将应用程序标记为一个 Web 应用程序。它会自动配置基本的 MVC 组件,如控制器、视图解析器等。@EnableTransactionManagement
:这个注解用于启用 Spring 的事务管理功能。它会自动配置事务管理器和事务相关的代理,以便在方法执行时进行事务管理。@EnableCaching
:这个注解用于启用 Spring 的缓存功能。它会自动配置缓存管理器,并根据配置的缓存注解(如@Cacheable
、@CachePut
)来对方法进行缓存。@EnableAsync
:这个注解用于启用 Spring 的异步方法执行功能。它会自动配置异步执行的执行器和任务执行器,并根据方法上的@Async
注解来实现异步执行。@EnableScheduling
:这个注解用于启用 Spring 的定时任务调度功能。它会自动配置定时任务调度器,并根据方法上的@Scheduled
注解来定义定时任务的执行规则。
2、其他常用注解:
答:
@Component、@Autowired、@RequestMapping、@ResponseBody、@Configuration、@Transactional。
@Component
:用于标记一个类为组件类,通常作为其他注解的基础,例如@Service
、@Repository
和@Controller
。@Autowired
:用于自动注入依赖关系。通过在需要依赖的属性、构造函数或方法上使用该注解,Spring 将自动查找匹配的 Bean 并进行注入。@RequestMapping
:用于映射请求 URL 到处理方法。可以在控制器类或处理方法上使用该注解来定义请求的路径和请求方法。@ResponseBody
:用于将方法返回的对象直接作为响应体返回给客户端,而不是解析为视图。@PathVariable
:用于将 URL 中的路径变量映射到方法的参数。@RequestParam
:用于将请求参数映射到方法的参数。@Valid
:用于开启参数校验,可以在方法的参数上使用该注解,以触发参数的校验。@Configuration
:用于标记一个类为配置类,通常与@Bean
注解一起使用,用于定义应用程序的配置。@Bean
:用于在配置类中定义一个 Bean 对象,该对象将由 Spring 管理和维护。@Transactional
:用于标记方法或类为事务性操作。通过在方法或类上使用该注解,可以在方法执行期间启用事务管理。
3、数据库字段怎么设计?
答:
操作人+操作时间+操作人IP地址+什么操作【哪个类+哪个方法+方法参数+结果+异常信息】+ 哪个系统 + 操作类型(枚举)
4、需要用到哪些技术?
答:
自定义注解 + AOP + 多线程 + 设计模式【单例+工厂】 + ApplicationAware + RequestContextHolder + @EnableXxxx + @Import + ......
5、怎么实现系统的用户操作日志记录?
答:
自定义注解 + AOP +异步多线程/MQ + 数据模型设计 + 设计模式【单例模式+工厂模式】
注:简历上任何一个项目都可以加上去。(万金油知识点,可以回答很多问题)
6、组件中的AOP技术是怎么用的?
答:
先编写切点表达式对所有方法上添加了@log注解的方式进行代码增强,然后编写通知方法。
通知方法中:
先选择通知类型@afterReturning @afterThrowing,然后编写通知方法,并在通知方法中完成日志的记录。
封装日志对象数据,通过异步多线程将日志对象写入日志表中。
7、组件中异步多线程怎么用的?
答:
具体是在异步管理器先创建线程池对象,通过AsyncManager.me()返回一个异步管理器的实例,通过该实例调用submitAsyncJob(LogFactory.createLogJob(log))方法来提交一个异步任务,该方法接受一个异步任务对象作为参数。
这个异步任务对象由LogFactory.createLogJob(log)工厂方法创建。
LogFactory.createLogJob(log)
是一个工厂方法,用于创建一个异步任务对象,该异步任务对象负责处理日志记录的逻辑。它接受一个日志对象
log`作为参数,并返回一个对应的异步任务对象。
通过异步方式处理日志记录,可以避免日志记录操作对主线程的阻塞,提高系统的并发能力和性能。
8、这个组件中用到了哪些设计模式?
答:
懒汉模式和工厂模式。懒汉模式是用的时候再创建。
工厂模式通过创建一个匿名内部类实现 Runnable 接口,从而创建了一个可以在后台线程中执行的任务对象。
拓展:
工厂模式是一种创建型设计模式,用于创建对象的过程中将实例化的逻辑封装在工厂类中。它的主要目的是将对象的创建与使用代码解耦,提供一种灵活的方式来创建具体类型的对象,同时隐藏对象的实例化细节。
工厂模式的主要作用如下:
- 封装对象的实例化逻辑:工厂模式将对象的实例化过程封装在工厂类中,客户端无需知道具体的实例化过程和细节,只需通过工厂类获取所需的对象。
- 提供灵活性和可扩展性:通过工厂模式,可以通过简单地修改工厂类的实现来创建不同类型的对象,而不需要修改客户端代码。这种可扩展性使得系统更加灵活,易于维护和扩展。
- 实现代码解耦:客户端与具体对象之间解耦,客户端只需与工厂类进行交互,不直接依赖于具体对象的实现。这样可以减少客户端代码的依赖性,提高代码的可维护性和可测试性。
- 封装复杂的对象创建过程:如果创建对象的过程比较复杂,涉及多个步骤或依赖关系,工厂模式可以将这些复杂的过程封装起来,使客户端代码更加简洁和易读。
总之,工厂模式提供了一种统一的方式来创建对象,降低了客户端与具体对象之间的耦合度,提供了灵活性和可扩展性,并封装了对象的实例化过程,使系统更加可维护和可测试。
9、怎么打成Jar包,用户只需要一个注解就能用?
答:
Maven->install->私服仓库 另外一个项目只需要导这个依赖包就行了。
它接受一个日志对象log
作为参数,并返回一个对应的异步任务对象。
通过异步方式处理日志记录,可以避免日志记录操作对主线程的阻塞,提高系统的并发能力和性能。