Spring详解【1】
1. Spring
概述
1.1 为什么需要Spring
框架
在 Spring
出现之前,开发企业级 Java
应用特别是基于 Java EE
的应用是一件非常复杂和繁琐的事情。开发者需要处理大量的样板代码、复杂的配置以及与底层技术的紧密耦合,这使得开发效率低下,代码难以测试和维护。Spring
框架的诞生就是为了解决这些痛点。它的核心理念是让开发者能够专注于业务逻辑,而不是基础设施。🧭Spring
解决的核心痛点如下:
问题 | Spring 解决方案 |
---|---|
Java EE 开发繁琐、XML 配置多、耦合强 | IoC 容器统一对象管理,解耦组件 |
通用功能如日志、事务、权限分散在各层 | AOP 抽离横切逻辑,集中管理 |
数据库操作 DAO 层样板代码多 | Spring Data 自动生成 Repository |
配置复杂、服务部署繁琐 | Spring Boot 自动配置 + 内嵌容器 |
微服务治理难、组件搭建慢 | Spring Cloud 提供标准化组件体系 |
除此之外,Spring
提供了一站式的、全面的生态系统:Spring
不仅仅是一个 IoC
容器,它已经发展成为一个庞大的生态系统,为应用开发提供了全方位的支持。无论你需要什么,几乎都能在 Spring
的生态中找到成熟的解决方案。
Web
开发:Spring MVC
、Spring WebFlux
- 轻松构建
RESTful API
和传统的Web
应用 - 提供了强大的
MVC
架构模式 WebFlux
支持响应式编程,适用于高并发、非阻塞的场景
- 轻松构建
- 数据访问:
Spring Data
- 极大地简化了与数据库的交互,无论是关系型数据库还是非关系型数据库
- 你只需定义一个接口,
Spring Data
会在运行时自动为你生成实现,无需编写繁琐的DAO/Repository
代码
- 安全:
Spring Security
- 一个功能强大且高度可定制的认证和授权框架
- 可以轻松地为你的应用添加登录、权限控制、
OAuth2
、JWT
等安全功能
- 简化配置与快速开发:
Spring Boot
Spring Boot
遵循约定优于配置的原则,提供了大量的自动配置- 开发者可以快速启动和运行一个独立的、生产级别的
Spring
应用,而无需进行繁琐的XML
配置;且内置了Web
服务器如Tomcat
,使得创建微服务变得异常简单
- 云原生与微服务:
Spring Cloud
,基于Spring Boot
,提供了一整套用于构建分布式系统的工具集,如服务发现、配置中心、断路器、网关等,是构建微服务架构的事实标准 - 测试:
Spring Test
,提供了顶级的测试支持,可以轻松地进行集成测试和单元测试,加载Spring
上下文并注入依赖
1.2 Spring
的八大模块
Spring
框架的八大核心模块是其分层架构的基础,每个模块专注于特定领域的功能支持,共同构建了灵活的企业级开发体系。以下是各模块的详细说明及相互关系:
模块名称 | 核心功能 | 关键组件/特性 |
---|---|---|
Spring Core | 提供IoC 容器实现,管理Bean 的生命周期和依赖注入 | BeanFactory 、ApplicationContext 、资源加载机制 |
Spring Context | 扩展Core 模块,提供企业级服务支持 | 国际化、事件传播、资源访问、JavaBeans 配置 |
Spring AOP | 实现面向切面编程,解耦横切关注点,如日志、事务 | 动态代理JDK/CGLIB 、AOP 联盟兼容API 、声明式事务管理 |
Spring DAO | 抽象JDBC 操作,简化数据库访问 | JdbcTemplate 、统一的数据访问异常体系 |
Spring ORM | 集成主流ORM 框架 | 支持Hibernat 、Mybatis 等 |
Spring Web | 提供Web 开发基础支持,如请求处理、文件上传 | MultipartResolver 、ContextLoaderListener 、与Struts/JSF 集成能力 |
Spring Web MVC | 实现MVC 设计模式,构建灵活的前端控制层 | DispatcherServlet 、控制器注解@Controller 、视图解析器ViewResolver |
Spring Test | 支持单元测试与集成测试 | 模拟Spring 上下文、JUnit/TestNG 集成、事务回滚测试 |
1.3 Spring
的特点
📊 轻量级和非侵入式
- 轻量级:
Spring
框架的核心容器体积小、启动开销低,可以整合到任何Java
应用中,甚至可以只使用其中一部分功能。 - 非侵入式:
Spring
应用中的对象不需要依赖Spring
特定的API
。你的对象是对立的,可以在Spring
环境之外进行测试和复用,保持了代码的纯粹性。
📌 控制反转
- 开发者不再需要通过
new
关键字手动创建对象和管理它们的依赖关系,一切都由容器来负责。 - 极大地降低了组件之间的耦合度,使得系统更加灵活、易于维护和测试。
🔚 面向切面编程
AOP
允许开发者将那些跨越多个应用模块的横切关注点从业务逻辑中分离出来,进行统一管理,如日志记录、事务管理、安全检查。- 提高了代码的模块化程度,使得业务代码更加纯粹、简洁,同时也减少了代码冗余。
👉 一站式框架与强大的生态系统、强大的集成能力与开放性、统一的声明式事务管理
- 生态系统:
Spring
不是一个单一功能的框架,它提供了一整套用于企业开发的解决方案,如Web
开发、数据访问、安全等。 - 事务管理:开发者可以通过简单的注解如
@Transactional
来声明性地管理事务,而无需编写繁琐的事务控制代码如try-catch-finally
、提交、回滚。它能够统一管理多种数据访问技术的事务。 - 集成能力:
Spring
并不试图取代所有东西,而是擅长与各种优秀的第三方框架进行无缝集成,如Mybatis
、RabbitMQ
、Redis
等等。
2. 控制反转与依赖注入
2.1 控制反转IoC
控制反转 Inversion Of Control
是一种编程思想,也是 Spring
框架中最核心的思想之一,它彻底改变了传统面向对象编程中对象自己创建依赖的做法,将控制权交给第三方容器,实现解耦、灵活、可测试的系统设计。在理解IoC
思想之前,需要了解什么是控制?什么是反转?
🧠 控制
在传统的程序设计中,一个对象通常会主动创建或获取它所依赖的对象,这就是所谓的正向控制或传统的控制流程。比如,你需要组装一辆汽车,如果采用正向控制:
- 你需要一个引擎。于是,你自己去
new Engine()
来制造一个引擎。 - 你需要四个轮子。于是,你自己去
new Wheel()
四次来制造轮子。 - 你需要一个底盘。于是,你自己去
new Chassis()
来制造一个底盘。 - 最后,你自己把这些零件组装起来。
在这个过程中,汽车这个对象掌握着完全的控制权,它精确地知道需要哪个具体型号的引擎、轮子,并亲自负责创建它们。
// 引擎实现
class V8Engine {public void start() {System.out.println("V8 引擎启动!");}
}// 汽车类
class Car {// Car 主动创建并管理它的依赖[Engine]private V8Engine engine = new V8Engine();public void drive() {engine.start();System.out.println("汽车行驶中...");}
}// 使用
public class Main {public static void main(String[] args) {Car myCar = new Car(); // 我们只需要创建 CarmyCar.drive(); // Car 自己解决了内部依赖问题}
}
上述方式导致了紧耦合,Car
类与 V8Engine
类仅仅的绑定在了一起,如果某一天想要给这辆车更换一个电能引擎 ElectricEngine
,你必须修改 Car
类的源代码,这违背了 OCP
原则,且这种修改会引发连锁反应,违背了 DIP
原则,难以维护。
🧠 反转
控制反转就是将这种流程完全颠倒过来,将【对象的创建】和【对象之间的依赖关系】交给第三方容器进行管理。从案例的角度上也就是说:
- 你不再亲自去制造引擎和轮子。
- 你只需要告诉一个装配工厂【
IoC
容器】:我需要一辆车,它需要一个引擎和一个底盘。这个工厂会根据你的订单【配置】,自动找到合适的引擎、轮子和底盘,并将它们组装好,最后把一辆完整的汽车交给你。
在这个过程中,控制权从汽车转移到了装配工厂,即对象到IoC
容器。汽车不再关心引擎怎么来的,它只需要指导自己需要一个引擎,工厂就会提供给它,这就是控制反转。
IoC
思想通过将对象的创建和依赖关系管理外包给容器【容器就是对象的托管者】,极大地降低了代码间的耦合度,使得整个系统更加灵活、可扩展、易于测试和维护。其中,Spring
的 IoC
容器是由 ApplicationContext
负责管理的,功能包括:
功能 | 说明 |
---|---|
创建和销毁 Bean 实例 | 生命周期管理 |
解析依赖并自动注入 | DI 实现 |
管理配置【注解 / XML 】 | 读取配置源 |
管理 Bean 的作用域、生命周期 | singleton 、prototype 等 |
提供 AOP 支持入口 | 与 AOP 协同 |
2.2 依赖注入DI
依赖注入 Dependency Injection
是实现控制反转最常见的方式:
-
依赖:一个对象需要另一个对象来完成工作,这就是依赖关系,如汽车依赖引擎。
-
注入:依赖的对象不是由自己创建,而是由外部【
IoC
容器】传递【注入】进来。@Component // 告诉Spring管理这个Car类 class Car {private final Engine engine;@Autowired // 告诉Spring在这里自动注入一个Engine类型的依赖public Car(Engine engine) {this.engine = engine;}// ... }@Component // 告诉Spring这是一个Engine的实现 class ElectricEngine implements Engine {// ... }
🛠️ IoC
的实现方式:Spring
提供了三种依赖注入方式
- 构造器注入:推荐使用,保证依赖完整性。
Set
注入:选择使用,可注入可选依赖。- 字段注入:不推荐使用,随简洁但是不利于单元测试。
在了解Set
注入、构造注入、字段注入之前,有必要了解下面三个注解:
-
@Value
:主要用于将值注入到Spring
管理的Bean
的字段、方法参数或构造函数参数中。它允许你从各种来源获取值,如下:// 字面值 @Value("hello world") private String message;@Value("100") private int count;
// application.properties 或 application.yml app.name=My Spring App db.url=jdbc:mysql://localhost:3306/mydb// 属性文件 // 当属性文件中找不到对应的值则使用默认值 @Value("${app.name:Bruce}") private String applicationName;@Value("${db.url}") private String databaseUrl;
// 调用spEL // 访问系统属性 @Value("#{systemProperties['java.home']}") private String javaHome;// 调用方法或访问Bean @Value("#{someService.someMethod()}") private String dynamicValue;// 注入集合或map // 假设properties文件中有 valuesMap={key1: '1', key2: '2'} @Value("#{${valuesMap}}") private Map<String, Integer> myMap;
// 构造注入 public AppInfoService(@Value("${app.name}") String applicationName,@Value("${app.version:default}") String applicationVersion, // 提供默认值@Value("AI Assistant") String creator // 字面量) {this.applicationName = applicationName;this.applicationVersion = applicationVersion;this.creator = creator;System.out.println("AppInfoService Constructor: " +"Name=" + applicationName +", Version=" + applicationVersion +", Creator=" + creator);
// set注入 @Value("${system.config.theme}") public void setTheme(String theme) {this.theme = theme; }
-
@Autowired
:Spring
提供的依赖注入DI
注解,按类型ByType
自动装配Bean
,如果有多个同一类型的Bean
,则需要配合@Qulifier
指定Bean
名称匹配。// 构造注入 @Autowired public ProductController(ProductRepository productRepository,NotificationService notificationService) {this.productRepository = productRepository;this.notificationService = notificationService; }
// set注入 @Autowired @Qulifier("student1") public void setProductRepository(ProductRepository productRepository) {this.productRepository = productRepository; }
// 字段注入 @Autowired private NotificationService notificationService;
-
@Resource
:使用与@Autowired
一致,只是查找机制略有不同。特性 @Value
@Autowired
@Resource
作用 注入配置值/字面量/ SpEL
表达式结果注入 Spring
容器中管理的Bean
实例注入 Spring
容器中管理的Bean
实例注入内容 基本类型、包装类、 String
、Class
、枚举等数据任何 Spring Bean
类型任何 Spring Bean
类型查找方式 直接取值或解析表达式 默认按类型 byType
,可结合@Qualifier
按名称匹配默认按名称 byName
,失败时回退到按类型byType
,效率比@Autowired
更高来源 Spring Framework
Spring Framework
Java EE
规范可移植性 仅限 Spring
环境仅限 Spring
环境更好 required
无此属性【无法设置非空校验】 支持 required
属性【默认true
】无此属性通过 name
属性控制查找优点 简化开发、配置外部化、灵活性 Spring
原生、对构造注入支持好可移植性好 使用范围 字段注入【推荐】、构造注入、 Set
注入字段注入、构造注入【推荐】、 Set
注入字段注入、 Set
注入、Set
注入常用场景 读取配置、注入常量、动态计算值 注入依赖的 Service
、Repository
等Bean
需明确名称匹配时,或兼容 Java EE
规范的项目
2.2.1 Set
注入
在Spring
框架中,Set
注入 是一种基于Setter
方法的依赖注入方式,通过调用目标对象的Setter
方法将依赖对象注入到属性中。Set
注入有以下特点:
- 可选性:通过
Setter
注入的依赖是可选的。如果某个属性没有Setter
方法,或者Spring
容器中没有匹配的Bean
,Bean
也能被成功创建,只是该属性不会被注入。 - 可变性:通过
Setter
注入的属性在Bean
创建后可以被修改。 - 依赖解耦:与构造器注入一样,它也实现了依赖的解耦,
Bean
之间通过接口而不是具体实现进行协作。 - 避免循环依赖:在某些情况下,如果
A
依赖B
,B
依赖A
,且两者都使用构造器注入时,会形成循环依赖,Spring
默认无法解决。此时,将其中一个或两个的依赖改为Setter
注入,可以打破这种循环,因为Setter
注入发生在对象实例化之后。
Spring
提供了 XML
配置和注解两种方式来实现 Setter
注入。
-
xml
配置:在Spring
的XML
配置文件中,使用<property>
标签来指定要注入的属性。// UserService.java public class UserService {private UserRepository userRepository;// Setter 方法用于注入依赖// 对于setter方法,命名必须是 set + Xxx,最好使用idea直接生成public void setUserRepository(UserRepository userRepository) {this.userRepository = userRepository;}public void doSomething() {System.out.println("UserService is doing something with: " + userRepository.getClass().getSimpleName());} }// UserRepository.java public class UserRepository {public void findUser() {System.out.println("Finding user in database...");} }
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"><bean id="userRepository" class="com.example.UserRepository"/><bean id="userService" class="com.example.UserService">// 也可以使用value注入字面量,如字符串、数字等<property name="userRepository" ref="userRepository"/> </bean></beans>
-
注解:最常用的注解是
@Autowired
、@Resource
。import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; // 或 @Service, @Repository// UserService.java @Component // 标识为 Spring Bean public class UserService {private UserRepository userRepository;// 使用 @Autowired注解在Setter方法上@Autowiredpublic void setUserRepository(UserRepository userRepository) {this.userRepository = userRepository;}public void doSomething() {System.out.println("UserService is doing something with: " + userRepository.getClass().getSimpleName());} }// UserRepository.java @Component // 标识为 Spring Bean public class UserRepository {public void findUser() {System.out.println("Finding user in database...");} }
如果使用注解的方式实现依赖注入,则需要开启组件扫描,如下:
// xml配置 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">// 在指定包下扫描带有Spring注解的类,如@Component注解<context:component-scan base-package="com.example"/> </beans>
// 通过java配置类 import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration;@Configuration // 标识为Spring配置类 @ComponentScan(basePackages = "com.example") public class AppConfig {// 无需显式定义Bean,@ComponentScan会自动发现并注册 }
2.2.2 构造注入
构造注入通过调用 Bean
的构造器来完成依赖的传递和初始化。Spring IoC
容器在创建 Bean
实例时,通过调用带有参数的构造器来将依赖项作为参数传递给 Bean
。这意味着 Bean
在被实例化时,其所有必要的依赖都已经被完全提供了。在现代 Spring
应用中,构造器注入通常是首选,便于单元测试。其核心思想如下:
- 依赖在构造时提供:
Bean
在被创建的那一刻就获得了所有必需的依赖。 - 不可变性:通过构造器注入的依赖通常是不可变的。因为这些依赖是通过
final
关键字修饰的成员变量来存储的,一旦对象被创建,这些依赖就不能再被改变。 - 强制性依赖:如果一个
Bean
缺少通过构造器注入的依赖,它将无法成功创建。可以避免空指针异常。
同样的,Spring
提供了 XML
配置和注解两种方式来实现构造器注入。
-
xml
配置:当有多个构造器参数时,<constructor-arg>
标签的顺序默认对应构造器参数的顺序。为了更明确或处理参数类型模糊的情况,可以使用index
或type
属性。// UserRepository.java public class UserRepository {public void findUser() {System.out.println("Finding user in database...");} }// UserService.java public class UserService {private final UserRepository userRepository; // 依赖可以声明为 final// 带有 UserRepository 参数的构造器public UserService(UserRepository userRepository) {this.userRepository = userRepository;}public void performUserOperation() {System.out.println("UserService is performing operation...");userRepository.findUser();} }
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"><bean id="userRepository" class="com.example.UserRepository"/><bean id="userService" class="com.example.UserService"><constructor-arg ref="userRepository"/></bean> </beans>
-
注解:最常用的注解是
@Autowired
。当然,仍然需要配置组件扫描。import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; // 或 @Component, @Repository// UserRepository.java @Repository // 标识为 Spring Bean public class UserRepository {public void findUser() {System.out.println("Finding user in database...");} }// UserService.java @Service // 标识为 Spring Bean public class UserService {private final UserRepository userRepository; // 推荐声明为 final// 使用 @Autowired 注解在构造器上// Spring 会自动将其视为构造器注入点@Autowiredpublic UserService(UserRepository userRepository) {this.userRepository = userRepository;}public void performUserOperation() {System.out.println("UserService is performing operation...");userRepository.findUser();} }
2.2.3 字段注入
字段注入,也称为属性注入,是指 Spring IoC
容器通过反射机制,直接将依赖注入到 Bean
的私有或保护字段中,而无需通过公共的 Setter
方法或构造器。其特点如下:
- 直接赋值:
Spring
容器在实例化Bean
之后,直接绕过构造器和Setter
方法,利用反射将依赖赋值给字段。 - 简洁性:代码看起来非常简洁,无需编写
Setter
方法或冗长的构造器参数。
字段注入主要通过注解【组件扫描】来实现,通常是 @Autowired
、@Resource
、@Value
。XML
配置无法直接实现字段注入,因为 XML
只能配置属性【通过 Setter
】或构造器参数。
@Autowired
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;// ProductRepository.java
@Repository // 标识为 Spring Bean
public class ProductRepository {public void findProductById(String id) {System.out.println("Finding product with ID: " + id + " in database...");}
}// OrderService.java
@Service // 标识为 Spring Bean
public class OrderService {// 使用 @Autowired 注解直接在字段上@Autowiredprivate ProductRepository productRepository; // 字段可以是 privatepublic void createOrder(String productId) {System.out.println("OrderService is creating an order...");productRepository.findProductById(productId);}
}
2.2.4 Set
注入专题
🔄 基本类型和字符串注入:在set
注入中,基本类型除了八大基本数据类型外,还可以将 String
、Class
、枚举和日期时间类型【比较特殊,一般看作引用类型】看作基本类型使用value
赋值。
-
xml
配置方式<!-- applicationContext.xml --> <bean id="student" class="com.example.Student"><property name="name" value="Alice"/><property name="age" value="22"/> </bean>
-
Java
注解@Component public class Student {private String name;private int age;@Value("Alice")public void setName(String name) { this.name = name; }@Value("22")public void setAge(int age) { this.age = age; } }
🔄 引用类型注入:Bean
的注入
-
xml
配置方式<bean id="address" class="com.example.Address"><property name="city" value="Beijing"/> </bean><bean id="student" class="com.example.Student"><property name="address" ref="address"/> </bean>
-
Java
注解@Component public class Address {private String city;@Value("Beijing")public void setCity(String city) { this.city = city; } }@Component public class Student {private Address address;@Autowiredpublic void setAddress(Address address) { this.address = address; } }
🔄 集合类型注入:List
、Set
、Map
、Properties
-
xml
注解方式<bean id="collectionBean" class="com.example.CollectionBean"><property name="list"><list><value>Apple</value><value>Banana</value></list></property><property name="set"><set><value>One</value><value>Two</value></set></property><property name="map"><map><entry key="A" value="Alpha"/><entry key="B" value="Beta"/></map></property><property name="properties"><props><prop key="username">admin</prop><prop key="password">1234</prop></props></property> </bean>
public class CollectionBean {private List<String> list;private Set<String> set;private Map<String, String> map;private Properties properties;public void setList(List<String> list) { this.list = list; }public void setSet(Set<String> set) { this.set = set; }public void setMap(Map<String, String> map) { this.map = map; }public void setProperties(Properties properties) { this.properties = properties; } }
-
Java
注解@Component public class CollectionBean {private List<String> list;private Set<String> set;private Map<String, String> map;private Properties properties;@Autowiredpublic void setList(List<String> list) { this.list = list; }@Autowiredpublic void setSet(Set<String> set) { this.set = set; }@Autowiredpublic void setMap(Map<String, String> map) { this.map = map; }@Autowiredpublic void setProperties(Properties properties) { this.properties = properties; } }
@Configuration public class AppConfig {@Beanpublic List<String> list() {return Arrays.asList("Apple", "Banana", "Cherry");}@Beanpublic Set<String> set() {return new HashSet<>(Arrays.asList("One", "Two"));}@Beanpublic Map<String, String> map() {Map<String, String> map = new HashMap<>();map.put("A", "Alpha");map.put("B", "Beta");return map;}@Beanpublic Properties properties() {Properties props = new Properties();props.setProperty("username", "admin");props.setProperty("password", "1234");return props;} }
🔄 注入null
-
xml
配置方式<bean id="user" class="com.example.User"><property name="nickname"><null/></property> </bean>
-
Java
注解@Value("#{null}") private String nickname;
🔄 注入特殊字符:主要是xml
会面临该问题,xml
会解析一些特殊字符;注解的方式不存在该问题,@Value
注解通常直接接受一个字符串字面量或者一个 SpEL
表达式。Java
字符串本身就可以包含这些特殊字符,无需像 XML
那样使用 CDATA
。
<property name="htmlContent"><value><![CDATA[<div><p>Hello, <b>World</b>!</p></div>]]></value>
</property>
3. 简单搭建一个Spring
应用
1️⃣ 创建Maven
项目,添加核心依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.person</groupId><artifactId>spring</artifactId><version>1.0-SNAPSHOT</version><properties><maven.compiler.source>17</maven.compiler.source><maven.compiler.target>17</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties><dependencies><!-- 引入spring核心容器 --><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>6.2.7</version></dependency></dependencies></project>
2️⃣ 创建 Service
接口和实现
// HelloService.java
package com.example.service;public interface HelloService {void sayHello(String name);
}
// HelloServiceImpl.java
package com.example.service;public class HelloServiceImpl implements HelloService {@Overridepublic void sayHello(String name) {System.out.println("Hello, " + name + "!");}
}
3️⃣ 配置 Spring
容器
-
使用
Java
类实现// AppConfig.java package com.example.config;import com.example.service.HelloService; import com.example.service.HelloServiceImpl; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration;@Configuration public class AppConfig {@Beanpublic HelloService helloService() {return new HelloServiceImpl();} }
-
使用
xml
配置实现:xml
文件一般放置在类路径下// resources/applicationContext.xml <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"><bean id="helloService" class="com.example.service.HelloServiceImpl"/> </beans>
4️⃣ 编写主类
-
使用
Java
类实现package com.example;import com.example.config.AppConfig; import com.example.service.HelloService; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext;public class App {public static void main(String[] args) {ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);// 面向接口编程HelloService helloService = context.getBean(HelloService.class);helloService.sayHello("World");} }
-
使用
xml
配置实现package com.example;import com.example.service.HelloService; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext;public class App {public static void main(String[] args) {ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");// 如果xml配置文件不在类路径时,很少使用,移植性差// ApplicationContext context = new FileSystemXmlApplicationContext("/opt/config/app-context.xml");// 查找id为"hello"的Bean,再根据类型强制转换HelloService helloService = context.getBean("helloService", HelloService.class);helloService.sayHello("World");} }
🎯 使用xml
配置时获取Bean
可以直接通过bean
的id
获取【id
不存在时代码报错】,当使用Java
类做配置时,又是怎么获取Bean
的呢?
- 使用
Java
类配置Bean
时,Spring
注入时根据类型查找。 - 如果匹配到多个
Bean
,需要通过@Primary
或@Qualifier
消除歧义,否则代码会报错。
🎯 Spring
要想实例化对象,必须得有无参构造器吗?答案是否。
-
默认行为:如果你没有明确指定
Spring
如何实例化一个Bean
,Spring
默认会尝试使用其无参构造器来创建实例。 -
带参构造器【构造注入】:
public class MyService {private MyRepository myRepository;// 只有一个带参数的构造器,没有无参构造器public MyService(MyRepository myRepository) {this.myRepository = myRepository;} }
// 使用Java配置类 @Configuration public class AppConfig {@Beanpublic MyRepository myRepository() {return new MyRepository();}@Beanpublic MyService myService(MyRepository myRepository) {// Spring 会自动将 myRepository Bean 注入到这里return new MyService(myRepository);} }
// 使用xml配置 <bean id="myRepository" class="com.example.MyRepository"/> <bean id="myService" class="com.example.MyService"><constructor-arg ref="myRepository"/> </bean>
// 组件扫描 / @Component + @Autowired @Repository public class MyRepository {// ... }@Service public class MyService {private final MyRepository myRepository;// Spring4.3以后,如果只有一个构造器,@Autowired可以省略// @Autowiredpublic MyService(MyRepository myRepository) {this.myRepository = myRepository;} }
🎯 如果有多个配置类或配置文件时如何使用?
-
xml
文件ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml","datasource.xml","service.xml" );
-
Java
配置类// 方式一: 在主配置类中引入其他配置类 @Configuration @Import({DataSourceConfig.class, ServiceConfig.class}) public class AppConfig { }
// 使用 AnnotationConfigApplicationContext 加载多个配置类 ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class, DataSourceConfig.class, ServiceConfig.class);
4. Spring
集成日志框架
在 Spring
中,日志框架是指一套组合:**一个日志门面加上一个具体的日志实现。**主流的日志门面毫无疑问是SLF4J
,它提供了一套通用的 API 接口,你的应用程序代码只需要依赖 SLF4J
的 API
,而不需要直接依赖某个具体的日志实现如 Logback
或 Log4j2
。这意味着你可以在不修改代码的情况下,轻松切换底层的日志实现。
综合来看,在大多数场景下,尤其是在高并发和异步日志方面,Log4j2
的性能通常优于 Logback
。但对于大多数项目而言,Logback
的性能已经绰绰有余,且其易用性和与 Spring Boot
的良好集成使其成为一个非常受欢迎的选择。下面在spring
中集成log4j2
日志框架:
-
引入相关依赖
<dependencies><!-- 排除Spring的commons-logging --><dependency><groupId>org.springframework</groupId><artifactId>spring-core</artifactId><version>6.2.7</version><exclusions><exclusion><groupId>commons-logging</groupId><artifactId>commons-logging</artifactId></exclusion></exclusions></dependency><!-- 引入Log4j2 --><dependency><groupId>org.apache.logging.log4j</groupId><artifactId>log4j-api</artifactId><version>2.17.2</version></dependency><dependency><groupId>org.apache.logging.log4j</groupId><artifactId>log4j-core</artifactId><version>2.17.2</version></dependency><!-- 桥接SLF4J --><dependency><groupId>org.apache.logging.log4j</groupId><artifactId>log4j-slf4j-impl</artifactId><version>2.17.2</version></dependency> </dependencies>
-
创建
Log4j2
配置文件:Log4j2
默认会在classpath
下查找名为log4j2.xml
、log4j2.json
或log4j2.yaml
的配置文件。推荐使用log4j2.xml
,放在src/main/resources
目录下。<?xml version="1.0" encoding="UTF-8"?> <Configuration status="WARN"><Appenders><!-- 控制台输出 --><Console name="Console" target="SYSTEM_OUT"><PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss} [%t] %-5level %logger{36} - %msg%n"/></Console><!-- 文件滚动日志 --><RollingFile name="File" fileName="logs/app.log"filePattern="logs/app-%d{yyyy-MM-dd}-%i.log"><PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss} [%t] %-5level %logger{36} - %msg%n"/><Policies><TimeBasedTriggeringPolicy interval="1" modulate="true"/><SizeBasedTriggeringPolicy size="100 MB"/></Policies><DefaultRolloverStrategy max="10"/></RollingFile></Appenders><Loggers><!-- 根日志级别 --><Root level="info"><AppenderRef ref="Console"/><AppenderRef ref="File"/></Root><!-- 自定义包日志级别 --><Logger name="com.example.service" level="debug" additivity="false"><AppenderRef ref="File"/></Logger></Loggers> </Configuration>
-
在
Spring
程序中使用日志package service.impl;import org.springframework.stereotype.Component; import service.HelloService; import org.slf4j.LoggerFactory; import org.slf4j.Logger;@Component public class HelloServiceImpl implements HelloService {Logger logger = LoggerFactory.getLogger(HelloServiceImpl.class);@Overridepublic void sayHello(String name) {logger.info("Hello, " + name + "!");} }
// 结合lombok使用 @Component @Slf4j public class HelloServiceImpl implements HelloService {@Overridepublic void sayHello(String name) {log.info("Hello, " + name + "!");} }
2025-06-09 11:25:37 [main] INFO service.impl.HelloServiceImpl - Hello, World!