为何以继承方式引入SpringBoot
提出疑问
以前我们在开发项目时,需要什么,引入对应的依赖就行,比如我们需要连接mysql数据,则引入mysql驱动的依赖,如下:
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.3.0</version>
</dependency>
现在我们要使用SpringBoot框架,按说也应该采用依赖的方式将SpringBoot框架引入,如下:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.5</version>
</dependency>
但是SpringBoot官方推荐的不是直接引入依赖,而是采用继承的方式实现,如下:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.5</version>
</parent>
为什么?
作为父项目和作为依赖的区别
继承父工程的优势
- 依赖管理:可以在父工程中定义依赖的版本,子模块可以直接引用而不必指定版本号。
- 插件管理:可以在父工程中配置常用的插件及其版本,子模块可以直接使用这些配置。
- 属性设置:可以在父工程中定义一些通用的属性,如项目编码、Java 版本等。
- 统一配置:可以统一多个子模块的构建配置,确保一致性。
直接引入依赖的局限性(如果你不使用继承父工程的方式,而是通过直接引入依赖的方式来管理项目,那么你将失去上述的一些优势)
- 依赖版本管理:每个子模块都需要单独指定依赖的版本,这会导致大量的重复配置,并且难以维护。
- 插件配置:每个子模块都需要单独配置插件及其版本,无法共享父工程中的插件配置。
- 属性设置:每个子模块都需要单独设置通用的属性,如项目编码、Java 版本等。
- 构建配置:每个子模块的构建配置需要单独维护,难以保证一致性。
总结:选择哪种方式取决于你的具体需求。
- 如果你希望多个项目之间共享构建配置,那么使用父项目是一个好的选择;
- 如果你只是想在项目之间共享代码,那么应该使用依赖关系。
原理揭晓
通过源码来分析一下:
依赖统一管理的好处
这样做有以下几个好处:
- 简化依赖声明:
开发者只需要在pom.xml
文件中声明需要的依赖而不需要指定其版本号,因为 Spring Boot 已经为这些依赖指定了版本。例如,如果你需要使用mysql驱动,你只需要添加相应的依赖声明而不需要关心版本。
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
</dependency>
- 避免版本冲突:
当多个库之间存在依赖关系的时候,如果手动管理版本可能会导致版本之间的冲突(即“依赖地狱”)。Spring Boot 提供的统一版本管理可以减少这种冲突的可能性。 - 易于升级:
当 Spring Boot 发布新版本时,通常会更新其依赖库到最新稳定版。因此,当你升级 Spring Boot 版本时,它所管理的所有依赖也会随之更新到兼容的版本。 - 减少配置错误:
由于 Spring Boot 自动处理了依赖的版本,减少了手动输入版本号可能引入的拼写或格式错误。 - 提高开发效率:
开发者可以专注于业务逻辑的编写,而不是花费时间在解决依赖问题上。
当然,如果你在项目中需要更改某个依赖的版本号,不想使用SpringBoot框架指定的版本号,只需要在引入依赖时强行执行版本号即可,maven是支持就近原则的:
这样做就是采用SpringBoot指定版本的依赖:
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
</dependency>
这样做就是不采用SpringBoot指定版本的依赖:
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.2.0</version>
</dependency>
Starter-启动器
启动器实现原理
- 依赖聚合:
每个启动器通常对应一个特定的功能集或者一个完整的应用模块,如spring-boot-starter-web
就包含了构建 Web 应用所需的所有基本依赖项,如 Spring MVC, Tomcat 嵌入式容器等。 - 依赖传递:
当你在项目中引入一个启动器时,它不仅会把自身作为依赖加入到你的项目中,还会把它的所有直接依赖项(transitive dependencies)也加入进来。这意味着你不需要单独声明这些依赖项,它们会自动成为项目的一部分。 - 版本管理:
启动器内部已经指定了所有依赖项的具体版本,这些版本信息存储在一个公共的 BOM(Bill of Materials,物料清单)文件中,通常是spring-boot-dependencies
。当引入启动器时,实际上也间接引用了这个 BOM,从而确保了所有依赖项版本的一致性。 - 自动配置:
许多启动器还提供了自动配置(Auto-configuration),这是一种机制,允许 Spring Boot 根据类路径上的可用组件自动设置你的应用程序。例如,如果类路径上有 Spring MVC 和嵌入式 Tomcat,则 Spring Boot 会自动配置它们,并准备好一个 web 应用程序。
使用启动器的示例
假设你想创建一个基于 Spring MVC 的 RESTful Web 应用,你可以简单地将 spring-boot-starter-web
添加到你的项目中:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
当你添加这个依赖时,Spring Boot 会处理所有必要的细节,包括添加 Spring MVC 和 Tomcat 作为嵌入式 Servlet 容器,并且根据类路径上的内容进行适当的自动配置。如下图所示:
这就是 Spring Boot 启动器的基本实现原理,它简化了依赖管理,让开发者能够更专注于业务逻辑的实现。
都有哪些启动器
启动器通常包括:
- SpringBoot官方提供的启动器
- 非官方提供的启动器
官方提供的启动器
启动器命名特点:spring-boot-starter-*
非官方的启动器
启动器命名特点:*-spring-boot-starter
Spring Boot核心注解
创建一个新的模块,来学习Spring Boot核心注解:
只加入web启动器
@SpringBootApplication注解
Spring Boot的主入口程序被@SpringBootApplication
注解标注,可见这个注解的重要性,查看它的源码:
可以看出这个注解属于组合注解
。拥有@SpringBootConfiguration
、@EnableAutoConfiguration
、@ComponentScan
的功能。
@SpringBootConfiguration注解
@SpringBootConfiguration
注解的源码如下:
@SpringBootApplication
public class Sb305CoreApplication {
@Bean
public Date getNowDate(){ // 方法名作为bean的id
return new Date();
}
/**
* 主程序入口
* 本方法负责启动Spring Boot应用,并从Spring应用上下文中获取Bean进行展示
* @param args 命令行参数,允许从命令行传递参数到程序
*/
public static void main(String[] args) {
// 启动Spring Boot应用,返回应用上下文
ConfigurableApplicationContext applicationContext = SpringApplication.run(Sb305CoreApplication.class, args);
// 从应用上下文中获取Date类型的Bean
Date dateBean1 = applicationContext.getBean(Date.class);
System.out.println(dateBean1);
// 从应用上下文中根据Bean名称获取特定的Date类型Bean
Date dateBean2 = applicationContext.getBean("getNowDate", Date.class);
System.out.println(dateBean2);
}
}
执行结果:
@EnableAutoConfiguration注解
例如:如果你在SpringBoot项目中进行了如下配置:
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/springboot
spring.datasource.username=root
spring.datasource.password=123456
并且在依赖中引入了mybatis依赖
/mybatis启动器
,那么SpringBoot框架将为你自动化配置以下bean:
- SqlSessionFactory: MyBatis的核心工厂SqlSessionFactory会被自动配置。这个工厂负责创建SqlSession实例,后者用来执行映射文件中的SQL语句。
- TransactionManager: DataSourceTransactionManager会被自动配置来管理与数据源相关的事务。
@ComponentScan注解
<context:component-scan base-package="com.liming.sb305core"/>
因此被@SpringBootApplication
注解标注之后,会启动组件扫描功能,扫描的包是主入口程序所在包及子包
,因此如果一个bean要纳入IoC容器的管理则必须放到主入口程序所在包及子包下。放到主入口程序所在包之外的话,扫描不到。
外部化配置
什么是外部化配置
外部化配置的方式
SpringBoot支持多种外部化配置方式,包括但不限于:
- properties文件
- YAML文件
- 系统环境变量
- 命令行参数
- …
外部化配置的优势
- 灵活性:配置文件可以独立于应用程序部署,这使得可以根据运行环境的不同来调整配置,而无需修改代码。
- 易于维护:配置变更不需要重新构建和部署应用程序,降低了维护成本。
- 安全性:敏感信息如数据库密码、API密钥等可以存储在外部,并且可以限制谁有权限访问这些配置信息。
- 共享性:多实例或多服务可以共享相同的配置信息,减少重复配置的工作量。
- 版本控制:配置文件可以存放在版本控制系统中,便于跟踪历史版本和回滚配置。
外部化配置对比传统配置
application.properties
-
application.properties
配置文件是SpringBoot框架默认的配置文件。 -
application.properties
不是必须的,SpringBoot对于应用程序来说,都提供了一套默认配置(就是我们所说的自动配置)。 -
如果你要改变这些默认的行为,可以在
application.properties
文件中进行配置。 -
application.properties
可以放在类路径当中,也可以放在项目之外。因此称为外部化配置。
Spring Boot 框架在启动时会尝试从以下位置加载 application.properties
配置文件:
- file:./config/:首先在Spring Boot 当前工作目录下的
config
文件夹中查找。- 注意:如果没有找到
application.properties
会继续找application.yml
,如果这两个都没有找到,才会进入以下位置查找,以此类推。
- 注意:如果没有找到
- file:./:如果在当前工作目录下
config
目录中找不到时,再从当前工作目录中查找。 - classpath:/config/:如果从工作目录中找不到,会从类路径中找,先从类路径的
/config/
目录下寻找配置文件。 - classpath:/:如果在
/config/
下没有找到,它会在类路径的根目录下查找。
Spring Boot 会按照这个顺序来加载配置文件,如果在多个位置有相同的属性定义,那么最先检查的位置中的属性值将优先使用。
如果你想要指定其他的配置文件位置或者改变默认的行为,可以通过 --spring.config.location=
后跟路径的方式来指定配置文件的具体位置。例如 :
java -jar sb3-01-first-web-1.0-SNAPSHOT.jar --spring.config.location=file:///E:\a\b\application.properties
这样,Spring Boot 将会首先从 E:\a\b\
这个路径加载配置文件。注意,这种方式可以用来覆盖默认的配置文件位置,并且可以结合以上提到的位置一起使用。
注意:以上的--spring.config.location=file:///E:\a\b\application.properties
就属于命令行参数,它将来会被传递到main方法的(String[] args)参数上。
使用@Value注解
语法格式:@Value(“${key}”)
使用@Value注解时也可以指定默认值,当指定默认值时,如果配置文件中没有指定配置值,则采用默认值。
语法格式:@Value(“${key:defalut}”)
YAML
YAML概述
YAML的语法规则
YAML的语法规则如下:
-
数据结构:YAML支持多种数据类型,包括:
- 字符串、数字、布尔值
- 数组、list集合
- map键值对 等。
-
YAML使用
一个空格
来分隔属性名
和属性值
,例如:properties
文件中这样的配置:name=jackyaml
文件中需要这样配置:name: jack
-
YAML用
换行+空格
来表示层级关系。注意不能使用tab,必须是空格,空格数量无要求,大部分建议2个或4个空格。例如:properties
文件中这样的配置:myapp.name=mallyaml
文件中就需要这样配置:
myapp: name: mall
-
同级元素左对齐。例如:
properties
文件中有这样的配置:
myapp.name=mall myapp.count=10
yaml
文件中就应该这样配置:
myapp: name: mall count: 10
-
键必须是唯一的:在一个映射中,键必须是唯一的。
-
注释:使用
#
进行注释。 -
大小写敏感
YAML的使用小细节
第一:普通文本也可以使用单引号或双引号括起来:(当然普通文本也可以不使用单引号和双引号括起来。)
- 单引号括起来:单引号内所有的内容都被当做普通文本,不转义(例如字符串中有\n,则\n被当做普通的字符串)
- 双引号括起来:双引号中有 \n 则会被转义为换行符
第二:保留文本格式
|
将文本写到这个符号的下层,会自动保留格式。
第三:文档切割
---
这个符号下面的配置可以认为是一个独立的yaml文件。便于庞大文件的阅读。
application.yml
Spring Boot框架同时支持properties
和yaml
。
强调:在同一个目录下同时存在application.properties
和application.yml
时,SpringBoot优先解析application.properties文件。
在resources/config
目录下新建application.yml
文件,进行如下配置:
myapp:
username: jim
email: jim@123.com
age: 40
password: jim123
一定要把resources/config
目录下application.properties
名字修改为application2.properties
,这样Spring Boot才会解析resources/config/application.yml
。
配置文件合并
properties文件
application-mysql.properties
spring.datasource.username=root
spring.datasource.password=123456
application-redis.properties
spring.data.redis.host=localhost
spring.data.redis.port=6379
application.properties
spring.config.import=classpath:application-mysql.properties,classpath:application-redis.properties
编写service测试,看看能否拿到配置信息:
package com.liming.sb307externalconfig.service;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
@Service("userServiceMulti")
public class UserServiceMulti {
@Value("${spring.datasource.username}")
private String username;
@Value("${spring.datasource.password}")
private String password;
@Value("${spring.data.redis.host}")
private String host;
@Value("${spring.data.redis.port}")
private String port;
public void printInfo(){
String str = String.join(",", username, password, host, port);
System.out.println(str);
}
}
yaml文件
application-mysql.yml
spring:
datasource:
username: root
password: 789789
application-redis.yml
spring:
data:
redis:
host: localhost
port: 6379
application.yml
spring:
config:
import:
- classpath:application-mysql.yml
- classpath:application-redis.yml
多环境切换
- 开发环境的配置文件名一般叫做:
application-dev.properties
spring.datasource.username=dev
spring.datasource.password=dev123
spring.datasource.url=jdbc:mysql://localhost:3306/dev
- 测试环境的配置文件名一般叫做:
application-test.properties
spring.datasource.username=test
spring.datasource.password=test123
spring.datasource.url=jdbc:mysql://localhost:3306/test
- 预生产环境的配置文件名一般叫做:
application-preprod.properties
spring.datasource.username=preprod
spring.datasource.password=preprod123
spring.datasource.url=jdbc:mysql://localhost:3306/preprod
- 生产环境的配置文件名一般叫做:
application-prod.properties
spring.datasource.username=prod
spring.datasource.password=prod123
spring.datasource.url=jdbc:mysql://localhost:3306/prod
如果你希望该项目使用生产环境的配置,你可以这样做:
- 第一种方式:在
application.properties
文件中添加这个配置:spring.profiles.active=prod - 第二种方式:在命令行参数上添加:–spring.profiles.active=prod
将配置绑定到bean
绑定简单bean
application.yml
app:
name: jack
age: 30
email: jack@123.com
Bean需要这样定义:
package com.liming.sb307externalconfig.bean;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Component
@ConfigurationProperties(prefix = "app")
public class AppBean {
private String name;
private Integer age;
private String email;
@Override
public String toString() {
return "AppBean{" +
"name='" + name + '\'' +
", age=" + age +
", email='" + email + '\'' +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
}
说明:
- 被绑定的bean,需要使用
@ConfigurationProperties(prefix = "app")
注解进行标注,prefix用来指定前缀,哪个是前缀,如下图所示:
配置文件中的name
、age
、email
要和bean对象的属性名name
、age
、email
对应上。(属性名相同)
并且bean中的所有属性都提供了setter
方法。因为底层是通过setter
方法给bean属性赋值的。
- 这样的bean需要使用
@Component
注解进行标注,纳入IoC容器的管理。@Component
注解负责创建Bean对象,@ConfigurationProperties(prefix = "app")
注解负责给bean对象的属性赋值。 - bean的属性需要是
非static
的属性。
@Configuration注解
@Configuration
@ConfigurationProperties(prefix = "app")
public class AppBean {
private String name;
private Integer age;
private String email;
//setter and getter
}
绑定嵌套bean
有这样的一个配置:
app:
name: jack
age: 30
email: jack@123.com
address:
city: BJ
street: ChaoYang
zipcode: 123456
需要编写这样的两个Bean:
package com.liming.sb307externalconfig.bean;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@Configuration(proxyBeanMethods = false) //proxyBeanMethods = false取消代理机制
@ConfigurationProperties(prefix = "app")
public class AppBean {
private String name;
private Integer age;
private String email;
private Address address;
@Override
public String toString() {
return "AppBean{" +
"name='" + name + '\'' +
", age=" + age +
", email='" + email + '\'' +
", address=" + address +
'}';
}
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
}
package com.liming.sb307externalconfig.bean;
public class Address {
private String city;
private String street;
private String zipcode;
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public String getStreet() {
return street;
}
public void setStreet(String street) {
this.street = street;
}
public String getZipcode() {
return zipcode;
}
public void setZipcode(String zipcode) {
this.zipcode = zipcode;
}
@Override
public String toString() {
return "Address{" +
"city='" + city + '\'' +
", street='" + street + '\'' +
", zipcode='" + zipcode + '\'' +
'}';
}
}
执行测试程序,结果如下:
@EnableConfigurationProperties与@ConfigurationPropertiesScan
- 第一种:@EnableConfigurationProperties
- 第二种:@ConfigurationPropertiesScan
这两个注解都是标注在SpringBoot主入口程序上的:
@EnableConfigurationProperties(AppBean.class)
@SpringBootApplication
public class Sb307ExternalConfigApplication {
public static void main(String[] args) {
SpringApplication.run(Sb307ExternalConfigApplication.class, args);
}
}
或者
@ConfigurationPropertiesScan(basePackages = "com.liming.sb307externalconfig.bean")
@SpringBootApplication
public class Sb307ExternalConfigApplication {
public static void main(String[] args) {
SpringApplication.run(Sb307ExternalConfigApplication.class, args);
}
}
将配置赋值到Bean的Map/List/Array属性上
代码如下:
package com.liming.sb307externalconfig.bean;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
@ConfigurationProperties
public class CollectionConfig {
private String[] names;
private List<Product> products;
private Map<String, Vip> vips;
@Override
public String toString() {
return "CollectionConfig{" +
"names=" + Arrays.toString(names) +
", products=" + products +
", vips=" + vips +
'}';
}
public String[] getNames() {
return names;
}
public void setNames(String[] names) {
this.names = names;
}
public List<Product> getProducts() {
return products;
}
public void setProducts(List<Product> products) {
this.products = products;
}
public Map<String, Vip> getVips() {
return vips;
}
public void setVips(Map<String, Vip> vips) {
this.vips = vips;
}
}
class Product {
private String name;
private Double price;
@Override
public String toString() {
return "Product{" +
"name='" + name + '\'' +
", price=" + price +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Double getPrice() {
return price;
}
public void setPrice(Double price) {
this.price = price;
}
}
class Vip {
private String name;
private Integer age;
@Override
public String toString() {
return "Vip{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
配置信息如下:application.yml
#数组
names:
- jackson
- lucy
- lili
#List集合
products:
- name: 西瓜
price: 3.0
- name: 苹果
price: 2.0
#Map集合
vips:
vip1:
name: 张三
age: 20
vip2:
name: 李四
age: 22
提醒:记得入口程序使用@ConfigurationPropertiesScan(basePackages = “com.liming.sb307externalconfig.bean”)进行标注。
将配置绑定到第三方对象
假设我们有这样一个类Address
,代码如下:
package com.liming.sb307externalconfig.bean;
public class Address {
private String city;
private String street;
private String zipcode;
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public String getStreet() {
return street;
}
public void setStreet(String street) {
this.street = street;
}
public String getZipcode() {
return zipcode;
}
public void setZipcode(String zipcode) {
this.zipcode = zipcode;
}
@Override
public String toString() {
return "Address{" +
"city='" + city + '\'' +
", street='" + street + '\'' +
", zipcode='" + zipcode + '\'' +
'}';
}
}
当然,我们是看不到这个源码的,只知道有这样一个字节码Address.class
。大家也可以看到这个Address
类上没有添加任何注解。假设我们要将以下配置绑定到这个Bean上应该怎么做?
address:
city: TJ
street: XiangYangLu
zipcode: 11111111
实现代码如下:
@Configuration
public class ApplicationConfig {
@Bean
@ConfigurationProperties(prefix = "address")
public Address getAddress(){
return new Address();
}
}
指定数据来源
在resources
目录下新建a
目录,在a
目录下新建b
目录,b
目录中新建group-info.properties
文件,进行如下的配置:
group.name=IT
group.leader=LaoDu
group.count=20
定义Java类Group
,然后进行注解标注:
package com.liming.sb307externalconfig.bean;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
@Configuration
@ConfigurationProperties(prefix = "group")
@PropertySource("classpath:a/b/group-info.properties")
public class Group {
private String name;
private String leader;
private Integer count;
@Override
public String toString() {
return "Group{" +
"name='" + name + '\'' +
", leader='" + leader + '\'' +
", count=" + count +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getLeader() {
return leader;
}
public void setLeader(String leader) {
this.leader = leader;
}
public Integer getCount() {
return count;
}
public void setCount(Integer count) {
this.count = count;
}
}
以下三个注解分别起到什么作用:
- @Configuration:指定该类为配置类,纳入Spring容器的管理
- @ConfigurationProperties(prefix = “group”):将配置文件中的值赋值给Bean对象的属性
- @PropertySource(“classpath:a/b/group-info.properties”):指定额外的配置文件
Spring Boot中如何进行AOP的开发
Spring Boot AOP概述
<!--aop启动器-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
可以看到,当引入aop启动器
之后,会引入aop依赖
和aspectj依赖
。
- aop依赖:如果只有这一个依赖,也可以实现AOP编程,这种方式表示使用了纯Spring AOP实现aop编程。
- aspectj依赖:一个独立的可以完成AOP编程的AOP框架,属于第三方的,不属于Spring框架。(我们通常用它,因为它的功能更加强大)
Spring Boot AOP实现
创建Spring Boot项目引入aop启动器
项目名:sb3-08-aop
<!--aop启动器-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
编写service并提供方法
package com.liming.service;
public interface OrderService {
/**
* 生成订单
*/
void generate();
/**
* 订单详情
*/
void detail();
}
package com.liming.service.impl;
import com.liming.service.OrderService;
import org.springframework.stereotype.Service;
@Service
public class OrderServiceImpl implements OrderService {
/**
* 生成订单
*/
@Override
public void generate() {
System.out.println("生成订单");
}
/**
* 订单详情
*/
@Override
public void detail() {
System.out.println("订单详情");
}
}
编写切面
package com.liming.aop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
@Component // 纳入IoC容器
@Aspect // 指定该类为切面类
public class LogAspect {
// 日期格式化器
private DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss SSS");
// 前置通知
// 切入点表达式:service包下任意类的任意方法
@Before("execution(* com.liming.service..*.*(..))")
public void sysLog(JoinPoint joinPoint) throws Throwable {
StringBuilder log = new StringBuilder();
LocalDateTime now = LocalDateTime.now();
String strNow = formatter.format(now);
// 追加日期
log.append(strNow);
// 追加冒号
log.append(":");
// 追加方法签名
log.append(joinPoint.getSignature().getName());
// 追加方法参数
log.append("(");
Object[] args = joinPoint.getArgs();
for (int i = 0; i < args.length; i++) {
log.append(args[i]);
if(i < args.length - 1) {
log.append(",");
}
}
log.append(")");
System.out.println(log);
}
}
测试
package com.liming;
import com.liming.service.OrderService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class Sb308AopApplicationTests {
@Autowired
private OrderService orderService;
@Test
void contextLoads() {
orderService.generate();
orderService.detail();
}
}
执行结果如下: