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 数据源的作用

数据源(连接池)用来提高数据库程序性能。

  • 事先实例化数据源,初始化部分连接资源
  • 使用连接资源时从数据源中获取
  • 使用完毕后将连接资源归还给数据源
  • 常见的数据源(连接池):DruidHikariCP、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命名空间和约束路径

springboot刷新页面 403 spring 刷新bean_java

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"/>

springboot刷新页面 403 spring 刷新bean_java_02

<!--配置注解的扫描(自定义需要扫描的注解)-->
<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();
    }
}