【苍穹外卖Day3】AOP落地与boot配置文件
我的仓库
目录
公共字段自动填充
核心思想:
2.aop切面类实现
1️⃣ AOP 拦截
2️⃣ 获取方法参数(即实体对象)
joinPoint.getArgs() 获取的是哪个方法的参数?
3️⃣ 反射查找实体的 setter 方法并调用
4️⃣ 根据操作类型区分
🧩 JoinPoint 是什么?
小结
文件上传功能
在实际开发中,项目通常会运行在不同环境中:
Spring Boot 提供了 多环境配置文件机制(Profile) 来自动切换这些配置。
怎么读取这些配置?
小结
多表查询(逻辑外键与动态sql)
从今天开始主要是回顾外卖项目,因为已经写完在写点评了。所以就写写博客回顾回顾,还是有很多有用的东西的。
公共字段自动填充
在实际开发中,我们的实体类(POJO)通常会分为三类:
DTO(Data Transfer Object):用于接收前端传来的数据;
VO(View Object):用于向前端返回展示的数据;
Entity(实体类):用于与数据库表一一对应。
在外卖项目中,实体类往往包含一些相同的通用字段,比如 createTime
(创建时间)、updateTime
(修改时间)、createUser
(创建人)、updateUser
(修改人)等。这些字段几乎出现在每一张表中,如果在每次插入或更新数据时都手动去设置,不仅代码冗余,还容易遗漏或出错。
我们需要一种机制——公共字段自动填充,让这些通用字段在插入或更新时能够被框架自动处理。这样可以统一管理公共字段逻辑,保持代码的简洁性和一致性,也方便后期的维护。
实现:
-
核心思想:
用自定义注解标记需要自动填充的字段,然后在执行
save
/update
方法时,通过 AOP 切入点拦截这些方法,在执行前动态为目标对象设置字段值。
//自动填充,标注那个类需要添加切面 //元注解(定义注解的注解) @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface AutoFill {// 这样在使用 @AutoFill 注解时,必须传入一个 OperationType 枚举值 // ,用于标识该方法需要什么类型的操作(比如插入、更新等)。OperationType value(); }
OperationType 是一个枚举类 用来标注当前方法是那种类型。
@AutoFill(OperationType.INSERT)void adddish(Dish dish);
2.aop切面类实现
@Aspect
@Component//交给ioc管理
@Slf4j
public class AutoFillAspect {/** 切入点**:匹配任意返回值类型。
com.sky.mapper.*:匹配 com.sky.mapper 包下的所有类。
*:匹配所有方法名。
(..):匹配任意参数(数量和类型不限)* *///通配符@Pointcut("execution(* com.sky.mapper.*.*(..)) && @annotation(com.sky.Annoation.AutoFill)")public void pointCut() {}//前置通知,再插入或更新前执行//括号里是切入点@Before("pointCut()")//连接点(下面括号里)public void Before(JoinPoint joinPoint) throws InvocationTargetException, IllegalAccessException {log.info("Before method invoked.");//获取被拦截方法的一些信息//方法签名对象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来定义实体Object arg = args[0];//数据LocalDateTime now = LocalDateTime.now();Long currentId= BaseContext.getCurrentId();//统一赋值if(operationType==OperationType.INSERT){try {Method setCreatTime = arg.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_TIME, LocalDateTime.class);Method setUpdatTime = arg.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);Method setCreateUser = arg.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_USER, Long.class);Method setUpdateUser = arg.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);setCreatTime.invoke(arg,now);setUpdatTime.invoke(arg,now);setCreateUser.invoke(arg,currentId);setUpdateUser.invoke(arg,currentId);} catch (NoSuchMethodException e) {throw new RuntimeException(e);}}else if(operationType==OperationType.UPDATE){try {Method setUpdatTime = arg.getClass().getDeclaredMethod("setUpdateTime", LocalDateTime.class);Method setUpdateUser = arg.getClass().getDeclaredMethod("setUpdateUser", Long.class);setUpdatTime.invoke(arg,now);setUpdateUser.invoke(arg,currentId);} catch (NoSuchMethodException e) {throw new RuntimeException(e);}}}}
解释:
-
execution(* com.sky.mapper.*.*(..))
表示匹配com.sky.mapper
包下所有类的所有方法,不管方法名、返回值或参数类型。 -
&& @annotation(com.sky.Annoation.AutoFill)
表示只有带有@AutoFill
注解的方法才会被拦截。
这样能确保 AOP 只作用于那些明确声明需要自动填充的 SQL 方法。
1️⃣ AOP 拦截
@Aspect
+ @Before("pointCut()")
这意味着:
在执行 Mapper 层(com.sky.mapper.*
)中、带有 @AutoFill
注解的方法前,
会自动执行 Before()
这个方法。
2️⃣ 获取方法参数(即实体对象)
joinPoint.getArgs()
获取的是哪个方法的参数?
👉 获取的是当前被拦截的目标方法(也就是匹配切点的那个方法)的参数。
在上面的例子中,它获取的就是 EmployeeMapper.insert(Employee employee)
的参数。
3️⃣ 反射查找实体的 setter 方法并调用
Method setCreateTime = arg.getClass().getDeclaredMethod("setCreateTime", LocalDateTime.class);
setCreateTime.invoke(arg, now);
getDeclaredMethod()
是反射的一部分,它会在运行时根据名字和参数类型查找方法。
invoke()
则是执行这个方法,也就是动态地调用 setter 给字段赋值。
这部分就是通过反射给对象设置:
-
createTime
-
updateTime
-
createUser
-
updateUser
4️⃣ 根据操作类型区分
-
如果是
INSERT
→ 同时赋值创建和更新时间 -
如果是
UPDATE
→ 只更新更新时间和更新人
🧩 JoinPoint
是什么?
JoinPoint
(连接点)是 Spring AOP 提供的一个接口对象,
当你的切面方法(比如 @Before
)被触发时,Spring 会自动把当前被拦截的方法相关的信息都封装到 JoinPoint
里。
它包含了:
-
被调用的方法信息(方法名、参数、注解、所在类等)
-
目标对象(即真正被调用的对象,比如你的 Mapper)
-
方法参数
-
调用位置(类名、包名等)
小结
这是我首次接触AOP思想的落地,就算在做过了之后再来看时我还是对aop的种种内容有所生疏,之后我会去好好看看spring部分!
文件上传功能
这里重点就是阿里云oss的配置,一步步来就好。
这里只多写一下boot中配置文件。
在实际开发中,项目通常会运行在不同环境中:
环境 | 说明 | 举例 |
---|---|---|
dev | 开发环境 | 本地运行、调试用 |
test | 测试环境 | 提测、联调用 |
prod | 生产环境 | 正式上线 |
Spring Boot 提供了 多环境配置文件机制(Profile) 来自动切换这些配置。
#application-dev.yml(开发环境)
aliyun:oss:endpoint: oss-cn-shanghai.aliyuncs.combucket-name: sky-takeout-devaccess-key-id: your-dev-key-idaccess-key-secret: your-dev-key-secret#application-prod.yml(生产环境)
aliyun:oss:endpoint: oss-cn-beijing.aliyuncs.combucket-name: sky-takeout-prodaccess-key-id: your-prod-key-idaccess-key-secret: your-prod-key-secret#application.yml(主配置)
spring:profiles:active: dev # 激活开发环境
当你在本地运行项目时,Spring Boot 会自动加载:application.yml + application-dev.yml
怎么读取这些配置?
定义一个配置类接收:
@Component
@ConfigurationProperties(prefix = "aliyun.oss")
@Data
public class OssProperties {private String endpoint;private String bucketName;private String accessKeyId;private String accessKeySecret;
}
Spring Boot 会自动根据当前环境加载对应的配置并注入。
你在 OSS 上传服务里只要注入它:
@Service
public class OssService {@Autowiredprivate OssProperties ossProperties;
}
小结
这就是boot配置文件的优势,可以在不同环境之间丝滑切换。
多表查询(逻辑外键与动态sql)
这里就不过多阐述了,只说一下逻辑外键的必要性。
在复杂系统中,真实外键会导致性能下降、无法分库分表、限制数据迁移并带来级联删除风险。
逻辑外键通过由应用层控制关联关系,既能保证数据逻辑一致,又提高系统的灵活性和扩展性。
唯一弊端就是我们要自己实现多表查询了哈哈哈。