• Java开发工程师(Web方向)


    第2章.IoC容器

    IoC容器概述

    abstract: 介绍IoC和bean的用处和使用

    IoC容器处于整个Spring框架中比较核心的位置:Core Container: Beans, Core, Context, SpEL

    为上层AOP/Aspects/Instrumentation/Messaging提供支持

    IoC容器的用途:

    创建对象的依赖,最后组装成所需的业务对象

    容器通过业务对象和配置(application-context.xml; xxxController.java)-->生成可用对象

    在Spring场景下,把对象A/B/C都称作Bean

    从代码角度看,一个ApplicationContext (org.springframework.context包下,属于spring-context依赖中)对象就是一个IoC容器

    初始化IoC容器:

    在web项目的web.xml中,用

    <context-param>

    <param-name>contextConfigLocation</param-name>

    <param-value>classpath:application-context.xml</param-value>

    </context-param>

    ApplicationContext context = new ClassPathXmlApplicationContext("application-context.xml");

    (文件系统中:ApplicationContext context = new FileSystemXmlApplicationContext("/home/user/conf/application-context.xml");)

    来初始化。

    定义一个Bean:

    在application-context.xml中

    <beans>

    ...

    <bean id="screwDriver" class="package_url"></bean>

    </beans>

    使用Bean:

    1. 初始化容器:ApplicationContext context = new ClassPathXmlApplicationContext("application-context.xml");

    2. 通过getBean()获取对象:ScrewDriver screwDriver = context.getBean("screwDriver", ScrewDriver.class);

    3. 使用对象:screwDriver.use();

    实例演练: 

    1. New Maven Project -> Create a simple project (skip archetype selection)

    GroupId: com.netease.nanodegree

    ArtifactId: spring-container

    2. 将所需依赖加入pom.xml

    <dependencies>
         <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>4.2.1.RELEASE</version>
        </dependency>
    </dependencies> 

    发现依赖的jar包自动被加入

    3. 在src/main/resources/下新建application-context.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"
        xmlns:p="http://www.springframework.org/schema/p"
        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">
        
    </beans>

    此时,还未定义任何bean,因此还不能从容器中获取任何bean

    4. 在src/main/java/下新建类ScrewDriver.java

    package: com.netease.nanodegree;

    package com.netease.nanodegree;
    
    public class ScrewDriver {
        public void ues () {
            System.out.println("Use screw driver");
        }
    }

    5. 此时有了bean,在application-context.xml中加入相关bean的定义

    <bean id = "screwDriver" class = "com.netease.nanodegree.ScrewDriver"></bean>

    6. 在src/main/java/com.netease.nanodegree下新建测试类TestContainer.java

    7. Run as -> Java Application

    Bean的作用域:Bean的有效范围

    singleton:与设计模式中的singleton有点像

    保证对象在整个容器中最多只能有一个单例,也保证在整个生命周期中只会创建一个实例

    上例中bean定义的默认作用域就是singleton

    也可以显式定义<bean id="screwDriver" class="com.netease.nanodegree.ScrewDriver" scope="singleton"></bean>

    例子Test:

    ScrewDriver.java 

    public class ScrewDriver {    
        private String color = "red";
        public void use () {
            //System.out.println("Use screw driver");
            System.out.println("Use " + color + " screw driver");
        }
        
        public void setColor (String color) {
            this.color = color;
        }
    }

    TestContainer.java

    public class TestContainer {
        public static void main(String[] args) {
            ApplicationContext context = new ClassPathXmlApplicationContext("application-context.xml");
    
            ScrewDriver screwDriver = context.getBean("screwDriver", ScrewDriver.class);
            screwDriver.setColor("green");
            screwDriver.use();
            
            ScrewDriver screwDriver2 = context.getBean("screwDriver", ScrewDriver.class);
            screwDriver2.use();
        }
    }

    要想测试是不是单例对象,使用getBean()得到两个对象并设置属性为"green",如果是单例,则两个对象的属性均为"green"

    应用:大部分场景下使用singleton模式就能满足业务需求,比如Servlet实例,开始时创建实例,结束时销毁实例,在中间时只需这一个实例来处理即可。

    prototype:每次引用时都需要创建一个新的对象/实例,比如每次使用时不希望保留上一次的属性值/状态

    <bean id=... class=... scope="prototype"></bean>

    例子Test:

    将bean screwDriver的scope改为prototype;

    此时打印出来的结果应该是第一次为green(因为使用setColor()改了),第二次为red(因为初始为red且并未无改动)

    在web应用场景下才出现的作用域:

    request:在每个request中分别创建和销毁bean

    比如在request1开始时创建了若干个beans,结束时这些beans就被销毁了;而另一个request中又是一个request scope,有它自己的bean的创建和销毁

    session:在每个session中的作用域

    每个用户对应一个session,在session中可能若干个requests,但是sessionBean在这些request之间是可以共享的

    而多个session会有各自的session scope

    global session:在此不做介绍

    application:每个session之间需要共享的信息

    Bean的生命周期回调:

    有了Bean的作用域后,我们知道/控制bean的创建/销毁,但是我们不能控制创建/销毁时可以做哪些事情(比如申请/释放资源、创建线程池)--生命周期回调的功能可以支持实现这些功能

    Spring提供了两种回调:

    InitializingBean接口中的afterPropertiesSet()函数:

    public interface InitializingBean {
        void afterPropertiesSet() throws Exception;
    }

    当一个bean创建完成后调用

    例子:

    <bean id=... class=... init-method="init"></bean>

    public class ScrewDriver {
        public void init() {
            Syso("Init screw driver");
        }
    }

     

    DisposableBean接口中的destroy()函数:

    public interface DisposableBean {
        void destroy() throws Exception;
    }

    当一个bean被销毁之后调用

    例子:

    <bean id=... class=... destroy-method="cleanup"></bean>

    public class ScrewDriver {
        public void cleanup() {
            Syso("Clean up screw driver");
        }
    }

    测试Test结果:

    <bean id = "screwDriver" class = "com.netease.nanodegree.ScrewDriver" scope="prototype" init-method="init" destroy-method="cleanup"></bean>

    output:

    发现没有进行cleanup的回调:因为bean没有被销毁

    手动关闭:

    ((ConfigurableApplicationContext) context).close();

    output:还是相同

    将bean定义中的scope="prototype"改为默认的singleton后

    成功执行cleanup()回调函数

    依赖注入

    第一章中举的例子:在ScrewDriver中声明变量private Header header = new StraightHeader();

    虽然有抽象类header的想法,但是直接用new StraightHeader()创建实例,并没有将抽象的想法实施到位。

    如果需要一个带有CrossHeader的screwDriver呢?需要修改ScrewDriver的代码:

    private Header header = new CrossHeader();

    如果要修改一字刀口螺丝刀的属性,比如颜色等,则需要修改constructor:

    private Header header = new StraightHeader("green", 15);

    此时:ScrewDriver和StraightHeader的关系是强依赖关系

    在第一章中的IoC解决方法是在ScrewDriver的constructor中传入一个Header(抽象接口对象)作为参数

    public ScrewDriver(Header header) { this.header = header; }

    1. 定义接口:抽象接口Header

    2. 实现接口:如StraightHeader和CrossHeader

    3. 以接口为基础来进行依赖的注入

    基于构造函数的依赖注入方式:如上例中的在constructor中传入接口对象的方法

    基于Setter方法的依赖注入方式:和基于构造函数的方式很相近

    public class ScrewDriver {
        private Header header;
        
        public setHeader(Header header) {
            this.header = header;
        }
    }

    那...究竟是选择哪一种方法呢?

    实际开发中的原则:

    如果关系是强依赖的,则基于构造函数(比如ScrewDriver必须有一个Header)

    如果关系是可选依赖的,则基于Setter方法(可有可无的依赖)

    实际开发中可能注入的是基本类型、集合、Bean,或通过配置文件注入等等

    例子(只演示基于构造函数的依赖注入):

    定义Header接口:Header.java

    package com.netease.nanodegree;
    
    public interface Header {
        public void doWork();
        public String getInfo();
    }

    定义实现类StraightHeader.java

    package com.netease.nanodegree;
    
    public class StraightHeader implements Header {
        private String color;
        private int size;
    
        public StraightHeader(String color, int size) {
            this.color = color;
            this.size = size;
        }
    
        @Override
        public void doWork() {
            System.out.println("Do work with straight header");
        }
    
        @Override
        public String getInfo() {
            return "Straight Header: " + this.color + ", " + this.size;
        }
    } 

    定义实现类CrossHeader.java(相似)

    定义StraightHeader bean和CrossHeader bean

    <bean id = "header" class = "com.netease.nanodegree.StraightHeader">
        <constructor-arg index="0" type="java.lang.String" value="red"></constructor-arg>
        <constructor-arg index="1" type="int" value="15"></constructor-arg>
    </bean>

    可以通过constructor-arg中的index来说明param的顺序;如index="0"; index="1";

    可以通过type来说明param的类型;如type="int"; type="java.lang.String";

    可以通过name来说明param的对应的变量名;如name="size"; name="color";

    (index/type/name都不是必要的)

    Test.java

    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("application-context.xml");
    
        Header header = context.getBean("header", StraightHeader.class);
        System.out.println(header.getInfo());
        header.doWork();
    }

    output:

    如何注入集合呢?

    通过map的方式注入

    假设StraightHeader的构造函数为:

    public StraightHeader(Map<String, String> params) {
        this.color = params.get("color");
        this.size = Integer.parseInt(params.get("size"));
    }

    此时,定义bean为(使用map的方法)

    <bean id = "header" class = "com.netease.nanodegree.StraightHeader">
        <constructor-arg>
            <map>
                <entry key="color" value="red"></entry>
                <entry key="size" value="15"></entry>
            </map>
        </constructor-arg>
    </bean> 

    也可以注入List/Set之类的集合,配置也相似

    <bean id = "header" class = "com.netease.nanodegree.StraightHeader">
        <constructor-arg>
            <list>
                <value>red</value>
                <value>14</value>
            </list>
        </constructor-arg>
    </bean>

    也可以通过配置文件加载一些配置项:比如注入Property的一些属性

    <bean id = "header" class = "com.netease.nanodegree.StraightHeader">
        <constructor-arg>
            <props>
                <prop key="color">red</prop>
                <prop key="size">14</prop>
            </props>
        </props>
    </bean>

    对应的构造函数:

    public StraightHeader(Properties props) {
        this.color = props.xxx;
        this.size = props.xxx;
    }

    那怎么通过配置文件注入呢?

    在src/main/resources/下创建一个配置文件header.properties

    color=green
    size=16

    在application-context.xml中加载该配置文件:

    <bean id="headerProperties"
        class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="location" value="classpath:header.properties"/>
    </bean>

     property->value为配置文件所在地址

    读取配置文件中的信息: 

    <bean id = "header" class = "com.netease.nanodegree.StraightHeader">
        <constructor-arg name="color" value="${color}"></constructor-arg>
        <constructor-arg name="size" value="${size}"></constructor-arg>
    </bean> 

    上例注入的都是一些基础类型,如果是ScrewDriver需要注入一个Header类型(bean)的参数呢?

    ScrewDriver.java

    public class ScrewDriver {
        private Header header;
        
        public ScrewDriver(Header header) {
            this.header = header;
        }
        
        public void use () {
            System.out.println("Use screw driver with header " + header.getInfo());
        }
    }

    注入:

    <bean id = "screwDriver" class = "com.netease.nanodegree.ScrewDriver">
        <constructor-arg>
            <ref bean="header"/>
        </constructor-arg>
    </bean>

    Test.java

    ScrewDriver screwDriver = context.getBean("screwDriver", ScrewDriver.class);
    screwDriver.use(); 

    输出:

    通过Setter实现注入:

    需要删除constructor

    加上Setter()

    public void setColor(String color) { 
        this.color = color; 
    }
    
    public void setSize(int size) { 
        this.size = size;
    }
    <!-- setter方法注入 -->
    <bean id = "header" class = "com.netease.nanodegree.StraightHeader">
        <property name="color" value="${color}"></property>
        <property name="size" value="${size}"></property>
    </bean>
    <bean id="headerProperties" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="location" value="classpath:header.properties"/>
    </bean>

    如果一个bean有很多依赖呢?使用一个依赖添加一个配置的方法吗?

    Spring提供了自动装配(Autowiring)的机制(三种)

    byName: 根据Bean的名称 (Setter方法)

    byType: 根据Bean的类型 (Setter方法)

    constructor: 构造函数,根据类型

    例子:

    <!-- autowiring自动装配的方式 -->

    <bean id = "screwDriver" class = "com.netease.nanodegree.ScrewDriver" autowire="byName"></bean>

    <bean id = "screwDriver" class = "com.netease.nanodegree.ScrewDriver" autowire="byType"></bean> 

    在ScrewDriver.java中,不再需要setter,只需在private Header header;前加上@Autowired即可

    或使用@Resource(@Autowired和@Resource是存在不同包中的,前者在springframework中,后者在javax.annotation中,相对标准一些)

    通过Annotation的方式

    @Component:定义Bean

    @Value:properties注入

    @Autowired (Spring)/ @Resource (Java):自动装配依赖

    @PostConstruct & @PreDestroy:生命周期回调

    例子:

    application-context.xml中:

    <context:component-scan base-package="com.netease.nanodegree" />

    表示支持通过Annotation来定义bean或其他属性。扫描/搜索com.netease.nanodegree包,使用Annotation的方式进行配置 

    <?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"
        xmlns:p="http://www.springframework.org/schema/p"
        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:component-scan base-package="com.netease.nanodegree" />
        
        <bean id="headerProperties"
            class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
            <property name="location" value="classpath:header.properties"/>
        </bean>
        
        <bean id = "screwDriver" class = "com.netease.nanodegree.ScrewDriver" autowire="byName"></bean>
    </beans> 

    StraightHeader.java中:

    删除setters;

    添加Annotation:

    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.stereotype.Component;
    
    @Componentpublic class StraightHeader implements Header {
        @Value("${color}")
        private String color;
        @Value("${size}")
        private int size;
    
        @Override
        public void doWork() {...}
    
        @Override
        public String getInfo() {...}
    }

    报错:No bean named 'header' is defined

    因为写@Component时,默认name为StraightHeader而不是header,因此需要specify

    @Component("header")

    与生命周期有关的Annotation:

    @PostConstruct
    public void init() {
        System.out.println("init the header");
    }
        
    @PreDestroy
    public void destroy() {
        System.out.println("destroy the header");
    }

    利弊:

    XML文件:繁琐,但是代码独立(代码和配置分离);实际开发中有些东西需要XML文件来配置

    Annotation:简洁,但是配置和代码耦合,配置位置分散,不好管理;实际开发中用于controller/service等

     

    IoC容器单元测试

    本次得分为:12.50/14.00, 本次测试的提交时间为:2017-09-11
    1单选(2分)

    下列关于IoC容器的描述错误的是:

    • A.容器必须由应用代码主动创建;�2.00/2.00
    • B.应用可以从容器中获取所配置的对象;
    • C.容器负责所管理对象的生命周期管理;
    • D.根据会根据依赖关系组装对象;
    2单选(2分)

    如下关于自动装配(Autowiring)说法中,错误的是:

    • A.可以根据Bean名称自动注入;
    • B.可以根据Bean的类型通过Setter方法注入;
    • C.不可以根据Bean的类型通过构建函数注入;�2.00/2.00
    • D.可以根据Bean的类型自动注入;
    3单选(2分)

    关于依赖注入方式,说法错误的是:

    • A.可选依赖可以通过构造函数注入;
    • B.可以通过构造函数注入;
    • C.强依赖必须通过构造函数注入;�2.00/2.00
    • D.可以通过Setter方法注入;
    4多选(3分)

    关于Bean作用域的singleton和prototype说法正确的是:

    • A.prototype只在容器中创建一个对象;
    • B.prototype在每次通过容器获取实例时创建一个对象;�1.50/3.00
    • C.singleton在每次通过容器获取实例时创建一个对象;
    • D.singleton只在容器中创建一个对象;�1.50/3.00
    5多选(3分)

    关于Bean的生命周期回调,说明正确的是:

    • A.生命周期回调发生在运行时;
    • B.可以通过创建(初始化时)回调进行申请资源的操作;�0.75/3.00
    • C.销毁回调发生在对象回收前
    • D.可以通过销毁时回调进行资源回收的操作�0.75/3.00
    5多选(3分)

    关于Bean的生命周期回调,说明正确的是:

    • A.销毁回调发生在对象回收后
    • B.可以通过创建(初始化时)回调进行申请资源的操作;�1.50/3.00
    • C.生命周期回调发生在编译时;
    • D.生命周期回调发生在运行时;�1.50/3.00
    6判断(2分)

    IoC容器在初始化时,必须要加载名称为application-context.xml的配置文件;

    • A.√
    • B.×�2.00/2.00

    IoC容器作业

    1(14分)

    我们在示例中通过

    context.getBean("header", StraightHeader.class);

    方式来获取容器管理的对象,查找并学习Spring的API文档,列出getBean函数的其它形式接口,并解释各个接口的差异。

    2(16分)

    假设我们有个应用(Application),它包含一个写文件的服务(FileWriterService),该服务在创建时会创建并打开一个文件,销毁时关闭该文件,并且该服务包含一个write(String content)方法:会将content写入到文件中,要求:

    1. 使用Spring IoC的相关内容组织该程序;

    2. 文件路径通过配置文件(properties)指定;

    (如果不清楚或者忘记如何打开,关闭以及写入文件,请回顾一个翁老师的 Java基础 课程) 

    基本要求:必须附加一个项目说明文档,说明每个功能点对应程序的运行结果(截图),项目的接口说明或者关键代码(不要把全部代码贴出来)等可以反映项目结果的内容。提交作业的时候必须有这个项目说明文档,否则会影响最终评分。

  • 相关阅读:
    [洛谷P1155] 双栈排序
    [洛谷P4315] 月下”毛景“树
    [洛谷P2486] [SDOI2011]染色
    [HNOI2010] 弾飞绵羊
    mysql注入总结
    cisco交换机实现端口聚合
    python为运维人员打造一个监控脚本
    复习ACCESS注入
    利用sfc文件构建网络渗透
    FTP站点设置
  • 原文地址:https://www.cnblogs.com/FudgeBear/p/7501943.html
Copyright © 2020-2023  润新知