IOC的配置文件开发方式
这里我们编写业务层和持久层代码进行演示,抛去表现层实现 Spring 框架开发
技术选择:创建 maven Java 工程,导入坐标依赖,在这里我们持久层选择使用原始的 JDBC 程序,连接池选择 Druid 连接池
<dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.0.2.RELEASE</version> </dependency> <dependency> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> <version>1.2</version> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.12</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <!--连接池--> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.10</version> </dependency> <!--mysql 驱动包--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.6</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.0.2.RELEASE</version> <scope>test</scope> </dependency> </dependencies>创建数据库,创建表结构
CREATE TABLE `account` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL, `money` double DEFAULT NULL, PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 9 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin ROW_FORMAT = Compact; INSERT INTO `account` VALUES (1, '小明', 100); INSERT INTO `account` VALUES (2, '小红', 120); INSERT INTO `account` VALUES (3, '小丽', 135); INSERT INTO `account` VALUES (4, '李华', 85); INSERT INTO `account` VALUES (5, '李莉', 110); INSERT INTO `account` VALUES (6, '熊大 ', 500); INSERT INTO `account` VALUES (7, '熊二', 900); INSERT INTO `account` VALUES (8, '小丽', 500);编写 JavaBean 的类 Account
public class Account implements Serializable { private Integer id; private String name; private Double money; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Double getMoney() { return money; } public void setMoney(Double money) { this.money = money; } @Override public String toString() { return "Account{" + "id=" + id + ", name='" + name + '\'' + ", money=" + money + '}'; } }Serializable 是 Java 中的一个标记(给类打上标记)接口,其核心功能是支持对象的序列化与反序列化操作,从而突破对象在内存中的存储限制
补充:对象序列化是指将内存中的 Java 对象(其状态包括成员变量的值、类信息等)转换为字节流的过程;序列化流是 Java IO 体系中专门用于实现对象转换成字节流和字节流转换成对象的工具类;由于内存中的对象是临时的,JVM 退出后就会消失,且无法直接跨进程跨网络传递,序列化的核心价值就是打破对象的内存束缚。
编写 AccountDao 的接口和实现类
public interface AccountDao { public List<Account> findAll(); }public class AccountDaoImpl implements AccountDao{ // 注入连接池对象 private DataSource dataSource; public void setDataSource(DataSource dataSource) { this.dataSource = dataSource; } /** * 使用原始的JDBC程序查询所有数据 */ public List<Account> findAll() { List<Account> list = new ArrayList<Account>(); Connection connection = null; PreparedStatement stmt = null; ResultSet rs = null; try { // 获取连接 connection = dataSource.getConnection(); // 编写 sql 语句 String sql = "select * from account"; // 预编译 stmt = connection.prepareStatement(sql); // 查询 rs = stmt.executeQuery(); // 遍历,封装数据 while (rs.next()){ Account account = new Account(); account.setId(rs.getInt("id")); account.setName(rs.getString("name")); account.setMoney(rs.getDouble("money")); list.add(account); } } catch (SQLException e) { e.printStackTrace(); }finally { try { connection.close(); } catch (SQLException e) { e.printStackTrace(); } try { stmt.close(); } catch (SQLException e) { e.printStackTrace(); } try { rs.close(); } catch (SQLException e) { e.printStackTrace(); } } return list; } }编写 AccountService 的接口和实现类,并实现依赖注入
public interface AccountService { public List<Account> findAll(); }public class AccountServiceImpl implements AccountService { // 依赖注入 private AccountDao accountDao; public void setAccountDao(AccountDao accountDao) { this.accountDao = accountDao; } /** * 查询所有的数据 */ public List<Account> findAll() { return accountDao.findAll(); } }在资源文件夹下编写配置文件 applicationContext1.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="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql:///ssm" /> <property name="username" value="root" /> <property name="password" value="root" /> </bean> <!--管理 bean--> <bean id="accountService" class="com.qcby.service.impl.AccountServiceImpl"> <property name="accountDao" ref="accountDao" /> </bean> <bean id="accountDao" class="com.qcby.dao.impl.AccountDaoImpl"> <property name="dataSource" ref="dataSource" /> </bean> </beans>编程测试程序
public class Demo2 { @Test public void run1(){ ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext1.xml"); AccountService accountService = (AccountService) ac.getBean("accountService"); // 调用方法 List<Account> list = accountService.findAll(); for (Account account : list) { System.out.println(account); } } }实现效果如下:
IOC的注解开发方式
半注解开发方式
依赖依赖没有变化,编写接口和实现类
public interface AdminService { public void hello(); }@Component(value = "as") public class AdminServiceImpl implements AdminService { public void hello() { System.out.println("Hello IOC 注解..."); } }在需要管理的类上添加@Component 注解,作用与<bean id="us" class="com.qcby.service.impl.UserServiceImpl"/> 相同
编写配置文件 applicationContext_anno.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"> <!--开启注解扫描 com.qcby所有的包中的所有的类 --> <context:component-scan base-package="com.qcby" /> </beans>编写测试方法
public class Demo3 { @Test public void run1(){ // 工厂 ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext_anno.xml"); // 获取对象 AdminService adminService = (AdminService) ac.getBean("as"); adminService.hello(); } }实现效果:
总结常用的注解:
Bean管理类常用的注解
@Component 通用的组件标识,可将任意类标识为 Spring 容器管理的 Bean
@Controller 用于标识表现层的类,负责接收用户请求并处理响应
@Service 用于标识业务逻辑层的类,封装核心业务处理逻辑
@Repository 用于标识数据持久层的类,负责数据的查询与存储(除标识 Bean 外,还能自动转换数据访问层的异常)
这些注解均用于类级别,使用位置与对应层级匹配,核心功能与 @Component 一致
注解在不指定 value 属性时,默认会将当前类名的首字母小写作为该 Bean 在 Spring 容器中的 ID;若需要自定义 Bean 名称,可通过 value 属性指定(如@Service(value = "userManager")或简写为@Service("userManager"))。
依赖注入常用的注解
注入普通类型:
@Value 注入基本类型、字符串或配置文件中的属性值;
注入引用数据类型:
@Autowired 默认按类型进行自动装配,当容器中存在唯一匹配类型的 Bean 时直接注入,可用于构造方法、成员变量、setter方法上;
@Qualifier 配合 @Autowired 一起使用,按名称注入 Bean,解决同类型多个 Bean 的冲突;
@Resource Java提供的注解也被支持,使用 name 属性,按名称注入对象;
注入普通类型:
@Value 用于注入基本类型、字符串类型;
注入引用数据类型:
@Autowired 由 Spring 提供,默认按类型自动装配。当容器中存在唯一匹配类型的 Bean 时直接注入,可用于构造方法、成员变量、setter 方法上;若存在多个同类型 Bean,需配合 @Qualifier 使用,否则会报错(默认要求注入对象必须存在,可通过required = false设置为非必须);
@Qualifier 需与 @Autowired 配合使用,通过value属性指定 Bean 的名称(ID),实现按名称注入,解决同类型多个 Bean 的装配冲突;
@Resource 由 Java EE 规范提供,Spring 也支持该注解。默认按名称注入(通过name属性指定 Bean 名称),若未指定name则默认取属性名匹配;若按名称未找到,会自动切换为按类型注入(需注意与 @Autowired 默认行为的区别)。
生命周期(作用范围)注解
@Scope 生命周期注解,常用取值为 singleton(默认值,单实例)和 prototype(多例)
初始化方法和销毁方法注解
@PostConstruct 标记销毁方法,相当于XML配置中bean标签的destroy-method属性
@PreDestroy 标记初始化方法,相当于XML配置中bean标签的init-method属性(用于标注Bean实例化并完成依赖注入后需要执行的初始化方法)
具体代码示例:
@Component(value = "c") public class Mobile { @Value("奥迪") private String cname; @Value(value = "400000") private Double money; @Autowired private Person person; /** * Car 对象创建完成后,调用 init 方法进行初始化操作 */ @PostConstruct public void init(){ System.out.println("操作..."); } @Override public String toString() { return "Car{" + "cname='" + cname + '\'' + ", money=" + money + ", person=" + person + '}'; } }@Component(value = "person") public class Person { @Value("张三") private String pname; @Override public String toString() { return "Person{" + "pname='" + pname + '\'' + '}'; } }applicationContext_anno.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"> <!--开启注解扫描 com.qcby.所有的包中的所有的类 --> <context:component-scan base-package="com.qcby" /> </beans>public class Demo4 { @Test public void run1(){ // 工厂 ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext_anno.xml"); // 获取对象 Mobile mobile = (Mobile) ac.getBean("c"); System.out.println(mobile); } }对于Mobile类,其在 Spring 容器中的生命周期流程如下:
① Spring 容器通过组件扫描发现Mobile类,执行构造方法创建其实例;
② 容器为Mobile实例注入依赖:通过@Value注入cname和money,通过@Autowired注入person对象;
③ 当所有依赖注入完成后,容器检测到init方法被@PostConstruct标注,立即调用init方法执行初始化操作;
④ 初始化完成后,Mobile实例成为 “可用状态”,存入 Spring 容器中;
⑤ 当测试代码 ac.getBean("c") 获取Mobile实例时,容器直接返回已初始化完成的 Bean。
测试代码的执行顺序:
当new ClassPathXmlApplicationContext(...)执行时,Spring 容器启动并完成上述①-④的所有流程(包括调用init方法);
之后ac.getBean("c")获取到的是已经初始化完成的Mobile实例,调用System.out.println(mobile)时才打印其属性信息。
实现效果:
纯注解开发方式
纯注解方式是当前微服务架构开发的主流模式,其核心目标就是通过注解和配置类替代传统的 XML 配置文件,让配置更简洁、更贴近代码逻辑,同时更适合微服务架构的灵活性和可扩展性需求。
编写实体类
@Component public class Order { @Value("北京") private String address; @Override public String toString() { return "Order{" + "address='" + address + '\'' + '}'; } }编写配置类 SpringConfig,用于替换掉 applicationContext_anno.xml 配置文件
@Configuration @ComponentScan(value = "com.qcby") public class SpringConfig { }@Configuration 标识当前类是Spring 配置类,相当于 XML 配置文件的根节点,Spring 会将该类视为配置信息的载体,类中被@Bean标注的方法会被解析,其返回值将作为 Bean 注册到 Spring 容器中。
@ComponentScan(value = "com.qcby") 指定 Spring 自动扫描组件的包路径,相当于 XML 中的,Spring 会扫描指定包及其子包下所有被@Component 等标注的类,自动将它们注册为容器中的 Bean,如果不指定value,默认扫描当前配置类所在的包及其子包。
测试方法的编写
public class Demo5 { @Test public void run1(){ // 创建工厂,加载配置类 ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfig.class); // 获取到对象 Order order = (Order) ac.getBean("order"); System.out.println(order); } }ApplicationContext 是 Spring 的核心容器接口,负责管理 Bean 的创建、依赖注入、生命周期等核心功能,AnnotationConfigApplicationContext 是 ApplicationContext 的实现类,专门用于加载基于注解的配置类,参数 SpringConfig.class 指定要加载的配置类,容器会根据该类中的@Configuration、@ComponentScan、@Bean等注解初始化 Bean。
实现效果:
我们看如下代码:
@Configuration @ComponentScan(value = "com.qcby") @Import(value = {SpringConfig2.class}) public class SpringConfig { //将第三方的类交给Spring框架管理 @Bean(name="dataSource") public DataSource createDataSource(){ DruidDataSource dataSource = new DruidDataSource(); dataSource.setDriverClassName("com.mysql.jdbc.Driver"); dataSource.setUrl("jdbc:mysql:///ssm"); dataSource.setUsername("root"); dataSource.setPassword("root"); return dataSource; } }@Import 用于导入其他配置类,相当于XML中的,可以导入多个配置类,value 属性接收的是一个 Class 数组,用逗号隔开。
除了使用@Import注解导入多个配置类,也可在创建工厂时直接加载多个配置文件
ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfig.class,SpringConfig2.class);@Bean 注解用于将第三方的类交给 Spring 框架管理,指定 Bean 的 name 属性为 dataSource,若不指定 name,默认名称为方法名即 createDataSource
为什么需要这样做:DruidDataSource是第三方类,我们无法修改其源码添加@Component等注解,因此必须通过@Bean方法手动创建对象并注册到 Spring 容器中,使其能被其他 Bean 依赖注入。
Spring框架整合JUnit单元测试
当需要测试 Spring 管理的 Bean 时,若不借助框架整合,每次都需要手动编写创建 Spring 上下文(如ApplicationContext)、加载配置文件等重复代码,操作比较繁琐。Spring 通过 spring-test 模块提供了整合 JUnit 单元测试的技术,能自动加载 Spring 上下文,无需手动创建和配置,可直接注入待测试的 Bean 进行测试,从而显著简化测试开发流程。
需要先有 Junit 单元测试环境,再导入 spring-test 的坐标依赖
<dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> <dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.0.2.RELEASE</version> <scope>test</scope> </dependency>编写类和方法,把该类交给 IOC 容器进行管理
public class User { public void sayHello(){ System.out.println("Hello...."); } }编写配置文件 applicationContext_test.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="user" class="com.qcby.model.User"/> </beans>编写测试代码
@RunWith(value = SpringJUnit4ClassRunner.class) // 测试单元 @ContextConfiguration(value = "classpath:applicationContext_test.xml") // 加载类路径下的配置文件 public class Demo6 { @Autowired private User user; @Test public void run1(){ user.sayHello(); } }@RunWith(SpringJUnit4ClassRunner.class) 指定 JUnit 4 的测试运行器为 Spring 提供的SpringJUnit4ClassRunner,使其能在测试前自动初始化 Spring 上下文
@ContextConfiguration 指定 Spring 配置文件或配置类的位置,告诉 Spring 从哪里加载上下文
常用属性:
locations:指定 XML 配置文件路径(如classpath:applicationContext.xml)
classes:指定 Java 配置类(如@Configuration标注的类,下面的纯注解方式用的是该属性)
实现效果如下:
Spring整合单元测试的纯注解方式
编写类和方法
@Component public class Customer { public void save(){ System.out.println("保存客户..."); } }编写配置类
@Configuration @ComponentScan(value = "com.qcby") public class SpringConfig3 { }编写测试方法
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = SpringConfig3.class) public class Demo7 { @Autowired private Customer customer; @Test public void run1(){ customer.save(); } }注意点:
在 Spring 框架整合 JUnit 单元测试时,无需手动编写创建工厂、加载配置文件以及调用getBean()获取对象的代码。这些操作实际上仍然存在,只是由 Spring Test 框架自动完成了,spring-test 模块通过注解和专用测试运行器,将这些重复性工作封装到框架内部,实现了自动化执行。
没有整合 JUnit 时,测试 Spring 管理的 Bean 需要手动创建Spring 的核心工厂 ApplicationContext,代码如下:
public class UserServiceTest { public static void main(String[] args) { // 手动创建工厂 ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); // 手动获取Bean UserService userService = context.getBean(UserService.class); // 测试逻辑 userService.test(); } }整合 JUnit 后,通过@RunWith(SpringJUnit4ClassRunner.class)注解,测试运行器会在测试方法执行前自动创建ApplicationContext,无需手动编写创建代码。
手动测试时,需要显式指定配置文件路径(如ClassPathXmlApplicationContext("applicationContext.xml")),整合后,通过@ContextConfiguration 指定配置文件 / 配置类位置,框架会自动根据配置加载 Bean 定义,无需手动调用加载方法。
手动测试时,需要通过context.getBean()获取目标对象,整合后,直接使用@Autowired注解在测试类中声明需要的 Bean,Spring 会自动从上下文(已创建的工厂)中找到对应的对象并注入,替代了手动调用getBean()。