• 【Spring总结】基于配置的方式来写Spring


    本篇文章是对这两天所学的内容做一个总结,涵盖我这两天写的所有笔记:

    1. 【Spring】 Spring中的IoC(控制反转)
    2. 【Spring】Spring中的DI(依赖注入)Dependence Import
    3. 【Spring】bean的基础配置
    4. 【Spring】bean的实例化
    5. 【Spring】bean的生命周期
    6. 【Spring】依赖注入方式,DI的方式
    7. 【Spring】使用三方包进行数据源对象(数据库)管理
    8. 【Spring】加载properties文件
    9. 【Spring】IoC容器的一些总结与补充

    文章目录

    1. 控制反转与依赖注入的定义

    为什么需要控制反转?

    我们在传统写业务层的时候,需要给业务层中的属性通过new操作生成对象。

    public class BookServiceImpl implements BookService{
    
    	private BookDao bookDao = new BookDaoImpl();
    	
    	public void save(){
    		bookDao.save()
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    假如某一天我不再希望使用BookDaoImpl这个实现类,而是希望使用BookDaoImpl2这个实现类,我们就需要再次修改代码,重新修改BookServiceImplbookDao属性的初始化。这样子的修改方式就不是太合理,耦合度较高。
    如果我们可以将这种主动式产生对象转换为由外部指定产生对象,就可以减少这种修改,降低耦合度,通俗的说,就是将对象的创建控制权由程序转移到外部,这就是控制反转(IoC, Inversion of Control)

    Spring中怎么做控制反转?

    Spring实现了IoC,此处介绍基于配置来写IoC:

    第一步:pom.xml引入相关依赖

    引入相关依赖,刷新maven:

    <dependency>
    	<groupId>org.springframeworkgroupId>
    	<artifactId>spring-contextartifactId>
    dependency>
    
    • 1
    • 2
    • 3
    • 4

    第二步:在Resource文件下创建Spring Config File(也就是配置IoC容器的内容),并配置bean

    
    <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.example.demo231116.dao.impl.BookDaoImpl" />
    
        <bean id="bookService" class="com.example.demo231116.service.impl.BookServiceImpl" />
    beans>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    第三步:在代码中调用IoC容器,并获取具体的bean

    public class Demo231116Application2 {
        public static void main(String[] args) {
            // 3. 获取IoC容器
            ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
            // 4. 获取bean
            BookService bookService = (BookService) ctx.getBean("bookService");
            bookService.save();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    为什么需要依赖注入?

    在上面给出的BookServiceImpl.java代码中,有一个bookDao的属性,我们定义为:

    private BookDao bookDao = new BookDaoImpl();
    
    • 1

    我们想要通过IoC去管理bean,为了真正实现解耦,我们不再保留new形式创建的Dao对象,这时候我们就需要使用依赖注入来完成这一点

    如何进行依赖注入?

    思考两个问题:

    1. Service中需要的Dao对象如何进入到Service中?(提供方法,使得我们可以修改Service中的Dao对象)
    2. ServiceDao间的关系如何描述?(通过配置的形式来描述)
      基于以上两个问题及答案,我们总结了不同的注入方式

    Setter注入引用类型和简单类型

    第一步:删除ServiceImpl中的new创建方式,建立set方法,使得可以对ServiceImpl中的属性做修改
    public class BookServiceImpl implements BookService {
    	// 删除原本的new方法,给bookDao设置一个setter方法
        public void setBookDao(BookDao bookDao) {
            this.bookDao = bookDao;
        }
        public void setBookName(String bookName) {
            this.bookName = bookName;
        }
    
        private BookDao bookDao;
    	private String bookName;
    
        public void save(){
            System.out.println("book service save... bookName:" + this.bookName);
            bookDao.save();
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    第二步:在配置文件中写明使用什么类别给bookDao属性做注入

    在配置文件中,在bookService的bean内部配置property,该标签表示配置当前bean的属性,其中name表示配置哪一个具体属性,ref表示引用类型参照的具体bean,value则是简单类型的对应值

    
    <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.example.demo231116.dao.impl.BookDaoImpl" />
    
        <bean id="bookService" class="com.example.demo231116.service.impl.BookServiceImpl">
            <property name="bookDao" ref="bookDao"/>
            <property name="bookName" value="bkName!!!" />
        bean>
    beans>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    第三步:测试

    运行如下代码:

    public class Demo231116Application2 {
        public static void main(String[] args) {
            // 获取IoC容器
            ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
            
            BookService bookService = (BookService) ctx.getBean("bookService");
            bookService.save();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    结果为:

    book service save... bookName:bkName!!!
    book dao save...
    
    • 1
    • 2

    构造器注入简单类型和引用类型

    第一步:删除ServiceImpl中的new创建方式,无需set方法,通过构造方法对ServiceImpl中的属性做修改
    public class BookServiceImpl implements BookService {
    
        private BookDao bookDao;
    	private String bookName;
    
    	// 删除原本的new方法,给bookDao设置一个setter方法
    	public BookServiceImpl(BookDao bookDao, String bookName){
    		this.bookDao = bookDao;
    		this.bookName = bookName;
    	}
    
        public void save(){
            System.out.println("book service save... bookName:" + this.bookName);
            bookDao.save();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    第二步:在配置文件中写明使用什么类别给构造方法

    在配置文件中,在bookService的bean内部配置constructor-arg,该标签表示构造方法的参数,其中name表示配置哪一个具体属性,ref表示引用类型参照的具体bean,value则是简单类型的对应值

    
    <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.example.demo231116.dao.impl.BookDaoImpl" />
    
        <bean id="bookService" class="com.example.demo231116.service.impl.BookServiceImpl">
            <constructor-arg name="bookDao" ref="bookDao"/>
            <constructor-arg name="bookName" value="bkName!!!" />
        bean>
    beans>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    第三步:测试

    运行如下代码:

    public class Demo231116Application2 {
        public static void main(String[] args) {
            // 获取IoC容器
            ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
            
            BookService bookService = (BookService) ctx.getBean("bookService");
            bookService.save();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    结果为:

    book service save... bookName:bkName!!!
    book dao save...
    
    • 1
    • 2

    构造器注入的参数适配(了解,通常不用这些方法)

    • 配置中使用constructor-arg的标签type属性按形参类型注入(为了解耦,避免配置和name耦合)
    
    <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.example.demo231116.dao.impl.BookDaoImpl" />
    
        <bean id="bookService" class="com.example.demo231116.service.impl.BookServiceImpl">
            <constructor-arg type="com.example.demo231116.dao.bookDao" ref="bookDao"/>
            <constructor-arg type="java.lang.String" value="bkName!!!" />
        bean>
    beans>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 配置中使用constructor-arg的标签index属性按形参位置注入(为了解决有同样类型的参数的问题)
    
    <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.example.demo231116.dao.impl.BookDaoImpl" />
    
        <bean id="bookService" class="com.example.demo231116.service.impl.BookServiceImpl">
            <constructor-arg index="0" ref="bookDao"/>
            <constructor-arg index="1" value="bkName!!!" />
        bean>
    beans>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    依赖注入方式选择

    在这里插入图片描述
    建议使用setter注入
    第三方技术根据情况选择


    依赖自动装配,通过autowire属性指定类型

    根据bean所依赖的资源在容器中自动查找并注入到bean的过程为自动装配
    自动装配的方式通过autowire指定,有四种类型:

    • 按类型(常用)
    • 按名称
    • 按构造方法
    • 不启用自动装配

    注意,想要让IoC容器实现自动装配,必须给这些属性配置setter方法!

    按类型注入:autowire="byType"

    在这里插入图片描述
    此时配置BookServiceImpl的代码应该这么写:

    
    <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.example.demo231116.dao.impl.BookDaoImpl" />
        <bean id="userDao" class="com.example.demo231116.dao.impl.UserDaoImpl" />
    
        <bean id="bookService" class="com.example.demo231116.service.impl.BookServiceImpl" autowire="byType" />
    beans>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    bean的类型指向必须唯一!,推荐使用。
    假如配置了两个指向同一类型,id不同的bean,执行报错会提示找到了两个bean,不知道匹配哪一个。

    按名称注入:autowire="byName"

    在这里插入图片描述
    此时配置BookServiceImpl的代码应该这么写:

    
    <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.example.demo231116.dao.impl.BookDaoImpl" />
        <bean id="userDao" class="com.example.demo231116.dao.impl.UserDaoImpl" />
    
        <bean id="bookService" class="com.example.demo231116.service.impl.BookServiceImpl" autowire="byName" />
    beans>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    属性的名称要能够在配置的bean中的id对应的上。此方式因变量名与配置耦合,不推荐使用。

    自动装配注意事项
    • 自动装配用于引用类型依赖注入,不能对简单类型操作(因为简单类型你可以有很多不同的值,除非一个一个写)
    • 自动装配优先级低于setter注入和构造器注入,同时出现时自动装配无效

    集合注入

    关于array、list、set、map、properties的注入
    假如BookDaoImpl.java代码如下:

    package com.example.demo.dao.impl;
    
    import com.example.demo.dao.BookDao;
    
    import java.util.*;
    
    public class BookDaoImpl implements BookDao {
    
        private int[] array;
        private List<String> list;
        private Set<String> set;
        private Map<String, String> map;
        private Properties properties;
    
        @Override
        public void save() {
            System.out.println("book dao save...");
    
            System.out.println("遍历数组:" + Arrays.toString(array));
    
            System.out.println("遍历List:" + list);
    
            System.out.println("遍历set:" + set);
    
            System.out.println("遍历Map:" + map);
    
            System.out.println("遍历properties:" + properties);
        }
    
        public void setProperties(Properties properties) {
            this.properties = properties;
        }
    
        public void setMap(Map<String, String> map) {
            this.map = map;
        }
    
        public void setSet(Set<String> set) {
            this.set = set;
        }
    
        public void setList(List<String> list) {
            this.list = list;
        }
    
        public void setArray(int[] array) {
            this.array = array;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    在集合中注入简单类型——value

    配置文件需要如下配置:

    <bean id="bookDao" class="com.example.demo.dao.impl.BookDaoImpl">
        <property name="array">
            <array>
                <value>100value>
                <value>200value>
                <value>300value>
            array>
        property>
        <property name="list">
            <list>
                <value>avalue>
                <value>bvalue>
                <value>cvalue>
            list>
        property>
        <property name="set">
            <set>
                <value>cvalue>
                <value>cvalue>
                <value>dvalue>
            set>
        property>
        <property name="map">
            <map>
                <entry key="country" value="china" />
                <entry key="province" value="guangdong" />
                <entry key="city" value="shenzhen" />
            map>
        property>
        <property name="properties">
            <props>
                <prop key="country">chinaprop>
                <prop key="province">henanprop>
                <prop key="city">kaifengprop>
            props>
        property>
    bean>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    其中,set属性自动过滤元素,如果执行以下java代码:

    public class Demo231116Application2 {
        public static void main(String[] args) {
            // 获取IoC容器
            ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
            
            BookService bookService = (BookService) ctx.getBean("bookService");
            bookService.save();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    则结果如下:

    book service save...
    book dao save...
    遍历数组:[100, 200, 300]
    遍历List:[a, b, c]
    遍历set:[c, d]
    遍历Map:{country=china, province=guangdong, city=shenzhen}
    遍历properties:{country=china, province=henan, city=kaifeng}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    在集合中注入引用类型——ref

    只需去掉简单类型的value属性,使用ref属性

    <bean id="bookDao" class="com.example.demo.dao.impl.BookDaoImpl">
        <property name="array">
            <array>
                <ref bean="beanId" />
                <ref bean="beanId" />
                <ref bean="beanId" />
            array>
        property>
    bean>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    控制反转与依赖注入的关系

    这部分我也思考了一下,认为网上有句话说的很对:简单地说,控制反转是一种设计思想,而依赖注入是控制反转思想的一种实现方式
    我认为,实际上控制反转就是我们将已有的类别放置到IoC容器中,便于让外部进行处理。而依赖注入是真正将具体的类别给属性完成注入的方式。

    2. bean的配置

    bean的别名配置——name属性配置

    通过name属性进行name的别名配置:

    
    <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" name="dao" class="com.example.demo231116.dao.impl.BookDaoImpl" />
        
    beans>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    这样,当我们在主代码里调用ctx.getBean("bookDao")ctx.getBean("dao"),起到的效果是相同的:

    package com.example.demo231116;
    
    import com.example.demo231116.dao.BookDao;
    import com.example.demo231116.service.BookService;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    public class Demo231116Application2 {
        public static void main(String[] args) {
            // 获取IoC容器
            ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
            
            BookDao bookDao = (BookDao) ctx.getBean("bookDao");
            bookDao = (BookDao) ctx.getBean("dao");
            bookDao.save();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    bean的单例多例——scope属性配置

    若保持别名配置里那样的bean配置,我们生成出来的bean是单例的,当我们执行以下代码:

    BookDao bookDao = (BookDao) ctx.getBean("dao");
    BookDao bookDao1 = (BookDao) ctx.getBean("dao");
    System.out.println(bookDao);
    System.out.println(bookDao1);
    
    • 1
    • 2
    • 3
    • 4

    会得到结果:

    com.example.demo231116.dao.impl.BookDaoImpl@309e345f
    com.example.demo231116.dao.impl.BookDaoImpl@309e345f
    
    • 1
    • 2

    两个对象出自同一个实例,但如果我们不希望它是以单例形式创建,而是以多例形式创建的时候,我们需要配置scope属性:

    
    <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" name="dao" class="com.example.demo231116.dao.impl.BookDaoImpl" scope="prototype" />
        
    beans>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    scope有两个属性:singleton单例(默认),prototype非单例
    配置scope属性后再执行上面的代码,会得到结果:

    com.example.demo231116.dao.impl.BookDaoImpl@309e345f
    com.example.demo231116.dao.impl.BookDaoImpl@56a6d5a6
    
    • 1
    • 2

    两个对象不出自同一个实例

    bean为什么默认为单例?

    如果每创建一个对象,都是不同的实例,那么对内存的消耗会很大

    bean的作用范围说明

    适合交给容器管理的bean:表现层对象、业务层对象、数据层对象、工具对象
    不适合交给容器管理的bean:封装实体的域对象

    bean的实例化

    构造方法

    当我们在Spring Config文件中配置:

    <bean id="bookDao" name="dao" class="com.example.demo231116.dao.impl.BookDaoImpl" />
    
    • 1

    这种bean的实例化默认调用无参构造方法,对于写有参构造,对有参构造中参数的注入则是上面依赖注入的部分

    静态工厂

    假如写了一个BookDao工厂BookDaoFactory.java

    package com.example.demo231116.factory;
    
    import com.example.demo231116.dao.BookDao;
    import com.example.demo231116.dao.impl.BookDaoImpl;
    
    public class BookDaoFactory {
        public static BookDao getBookDao(){
            System.out.println("Factory method....");
            return new BookDaoImpl();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    我们想让bean通过调用这个工厂方法返回里面的BookDaoImpl,则需要如下配置:

    <bean id="bookDaoFactory" class="com.example.demo231116.factory.BookDaoFactory" factory-method="getBookDao" />
    
    • 1

    id属性和class属性与之前差异不大,分别为bean名称和bean类别,而factory-method指定具体地工厂方法

    实例工厂

    注意:实例工厂与静态工厂的区别是,实例工厂中的方法不是静态方法!
    静态方法和非静态方法的区别是:

    • 静态方法可以在不建立该类对象的情况下,通过类名.方法进行调用
    • 非静态方法需要建立该类对象后,通过对象名.方法进行调用

    详细可以看第4个笔记中的实验

    假如写了一个BookDao工厂BookDaoFactory.java

    package com.example.demo231116.factory;
    
    import com.example.demo231116.dao.BookDao;
    import com.example.demo231116.dao.impl.BookDaoImpl;
    
    public class BookDaoFactory {
        public BookDao getBookDao(){
            System.out.println("Factory method....");
            return new BookDaoImpl();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    在配置文件中需要如下写:

    <bean id="bookDaoFactory2" class="com.example.demo231116.factory.BookDaoFactory" />
    <bean id="bd" factory-method="getBookDaoUnstatic" factory-bean="bookDaoFactory2"  />
    
    • 1
    • 2

    即先建立出工厂类的bean,再建立下面这个使用该工厂方法的bean。factory-method指定其中具体的工厂方法,factory-bean指定工厂方法所属的父类,在最后调用的时候,直接调用bd就可以完成初始化:

    BookDao bookDao3 = (BookDao) ctx.getBean("bd");
    System.out.println(bookDao3);
    
    • 1
    • 2

    在这种方法里,实际上我们创建出的bookFactory2这个bean没有被用到,所以还有如下的方法FactoryBean

    FactoryBean

    直接写一个工厂方法,implement FactoryBean。FactoryBean是一个泛型方法,指定其中的类型,实现其方法:
    第一个getObject()返回具体地对象
    第二个getObjectType()返回对象的类型

    package com.example.demo231116.factory;
    
    import com.example.demo231116.dao.BookDao;
    import com.example.demo231116.dao.impl.BookDaoImpl;
    import org.springframework.beans.factory.FactoryBean;
    
    public class BookDaoFactoryBean implements FactoryBean<BookDao>{
        @Override
        public BookDao getObject() throws Exception {
            return new BookDaoImpl();
        }
    
        @Override
        public Class<?> getObjectType() {
            return BookDao.class;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    如此,在bean配置中,只需要像原本普通的一行即可:

    <bean id="bookDaoFactoryMethod" class="com.example.demo231116.factory.BookDaoFactoryBean" />
    
    • 1

    注意,当我们执行以下代码后,会发现打印出来的结果相同,说明这是单例模式创建的。

    BookDao bookDao4 = (BookDao) ctx.getBean("bookDaoFactoryMethod");
    BookDao bookDao5 = (BookDao) ctx.getBean("bookDaoFactoryMethod");
    System.out.println(bookDao4);
    System.out.println(bookDao5);
    
    • 1
    • 2
    • 3
    • 4

    如果我们想要该工厂方法返回的不是单例模式,而是不同的实例,则我们需要补充FactoryBean中的isSingleton()方法,当这个方法设置为True时,就是用单例模式创建的对象,如果这个方法返回为False,就不会使用单例模式,每一次构造都会创建出新的对象:

    @Override
    public boolean isSingleton() {
        return true;
    }
    
    • 1
    • 2
    • 3
    • 4

    bean的生命周期

    假如我们希望在建立bean时候执行一些资源的初始化,在bean销毁之前执行一些资源的销毁,我们就会需要考虑到bean的声明周期,通过以下方式进行生命周期的控制。

    在类中提供生命周期控制方法,并在配置文件中配置init-method&destroy-method(配置)

    定义实现类如下:

    package com.example.demo231116.dao.impl;
    
    import com.example.demo231116.dao.BookDao;
    
    public class BookDaoImpl implements BookDao {
        public void save(){
            System.out.println("book dao save...");
        }
    
        public void init(){
            System.out.println("book dao init...");
        }
    
        public void destroy(){
            System.out.println("book dao destroy...");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    配置时,我们只需要指定具体的初始化方法init-method和销毁方法destroy-method即可:

    <bean id="bookDaoCycle" class="com.example.demo231116.dao.impl.BookDaoImpl" init-method="init" destroy-method="destroy" />
    
    • 1

    这样,当我们在主函数中执行:

    // IoC容器
    ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
    
    BookDao bookDao = (BookDao) ctx.getBean("bookDaoCycle");
    System.out.println(bookDao);
    
    • 1
    • 2
    • 3
    • 4
    • 5

    结果是:

    book dao init...
    com.example.demo231116.dao.impl.BookDaoImpl@5dd6264
    
    • 1
    • 2

    这时候的疑问可能是,为什么destroy方法没有被执行?
    答案是:在程序执行结束之后,IoC容器还没有执行关闭操作,java虚拟机就已经强行关闭了。那么应该如何在java虚拟机关闭之前执行IoC容器关闭呢?如下有两种方法

    通过ctx.close()执行IoC容器关闭

    ApplicationContext并没有close方法,ApplicationContext下的一个接口ClassPathXmlApplicationContext才有定义close方法,所以这里想要使用close方法,需要修改IoC容器定义,然后在末尾调用ctx.close()

    // IoC容器
    ClassPathXmlApplicationContextctx = new ClassPathXmlApplicationContext("applicationContext.xml");
    
    BookDao bookDao = (BookDao) ctx.getBean("bookDaoCycle");
    System.out.println(bookDao);
    
    ctx.close()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    输出结果为:

    book dao init...
    com.example.demo231116.dao.impl.BookDaoImpl@5dd6264
    book dao destroy...
    
    • 1
    • 2
    • 3

    但是如果是这样的话,ctx.close()只能在程序的末尾写,因为在开头定义结束就写的话,这个IoC容器就被销毁了,也不可能执行一些getBean的操作,还有一个方法是通过注册钩子关闭IoC容器

    通过注册关闭钩子执行IoC容器关闭

    注册一个关闭钩子,在不用强行关闭IoC容器的情况下,设置在java虚拟机关闭之前让程序执行销毁的方法:

    ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
    ctx.registerShutdownHook();
    
    BookDao bookDao = (BookDao) ctx.getBean("bookDaoCycle");
    System.out.println(bookDao);
    
    • 1
    • 2
    • 3
    • 4
    • 5

    这样就不再需要强硬地执行ctx.close()方法了

    实现接口来做和init和destroy(接口)

    只需要在bean类下多实现InitializingBeanDisposableBean这两个接口,并实现其中的afterPropertiesSetdestroy方法:

    package com.example.demo231116.dao.impl;
    
    import com.example.demo231116.dao.BookDao;
    import org.springframework.beans.factory.DisposableBean;
    import org.springframework.beans.factory.InitializingBean;
    
    public class BookDaoImpl implements BookDao, InitializingBean, DisposableBean {
        public void save(){
            System.out.println("book dao save...");
        }
    
        @Override
        public void destroy() throws Exception {
            System.out.println("接口destroy");
        }
    
        @Override
        public void afterPropertiesSet() throws Exception {
            System.out.println("接口init");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    然后配置bean只需要简单地配置如下:

    <bean id="bookDaoCycle" class="com.example.demo231116.dao.impl.BookDaoImpl" />
    
    • 1

    这个afterPropertiesSet的init方法,是在先执行属性设置后再执行init方法

    bean的生命周期执行顺序

    • 初始化容器:
      创建对象(内存分配)
      执行构造方法
      执行属性注入(setter注入)
      执行bean的初始化方法
    • 使用bean
      执行业务操作
    • 关闭/销毁容器
      执行bean的销毁方法

    3. 使用第三方包进行数据源的管理(例子:使用com.alibaba.druid进行数据库连接)

    第一步:在pom.xml中导入坐标

    <dependency>
    	<groupId>com.alibabagroupId>
    	<artifactId>druidartifactId>
    	<version>1.1.21version>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    第二步:在bean中配置连接

    在这里,首先应该查看这个Druid包给我们提供了一些什么方法。
    先查看构造方法能否让我们通过构造注入写入一些配置信息,但在具体查看后发现是没有的(详细可见笔记7)。
    但观察到可以通过setter注入来写一些配置信息,所以这里就使用setter注入:

    
    <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="url" value="jdbc:mysql://localhost:3306/ecommercedb"/>
            <property name="username" value="root"/>
            <property name="password" value="123456"/>
            <property name="driverClassName" value="com.mysql.jdbc.Driver" />
        bean>
    beans>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    4. 在配置文件中读取properties文件中的属性

    第一步:建立jdbc.properties文件,内容如下

    jdbc.url=jdbc:mysql://localhost:3306/ecommercedb
    jdbc.username=root
    jdbc.password=123456
    jdbc.driverClassName=com.mysql.jdbc.Driver
    
    • 1
    • 2
    • 3
    • 4

    第二步:开启context命名空间

    我理解需要开辟新的命名空间,是读取非工程文件中的内容,而是读取外部文件中的内容

    第三步:使用context命名空间加载指定properties文件

    在这一步中需要通过location写清楚加载properties文件的位置

    第四步:使用${}读取加载的属性值

    第二——第四步具体代码如下,最终只需要通过${}读取加载的属性值即可:

    
    <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">
    
    
    
        <context:property-placeholder location="jdbc.properties"/>
    
        <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
            <property name="url" value="${jdbc.url}"/>
            <property name="username" value="${jdbc.username}"/>
            <property name="password" value="${jdbc.password}"/>
            <property name="driverClassName" value="${jdbc.url}" />
        bean>
    beans>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    其他问题

    1. 假如在配置文件中写的是username=root,而不是jdbc.username=root,则可能使用${username}得到的结果并不会是root
      是因为系统环境变量里面有和properties里面定义的变量重名,则properties里面的变量不会被加载,也就是说系统环境变量的优先级会比properties里面的变量优先级高。
      为了使用properties里面面的变量,而不是系统环境变量,我们可以对xml配置文件,让系统环境变量不被使用:
    <context:property-placeholder location="jdbc.properties" system-properties-mode="NEVER"/>
    
    • 1
    1. 加载多个properties文件可以用逗号分隔
    <context:property-placeholder location="jdbc.properties, msg.properties"/>
    
    • 1
    1. 加载所有properties文件可以用正则*.properties
    <context:property-placeholder location="*.properties"/>
    
    • 1
    1. 以上写法不够标准,标准的是classpath:*.properties
    <context:property-placeholder location="classpath:*.properties"/>
    
    • 1
    1. 如果不止要从工程中加载properties文件,还要从jar包等中加载,则写classpath*:*properties
    <context:property-placeholder location="classpath*:*.properties"/>
    
    • 1

    第4、5种根据不同的需求来写

    5. 关于IoC容器其他内容的总结与补充

    创建容器的两种方式

    相对路径导入

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

    绝对路径导入

    ApplicationContext ctx = new FileSystemXmlApplicationContext("E:\\Code\\Java\\SpringStudy\\project1\\src\\main\\resources\\applicationContext.xml");
    
    • 1

    获取Bean的三种方式

    获取后强转类型

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

    在获取时指定类型

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

    通过类型获取Bean

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

    容器类层次结构图

    在这里插入图片描述

    BeanFactory

    这是最早的加载IoC容器的方法,使用BeanFactory的方法如下:

    Resource resource = new ClassPathResource("applicationContext.xml");
    BeanFactory bf = new XmlBeanFactory(resource);
    BookDao bookDao = bf.getBean(BookDao.class);
    bookDao.save();
    
    • 1
    • 2
    • 3
    • 4

    BeanFactory与我们现在ApplicationContext的区别在于:
    BeanFactory是延迟加载bean,ApplicationContext是立即加载bean
    即前者在没有getBean的时候是不会提前执行类的构造方法的,而Application就算没有getBean也会执行构造方法
    如果想在ApplicationContext上实现延迟加载bean,只需要加参数:lazy-init="true"

    <bean id="bookDao" class="com.example.project1.dao.impl.BookDaoImpl" lazy-init="true"/>
    
    • 1

    6. 复习内容

    在这里插入图片描述
    在这里插入图片描述

  • 相关阅读:
    第九章 泛型编程
    软件产品测试怎么做
    如何解决PHP base64编码后解码乱码的问题
    10 个 效果不错的值得收藏的 css 代码片段
    云服务器购买流程
    【JavaSE】接口
    Vscode常用插件
    集线器与交换机的区别
    Java面试:MySQL间隙锁是什么鬼?
    opencv控制鼠标事件
  • 原文地址:https://blog.csdn.net/passer__jw767/article/details/134481268