IOC容器加载Bean的过程
构造方法
public ClassPathXmlApplicationContext(
String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
throws BeansException {
// 设置父容器
super(parent);
// 设置配置文件地址
setConfigLocations(configLocations);
// 判断是否自动刷新上下文环境
if (refresh) {
// 加载或刷新从XML配置、Java注解等方式配置的组件对象
refresh();
}
}
加载方法
@Override
public void refresh() throws BeansException, IllegalStateException {
// 同步刷新和销毁的监视器
synchronized (this.startupShutdownMonitor) {
// 记录spring容器开始刷新的步骤
StartupStep contextRefresh =
this.applicationStartup.start("spring.context.refresh");
// 执行刷新前的前准备:标记开始时间,容器激活状态,初始化属性,验证必要的环境变量
prepareRefresh();
// 刷新子类的BeanFactory:如果原来存在BeanFactory就销毁其中的Bean并关闭
// 创建一个全新的BeanFactory:定制化配置,加载Bean的定义
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// 执行BeanFactory的前期准备:
// 设置类加载器,添加回调处理,
// 注册Spring提供的BeanFactory、ResourceLoader、事件、容器对象
// 注册系统变量对象等
prepareBeanFactory(beanFactory);
try {
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);
StartupStep beanPostProcess =
this.applicationStartup.start("spring.context.beans.post-process");
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);
beanPostProcess.end();
// 初始化消息资源配置
initMessageSource();
// 初始化事件分发程序
initApplicationEventMulticaster();
// 初始化特殊的加载或刷新:子类需要重写实现
onRefresh();
// 注册监听器
registerListeners();
// 实例化所有需要及时加载的对象
finishBeanFactoryInitialization(beanFactory);
// 完成加载或刷新并发布事件
finishRefresh();
}
catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
// Destroy already created singletons to avoid dangling resources.
destroyBeans();
// Reset 'active' flag.
cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
}
finally {
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
contextRefresh.end();
}
}
}
1. Spring配置数据源
1.1 数据源的作用
数据源(连接池)用来提高数据库程序性能。
事先实例化数据源,初始化部分连接资源
使用连接资源时从数据源中获取
使用完毕后将连接资源归还给数据源
常见的数据源(连接池):Druid、HikariCP、BoneCP、C3P0、DBCP等
1.2 开发步骤
1.2.1 导入数据库驱动坐标
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<!--依赖的版本(统一管理)-->
<spring.version>5.2.7.RELEASE</spring.version>
<mysql.version>8.0.20</mysql.version>
<druid.version>1.1.23</druid.version>
</properties>
<!--JDBC的驱动依赖坐标-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
1.2.2 导入数据源的坐标
<!--Druid数据源的依赖坐标-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>${druid.version}</version>
</dependency>
1.2.3 配置数据源
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://127.0.0.1:3306/forum?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=false"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</bean>
1.2.4 从容器中获取数据源
package com.xuetang9.spring.demo.dao;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import javax.sql.DataSource;
import java.sql.Connection;
public class DruidTest {
ApplicationContext context;
DataSource dataSource;
@Before
public void setUp() throws Exception {
context = new ClassPathXmlApplicationContext("applicationContext.xml");
dataSource = context.getBean(DataSource.class);
}
@Test
public void test() throws Exception {
Connection connection = dataSource.getConnection();
System.out.println(connection);
}
}
1.3 读取外部属性文件
通常数据源的配置信息会使用properties属性文件
使用Spring读取外部文件
1.3.1 创建属性文件
druid.driverClassName=com.mysql.cj.jdbc.Driver
druid.url=jdbc:mysql://127.0.0.1:3306/forum?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=false
druid.username=root
druid.password=root
1.3.2 引入context命名空间和约束路径
1.3.3 加载属性文件
<context:property-placeholder location="classpath:database.properties"/>
1.3.4 使用SpEL
取值属性
<bean class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${druid.driverClassName}"/>
<property name="url" value="${druid.url}"/>
<property name="username" value="${druid.username}"/>
<property name="password" value="${druid.password}"/>
</bean>
2. Spring注解配置
Spring是基于IOC容器实现依赖注入的框架,所以配置比较繁琐,影响开发效率,所以在后面出现了注解开发,并成为了目前最流行的趋势,注解代替xml配置文件可以简化配置,提高开发效率。
2.1 Spring 2.x 注解
基础注解最主要是为了替代
bean
标签,在2.5的版本就出现使用的是配置文件 + 注解的形式开发
注解 | 描述 |
---|---|
@Component | 使用在类上用于实例化Bean |
@Repository | 使用在dao层类上用于实例化Bean |
@Service | 使用在service层类上用于实例化Bean |
@Controller | 使用在web层类上用于实例化Bean,使用SpringWebMVC |
@Autowired | 使用在字段上用于根据类型依赖注入,常用 |
@Qualifier | 结合@Autowired一起使用用于根据名称进行依赖注入 |
@Resource | 相当于@Autowired+@Qualifier,按照名称进行注入 |
@Scope | 标注Bean的作用范围 |
@PostConstruct | 使用在方法上标注该方法是Bean的初始化方法 |
@PreDestroy | 使用在方法上标注该方法是Bean的销毁方法 |
2.2 Spring 2.x 注解实现示例
2.2.1 @Component或者@Repository
package com.xuetang9.spring.demo.dao.impl;
import com.xuetang9.spring.demo.dao.SectionDao;
import com.xuetang9.spring.demo.entity.Section;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public class SectionDaoImpl implements SectionDao {
@Override
public List<Section> selectAll() {
System.out.println("执行查询全部的版块");
return null;
}
}
2.2.2 @Service注解标注业务层对象
package com.xuetang9.spring.demo.service.impl;
import com.xuetang9.spring.demo.entity.Section;
import com.xuetang9.spring.demo.service.SectionService;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class SectionServiceImpl implements SectionService {
@Override
public List<Section> listAll() {
return null;
}
}
2.2.3 使用配置文件读取注解信息
设置容器查找哪些包下面的注解
<!--配置注解的扫描-->
<context:component-scan base-package="com.xuetang9.spring.demo"/>
<!--配置注解的扫描(自定义需要扫描的注解)-->
<context:component-scan
base-package="com.xuetang9.spring.demo"
use-default-filters="true">
<!--排除@Controller注解不扫描-->
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
<!--排除@Repository注解不扫描-->
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Repository"/>
</context:component-scan>
2.2.4 使用注解实现依赖注入
package com.xuetang9.spring.demo.service.impl;
import com.xuetang9.spring.demo.dao.SectionDao;
import com.xuetang9.spring.demo.entity.Section;
import com.xuetang9.spring.demo.service.SectionService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.List;
@Service
public class SectionServiceImpl implements SectionService {
//@Autowired
//@Qualifier("sectionDaoImpl")
@Resource(name = "sectionDaoImpl")
private SectionDao sectionDao;
@Override
public List<Section> listAll() {
System.out.println("执行listAll的业务方法");
return sectionDao.selectAll();
}
}
2.2.5 配置文件 + 注解的注意事项
注解一般使用在我们自己书写的类上
通常使用注解创建对象时不用书写名称
依赖注入使用@Autowired根据类型查找
使用第三方提供的工具类需要在配置文件中使用bean标签配置
在配置文件中需要使用context:component-san标签扫描
2.3 Spring 3.x 注解
上面的注解还不能完全替代
xml
配置文件, 在3.0以后推出了高级的注解在国内
Spring Boot
大火之后就完全使用纯注解开发
注解 | 描述 |
---|---|
@Configuration | 用于指定当前类是一个 Spring 配置类,当创建容器时会从该类上加载注解。用来替代 xml 配置文件。 |
@Bean | 使用在方法上,标注将该方法的返回值将作为一个 bean 对象存储到 Spring 容器中。 |
@Value | 注入普通属性,使用 SpEL 表达式。 |
@Import | 用于导入其他配置类 |
@ComponentScan | 用于指定 Spring 在初始化容器时要扫描的包。 用来替代 <context:component-scan base-package=“com.xuetang9”/> |
@PropertySource | 用于加载.properties 文件中的配置 |
2.4 Spring 3.x 注解实现示例
2.4.1 @Configuration配置类和@ComponentScan扫描注解
package com.xuetang9.spring.demo.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan("com.xuetang9.spring.demo")
public class AppConfig {
}
package com.xuetang9.spring.demo.config;
import com.xuetang9.spring.demo.dao.SectionDao;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import static org.junit.Assert.*;
public class AppConfigTest {
ApplicationContext context;
@Before
public void setUp() throws Exception {
// 使用配置类创建容器
context = new AnnotationConfigApplicationContext(AppConfig.class);
}
@After
public void tearDown() throws Exception {
}
@Test
public void testContext(){
System.out.println(context.getBean(SectionDao.class));
}
}
2.4.2 @Bean用来替代bean标签配置第三方的类
package com.xuetang9.spring.demo.config;
import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
@Configuration
@ComponentScan("com.xuetang9.spring.demo")
public class AppConfig {
/**
* 把方法的返回值作为bean对象,保存到容器中
* @return
*/
@Bean
public DataSource createDataSource(){
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
druidDataSource.setUrl("jdbc:mysql://127.0.0.1:3306/forum?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=false");
druidDataSource.setUsername("root");
druidDataSource.setPassword("root");
return druidDataSource;
}
}
2.4.3 @Value的基础使用
package com.xuetang9.spring.demo.config;
import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
@Configuration
@ComponentScan("com.xuetang9.spring.demo")
public class AppConfig {
@Value("com.mysql.cj.jdbc.Driver")
private String driverClassName;
@Value("jdbc:mysql://127.0.0.1:3306/forum?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=false")
private String url;
@Value("root")
private String username;
@Value("root")
private String password;
/**
* 把方法的返回值作为bean对象,保存到容器中
* @return
*/
@Bean
public DataSource createDataSource(){
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setDriverClassName(driverClassName);
druidDataSource.setUrl(url);
druidDataSource.setUsername(username);
druidDataSource.setPassword(password);
return druidDataSource;
}
}
2.4.4 @PropertySource加载属性文件
package com.xuetang9.spring.demo.config;
import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import javax.sql.DataSource;
/**
* 用来替代Spring的XMl配置文件
*/
@Configuration
@ComponentScan("com.xuetang9.spring.demo")
@PropertySource("classpath:database.properties")
public class AppConfig {
@Value("${druid.driverClassName}")
private String driverClassName;
@Value("${druid.url}")
private String url;
@Value("${druid.username}")
private String username;
@Value("${druid.password}")
private String password;
/**
* 把方法的返回值作为bean对象,保存到容器中
* @return
*/
@Bean
public DataSource createDataSource(){
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setDriverClassName(driverClassName);
druidDataSource.setUrl(url);
druidDataSource.setUsername(username);
druidDataSource.setPassword(password);
return druidDataSource;
}
}
2.4.5 @Configuration和@Import多配置类使用
方式一:在每个配置上添加 @Configuration
方式二:在主配置类上使用@Import导入其它的配置类
package com.xuetang9.spring.demo.config;
import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import javax.sql.DataSource;
@Configuration
@PropertySource("classpath:database.properties")
public class DatabaseConfig {
@Value("${druid.driverClassName}")
private String driverClassName;
@Value("${druid.url}")
private String url;
@Value("${druid.username}")
private String username;
@Value("${druid.password}")
private String password;
/**
* 把方法的返回值作为bean对象,保存到容器中
* @return
*/
@Bean
public DataSource createDataSource(){
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setDriverClassName(driverClassName);
druidDataSource.setUrl(url);
druidDataSource.setUsername(username);
druidDataSource.setPassword(password);
return druidDataSource;
}
}
package com.xuetang9.spring.demo.config;
import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.*;
import javax.sql.DataSource;
/**
* 用来替代Spring的XMl配置文件
*/
@Configuration
@ComponentScan("com.xuetang9.spring.demo")
//@Import({DatabaseConfig.class})
public class AppConfig {
}
3. Spring整合Junit单元测试
在测试类中,每次测试都要手动创建容器,然后从容器中获取bean对象
使用spring的test模块简化测试
3.1 Spring整合Junit步骤
3.1.1 导入spring集成Junit的坐标
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!--Spring的test模块-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
<scope>test</scope>
</dependency>
3.1.2 创建单元测试
package com.xuetang9.spring.demo.service.impl;
import com.xuetang9.spring.demo.config.AppConfig;
import com.xuetang9.spring.demo.service.SectionService;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import static org.junit.Assert.*;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {AppConfig.class})
public class SectionServiceImplTest {
@Autowired
private SectionService sectionService;
@Before
public void setUp() throws Exception {
}
@After
public void tearDown() throws Exception {
}
@Test
public void listAll() {
sectionService.listAll();
}
}
3.1.3 使用@Runwith注解替换原来的运行期
@RunWith(SpringJUnit4ClassRunner.class)
3.1.4 使用@ContextConfiguration指定配置
-
@ContextConfiguration(classes = {AppConfig.class})
-
@ContextConfiguration("classpath:applicationContext.xml")
3.1.5 使用@Autowired注入需要测试的对象
@Autowired
private SectionService sectionService;
3.1.6 实现测试方法进行测试
package com.xuetang9.spring.demo.service.impl;
import com.xuetang9.spring.demo.config.AppConfig;
import com.xuetang9.spring.demo.service.SectionService;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import static org.junit.Assert.*;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class SectionServiceImplTest {
@Autowired
private SectionService sectionService;
@Test
public void listAll() {
sectionService.listAll();
}
}
### 3.1.6 实现测试方法进行测试
package com.xuetang9.spring.demo.service.impl;
import com.xuetang9.spring.demo.config.AppConfig;
import com.xuetang9.spring.demo.service.SectionService;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import static org.junit.Assert.*;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class SectionServiceImplTest {
@Autowired
private SectionService sectionService;
@Test
public void listAll() {
sectionService.listAll();
}
}