天天看点

IOC容器中的核心容器

作者:tianlongbabu

这里所说的核心容器,大家可以把它简单的理解为ApplicationContext,前面虽然已经用到过,但 是并没有系统的学习,接下来咱们从以下几个问题入手来学习下容器的相关知识:

  • 如何创建容器?
  • 创建好容器后,如何从容器中获取bean对象?
  • 容器类的层次结构是什么?
  • BeanFactory是什么?

容器的创建方式

案例中创建ApplicationContext的方式为:

ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");

这种方式翻译为:类路径下的XML配置文件

除了上面这种方式,Spring还提供了另外一种创建方式为:

ApplicationContext ctx = new

FileSystemXmlApplicationContext("D:\\workspace\\spring\\spring_10_container\\s rc\\main\\resources\\applicationContext.xml");

这种方式是从项目路径下开始查找applicationContext.xml配置文件的.

这种方式虽能实现,但是当项目的位置发生变化后,代码也需要跟着改,耦合度较高,不推荐使用。

Bean的三种获取方式

方式一

BookDao bookDao = (BookDao) ctx.getBean("bookDao");           

这种方式存在的问题是每次获取的时候都需要进行类型转换,有没有更简单的方式呢?

方式二

BookDao bookDao = ctx.getBean("bookDao",BookDao.class);           

这种方式可以解决类型强转问题,但是参数又多加了一个,相对来说没有简化多少。

方式三

BookDao bookDao = ctx.getBean(BookDao.class);           

这种方式就类似我们之前所学习依赖注入中的按类型注入。必须要确保IOC容器中该类型对应的bean对象只能有一个。

BeanFactory的使用

使用BeanFactory来创建IOC容器的具体实现方式为:

public class AppForBeanFactory {
    public static void main(String[] args) {
        Resource resources = new ClassPathResource("applicationContext.xml");
        BeanFactory bf = new XmlBeanFactory(resources);
        BookDao bookDao = bf.getBean(BookDao.class);
        bookDao.save();
   }
}
           

如果不去获取bean对象,打印会发现:

  • BeanFactory是延迟加载,只有在获取bean对象的时候才会去创建
  • ApplicationContext是立即加载,容器加载的时候就会创建bean对象
  • ApplicationContext要想成为延迟加载,只需要按照如下方式进行配置
<?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="bookDao" class="com.itheima.dao.impl.BookDaoImpl"  lazyinit="true"/>
</beans>           

bean相关

IOC容器中的核心容器

依赖注入相关

IOC容器中的核心容器

IOC/DI注解开发

注解开发定义bean

步骤1:删除原XML配置

将配置文件中的标签删除掉

<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl"/>           

步骤2:Dao上添加注解

在BookDaoImpl类上添加@Component注解

@Component("bookDao")

public class BookDaoImpl implements BookDao {
    public void save() {
        System.out.println("book dao save ..." );
   }
}
           

注意:@Component注解不可以添加在接口上,因为接口是无法创建对象的。

XML与注解配置的对应关系

IOC容器中的核心容器

步骤3:配置Spring的注解包扫描

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

           <context:component-scan base-package="com.itheima"/>
</beans>           

说明: component-scan component:组件,Spring将管理的bean视作自己的一个组件

scan:扫描

  • base-package指定Spring框架扫描的包路径,它会扫描指定包及其子包中的所有类上的注解。
  • 包路径越多[如:com.itheima.dao.impl],扫描的范围越小速度越快
  • 包路径越少[如:com.itheima],扫描的范围越大速度越慢
  • 一般扫描到项目的组织名称即Maven的groupId下[如:com.itheima]即可。

说明: BookServiceImpl类没有起名称,所以在App中是按照类型来获取bean对象

BookService bookService = ctx.getBean(BookService.class);           

@Component注解如果不起名称,会有一个默认值就是当前类名首字母小写,所以也可以按照名称 获取,如

BookService bookService = (BookService)ctx.getBean("bookServiceImpl");

System.out.println(bookService);
           

对于@Component注解,还衍生出了其他三个注解@Controller、@Service、@Repository

,方便我们后期在编写类的时候能很好的区分出这个类是属于表现层、业务层还是数据层的类。

纯注解开发模式

实现步骤

步骤1:创建配置类

public class SpringConfig {
}           

步骤2:标识该类为配置类

在配置类上添加@Configuration注解,将其标识为一个配置类,替换applicationContext.xml

@Configuration

public class SpringConfig {
}           

步骤3:用注解替换包扫描配置

在配置类上添加包扫描注解@ComponentScan替换-scan base-package=""/>

@Configuration
@ComponentScan("com.itheima")

public class SpringConfig {
}           
  • @Configuration注解用于设定当前类为配置类
  • @ComponentScan注解用于设定扫描路径,此注解只能添加一次,多个数据请用数组格式

@ComponentScan({com.itheima.service","com.itheima.dao"})

  • 读取Spring核心配置文件初始化容器对象切换为读取Java配置类初始化容器对象
//加载配置文件初始化容器

    ApplicationContext ctx = new

            ClassPathXmlApplicationContext("applicationContext.xml");

//加载配置类初始化容器

    ApplicationContext ctx = new

            AnnotationConfigApplicationContext(SpringConfig.class);
           

注解开发bean作用范围与生命周期管理

Bean的作用范围

默认情况下bean是单例

要想将BookDaoImpl变成非单例,只需要在其类上添加@scope注解

@Repository

//@Scope设置bean的作用范围

@Scope("prototype")

public class BookDaoImpl implements BookDao {
 
    public void save() {
        System.out.println("book dao save ...");
   }
}           
IOC容器中的核心容器

Bean的生命周期

(1)在BookDaoImpl中添加两个方法,init和destroy ,方法名可以任意

@Repository

public class BookDaoImpl implements BookDao {
    public void save() {
        System.out.println("book dao save ...");
   }
    public void init() {
        System.out.println("init ...");
   }
    public void destroy() {
        System.out.println("destroy ...");
   }
}           

2)如何对方法进行标识,哪个是初始化方法,哪个是销毁方法?

只需要在对应的方法上添加@PostConstruct和@PreDestroy注解即可。

@Repository

public class BookDaoImpl implements BookDao {
    public void save() {
        System.out.println("book dao save ...");
   }
    @PostConstruct //在构造方法之后执行,替换 init-method

    public void init() {
        System.out.println("init ...");
   }
    @PreDestroy //在销毁方法之前执行,替换 destroy-method

    public void destroy() {
        System.out.println("destroy ...");
   }
}
           

(3)要想看到两个方法执行,需要注意的是destroy只有在容器关闭的时候,才会执行,所以需要修 改App的类

public class App {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext ctx = new

                AnnotationConfigApplicationContext(SpringConfig.class);
        BookDao bookDao1 = ctx.getBean(BookDao.class);
        BookDao bookDao2 = ctx.getBean(BookDao.class);
        System.out.println(bookDao1);
        System.out.println(bookDao2);
        ctx.close(); //关闭容器

   }
}
           

注意:@PostConstruct和@PreDestroy注解如果找不到,需要导入下面的jar包

<dependency>

  <groupId>javax.annotation</groupId>

         <artifactId>javax.annotation-api</artifactId>

         <version>1.3.2</version>
</dependency>
           
IOC容器中的核心容器

注解开发依赖注入

Spring为了使用注解简化开发,并没有提供构造函数注入、setter注入对应的注解,只提供了自动装 配的注解实现。

注解实现按照类型注入

(1) 在BookServiceImpl类的bookDao属性上添加@Autowired注解

@Service

public class BookServiceImpl implements BookService {
    @Autowired

    private BookDao bookDao;
    

         // public void setBookDao(BookDao bookDao) {
//       this.bookDao = bookDao;
//   }

            public void save() {
        System.out.println("book service save ...");
        bookDao.save();
   }
}
           

注意:

  • @Autowired可以写在属性上,也可也写在setter方法上,最简单的处理方式是写在属性上并将setter方法删除掉
  • 为什么setter方法可以删除呢?
  • 自动装配基于反射设计创建对象并通过暴力反射为私有属性进行设值
  • 普通反射只能获取public修饰的内容
  • 暴力反射除了获取public修饰的内容还可以获取private修改的内容
  • 所以此处无需提供setter方法

(2)@Autowired是按照类型注入,那么对应BookDao接口如果有多个实现类,比如添加

BookDaoImpl2

按照类型注入就无法区分到底注入哪个对象,解决方案:按照名称注入

先给两个Dao类分别起个名称

@Repository("bookDao")

public class BookDaoImpl implements BookDao {
    public void save() {
        System.out.println("book dao save ..." );
   }
}

@Repository("bookDao2")

public class BookDaoImpl2 implements BookDao {
    public void save() {
        System.out.println("book dao save ...2" );
   }
}           

注解实现按照名称注入

当根据类型在容器中找到多个bean,注入参数的属性名又和容器中bean的名称不一致,这个时候该如 何解决,就需要使用到@Qualifier来指定注入哪个名称的bean对象。

public class BookServiceImpl implements BookService {
    @Autowired

    @Qualifier("bookDao1")
    private BookDao bookDao;
    
            public void save() {
        System.out.println("book service save ...");
        bookDao.save();
   }
}           

@Qualifier注解后的值就是需要注入的bean的名称。

注意:@Qualifier不能独立使用,必须和@Autowired一起使用

简单数据类型注入

引用类型看完,简单类型注入就比较容易懂了。简单类型注入的是基本数据类型或者字符串类型,下 面在BookDaoImpl类中添加一个name属性,用其进行简单类型注入

@Repository("bookDao")

public class BookDaoImpl implements BookDao {
    private String name;
    public void save() {
        System.out.println("book dao save ..." + name);
   }
}
           

数据类型换了,对应的注解也要跟着换,这次使用@Value注解,将值写入注解的参数中就行了

@Repository("bookDao")

public class BookDaoImpl implements BookDao {
    @Value("itheima")
    private String name;
    public void save() {
        System.out.println("book dao save ..." + name);
   }
}
           

注解读取properties配置文件

@Value一般会被用在从properties配置文件中读取内容进行使用,具体如何实现?

步骤1:resource下准备properties文件

jdbc.properties

name=itheima888           

步骤2: 使用注解加载properties配置文件

在配置类上添加@PropertySource注解

@Configuration
@ComponentScan("com.itheima")

@PropertySource("jdbc.properties")

public class SpringConfig {
}
           

步骤3:使用@Value读取配置文件中的内容

@Repository("bookDao")

public class BookDaoImpl implements BookDao {
    @Value("${name}")
    private String name;
    public void save() {
        System.out.println("book dao save ..." + name);
   }
}           

注意:

如果读取的properties配置文件有多个,可以使用@PropertySource的属性来指定多个

@PropertySource({"jdbc.properties","xxx.properties"})
           

@PropertySource注解属性中可以把classpath:加上,代表从当前项目的根路径找文件

@PropertySource({"classpath:jdbc.properties"})           

注解开发管理第三方bean

在上述环境中完成对Druid数据源的管理,具体的实现步骤为:

步骤1:导入对应的jar包

<dependency>

    <groupId>com.alibaba</groupId>

           <artifactId>druid</artifactId>

           <version>1.1.16</version>
</dependency>           

步骤2:在配置类中添加一个方法

注意该方法的返回值就是要创建的Bean对象类型

@Configuration

public class SpringConfig {
    public DataSource dataSource(){
        DruidDataSource ds = new DruidDataSource();
        ds.setDriverClassName("com.mysql.jdbc.Driver");
        ds.setUrl("jdbc:mysql://localhost:3306/spring_db");
        ds.setUsername("root");
        ds.setPassword("root");
        return ds;
   }
}
           

步骤3:在方法上添加@Bean注解

@Bean注解的作用是将方法的返回值制作为Spring管理的一个bean对象

@Configuration

public class SpringConfig {
    @Bean

    public DataSource dataSource(){
        DruidDataSource ds = new DruidDataSource();
        ds.setDriverClassName("com.mysql.jdbc.Driver");
        ds.setUrl("jdbc:mysql://localhost:3306/spring_db");
        ds.setUsername("root");
        ds.setPassword("root");
        return ds;
   }
}           

注意:不能使用DataSource ds = new DruidDataSource()

因为DataSource接口中没有对应的setter方法来设置属性。

步骤4:从IOC容器中获取对象并打印

public class App {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext ctx = new

                AnnotationConfigApplicationContext(SpringConfig.class);
        DataSource dataSource = ctx.getBean(DataSource.class);
        System.out.println(dataSource);
   }
}

           

至此使用@Bean来管理第三方bean的案例就已经完成。

如果有多个bean要被Spring管理,直接在配置类中多些几个方法,方法上添加@Bean注解即可。

引入外部配置类

如果把所有的第三方bean都配置到Spring的配置类SpringConfig中,虽然可以,但是不利于代码阅 读和分类管理,所有我们就想能不能按照类别将这些bean配置到不同的配置类中?

对于数据源的bean,我们新建一个JdbcConfig配置类,并把数据源配置到该类下。

public class JdbcConfig {
    @Bean

    public DataSource dataSource(){
        DruidDataSource ds = new DruidDataSource();
        ds.setDriverClassName("com.mysql.jdbc.Driver");
        ds.setUrl("jdbc:mysql://localhost:3306/spring_db");
        ds.setUsername("root");
        ds.setPassword("root");
        return ds;
   }
}
           

现在的问题是,这个配置类如何能被Spring配置类加载到,并创建DataSource对象在IOC容器中?

针对这个问题,有两个解决方案:

使用包扫描引入

步骤1:在Spring的配置类上添加包扫描

@Configuration
@ComponentScan("com.itheima.config")

public class SpringConfig {

}
           

步骤2:在JdbcConfig上添加配置注解

JdbcConfig类要放入到com.itheima.config包下,需要被Spring的配置类扫描到即可

@Configuration

public class JdbcConfig {
    @Bean

    public DataSource dataSource(){
        DruidDataSource ds = new DruidDataSource();
        ds.setDriverClassName("com.mysql.jdbc.Driver");
        ds.setUrl("jdbc:mysql://localhost:3306/spring_db");
        ds.setUsername("root");
        ds.setPassword("root");
        return ds;
   }
}
           

步骤3:运行程序

依然能获取到bean对象并打印控制台。 这种方式虽然能够扫描到,但是不能很快的知晓都引入了哪些配置类,所有这种方式不推荐使用。

使用@Import引入

方案一实现起来有点小复杂,Spring早就想到了这一点,于是又给我们提供了第二种方案。 这种方案可以不用加@Configuration注解,但是必须在Spring配置类上使用@Import注解手动引入 需要加载的配置类

步骤1:去除JdbcConfig类上的注解

public class JdbcConfig {
    @Bean

    public DataSource dataSource(){
        DruidDataSource ds = new DruidDataSource();
        ds.setDriverClassName("com.mysql.jdbc.Driver");
        ds.setUrl("jdbc:mysql://localhost:3306/spring_db");
        ds.setUsername("root");
        ds.setPassword("root");
        return ds;
   }
}           

步骤2:在Spring配置类中引入

@Configuration

//@ComponentScan("com.itheima.config")

@Import({JdbcConfig.class})

public class SpringConfig {

}
           

注意:

  • 扫描注解可以移除
  • @Import参数需要的是一个数组,可以引入多个配置类。
  • @Import注解在配置类中只能写一次,下面的方式是不允许的
@Configuration

//@ComponentScan("com.itheima.config")

@Import(JdbcConfig.class)

@Import(Xxx.class)

public class SpringConfig {

}