2024.11.08 周五
今天学习时间不多,只是做了一下苍穹外卖的项目,并巩固和学习了一下相关的知识。明天打算去南京找朋友玩,学习进度要稍微搁置一下(浅浅休息一下问题应该不大吧)
八股
今日无该板块学习内容。
算法
今日无该板块学习内容。
项目
接下来的计划是把苍穹外卖先做一遍巩固基础知识,再做项目来准备简历。
零碎的知识点整理
@Builer
@Data
、@Builder
、@NoArgsConstructor
、@AllArgsConstructor
都是Lombok
库提供的功能。
其中@Builder
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Employee implements Serializable {
private static final long serialVersionUID = 1L;
private Long id;
private String username;
private String name;
private String password;
private String phone;
private String sex;
private String idNumber;
private Integer status;
//@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createTime;
//@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime updateTime;
private Long createUser;
private Long updateUser;
}
Employee employee = Employee.builder()
.id(1L) //1L指值为1的Long型
.username("john_doe")
.name("John Doe")
.password("password123")
.phone("1234567890")
.sex("Male")
.idNumber("123456789012345678")
.status(1)
.createTime(LocalDateTime.now())
.updateTime(LocalDateTime.now())
.createUser(1L)
.updateUser(1L)
.build();
请求参数及处理方法
下图是api接口文档的一部分,在其中能够清晰的看到请求参数包括:Path参数、Query参数、Header参数、Body参数。
- Header和Body存储于Http请求的请求头和请求体中。
@RestController
public class MyController {
// Header参数获取演示(使用@RequestHeader注解)
@GetMapping("/example")
public String getHeaderParam(@RequestHeader("Content-Type") String contentType) {
return "Header value: " + contentType;
}
// Header参数获取演示(使用HttpServletRequest对象)
@GetMapping("/example")
public String getHeaderParam(HttpServletRequest request) {
String myHeader = request.getHeader("Content-Type");
return "Header value: " + contentType;
}
}
// Body参数获取演示(使用@RequestBody注解)
@PostMapping
@ApiOperation("新增员工")
public Result save(@RequestBody EmployeeDTO employeeDTO){
log.info("新增员工:{}",employeeDTO);
employeeService.save(employeeDTO);
return Result.success();
}
- Path和Query存储于Url中。
// Path参数通过 @PathVariable 获取{status}
// Query参数无需注解
// 该url实际是: ip:port/status/{status}?id={id}
@PostMapping("/status/{status}")
@ApiOperation("启用/禁用员工账号")
public Result startOrStop(@PathVariable Integer status, Long id){
// status属于Path参数,id属于Query参数
log.info("启用/禁用员工账号:{},{}",status, id);
employeeService.startOrStop(status, id);
return Result.success();
}
enum是什么?
在Java中,枚举(enum
)实际上是一种特殊的类。当你定义一个枚举时,你实际上是在定义一个继承自java.lang.Enum
的类,并且这个类有一些特定的限制:
- 枚举类型的实例是固定的,它们在编译时就已经确定,通常称为枚举常量。
- 枚举类型可以包含方法和属性,就像常规类一样。
- 枚举类型可以有构造器,但它们必须是私有的,因为枚举常量是在枚举类型内部创建的。
- 枚举类型可以实现的接口,并且可以添加额外的行为,比如方法。
苍穹外卖在定义OperationType
用到了enum
/**
* 数据库操作类型
*/
// OperationType是一个枚举类,它有两个枚举常量UPDATE和INSERT
public enum OperationType {
/**
* 更新操作
*/
UPDATE,
/**
* 插入操作
*/
INSERT
}
AOP(面向切面编程)
Spring的aop介绍一下
Spring AOP是Spring框架中的一个重要模块,用于实现面向切面编程。
我们知道,Java 就是一门面向对象编程的语言,在OOP
中最小的单元就是“Class 对象”,但是在 AOP
中最小的单元是“切面”。一个“切面”可以包含很多种类型和对象,对它们进行模块化管理,例如事务管理。
在AOP的思想中,功能分为两种:
- 核心业务:登陆、注册、增、删、改、查、都叫核心业务
- 周边功能:日志、事务管理这些次要的为周边业务
在 AOP 中有以下几个概念:
- AspectJ:切面,只是一个概念,没有具体的接口或类与之对应,是 Join point,Advice 和 Pointcut 的一个统称。
- Join point:连接点,指程序执行过程中的一个点,例如方法调用、异常处理等。在 Spring AOP 中,仅支持方法级别的连接点。
- Advice:通知,即我们定义的一个切面中的横切逻辑,有“
around
”,“before
”和“after
”三种类型。在很多的 AOP 实现框架中,Advice 通常作为一个拦截器,也可以包含许多个拦截器作为一条链路围绕着 Join point 进行处理。 - Pointcut:切点,用于匹配连接点,一个 AspectJ 中包含哪些 Join point 需要由 Pointcut 进行筛选。
- Introduction:引介,让一个切面可以声明被通知的对象实现任何他们没有真正实现的额外的接口。例如可以让一个代理对象代理两个目标类。
- Weaving:织入,在有了连接点、切点、通知以及切面,如何将它们应用到程序中呢?没错,就是织入,在切点的引导下,将通知逻辑插入到目标方法上,使得我们的通知逻辑在方法调用时得以执行。
- AOP proxy:AOP 代理,指在 AOP 实现框架中实现切面协议的对象。在 Spring AOP 中有两种代理,分别是 JDK 动态代理和 CGLIB 动态代理。
- Target object:目标对象,就是被代理的对象。
Spring AOP 是基于 JDK 动态代理和 Cglib 提升实现的,两种代理方式都属于运行时的一个方式,所以它没有编译时的一个处理,那么因此 Spring 是通过 Java 代码实现的。
苍穹外卖中的AOP实现:
- 1).自定义注解 AutoFill,用于标识需要进行公共字段自动填充的方法
package com.sky.annotation;
/**
* 自定义注解,用于标识某个方法需要进行功能字段自动填充处理
*/
// @Target定义了注解AutoFill可以应用的元素类型
// 指定ElementType.METHOD 意味着 AutoFill注解只能用于方法上
@Target(ElementType.METHOD)
// @Retention定义了注解 AutoFill 的生命周期
// 指定RetentionPolicy.RUNTIME 意味着
// 注解信息将被保留到运行时,可以通过反射在运行时访问
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoFill {
// 数据库操作类型:UPDATE INSERT
/**
定义了AutoFill注解的元素value
它没有默认值,所以使用该注解时必须指定一个值
*/
OperationType value();
}
package com.sky.enumeration;
/**
* 数据库操作类型
*/
// enum关键字用于创建枚举类型,该数据类型用于定义一组固定的常量
// 且每个枚举常量都是该类型的一个实例
public enum OperationType {
/**
* 更新操作
*/
UPDATE,
/**
* 插入操作
*/
INSERT
}
- 2).自定义切面类 AutoFillAspect,统一拦截加入了 AutoFill注解的方法,通过反射为公共字段赋值
package com.sky.aspect;
/**
* 自定义切面,实现公共字段自动填充处理逻辑
*/
@Aspect
@Component
@Slf4j
public class AutoFillAspect {
/**
* 切入点
*/
@Pointcut("execution(* com.sky.mapper.*.*(..)) && @annotation(com.sky.annotation.AutoFill)")
public void autoFillPointCut(){}
/**
* 前置通知,在通知中进行公共字段的赋值
*/
@Before("autoFillPointCut()")
public void autoFill(JoinPoint joinPoint){
log.info("开始进行公共字段自动填充...");
//获取到当前被拦截的方法上的数据库操作类型
MethodSignature signature = (MethodSignature) joinPoint.getSignature();//方法签名对象
AutoFill autoFill = signature.getMethod().getAnnotation(AutoFill.class);//获得方法上的注解对象
OperationType operationType = autoFill.value();//获得数据库操作类型
//获取到当前被拦截的方法的参数--实体对象
Object[] args = joinPoint.getArgs();
if(args == null || args.length == 0){
return;
}
Object entity = args[0];
//准备赋值的数据
LocalDateTime now = LocalDateTime.now();
Long currentId = BaseContext.getCurrentId();
//根据当前不同的操作类型,为对应的属性通过反射来赋值
if(operationType == OperationType.INSERT){
//为4个公共字段赋值
try {
Method setCreateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_TIME, LocalDateTime.class);
Method setCreateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_USER, Long.class);
Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);
Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);
//通过反射为对象属性赋值
setCreateTime.invoke(entity,now);
setCreateUser.invoke(entity,currentId);
setUpdateTime.invoke(entity,now);
setUpdateUser.invoke(entity,currentId);
} catch (Exception e) {
e.printStackTrace();
}
}else if(operationType == OperationType.UPDATE){
//为2个公共字段赋值
try {
Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);
Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);
//通过反射为对象属性赋值
setUpdateTime.invoke(entity,now);
setUpdateUser.invoke(entity,currentId);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
- 3).在 Mapper 的方法上加入 AutoFill注解
package com.sky.mapper;
@Mapper
public interface CategoryMapper {
/**
* 插入数据
* @param category
*/
@Insert("insert into category(type, name, sort, status, create_time, update_time, create_user, update_user)" +
" VALUES" +
" (#{type}, #{name}, #{sort}, #{status}, #{createTime}, #{updateTime}, #{createUser}, #{updateUser})")
@AutoFill(value = OperationType.INSERT)
void insert(Category category);
/**
* 根据id修改分类
* @param category
*/
@AutoFill(value = OperationType.UPDATE)
void update(Category category);
}
SpringAOP的原理了解吗?
Spring AOP的实现依赖于动态代理技术。动态代理是在运行时动态生成代理对象,而不是在编译时。它允许开发者在运行时指定要代理的接口和行为,从而实现在不修改源码的情况下增强方法的功能。
Spring AOP支持两种动态代理:
- 基于JDK的动态代理:使用
java.lang.reflect.Proxy
类和java.lang.reflect.InvocationHandler
接口实现。每一个动态代理类都必须实现InvocationHandler
接口,并且每个代理类的实例都关联到一个handler
。当通过代理对象调用一个方法时,这个方法的调用会被转发为由InvocationHandler
接口的invoke()
方法来进行调用。
// 指定切点的范围(mapper下的方法类+AutoFill注解)
@Pointcut("execution(* com.sky.mapper.*.*(..)) && @annotation(com.sky.annotation.AutoFill)")
public void autoFillPointCut(){}
// 在方法执行之前执行通知
@Before("autoFillPointCut()")
public void autoFill(JoinPoint joinPoint){
/**
jointpoint是连接点对象,代表AOP代理正在执行的切点(Pointcut)的具体实例。
getSignature()是JoinPoint接口的一部分,返回连接点的方法签名
方法签名包含:连接点对应的方法的详细信息,如方法名、参数类型等
*/
MethodSignature signature = (MethodSignature) joinpoint.getSignature();
/**
getAnnotation()——获取方法上指定类型的注解实例
此处被用来获取类型为AutoFill.class的注解
如果目标方法上存在@AutoFill注解,则返回该注解的实例,不存在则返回null
*/
AutoFill autoFill = signature.getMethod().getAnnotation(AutoFill.class);
// 获取autoFill注解中的value的值
OperationType operationType = autoFill.value();
// 获取当前实例方法的参数
Object[] args = joinPoint.getArgs();
Object entity = args[0];
/**
getClass()获取entity对象的类
getDeclaredMethod()接收两个参数
AutoFillConstant.SET_CREATE_TIME 表示要获取的方法的名称
LocalDateTime.class 表示要获取的方法的参数的类型
最后将获取的entity中的该方法用Method类接收
*/
Method setCreateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_TIME, LocalDateTime.class);
/**
invoke()接收两个参数,用于在运行时执行这个方法
entity 表示要调用方法的对象实例
LocalDateTime.now() 表示传递给setCreateTime方法的参数
*/
setCreateTime.invoke(entity, LocalDateTime.now());
}
- 基于CGLIB的动态代理:当被代理的类没有实现接口时,Spring会使用CGLIB库生成一个被代理类的子类作为代理。CGLIB(Code Generation Library)是一个第三方代码生成库,通过继承方式实现代理
AOP实现有哪些注解?
常用的注解包括:
- @Aspect:用于定义切面,标注在切面类上。
- @Pointcut:定义切点,标注在方法上,用于指定连接点。
- @Before:在方法执行之前执行通知。
- @After:在方法执行之后执行通知。
- @Around:在方法执行前后都执行通知。
- @AfterReturning:在方法执行后返回结果后执行通知。
- @AfterThrowing:在方法抛出异常后执行通知。
- @Advice:通用的通知类型,可以替代@Before、@After等
什么是反射?有哪些使用场景?
反射具有以下特性(即在运行时对 类 进行操作):
- 运行时类信息访问:反射机制允许程序在运行时获取类的完整结构信息,包括类名、包名、父类、实现的接口、构造函数、方法和字段等。
- 动态对象创建:可以使用反射API动态地创建对象实例,即使在编译时不知道具体的类名。这是通过
Class
类的newInstance()
方法或Constructor
对象的newInstance()
方法实现的。 - 动态方法调用:可以在运行时动态地调用对象的方法,包括私有方法。这通过
Method
类的invoke()
方法实现,允许你传入对象实例和参数值来执行方法。 - 访问和修改字段值:反射还允许程序在运行时访问和修改对象的字段值,即使是私有的。这是通过
Field
类的get()
和set()
方法完成的。
反射机制在spring框架中的应用场景:
- Spring框架的依赖注入(DI)和控制反转(IoC)
- 在Spring中,开发者可以通过XML配置文件或者基于注解的方式声明组件之间的依赖关系。当应用程序启动时,Spring容器会扫描这些配置或注解,然后
利用反射来实例化Bean(即Java对象),并根据配置自动装配它们的依赖
。 - 例如,当一个Service类需要依赖另一个
DAO(Mapper)
类时,开发者可以在Service类中使用@Autowired
注解,而无需自己编写创建DAO实例的代码。Spring容器会在运行时解析这个注解,通过反射找到对应的DAO类,实例化它,并将其注入到Service类中
。这样不仅降低了组件之间的耦合度,也极大地增强了代码的可维护性和可测试性。
- 在Spring中,开发者可以通过XML配置文件或者基于注解的方式声明组件之间的依赖关系。当应用程序启动时,Spring容器会扫描这些配置或注解,然后
- 动态代理的实现
- 一个典型的例子是
Spring AOP(面向切面编程)
的实现。Spring AOP允许开发者定义切面(Aspect),这些切面可以横切关注点(如日志记录、事务管理),并将其插入到业务逻辑中,而不需要修改业务逻辑代码。 - 例如,为了给所有的服务层方法添加日志记录功能,可以定义一个切面,在这个切面中,
Spring会使用JDK动态代理或CGLIB(如果目标类没有实现接口)来创建目标类的代理对象
。这个代理对象在调用任何方法前或后,都会执行切面中定义的代码逻辑(如记录日志),而这一切都是在运行时通过反射来动态构建和执行
的,无需硬编码到每个方法调用中。
- 一个典型的例子是