Spring Boot - 自定义starter
# 一、什么是SpringBoot starter机制
SpringBoot中的starter是一种非常重要的机制(自动化配置),能够抛弃以前繁杂的配置,将其统一集成进starter,应用者只需要在maven中引入starter依赖,SpringBoot就能自动扫描到要加载的信息并启动相应的默认配置。
starter让我们摆脱了各种依赖库的处理,需要配置各种信息的困扰。SpringBoot会自动通过classpath路径下的类发现需要的Bean,并注册进IOC容器。SpringBoot提供了针对日常企业应用研发各种场景的spring-boot-starter依赖模块。
所有这些依赖模块都遵循着约定成俗的默认配置,并允许我们调整这些配置,即遵循“约定大于配置”的理念。
# 二、为什么要自定义starter
在我们的日常开发工作中,经常会有一些独立于业务之外的配置模块,我们经常将其放到一个特定的包下,然后如果另一个工程需要复用这块功能的时候,需要将代码硬拷贝到另一个工程,重新集成一遍,麻烦至极。
如果我们将这些可独立于业务代码之外的功配置模块封装成一个个starter,复用的时候只需要将其在pom中引用依赖即可,SpringBoot为我们完成自动装配,简直不要太爽。
# 三、什么时候需要创建自定义starter
在我们的日常开发工作中,可能会需要开发一个通用模块,以供其它工程复用。SpringBoot就为我们提供这样的功能机制,我们可以把我们的通用模块封装成一个个starter,这样其它工程复用的时候只需要在pom中引用依赖即可,由SpringBoot为我们完成自动装配。
常见场景:
# 1. 通用功能模块的集成
如果你有一些功能模块,如短信发送服务,这些功能在多个项目中都需要使用,你可以将这些通用功能封装成一个starter。这样,其他项目只需要简单地添加一个Maven依赖就能集成该功能,同时享受到Spring Boot的自动配置特性。
# 2. AOP日志管理
利用AOP(面向切面编程)技术实现的日志管理,比如自动记录方法的执行时间、参数和返回值,是许多应用都需要的功能。将这样的AOP逻辑封装成一个starter可以使得在任何新项目中引入和使用变得非常简单。
# 3. 解决特定问题的工具
对于某些特定的技术问题,如分布式系统中的ID生成策略(例如雪花ID),或者在使用数据序列化/反序列化库(如Jackson或Fastjson)处理Long类型到String类型转换以解决精度问题时,一个专门针对这些问题的starter可以提供一个统一且可重用的解决方案。
# 4. 微服务环境下的配置共享
在微服务架构中,可能会有一些跨服务的共享配置,如数据库连接池配置和Redis配置。创建一个或多个starter,用于封装这些共享配置和服务(例如,统一的RedisTemplate
配置),可以大幅简化服务的配置和维护。
# 5. 提高开发效率
当你发现在多个项目中重复实现相同的配置或模板代码时,通过创建自定义starter来封装这些共通部分,不仅可以减少冗余、提高代码的可维护性,还可以加快新项目的开发进度。
总结来说,当你面对需要在多个项目之间共享的通用功能、配置问题,或者需要提供一种标准化的集成方式时,创建自定义starter便显得非常有价值。它不仅提高了开发效率,还有助于保持项目的一致性和可维护性。
# 四、自动加载核心注解说明
在Spring Boot中,条件注解(Conditional Annotations)允许在特定条件满足时进行自动配置。这些条件可以基于类路径上的类存在、Bean的存在、配置属性的值、环境特性等因素。理解这些注解及其加载顺序对于高效地使用和创建Spring Boot自定义starters至关重要。
# 4.1 核心条件注解
Spring Boot的条件注解是一套强大的工具,允许开发者在满足特定条件时自动配置beans。这种灵活性特别适用于创建模块化和可配置的应用程序,以及开发自定义starters。以下是对核心条件注解的详细总结:
- @Conditional
- 基本用法:它是条件注解的基础,可以实现基于特定条件的bean注册。通常与自定义的条件类一起使用,这些条件类实现了
Condition
接口,并重写matches
方法以定义满足条件的逻辑。 - 使用场景:当需要基于复杂或非标准条件进行bean注册时,比如组合多个条件或创建全新的判断逻辑。
- @ConditionalOnBean / @ConditionalOnMissingBean
- 基本用法:这两个注解控制基于Spring上下文中特定Bean的存在或缺失来进行自动配置。
- @ConditionalOnBean:当Spring上下文中存在一个或多个指定的beans时,条件满足。
- @ConditionalOnMissingBean:当Spring上下文中缺少一个或多个指定的beans时,条件满足。
- 使用场景:用于在特定bean的依赖存在时启用配置或在缺少时提供默认配置。
- @ConditionalOnClass / @ConditionalOnMissingClass
- 基本用法:根据类路径上的类的存在或缺失来控制配置。
- @ConditionalOnClass:当类路径上存在指定的类时,条件满足。
- @ConditionalOnMissingClass:当类路径上不存在指定的类时,条件满足。
- 使用场景:常用于基于特定库的存在来启用自动配置,例如只有当某个特定的库类存在时才自动配置与之相关的beans。
- @ConditionalOnProperty
- 基本用法:根据Spring环境中的属性存在或匹配特定值来控制配置。
- 使用场景:用于在配置文件中设置的属性值满足特定条件时启用或禁用配置。
- @ConditionalOnExpression
- 基本用法:基于SpEL(Spring Expression Language)表达式的评估结果来控制配置。
- 使用场景:当需要基于更复杂的条件逻辑来启用配置时使用,比如依赖于多个环境属性或调用方法的结果。
- @ConditionalOnResource
- 基本用法:当类路径上存在特定资源文件时,条件满足。
- 使用场景:用于基于资源文件的存在来启用配置,例如仅当特定的配置文件存在于类路径上时才加载相关配置。
- @ConditionalOnWebApplication / @ConditionalOnNotWebApplication
- 基本用法:根据应用是否为Web应用来控制配置。
- @ConditionalOnWebApplication:当应用是一个Web应用时,条件满足。
- @ConditionalOnNotWebApplication:当应用不是一个Web应用时,条件满足。
- 使用场景:用于仅在Web应用上下文或非Web应用上下文中启用特定配置。
- @ConditionalOnJava
- 基本用法:根据JVM的版本来控制配置。
- 使用场景:用于只有在特定版本的Java环境下才启用的配置。
- @ConditionalOnJndi
- 基本用法:在JNDI资源存在时条件满足。
- 使用场景:用于只有在JNDI资源可用时才启用的配置,常见于需要连接到企业级服务如数据库和消息服务时。
# 4.2 加载顺序和优先级
Spring Boot的自动配置通过@EnableAutoConfiguration
注解启用,该注解通过spring.factories
文件加载应用的自动配置类。自动配置类的加载和处理顺序受以下因素影响:
- Auto-configuration类的定义顺序:在
spring.factories
中列出的自动配置类按列出的顺序进行处理。 - @AutoConfigureAfter 和 @AutoConfigureBefore:这两个注解可以控制自动配置类之间的相对顺序。通过这些注解,你可以声明一个配置类应该在另一个配置类之前或之后加载。
- 条件评估:Spring Boot在处理自动配置类时会评估每个类上的条件注解。只有当所有相关条件都满足时,配置类中定义的Bean才会被注册到Spring上下文中。
# 五、自定义starter的开发流程
那么前面对自定义 starter 机制简单介绍了一下,接下来就进入正题!
自定义starter的开发流程:
- 创建Starter项目(spring-initl 2.1.14)
- 定义Starter需要的配置类(Properties)
- 编写Starter项目的业务功能
- 编写自动配置类
- 编写spring.factories文件加载自动配置类
- 打包安装
- 其它项目引用
# 案例一:为短信发送功能创建一个starter
# 1. 创建Starter项目
1.1 命名规范
- 官方Starter命名:遵循
spring-boot-starter-{模块名}
的格式,如spring-boot-starter-web
。 - 自定义Starter命名:采用
{模块名}-spring-boot-starter
的格式,例如,如果你的模块名为mystarter
,则命名为mystarter-spring-boot-starter
。
1.2 必须引入的依赖
对于自定义starter,通常需要引入spring-boot-configuration-processor
依赖,此依赖用于处理配置属性类,生成元数据信息,从而支持属性的自动提示功能。使用<optional>true</optional>
标记,表示这个依赖在引用此starter的项目中不是必须的。
<!--表示两个项目之间依赖不传递;不设置optional或者optional是false,表示传递依赖-->
<!--例如:project1依赖a.jar(optional=true),project2依赖project1,则project2不依赖a.jar-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
2
3
4
5
6
7
# 2. 编写相关属性类(XxxProperties):SmsProperties.java
- 使用
@ConfigurationProperties(prefix = "zzcloud.sms")
注解来定义属性类,prefix
指定了配置文件中的属性前缀。 - 类的属性名称需要根据Spring Boot的宽松绑定规则与配置文件中的属性相匹配。
- 可以通过简单地初始化字段值来定义默认值。
- 属性类可以设置为包私有,但其字段需要有公共的setter方法,以便Spring Boot能够进行绑定。
注意事项
在初步创建SmsProperties
类时,可能会看到IDE提示错误,提示未通过@EnableConfigurationProperties
注册或未标记为Spring组件。这是因为还需要创建自动配置类并使用@EnableConfigurationProperties(SmsProperties.class)
来启用对SmsProperties
类的配置属性绑定。
package com.zking.zzcloudspringbootstarter.sms;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.io.Serializable;
/**
* SmsProperties类通过@ConfigurationProperties注解绑定配置属性,
* 以"zzcloud.sms"作为前缀。这个类允许Spring Boot应用在application.properties
* 或application.yml中配置SMS服务的访问密钥和密钥ID。
*/
@ConfigurationProperties("zzcloud.sms")
public class SmsProperties implements Serializable {
// 访问ID,用作访问SMS服务的账号
private String accessKeyId;
// 访问凭证,用作访问SMS服务的密码
private String accessKeySecret;
// 获取访问ID的公共方法
public String getAccessKeyId() {
return accessKeyId;
}
// 设置访问ID的公共方法
public void setAccessKeyId(String accessKeyId) {
this.accessKeyId = accessKeyId;
}
// 获取访问凭证的公共方法
public String getAccessKeySecret() {
return accessKeySecret;
}
// 设置访问凭证的公共方法
public void setAccessKeySecret(String accessKeySecret) {
this.accessKeySecret = accessKeySecret;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# 3. 编写Starter项目的业务功能
在自定义starter项目中,开发业务功能是核心部分之一。对于SMS服务的starter,需要先定义一个服务接口(如ISmsService
),以及实现这个接口的实现类(SmsServiceImpl
)。
package com.zking.zzcloudspringbootstarter.sms;
/**
* 短信服务接口定义。
* 该接口提供发送短信的基本功能,包括指定手机号、短信签名、模板以及内容。
*/
public interface ISmsService {
/**
* 发送短信的方法。
*
* @param phone 手机号码,短信发送的目标手机号。
* @param signName 短信签名,用于标识发送短信的个体或企业,需在短信服务平台注册。
* @param templateCode 短信模板代码,指定发送短信使用的模板,需在短信服务平台预设。
* @param data 短信内容,根据短信模板填充的具体内容,通常以JSON格式提供。
*/
void send(String phone, String signName, String templateCode, String data);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.zking.zzcloudspringbootstarter.sms;
// 引入短信服务接口
import com.zking.zzcloudspringbootstarter.sms.ISmsService;
/**
* 短信服务的实现类,实现了ISmsService接口,提供发送短信的功能。
*/
public class SmsServiceImpl implements ISmsService {
// 访问ID,即账号。用于访问短信服务提供商的API。
private String accessKeyId;
// 访问凭证,即密码。用于验证访问身份。
private String accessKeySecret;
/**
* 类的构造函数,用于初始化短信服务所需的访问ID和访问凭证。
*
* @param accessKeyId 访问ID
* @param accessKeySecret 访问凭证
*/
public SmsServiceImpl(String accessKeyId, String accessKeySecret) {
this.accessKeyId = accessKeyId;
this.accessKeySecret = accessKeySecret;
}
/**
* 实现接口中的send方法,用于发送短信。
*
* @param phone 手机号码,接收短信的目标手机号。
* @param signName 短信签名,用于标识发送者。
* @param templateCode 短信模板代码,指定使用哪个模板发送短信。
* @param data 短信内容,根据模板填充的具体内容。
*/
@Override
public void send(String phone, String signName, String templateCode, String data) {
// 打印访问信息,实际开发中应替换为调用短信服务提供商的API进行短信发送。
System.out.println("接入短信系统,accessKeyId=" + accessKeyId + ",accessKeySecret=" + accessKeySecret);
System.out.println("短信发送,phone=" + phone + ",signName=" + signName + ",templateCode=" + templateCode + ",data=" + data);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# 4. 自定义Condition条件(可选)
如果想确保只有在提供了accessKeyId
和accessKeySecret
时才启用SMS服务,可以通过检查这些属性是否被设置为非空来实现。不过,@ConditionalOnProperty
不能直接用于检查属性值是否非空,它更多是用于检查属性是否存在或等于特定值。
一个更灵活的方式是实现自定义条件。可以创建一个实现了Condition
接口的类,这个类包含了决定是否创建bean的逻辑。这种方法可以让你基于accessKeyId
和accessKeySecret
是否被定义来决定是否创建SmsServiceImpl
。
public class SmsServiceCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
Environment env = context.getEnvironment();
// 尝试通过不同的命名约定获取属性值
String accessKeyId = Stream.of("zzcloud.sms.accessKeyId", "zzcloud.sms.access-key-id",)
.map(env::getProperty)
.filter(Objects::nonNull)
.findFirst()
.orElse(null);
String accessKeySecret = Stream.of("zzcloud.sms.accessKeySecret", "zzcloud.sms.access-key-secret")
.map(env::getProperty)
.filter(Objects::nonNull)
.findFirst()
.orElse(null);
return accessKeyId != null && !accessKeyId.isEmpty() && accessKeySecret != null && !accessKeySecret.isEmpty();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
我们直接通过Environment
接口使用getProperty
方法获取属性值时,需要确保使用的属性名与配置文件中的完全匹配,为了确保适配不同的配置文件,提供了两种匹配模式去确保成功获取属性值。
然后,在SmsAutoConfig
类(自动配置类)中使用@Conditional
注解来引用这个自定义条件
# 5. 编写自动配置类AutoConfig
5.1 使用@Configuration
注解
这个注解表明该类是一个配置类,Spring Boot会在启动时自动扫描并加载这个类的配置信息。
5.2 应用@EnableConfigurationProperties
自定义Spring Boot starter的自动配置类SmsAutoConfig
,它负责基于SmsProperties
中定义的属性创建并配置SmsServiceImpl
bean。
package com.zking.zzcloudspringbootstarter.config;
import com.zking.zzcloudspringbootstarter.sms.SmsProperties;
import com.zking.zzcloudspringbootstarter.sms.SmsServiceImpl;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.annotation.Resource;
/**
* SMS自动配置类,负责创建和配置SMS服务的bean。
*/
@Configuration // 标记这个类是一个配置类,Spring Boot会在启动时自动扫描并加载它
// 使SmsProperties类上的@ConfigurationProperties注解生效,允许从配置文件中绑定属性值
@EnableConfigurationProperties({SmsProperties.class})
public class SmsAutoConfig {
@Resource
private SmsProperties smsProperties;
/**
* 定义一个SmsServiceImpl的Bean。
* 使用SmsProperties中的属性来初始化SmsServiceImpl实例。
*
* @return SmsServiceImpl的实例,配置了访问ID和访问凭证。
*/
@Bean
@Conditional(SmsServiceCondition.class) // 使用自定义条件
public SmsServiceImpl smsServiceImpl(){
// 创建SmsServiceImpl的实例,通过SmsProperties提供的访问ID和访问凭证进行初始化
return new SmsServiceImpl(smsProperties.getAccessKeyId(), smsProperties.getAccessKeySecret());
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
通过自定义的这种条件,SmsServiceImpl
的bean只有在accessKeyId
和accessKeySecret
属性被正确配置时才会被创建。如果没有这个属性或者属性值为空,则不会创建该bean,从而避免了不必要的资源消耗。
注意事项
- 在自己的项目中使用时,如果
@ConfigurationProperties
类被标记为一个组件(加上@Component
注解),那么不需要显式使用@EnableConfigurationProperties
。 - 在自定义starter或自动配置类中,
@ConfigurationProperties
类不会被标记为一个组件,@EnableConfigurationProperties
注解确保了即使属性类没有被标记为组件(@Component
),Spring Boot也会实例化并注入配置属性。
# 6. 编写spring.factories文件加载自动配置类
6.1 创建文件
在resources/META-INF
目录下新建spring.factories
文件,这个文件是Spring Boot自动配置机制的关键,它指示Spring Boot在启动时加载指定的自动配置类。
6.2 配置自动配置类
在spring.factories
文件中,指定你的自动配置类的完整类名,以便Spring Boot能够加载它。
# Auto Configuration
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.zking.zzcloudspringbootstarter.sms.SmsAutoConfig
2
3
注意:AutoConfig
在这里指的是你的自动配置类的类名。如果有多个自动配置类,可以使用逗号分隔每个类的完整类名,或者使用反斜杠\
来继续在新行列出其他类名。如下图所示
# Auto Configure(以MybatisPlus为例)
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.baomidou.mybatisplus.autoconfigure.MybatisPlusLanguageDriverAutoConfiguration,\
com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration
2
3
4
通过spring.factories实现的全自动配置
当你在spring.factories
文件中指定了自动配置类,Spring Boot启动时会自动检测并加载这些配置类,前提是starter已经被加入到项目的依赖中。这个机制不需要在应用的任何地方显式声明或使用注解来启用配置,符合"约定大于配置"的原则,提供了无缝的自动配置体验。
# 7. 使用@EnableXxx机制(可选)
你还以通过自定义注解手动导入自动配置类来实现半自动化的配置,这种方法提供了更多的控制权,允许开发者显式地启用某些自动配置。这种做法依赖于Spring的@Import
注解来实现,与自定义的启用注解(如@EnableXxx
)结合使用。
首先,你需要定义一个自定义注解,比如@EnableSms
,用于在需要这个starter功能的Spring Boot应用中启用SMS功能。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(SmsAutoConfig.class) // 这里导入自动配置类
public @interface EnableSms {
}
2
3
4
5
6
这个注解通过@Import
引入了SmsAutoConfig
自动配置类,使得只要在Spring Boot应用的任一配置类上使用了@EnableSms
,相关的自动配置就会被应用(一般我们会放在启动类上面,便于维护和查找)。
应用自定义启用注解
然后,在任何需要使用SMS功能的Spring Boot应用中,你只需在配置类上加上@EnableSms
注解。这样,SmsAutoConfig
中定义的beans就会被自动注册到Spring上下文中。
@SpringBootApplication
@EnableSms // 启用SMS功能
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
2
3
4
5
6
7
通过自定义注解实现的半自动配置
使用自定义注解(如@EnableSms
)配合@Import
来引入自动配置类,则需要在应用中显式地通过添加该注解来启用特定的配置。这种方式提供了更多的控制,因为它允许开发者决定是否以及在哪里启用这些配置。
# 8. 打包安装
在自定义Spring Boot starter项目中进行打包时,一个重要的注意点是Spring Boot应用打包成的JAR文件是一个可执行的JAR(uber-jar),它包含了BOOT-INF
目录用于存放应用的类和依赖。这种打包方式可能导致当这个JAR被其他项目作为依赖引入时,这些项目无法直接访问到BOOT-INF
内的类。为了解决这个问题,你可以在pom.xml
文件中对spring-boot-maven-plugin
插件进行配置,使得在打包时生成一个额外的“非执行”JAR文件,该文件适用于作为依赖被其他项目引用
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<!-- 设置classifier为exec,这将在原有的可执行JAR之外,额外生成一个带有exec分类器的JAR文件。 -->
<!-- 这个额外的JAR文件不包含用于运行Spring Boot应用的特定结构(如BOOT-INF目录), -->
<!-- 从而使得它可以被其他项目作为依赖库正常引用。 -->
<classifier>exec</classifier>
</configuration>
</plugin>
2
3
4
5
6
7
8
9
10
如果在本地测试starter,直接执行install命令安装到本地仓库即可。
# 9. 在其他项目中的应用
9.1.首先在其他项目的pom.xml中引入相关依赖:
<dependency>
<groupId>com.scholar</groupId>
<artifactId>scholar-spring-boot-starter</artifactId>
<version>1.0</version>
</dependency>
2
3
4
5
9.2.在application.yml文件中添加配置
zzcloud:
sms:
access-key-id: 1314
access-key-secret: scholar
2
3
4
为了展示效果,我们写一个测试类,看能否看到我们想要的效果,测试代码如下:
@SpringBootTest
class SpringBootStarterTestApplicationTests {
@Resource
private ISmsService iSmsService;
@Test
void test() {
iSmsService.send("55434818","书生","关注微信公众号:书生带你学编程", String.valueOf(new Date()));
}
}
2
3
4
5
6
7
8
9
10
11
12
控制台效果如下:
如果控制台有如上效果,说明我们整个自定义starter的过程就成功了!
# 案例二:AOP方式统一服务日志
原先实现统一日志都是放到每个工程中以AOP方式实现,现在有了starter方式,就可以将公司的日志规范集中到这里来统一管理。
这里我们使用之前创建好的项目进行案例二的编写,步骤与上同理:
# 1. 导入aop相关依赖
<!--aop相关依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2
3
4
5
# 2. 编写相关属性类(XxxProperties):WebLogProperties.java
package com.zking.zzcloudspringbootstarter.sms;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.io.Serializable;
/**
* 专用于存储web日志相关配置的属性类。
* 通过@ConfigurationProperties注解,该类可以绑定配置文件中以"zzcloud.weblog"为前缀的属性。
*/
@ConfigurationProperties("zzcloud.weblog")
public class WebLogProperties implements Serializable {
// 用于控制是否启用web日志功能的属性。通过配置文件可进行设置。
// Boolean类型允许这个属性在配置文件中不被设置时保持null,提供了更灵活的配置选择,例如默认不启用但允许通过配置启用。
public Boolean enabled;
/**
* 获取是否启用web日志功能的属性值。
*
* @return 当前配置的是否启用web日志的布尔值。
*/
public Boolean getEnabled() {
return enabled;
}
/**
* 设置是否启用web日志功能的属性值。
*
* @param enabled 布尔值,指示是否启用web日志功能。
*/
public void setEnabled(Boolean enabled) {
this.enabled = enabled;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# 3. 编写Starter项目的业务功能
package com.zking.zzcloudspringbootstarter.weblog;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
/**
* 使用AOP(面向切面编程)技术实现的web日志记录切面。
*/
@Aspect // 表明这是一个切面类
@Component
@Slf4j
public class WebLogAspect {
/**
* 定义切入点,指定哪些方法的执行需要被拦截记录日志。
* 这里的切入点表达式指定了所有Controller下的所有方法。
*/
@Pointcut("execution(* *..*Controller.*(..))")
public void webLog(){}
/**
* 在切入点前执行的方法,用于记录请求到达前的相关信息。
*
* @param joinPoint 连接点信息,提供了目标方法的详细信息。
* @throws Throwable 可能抛出的异常。
*/
@Before("webLog()")
public void doBefore(JoinPoint joinPoint) throws Throwable {
// 从RequestContextHolder中获取请求的信息
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
// 记录请求到达时的相关信息,包括请求URL、客户端IP地址和请求参数。
log.info("开始服务:{}", request.getRequestURL().toString());
log.info("客户端IP :{}", request.getRemoteAddr());
log.info("参数值 :{}", Arrays.toString(joinPoint.getArgs()));
}
/**
* 在切入点返回后执行的方法,用于记录返回的信息。
*
* @param ret 目标方法执行完成后的返回值。
* @throws Throwable 可能抛出的异常。
*/
@AfterReturning(returning = "ret", pointcut = "webLog()")
public void doAfterReturning(Object ret) throws Throwable {
// 记录请求处理完成后的返回值
log.info("返回值 : {}", ret);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# 4. 编写自动配置类AutoConfig
4.1@ConditionalOnProperty(prefix = "zzcloud.weblog",value = "enabled", matchIfMissing = true):
- prefix和value:一起定义了需要检查的具体属性。例如,
prefix = "zzcloud.weblog"
和value = "enabled"
一起指向zzcloud.weblog.enabled
这个属性。 - matchIfMissing:指定了当检查的属性缺失时,配置是否应该生效。
true
表示如果没有提供zzcloud.weblog.enabled
属性,自动配置还是会默认生效。
4.2 @ConditionalOnMissingBean:
这个注解用于仅在Spring上下文中缺少指定类型的Bean时,才创建一个Bean。它确保了如果上下文中已经存在了一个相同类型的Bean,配置中的Bean定义将不会被加载,从而避免了Bean的重复定义。
package com.zking.zzcloudspringbootstarter.config;
import com.zking.zzcloudspringbootstarter.weblog.WebLogAspect;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Web日志的自动配置类。
* 该类负责在满足特定条件下自动配置web日志相关的Bean。
*/
@Configuration // 标记为配置类,Spring Boot将自动扫描并加载该类的配置信息
@EnableConfigurationProperties({WebLogProperties.class}) // 启用WebLogProperties类的@ConfigurationProperties注解,使其可以从配置文件中绑定属性
@ConditionalOnProperty(prefix = "zzcloud.weblog", value = "enabled", matchIfMissing = true) // 条件注解,仅当zzcloud.weblog.enabled属性为true或未设置时,该配置类才有效
public class WebLogAutoConfig {
/**
* 定义web日志切面的Bean。
* 只有当应用上下文中不存在WebLogAspect类型的Bean时,才会创建一个新的WebLogAspect Bean。
* 这避免了Bean的重复定义。
*
* @return WebLogAspect Bean实例。
*/
@Bean
@ConditionalOnMissingBean // 在上下文中不存在WebLogAspect Bean时,才创建WebLogAspect Bean
public WebLogAspect webLogAspect(){
return new WebLogAspect(); // 创建并返回WebLogAspect实例
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# 5. 编写spring.factories文件加载自动配置类
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.zking.zzcloudspringbootstarter.config.SmsAutoConfig,\
com.zking.zzcloudspringbootstarter.config.WebLogAutoConfig
2
3
# 6. 打包,操作同上
# 7.在其他项目中引用
7.1.导入依赖,由于这里我们使用的是之前的那个项目,这里就不需要重复导入依赖了
7.2.在application.yml文件中添加配置
zzcloud:
sms:
access-key-id: 1314
access-key-secret: scholar
weblog:
enabled: true # 开启日志
2
3
4
5
6
如果我使用日志,就会拿到我请求的路径、我的客户端以及我的参数值,效果如下:
如果由以上效果,说明我的案例二:AOP方式统一服务日志就成功了!