JavaWeb开发基础:后端框架进阶——事务管理、AOP、Spring原理、Maven高级
文章目录
1 事务管理
事务(Transaction)是一组操作的集合,是一个不可分割的工作单位,这些操作要么同时成功,要么同时失败。
- 开启事务(一组操作开始前):
start transaction;
/begin;
- 提交事务(这组操作全部成功后):
commit;
- 回滚事务(中间任何一个操作出现异常):
rollback;
1.1 Spring事务管理
根据事务的定义,调整基础(4)案例中解散部门的方法为删除部门同时删除该部门下的所有员工,并在Service层的方法添加注解@Transactional
,将其交给spring进行事务管理。
方法执行前,开启事务;成功执行完毕,提交事务;出现异常,回滚事务。
在Service层类、接口上使用该注解
@Transactional
时,表示其所属所有方法均进行事务管理。常加在Service层经常使用增删改操作的方法上。
/* DeptServiceImpl.java */
// 新增empMapper
@Autowired
private EmpMapper empMapper;
@Transactional // 交给spring进行事务管理
@Override
public void delete(Integer id) {
deptMapper.deleteById(id); // 根据ID删除部门数据
empMapper.deleteByDeptId(id); // 根据ID删除该部门下的员工
}
/* EmpMapper.java */
// 根据部门ID删除该部门下的员工数据
@Delete("delete from emp where dept_id = #{deptId}")
void deleteByDeptId(Integer deptId);
在yml文件中配置spring事务日志开关:
# 开启事务管理日志
logging:
level:
org.springframework.jdbc.support.jdbcTransactionManager: debug
1.2 rollbackFor和propagation
- rollbackFor属性:默认情况下,只有出现RuntimeException才回滚异常。可以设置
rollbackFor
属性控制出现何种异常类型时回滚事务,要想出现任何异常时都回滚可设置@Transactional(rollbackFor = Exception.class)
。 - propagation属性:当一个事务方法被另一个事务方法调用时,该事务方法进行事务控制的方式称为事务传播行为。可以设置
propagation
属性来控制该方法运行时是否需要/支持事务。@Transactional public void a() { userService.b(); } @Transactional(propagation = Propagation.REQUIRED) // 该方法运行时需要事务,有则加入,无则新建 public void b() { ... }
常用的propagation属性值如下表所示:
propagation属性值 | 含义 |
---|---|
REQUIRED | 需要事务。有则加入,无则创建新事务【默认值】 |
REQUIRES_NEW | 需要新事务。无论有无,总是创建新事务 |
SUPPORTS | 支持事务。有则加入,无则在无事务状态中运行 |
NOT_SUPPORTED | 不支持事务。在无事务状态下运行,如果当前存在已有事务,则挂起当前事务 |
MANDATORY | 必须有事务,否则抛异常 |
NEVER | 必须无事务,否则抛异常 |
… |
综上,进一步修改解散部门方法:要求解散部门时,无论是成功还是失败,都要记录操作日志。即新增记录日志到数据库表中。需要创建对应的接口及其实现类,具体如下所示:
/* DeptServiceImpl.java */
// 新增deptLogService
@Autowired
private DeptLogService deptLogService;
@Transactional(rollbackFor = Exception.class) // 设为出现所有异常都进行回滚
@Override
public void delete(Integer id) {
try {
deptMapper.deleteById(id);
empMapper.deleteByDeptId(id);
} finally { // 将记录日志部分的代码放在finally中
DeptLog deptLog = new DeptLog(); // 部门日志表POJO,表参数有create_time、description
deptLog.setCreateTime(LocalDateTime.now());
deptLog.setDescription("执行了解散部门的操作,此次解散的是" + id + "号部门");
deptLogService.insert(deptLog);
}
}
/* DeptLogService.java */
public interface DeptLogService {
void insert(DeptLog deptLog);
}
/* DeptLogServiceImpl.java */
public class DeptLogServiceImpl implements DeptLogService {
@Autowired
private DeptLogMapper deptLogMapper;
@Transactional(propagation = Propagation.REQUIRES_NEW) // 设置为需要新事务,使得不被同时结束
@Override
public void insert(DeptLog deptLog) {
deptLogMapper.insert(deptLog);
}
}
REQUIRED
:大部分情况下都是用该传播行为即可。REQUIRES_NEW
:当不希望事务之间相互影响时,可以使用该传播行为。比如:下订单前需要记录日志,不论订单保存成功与否,都需要保证日志记录能够记录成功。
2 AOP
AOP(Aspect Oriented Programming,面向切面编程、面向方面编程)即为面向特定方法编程。
2.1 AOP快速入门
动态代理是面向切面编程最主流的实现。而SpringAOP是Spring框架的高级技术,旨在管理bean对象的过程中,主要通过底层的动态代理机制,对特定的方法进行编程。
案例:统计各个业务层方法执行耗时——获取方法运行开始时间;运行原始方法;获取方法运行结束时间并计算执行耗时
- 导入依赖:在pom.xml中导入AOP的依赖
<!-- pom.xml -->
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-aop -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<version>3.2.1</version>
</dependency>
- 编写AOP程序:针对于特定方法根据业务需要进行编程。在类上添加注解
@Aspect
,在模板方法上添加通知注解,此处用@Around
(详见2.3),并设置切入点表达式指定特定方法(详见2.5)
/* TimeAspect.java */
@Slf4j
@Component
@Aspect //AOP类
public class TimeAspect {
// 将切入点表达式进行抽取,提高复用性
@Pointcut("execution(* com.hyplus.tlias.service.*.*())") // 执行目录下所有接口/类的方法时都会调用!
private void pt() {}
// 统计方法运行耗时
@Around("pt()") // 引用抽取至pt方法的切入点表达式
public Object recordTime(ProceedingJoinPoint joinPoint) throws Throwable {
// 1. 记录开始时间
long begin = System.currentTimeMillis();
// 2. 调用原始方法
Object result = joinPoint.proceed();
// 3. 获取方法运行结束时间并计算执行耗时
long end = System.currentTimeMillis();
log.info(joinPoint.getSignature() + "方法执行耗时:" + (end - begin) + "ms"); // getSignature:获取方法签名
return result;
}
}
2.2 核心概念
- 连接点(Join Point):可以被AOP控制的原始方法/目标方法(暗含方法执行时的相关信息,详见2.6)
- 通知(Advice):指哪些重复的逻辑,也就是共性功能(最终体现为一个方法)
- 切入点(Point Cut):匹配连接点的条件,通知仅会在切入点方法执行时被应用(即实际被AOP控制的方法)
- 切面(Aspect):描述通知与切入点的对应关系(通知+切入点)
- 目标对象(Target):通知所应用的对象
2.3 通知类型
5种常用的通知注解:
注解 | 类型 | 说明 |
---|---|---|
@Before | 前置通知 | 此注解标注的通知方法在目标方法前被执行 |
@Around | 环绕通知 | 此注解标注的通知方法在目标方法前、后都被执行【最常用】 ① 需有方法参数来传入目标方法,类型为ProceedingJoinPoint,并要求其调用方法 proceed() 来运行(更多信息调用详见2.6)② 返回值必须指定为Object,来接收原始方法的返回值 |
@After | 后置通知 | 此注解标注的通知方法在目标方法后被执行,无论是否有异常都会执行,又称最终通知 |
@AfterReturning | 返回后通知 | 此注解标注的通知方法在目标方法后被执行,有异常不会执行 |
@AfterThrowing | 异常后通知 | 此注解标注的通知方法发生异常后执行 |
@PointCut
:将公共的切入点表达式抽取出来,需要用到时引用该切入点表达式即可("方法名()"
)。为private时仅能在当前类中被引用,为public时可在外部类中被引用。
2.4 通知顺序
当有多个切面的切入点都匹配到了目标方法,目标方法运行时,多个通知方法都会被执行。
- 不同切面类中,默认按照切面类的类名字母排序
- 目标方法前的通知方法:字母排名靠前的先执行
- 目标方法后的通知方法:字母排名靠前的后执行
- 在切面类上添加注解
@Order(数字)
来控制顺序- 目标方法前的通知方法:数字小的先执行
- 目标方法后的通知方法:数字小的后执行
2.5 切入点表达式
切入点表达式:描述切入点方法的一种表达式,主要作用为决定项目中的哪些方法需要加入通知。
@Pointcut("切入点表达式")
2.5.1 execution
execution(...)
:根据方法签名(方法的返回值、包名、类名、方法名、方法参数等信息)来匹配
execution(访问修饰符? 返回值 包名.类名.?方法名(方法参数) throws 异常?)
execution(public void com.hyplus.tilas.mapper.EmpMapper.*(java.lang.Integer))
- 带
?
的表示可省略的部分:- 访问修饰符(比如public、protected等。建议省略)
- 包名.类名(省略后匹配范围为整个项目,范围过大,因此不建议省略)
- throws 异常(注意是方法上声明抛出的异常,不是实际抛出的异常。通常不指定)
- 两种通配符:
*
:一级通配符单个独立的任意符号,可以通配任意返回值、包名、类名、方法名、任意类型的一个参数,也可以通配包、类、方法名的一部分。// 例:通配如下所示包下所有以Service结尾的类中所有以delete开头的单参数方法 execution(* com.*.service.*Service.delete*(*))
..
:多个连续的任意符号,可以通配任意层级的包,或任意类型、任意个数的参数。// 例1:通配com.hyplus目录下所有DeptService类的任意参数方法 execution(* com.hyplus..DeptService.*(..)) // 例2:通配任意方法(慎用) execution(* *(..))
- 对于多个execution,可正常使用与
&&
、或||
、非!
来连接@Pointcut("execution(* com..service.DeptService.list()) || " + "execution(* com..service.DeptService.delete(*))")
- 书写建议
- 所有业务方法名在命名时尽量规范,方便切入点表达式快速匹配。如:查询类方法都是find开头,更新类方法都是update开头。
- 描述切入点方法通常基于接口描述,而不是直接描述实现类,增强拓展性。
- 在满足业务需要的前提下,尽量缩小切入点的匹配范围。如:包名匹配尽量不使用
..
,而是使用*
匹配单个包。
2.5.2 @annotation
@annotation
:用于匹配标识有特定注解的方法
@annotation(注解全类名)
// 例:匹配所有含有MyLog注解的方法
@Pointcut("@annotation(com.hyplus.aop.MyLog)")
若想某方法被匹配,只需给它加上相应的被检测注解即可。
2.6 连接点
在Spring中用JoinPoint抽象了连接点,用它可以获得方法执行时的相关信息,如目标类名、方法名、方法参数等,如2.2中所述。
- 对于
@Around
通知,获取连接点信息只能用类ProceedingJoinPoint。
/* MyAspectExamples.java */
@Around("pt()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
log.info("MyAspectExamples around before ...");
// 1. 获取目标对象的类名
String className = joinPoint.getTarget().getClass().getName();
log.info("目标对象的类名: {}", className);
// 2. 获取目标方法的方法名
String methodName = joinPoint.getSignature().getName();
log.info("目标方法的方法名: {}", methodName);
// 3. 获取目标方法运行时传入的参数
Object[] args = joinPoint.getArgs();
log.info("目标方法运行时传入的参数: {}", Arrays.toString(args));
// 4. 放行:目标方法执行,并获取其返回值
Object result = joinPoint.proceed();
log.info("目标方法运行时的返回值: {}", result);
log.info("MyAspectExamples around after ...");
return result;
}
- 对于其他4种通知,获取连接点信息只能用类JoinPoint,其为ProceedingJoinPoint的父类型。
- 调用方法同上,但某些通知由于其作用时机,无法获得返回值。
案例:记录接口操作日志至数据库表中
将前述案例中增、删、改相关接口的操作日志记录至数据库表中。
日志信息包含:操作人、操作时间、执行方法的全类名、执行方法名、方法运行时参数、返回值、方法执行时长。
思路分析:需要对所有业务类中的增、删、改方法添加统一功能,使用AOP技术最为方便——@Around
环绕通知;由于增、删、改方法名没有规律,可以自定义@Log
注解完成目标方法匹配。
- 准备
- 在案例工程中引入AOP的起步依赖(详见2.1)
- 导入资料中准备好的数据库表结构,并引入对应的实体类
/* com.hyplus.tlias.pojo.OperateLog.java 表结构对应的POJO */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class OperateLog {
private Integer id; // ID
private Integer operateUser; // 操作人ID
private LocalDateTime operateTime; // 操作时间
private String className; // 操作类名
private String methodName; // 操作方法名
private String methodParams; // 操作方法参数
private String returnValue; // 操作方法返回值
private Long costTime; //操作耗时
}
/* com.hyplus.OperateLogMapper.java */
@Mapper
public interface OperateLogMapper {
// 插入日志数据
@Insert("insert into operate_log (operate_user, operate_time, class_name, method_name, method_params, return_value, cost_time) " +
"values (#{operateUser}, #{operateTime}, #{className}, #{methodName}, #{methodParams}, #{returnValue}, #{costTime})")
public void insert(OperateLog log);
}
- 编码
- 自定义注解
@Log
- 定义切面类,完成记录操作日志的逻辑
- 自定义注解
/* com.hyplus.tlias.anno.Log.java */
@Retention(RetentionPolicy.RUNTIME) // 设为运行时生效
@Target(ElementType.METHOD) // 设为只能添加在方法上
public @interface Log {}
获取当前登录用户的方法:获取request对象,从请求头中获取JWT令牌,解析令牌获取当前用户的ID。详见基础(4)5.2.2
/* com.hyplus.tlias.aop.LogAspect.java */
@Slf4j
@Component
@Aspect // 切面类
public class LogAspect {
// 直接注入一个Servlet请求Bean来获取JWT令牌(相关概念及工具类见基础(4)5.2.2)
@Autowired
private HttpServletRequest request;
@Autowired
private OperateLogMapper operateLogMapper;
@Around("@annotation(com.hyplus.tlias.anno.Log)")
public Object recordLog(ProceedingJoinPoint joinPoint) throws Throwable {
// 获取操作人ID(当前登录员工ID):从令牌获取
// 获取请求头中的JWT令牌,解析令牌
String jwt = request.getHeader("token");
Claims claims = JwtUtils.parseJWT(jwt);
Integer operateUser = (Integer) claims.get("id");
// 获取操作时间
LocalDateTime operateTime = LocalDateTime.now();
// 获取操作类名
String className = joinPoint.getTarget().getClass().getName();
// 获取操作方法名
String methodName = joinPoint.getSignature().getName();
// 获取操作方法参数
Object[] args = joinPoint.getArgs();
String methodParams = Arrays.toString(args);
// 调用原始方法运行
long begin = System.currentTimeMillis();
Object result = joinPoint.proceed();
long end = System.currentTimeMillis();
// 获取方法返回值
String returnValue = JSONObject.toJSONString(result);
// 获取操作耗时
long costTime = end - begin;
// 记录操作日志
OperateLog operateLog = new OperateLog(null, operateUser, operateTime, className,
methodName, methodParams, returnValue, costTime);
operateLogMapper.insert(operateLog);
log.info("AOP已记录操作日志: {}", operateLog);
return result;
}
}
再给所有需要的方法添加自定义注解@Log
即可。
3 Spring原理篇
3.1 配置优先级
如基础(4)4.2所述,SpringBoot中支持三种格式的配置文件,优先级:properties
> yml
> yaml
。
在项目开发时,推荐统一使用一种格式的配置(yml是主流)。
除了配置文件外,SpringBoot还支持Java系统属性-Dserver.port=9000
和命令行参数--server.port=10010
的方式进行属性配置。
Idea中提供了可视化界面来配置:
打包后配置属性:
- 执行maven打包指令package生成jar包
- 执行java指令运行jar包,与此同时可执行上述属性配置
java -Dserver.port=9000 -jar tlias-web-management-0.8.1-SNAPSHOT.jar --server.port=10019
3.2 Bean管理
3.2.1 获取Bean
默认情况下,Spring项目启动时会把bean都创建好放在IOC容器中。
会受到作用域及延迟初始化影响,本小节主要针对于默认的单例、非延迟加载的bean而言。
若想要主动获取这些bean,可通过如下方法:
- 根据名称获取bean:
Object getBean(String name)
- 根据类型获取bean:
<T> T getBean(Class<T> requiredType)
- 根据名称和类型获取bean:
<T> T getBean(String name, Class<T> requiredType)
(自带类型转换)
/* 单元测试类 示例 */
// 需注入一个IOC容器对象
@Autowired
private ApplicationContext applicationContext;
@Test
public void testGetBean() {
// 根据名称获取bean(名称默认为类名首字母小写)
DeptController bean1 = (DeptController) applicationContext.getBean("deptController"); // 返回Object,需强转
System.out.println(bean1);
// 根据类型获取bean
DeptController bean2 = applicationContext.getBean(DeptController.class);
System.out.println(bean2);
// 根据名称与类型获取bean
DeptController bean3 = applicationContext.getBean("deptController", DeptController.class);
System.out.println(bean3);
}
3.2.2 Bean作用域
Spring支持五种作用域,后三种在web环境才生效:
作用域 | 说明 |
---|---|
singleton | 容器内同名称的bean只有一个实例(单例)【默认】 |
prototype | 每次使用该bean时会创建新的实例(非单例) |
request | 每个请求范围内会创建新的实例(web环境中,了解) |
session | 每个会话范围内会创建新的实例(web环境中,了解) |
application | 每个应用范围内会创建新的实例(web环境中,了解) |
可以通过@Scope
注解来配置作用域
@Scope("prototype") // 设置该bean为非单例
@RestController
@RequestMapping("/depts")
public class DeptController { ... }
默认singleton的bean,会在容器启动时被创建。添加注解@Lazy
可延迟初始化(延迟到其使用时才初始化)。
prototype的bean,每一次使用该bean的时候都会创建一个新的实例。
实际开发当中,绝大部分的Bean是单例的,也就是说绝大部分Bean不需要配置scope属性。
3.2.3 第三方Bean
如果要管理的Bean对象来自于第三方(不是自定义的),是无法用@Component及衍生注解声明Bean的。需要用到@Bean
注解。
- 在启动类中定义方法,将方法返回值交给IOC容器管理,成为IOC容器的Bean对象(不建议)
@SpringBootApplication
public class SpringbootWebConfig2Application {
// 声明第三方Bean
@Bean(name = "method1") // 将方法返回值交给IOC容器管理,成为IOC容器的Bean对象
public SAXReader saxReader() {
return new SAXReader();
}
}
- 通过
@Configuration
注解声明一个配置类,对这些bean进行集中分类配置,其他同上
@Configuration
public class SpringbootWebConfig2Application {
@Bean(value = "method2")
public SAXReader saxReader() {
return new SAXReader();
}
}
通过@Bean
注解的name
或value
属性可以声明bean的名称,默认为方法名。
若第三方Bean需要依赖其它Bean对象,直接在Bean定义方法中设置形参即可,容器会根据类型自动装配。
3.3 SpringBoot原理1:起步依赖
Spring Boot两大原理:起步依赖、自动配置。
原始的Spring框架进行Web程序开发,需要引入大量依赖
Spring Boot的起步依赖原理就是maven的依赖传递
3.4 SpringBoot原理2:自动配置
SpringBoot的自动配置就是当spring容器启动后,一些配置类、bean对象就自动存入到了IOC容器中,不需要手动声明,从而简化了开发,省去了繁琐的配置操作。
3.4.1 扫描方案
@ComponentScan
组件扫描(使用繁琐,性能低)
@ComponentScan({"com.example", "com.hyplus"}) // 设定组件扫描的包
@SpringBootApplication
public class SpringbootWebConfig2Application { ... }
@Import
导入,使用该注解导入的类会被Spring加载到IOC容器中。导入形式有:- 直接导入普通类
- 直接导入配置类
- 导入ImporterSelector接口实现类
/* ImporterSelector接口实现类 */
public class MyImportSelector implements ImportSelector {
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{"com.example.HeaderConfig"};
}
}
@Import({TokenParser.class, HeaderConfig.class, MyImportSelector.class}) // 导入
@SpringBootApplication
public class SpringbootWebConfig2Application { ... }
- 第三方包提供Enable开头的注解(形如
@EnableXxxx
),封装@Import
注解(更方便、优雅,被Spring Boot采用)
/* 第三方包中提供的 @EnableXxxx 格式的注解 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import(MyImportSelector.class) // 封装导入注解@Import
public @interface EnableHeaderConfig {
}
@EnableHeaderConfig // 添加该注解相当于直接使用上述Import
@SpringBootApplication
public class SpringbootWebConfig2Application { ... }
3.4.2 底层原理
@SpringBootApplication
注解标识在SpringBoot工程引导类上,是SpringBoot中最最最重要的注解。由三个部分组成:
@SpringBootConfiguration
: 该注解与@Configuration
注解作用相同,用来声明当前也是一个配置类。@ComponentScan
:组件扫描,默认扫描当前引导类所在包及其子包。@EnableAutoConfiguration
: SpringBoot实现自动化配置的核心注解。
如上所示,并非全部装配为IOC容器的Bean,而是添加了@Conditional及其子注释来进行条件装配。
@Conditional
:在方法、类上使用,按照一定的条件进行判断,在满足给定条件后才会注册对应的bean对象到Spring IOC容器中。
其子注解如下图所示
常用的3个为:
@ConditionalOnClass
:判断环境中是否有对应字节码文件,才注册bean到IOC容器。需使用属性name
指定类的全类名,或属性value
直接指定字节码文件对象。@Bean @ConditionalOnClass(name = "io.jsonwebtoken.jwts") //当前环境存在指定的这个类时,才声明该bean public HeaderParser headerParser() { ... }
@ConditionalOnMissingBean
:判断环境中没有对应的bean,才注册bean到IOC容器。可指定类型(value
属性)或名称(name
属性)。@Bean @ConditionalOnMissingBean // 当不存在当前类型的bean时,才声明该bean public HeaderParser headerParser() { ... }
@ConditionalOnProperty
:判断配置文件中有对应属性(name
属性)和对应值(havingValue
属性),才注册bean到IOC容器。@Bean @ConditionalOnProperty(name = "name", havingValue = "hyplus") // 配置文件中存在对应的属性和值,才注册bean到IOC容器。 public HeaderParser headerParser() { ... }
3.4.3 自定义starter
在实际开发中,经常会定义一些公共组件,提供给各个项目团队使用。而在SpringBoot的项目中,一般会将这些公共组件封装为SpringBoot的starter。
案例需求:自定义aliyun-Oss-spring-boot-starter,完成阿里云OSS操作工具类AliyunOSSUtils的自动配置。
目标:引入起步依赖引入之后,要想使用阿里云OSS,注入AliyunOSSUtils直接使用即可。
步骤:
- 创建aliyun-oss-spring-boot-starter模块
- 创建aliyun-oss-spring-boot-autoconfigure模块,在starter中引入该模块
- 在aliyun-oss-spring-boot-autoconfigure模块中的定义自动配置功能,并定义自动配置文件META-INF/spring/xxxx.imports
4 Maven高级
4.1 分模块设计与开发
分模块设计即将项目按照功能拆分成若干个子模块。
作用:方便项目的管理维护、扩展,也方便模块间的相互调用,资源共享。
注:分模块开发需要先针对模块功能进行设计,再进行编码。不会先将工程开发完毕,然后进行拆分。
4.2 继承
4.2.1 继承关系
继承描述的是两个工程间的关系,子工程可以继承父工程中的配置信息,常见于依赖关系的继承。与Java类继承相似,Maven工程继承只能单继承,但支持多重继承。
作用:简化依赖配置、统一管理依赖
实现:<parent> ... </parent>
- 创建maven模块tlias-parent,该工程为父工程,设置打包方式为pom
- jar:普通模块打包,springboot项目基本都是jar包(内嵌tomcat运行)【默认】
- war:普通web程序打包,需要部署在外部的tomcat服务器中运行
- pom:父工程或聚合工程,该模块不写代码,仅进行依赖管理
- 在子工程的pom.xml文件中,配置继承关系
- 配置了继承关系之后坐标中的
groupId
可省略,因为会自动继承父工程的。 <relativePath>
指定父工程的pom文件的相对位置。若不指定,将从本地仓库/远程仓库查找该工程。
- 配置了继承关系之后坐标中的
- 在父工程中配置各个工程共有的依赖,如lombok、各种starter等(子工程会自动继承父工程的依赖)
- 若父子工程都配置了同一个依赖的不同版本,以子工程的为准。
4.2.2 版本锁定
在maven中,可以在父工程的pom文件中通过<dependencyManagement>
来统一管理依赖版本。子工程引入依赖时,无需指定<version>
版本号,父工程统一管理。变更依赖版本,只需在父工程中统一变更。
<dependencies>
和<dependencyManagement>
的区别:
<dependencies>
是直接依赖,在父工程配置了依赖,子工程会直接继承下来<dependencyManagement>
是统一管理依赖版本,不会直接依赖,还需要在子工程中引入所需依赖(无需指定版本)
自定义属性/引用属性
4.3 聚合
聚合指将多个模块组织成一个整体,同时进行项目的构建。聚合工程为一个不具有业务功能的“空”工程(有且仅有一个pom文件)。
作用:快速构建项目(无需根据依赖关系手动构建,直接在聚合工程上构建即可)
maven中可以通过<modules>
设置当前聚合工程所包含的子模块名称。
聚合工程中所包含的模块,在构建时,会自动根据模块间的依赖关系设置构建顺序,与聚合工程中模块的配置书写位置无关。
继承与聚合
- 作用
- 聚合用于快速构建项目
- 继承用于简化依赖配置、统一管理依赖
- 相同点
- 聚合与继承的pom.xml文件打包方式均为pom,可以将两种关系制作到同一个pom文件中
- 聚合与继承均属于设计型模块,并无实际的模块内容
- 不同点
- 聚合是在聚合工程中配置关系,聚合可以感知到参与聚合的模块有哪些
- 继承是在子模块中配置关系,父模块无法感知哪些子模块继承了自己
4.4 私服
私服是一种特殊的远程仓库,它是架设在局域网内的仓库服务,用来代理位于外部的中央仓库,用于解决团队内部的资源共享与资源同步问题。
依赖查找顺序:本地仓库 → 私服 → 中央仓库
私服在企业项目开发中,一个项目/公司,只需要1台即可(无需我们自己搭建,会使用即可)
资源上传与下载如下图所示
资源版本
- RELEASE(发行版本):功能趋于稳定、当前更新停止,可以用于发行的版本,存储在私服中的RELEASE仓库中
- SNAPSHOT(快照版本):功能不稳定、尚处于开发中的版本,存储在私服的SNAPSHOT仓库中
流程
- 设置私服的访问用户名/密码(maven配置文件settings.xml中的
<servers>
中配置)
- 在IDEA的maven工程的pom文件中配置上传(发布)地址
- 设置私服依赖下载的仓库组地址(maven配置文件settings.xml中的
<mirrors>
、<profiles>
中配置)