在 Java 企业级开发领域,Spring 框架以其强大且灵活的特性成为众多开发者的首选。而 Spring 框架的核心基石之一,便是 Bean 管理与控制反转(Inversion of Control,简称 IOC)。这两项技术不仅极大地简化了应用程序的开发流程,还显著提升了代码的可维护性与可扩展性。
一、认识 Spring 框架的 Bean
1.1 Bean 的定义
在 Spring 框架中,Bean 是指被 Spring 容器管理的对象。Spring 容器负责创建、配置和管理 Bean,通过依赖注入(Dependency Injection,DI,IOC 的一种实现方式)将 Bean 之间的依赖关系进行连接。简单来说,任何一个被 Spring 容器管理的 Java 对象,都可以称之为 Bean。例如,在一个 Web 应用中,业务逻辑层的 Service 类、数据访问层的 DAO 类,都可以被配置为 Spring 容器中的 Bean,由 Spring 容器统一管理其生命周期。
1.2 Bean 的配置方式
Spring 提供了多种配置 Bean 的方式,常见的有 XML 配置、注解配置以及 Java 配置。
- XML 配置:早期 Spring 项目中常用的方式,通过在 XML 文件中定义 Bean 的相关信息,如 Bean 的 id、class、属性值等。例如,定义一个简单的 UserService Bean:
<bean id="userService" class="com.example.service.UserService"> <property name="userDao" ref="userDao"/> </bean> <bean id="userDao" class="com.example.dao.UserDao"/>
在上述代码中,userService Bean 依赖于userDao Bean,通过<property>标签将userDao的引用注入到userService中。
- 注解配置:随着 Spring 版本的更新,注解配置逐渐成为主流。常用的注解有@Component、@Service、@Repository、@Controller等,这些注解用于将类标记为可被 Spring 容器扫描并管理的 Bean。例如:
@Service
public class UserService {
private UserDao userDao;
@Autowired
public UserService(UserDao userDao) {
this.userDao = userDao;
}
}
@Repository
public class UserDao {
// 数据访问层逻辑
}
@Service注解将UserService类标记为业务逻辑层的 Bean,@Repository注解将UserDao类标记为数据访问层的 Bean,@Autowired注解则实现了依赖注入,自动将UserDao的实例注入到UserService的构造函数中。
- Java 配置:通过 Java 类和注解来配置 Bean,使用@Configuration和@Bean注解。例如:
@Configuration
public class AppConfig {
@Bean
public UserService userService() {
return new UserService(userDao());
}
@Bean
public UserDao userDao() {
return new UserDao();
}
}
@Configuration注解标识该类为配置类,@Bean注解定义了具体的 Bean 实例,通过方法调用的方式实现 Bean 之间的依赖关系。
二、深入理解控制反转(IOC)
2.1 IOC 的概念
控制反转(IOC)是一种设计思想,它将对象的创建和依赖关系的管理从程序代码中转移到外部容器(即 Spring 容器)中。在传统的编程模式下,对象的创建和依赖关系的维护都由开发者在代码中显式实现,这导致代码耦合度高,难以维护和扩展。而引入 IOC 后,Spring 容器负责创建对象,并根据配置将对象之间的依赖关系进行注入,开发者只需关注业务逻辑的实现,无需关心对象的创建和管理细节。
2.2 IOC 的实现方式 —— 依赖注入(DI)
依赖注入是 IOC 的具体实现方式,它有三种常见的注入方式:构造函数注入、Setter 方法注入和字段注入。
- 构造函数注入:通过类的构造函数将依赖对象传递进来。如前面UserService的例子中,通过构造函数注入UserDao实例,这种方式保证了依赖对象在对象创建时就已被初始化,并且不可变,安全性较高。
- Setter 方法注入:通过类的 Setter 方法将依赖对象设置进去。例如:
public class UserService {
private UserDao userDao;
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
}
在 XML 配置中,可以通过<property>标签的name属性指定 Setter 方法对应的属性名,来实现 Setter 方法注入。Setter 方法注入的优点是更加灵活,可以在对象创建后动态地设置依赖对象。
- 字段注入:直接在类的字段上使用@Autowired等注解进行依赖注入。例如:
@Service
public class UserService {
@Autowired
private UserDao userDao;
}
2.3 IOC 的优势
IOC 带来的优势主要体现在以下几个方面:
- 降低代码耦合度:通过将对象的创建和依赖管理交给 Spring 容器,不同组件之间的依赖关系变得更加清晰,降低了代码之间的耦合度,提高了代码的可维护性和可扩展性。
- 提高代码的可测试性:由于对象的依赖关系由 Spring 容器注入,在进行单元测试时,可以方便地替换依赖对象,使用模拟对象(Mock 对象)来进行测试,从而提高测试的效率和准确性。
- 便于代码的复用:IOC 使得组件更加独立,可复用性增强。不同的项目或模块可以共享相同的 Bean,减少了重复开发的工作量。
三、Spring 容器对 Bean 的生命周期管理
Spring 容器对 Bean 的生命周期管理是其 Bean 管理的重要组成部分。一个 Bean 从创建到销毁,通常会经历以下几个阶段:
- 实例化:Spring 容器根据配置信息创建 Bean 的实例,这相当于 Java 中的new操作。
- 属性注入:在 Bean 实例化后,Spring 容器会根据配置将 Bean 的依赖属性注入到相应的位置,如通过构造函数注入、Setter 方法注入等。
- 初始化:如果 Bean 实现了InitializingBean接口,Spring 容器会调用其afterPropertiesSet方法;或者在配置文件中指定了init-method属性,Spring 容器会调用该方法,用于执行一些初始化操作,如资源的加载、连接的建立等。
- 使用:Bean 完成初始化后,就可以在应用程序中被使用,执行相应的业务逻辑。
- 销毁:当 Spring 容器关闭时,如果 Bean 实现了DisposableBean接口,Spring 容器会调用其destroy方法;或者在配置文件中指定了destroy-method属性,Spring 容器会调用该方法,用于释放资源,如关闭连接、释放内存等。
通过对 Bean 生命周期的管理,Spring 容器确保了 Bean 在合适的时机执行相应的操作,保证了应用程序的稳定性和可靠性。
四、实战演练
类组成如下
实体类
package com.qcby;
import org.springframework.stereotype.Component;
public class Cat {
private String name;
private int age;
private Integer num;
private double height ;
private Demo demo;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Integer getNum() {
return num;
}
public void setNum(Integer num) {
this.num = num;
}
public double getHeight() {
return height;
}
public void setHeight(double height) {
this.height = height;
}
public Demo getDemo() {
return demo;
}
public void setDemo(Demo demo) {
this.demo = demo;
}
@Override
public String toString() {
return "Cat{" +
"name='" + name + '\'' +
", age=" + age +
", num=" + num +
", height=" + height +
", demo=" + demo +
'}';
}
public Cat() {
}
public Cat(String name, int age, Integer num, double height, Demo demo) {
this.name = name;
this.age = age;
this.num = num;
this.height = height;
this.demo = demo;
}
public void eat() {
System.out.println("猫吃鱼");
}
}
package com.qcby;
import org.springframework.stereotype.Service;
@Service
public class Demo {
private String name;
public void hello() {
System.out.println("Hello World!");
}
}
package com.qcby;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
public class Dog {
private int[] arr;
private List<String> List;
private Map<String,String> map;
public int[] getArr() {
return arr;
}
public void setArr(int[] arr) {
this.arr = arr;
}
public java.util.List<String> getList() {
return List;
}
public void setList(java.util.List<String> list) {
List = list;
}
public Map<String, String> getMap() {
return map;
}
public void setMap(Map<String, String> map) {
this.map = map;
}
@Override
public String toString() {
return "Dog{" +
"arr=" + Arrays.toString(arr) +
", List=" + List +
", map=" + map +
'}';
}
public void jump()
{
System.out.println("狗会跳");
}
}
package com.qcby;
public class User {
private String name;
private double height;
private Demo demo;
public User(String name, double height, Demo demo) {
this.name = name;
this.height = height;
this.demo = demo;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", height=" + height +
", demo=" + demo +
'}';
}
public void fly (){
System.out.println("我会飞");
}
}
spring.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.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!--IOC-->
<!-- <bean id ="demo" class="com.qcby.Demo"/>-->
<!-- <bean id ="user" class="com.qcby.User"/>-->
<bean id="cat" class="com.qcby.Cat">
<property name="age" value="10"/>
<property name="name" value="Tom"/>
<property name="num" value="1"/>
<property name="height" value="180.5"/>
<property name="demo" ref="demo"/>
</bean>
<bean id="demo" class="com.qcby.Demo"/>
<bean id="dog" class="com.qcby.Dog">
<property name="arr" >
<array>
<value>1</value>
<value>2</value>
<value>3</value>
</array>
</property>
<property name="list">
<list>
<value>张三</value>
<value>李四</value>
<value>王五</value>
</list>
</property>
<property name="map" >
<map>
<entry key="张三" value="zs"/>
<entry key="李四" value="ls"/>
<entry key="王五" value="ww"/>
</map>
</property>
</bean>
<bean id="user" class="com.qcby.User">
<constructor-arg name="name" value="张三"/>
<constructor-arg name="height" value="180.5"/>
<constructor-arg name="demo" ref="demo"/>
</bean>
<context:component-scan base-package="com.qcby"/>
</beans>
测试类
import com.qcby.Cat;
import com.qcby.Demo;
import com.qcby.Dog;
import com.qcby.User;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class UserTest {
@Test
public void run (){
Demo demo = new Demo();
demo.hello();
}
@Test
public void run1 (){
ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
Demo demo = (Demo) context.getBean("demo");
demo.hello();
}
@Test
public void run2 (){
ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
Dog dog = (Dog) context.getBean("dog");
System.out.println( dog.toString());
}
@Test
public void run3 (){
ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
Cat cat = (Cat) context.getBean("cat");
System.out.println( cat.toString());
}
@Test
public void run4 (){
ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
User user = (User) context.getBean("user");
System.out.println( user.toString());
}
}