Spring(一)-Spring IoC和DI容器

1.Spring简介

Spring是J2EE开发中一个很重要的框架。它主要用来解决下面两个问题。

  • 解决大型软件开发中,对象之间由于复杂的依赖关系导致的牵一发而动全身的强耦合问题.使用IoC思想解决。
  • 使用面向切面编程, 解决控制事务的繁琐操作.使用AOP解决。

根据Spring解决的问题,和其对应的专业术语。我们也说Spring是一个轻量级的DI/IoCAOP容器开源框架, 其提倡最小侵入式管理应用中的代码,意味着我们可以随时卸载和安装Spring.

接下去我们需要理解下Spring以下的专业术语,我就不抄写网络上的术语内容,并不是那么通俗,以我的理解如下。

1.2 Spring术语

  • 应用程序(Application).
    • 对于JavaWeb来将,应用程序就是一个Web App.例如OA系统等.
  • 框架:
    • 框架是一种抽象, 其抽取了开发中可以重用和常见的功能的集合。
  • 非侵入式设计:
    • 非侵入式是编程中常见的一种设计方式,它保障了别人用你的框架,即使到时候不用了,也不会应用删除你的框架而要更改大量的代码, 可以不用的时候就直接删除,不会带来依赖问题。
    • 从代码的角度来说.非侵入式,保证了不用继承框架的类.也就不会和依赖于框架的类.是一种解耦的设计、
  • 轻量级和重量级
    • 所谓的轻量级是相对于重量级来说。一般是由以下几个特点.非侵入式资源占用少部署简单易用.所以所谓的轻是在有重的前提下的对比。
  • POJO:
    • Plain Ordinary Java Object / Pure Old Java Object.所谓的POJO是不继承Java的任何,不实现任何的接口,但可以包含业务逻辑持久化逻辑
  • 容器:
    • 容器主要用来存放对象.管理对象的整个生命周期.比如Tomcat是Servlet/JSP容器.

1.3 Spring的框架构架

之所以要了解Spring构架是在学习之前我们要对一样事务大体的骨架有一个了解。在介绍Spring的构架之前,我们简要的说下Spring的优势。

Spring的优势

  • 低侵入、低耦合
  • 声明式事务管理
  • 方便集成其他框架
  • Spring框架包含JavaEE 三层的每一层的解决方案(一站式)

构架

从上之下。主要分为

  • 数据访问层(Data Access)
  • Web层
  • AOP模块
  • 核心容器层(Core Coniainer)Beans、Core、Context、SpEL
  • 测试层(Test)

Spring框架版本

这里简要介绍下Spring各个版本的变化。其中Spring2.5是变化最大的一个版本,其已经完成了大部分Spring的核心功能。而后续版本都是在其上增加新的语法支持等。

  • Spring2.5: 驱动编程、支持SimpleJdbcTemplate的命名参数操作
  • Spring3.x: 全面支持泛型不支持JDK 1.4。支持SpEL、支持WebService的OXM
  • Spring4.x: 支持Java8支持JavaEE6规范泛型限定式依赖注入、对Hiberante4的集成事务管理提供更好的管理方案.

介绍完Spring的一些基础常识,我们看是以Spring来写一个最简单的HelloWorld,学习基于Spring搭建应用的基本步骤。

2. 基于Spring如何编写程序

关于Spring插件

如果不是用Spring官方提供的Eclipse。需要自己安装SIS插件.

2.1 基于Spring的HelloWorld编写步骤

  • 导入jar包。并build到classpath。
    • spring-beans-4.0.0.RELEASE.jar
    • spring-core-4.0.0.RELEASE.jar
    • commons-logging-1.1.3.jar
  • 将我们的控制权转交给Spring去控制
    • 创建bean的xml配置文件
    • 注册bean到容器, 进行属性注入。注册HelloWorld到IoC容器中。
  • 启动IoC容器, 根据Bean的idBean的类型, 从Ioc容器中取出对应的bean实例.bean的获取方式又有下面三种.一般我们使用最后一种,因为最后这种既能获取到bean,又无需强转.
    • 根据ID或者name获取bean.ioc.getBean(String)
    • 根据类型获取bean.ioc.getBean(Class)
    • 根据ID和类型获取bean.ioc.getBean(String, Class)

目录结构

测试代码

public class HelloWorldTest {
    
    @Test
    public void testHelloWorld() {
        
            Resource resource = new ClassPathResource("com/sweetcs/_01helloworld/hello.xml");
            BeanFactory beanFactory = new XmlBeanFactory(resource);
            HelloWorld helloWorld = beanFactory.getBean("helloWorld", HelloWorld.class);
            helloWorld.say();
    }
}

输出

可以看到我们使用IoC容器成功的获取到对应的Bean对象,并调用其say方法,输出Hello Spring.

2.2 Spring基本配置

使用Spring编写完第一个HelloWorld演示程序,接下来我细致的分享下Spring基本的配置方式

2.2.1 bean的id和name

在Spring的配置文件中, 也就是配置bean的文件中.我们需要给Bean配置属性。其中比较常用,用来定位Bean的用两种方式,一种是id一种是name.

id方式
<?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="helloWorld" class="com.sweetcs._01helloworld.HelloWorld"></bean>
</beans>

测试代码

    @Test
    public void testHelloWorldByID() {
        
            Resource resource = new ClassPathResource("com/sweetcs/_01helloworld/hello.xml");
            BeanFactory beanFactory = new XmlBeanFactory(resource);
            HelloWorld helloWorld = beanFactory.getBean("helloWorld", HelloWorld.class);
            helloWorld.say();
    }

ID方式输出

name方式
<?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="helloWorld" class="com.sweetcs._01helloworld.HelloWorld"></bean> -->
    <bean name="helloWorld,helloWorld2,helloWorld3" class="com.sweetcs._01helloworld.HelloWorld"></bean>
</beans>

测试代码

    @Test
    public void testHelloWorldByName() {
        
            Resource resource = new ClassPathResource("com/sweetcs/_01helloworld/hello.xml");
            BeanFactory beanFactory = new XmlBeanFactory(resource);
            HelloWorld helloWorld = beanFactory.getBean("helloWorld", HelloWorld.class);
            HelloWorld helloWorld2 = beanFactory.getBean("helloWorld2", HelloWorld.class);
            HelloWorld helloWorld3 = beanFactory.getBean("helloWorld3", HelloWorld.class);
            
            System.out.println(helloWorld == helloWorld2);
            System.out.println(helloWorld2 == helloWorld3);
    }

输出

ID和name的区别
  • 如果是ID方式,一个bean只能配置一个id.并且多个bean的ID值不能重复.如果是Name方式, 一个bean能配置多个别名.

2.3 模块化xml配置文件

如果我们项目中有很多的bean需要注册, 那么这时候如果还只是用一个bean的配置文件,那这个文件配置的bean会爆炸性的增长, 这带来的后果就是十分的难以维护这个配置文件。这个时候我们可以将xml进行拆分, 拆分多个xml文件。一般项目都是按模块划分的,我们可以每个模块下都配置一个xml文件。最后再通过一个统一xml文件,将这些xml文件引入。

引入其他XML配置文件

import元素

使用import元素我们可以将其他的xml配置文件导入。其使用resource属性代表要导入的文件路径,并且提供了classpath:file:协议分别表示从classpath路径寻找和从文件系统的路径寻找.

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

    <!-- 导入_01helloworld包下的配置文件 -->
    <import resource="classpath:com/sweetcs/_01helloworld/hello.xml"/>

</beans>

对应的测试类代码
注解的意思可以先忽略,看完1.6就明白

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class ApplicationContextTest {
    
    @Autowired
    BeanFactory ioc;
    
    @Test
    public void testHelloSpring() {
        HelloWorld bean = ioc.getBean("helloWorld", HelloWorld.class);
        bean.say();
    }   
}

2.4 Spring中的测试

问题:在编写Spring的测试用例中, 我们直接使用Spring提供的Test。因为该Test是包含在容器中, 我们不用每一次都创建容器,关闭容器, 这对系统开销十分大。

2.4.1 测试步骤

  • 导入依赖jar包
    • junit4。需要两个个jar包junithamcrest-core.jar
    • spring。需要三个jar包spring-testspring-aopspring-expression
  • 使用Junit提供的注解
    @RunWith: 用于指定junit运行环境,是junit提供给其他框架测试环境接口扩展.
    @ContextConfiguration: 用于指定容器上下文配置文件。该文件用于指定容器创建的时候需要生成哪些bean对象。
  • 编写测试类
// 告诉JVM,Spring测试运行于JVM上.而不是让Junit运行于JVM上.
@RunWith(SpringJUnit4ClassRunner.class)
// 告诉Spring去哪里寻找配置文件.
@ContextConfiguration("classpath:com/sweetcs/_02springtest/HellWoldTest.xml")
public class HelloWorldTest {
    // 自动装配IoC容器
    @Autowired
    BeanFactory ioc;
    
    @Test
    public void testHelloSpring() {
        
        HelloWorld helloWorld =  ioc.getBean("helloWorld", HelloWorld.class);
        helloWorld.say();
    }
}

配置文件命名的小技巧

  • 测试类的配置文件名可以命名为所在的类名-context.放在和测试类一个目录
    原理

@ContextConfiguration也可以不用指定配置文件路径, 如果不指定,其默认配置文件所在的类文件同一级目录下,且该配置文件名类名-context.xml.对于上述程序可以的配置文件名就可以改成HellWoroldTest-context

  • 务必指定classpath:.指定该前缀能让其一定去classpath目录寻找.

上述主要讲了

  • 如何像IoC容器转交控制权,让IoC容器替我们管理这些对象.而不是我们手动去管理。
  • 如何编写Spring中的测试。

接下来我们要比较系统的介绍下IoC容器的内容。

3.IoC

IoC容器, 即Inversion Of Control, 中文翻译就是反转控制。IoC容器反转了哪些控制呢?其实IoC是一种思想,这种思想被引入软件工程学中, 主要是为了解决大型软件项目中的耦合过深问题。

3.1 思想

传统的思维

传统的思维下,我们需要哪个对象就去new一个。比如下面的代码。

public class EmployeeService {
    private IEmployeeDAO employeeDAO = new EmployeeDAO();
}

这中代码本省写得并没有错,但是随着软件复杂度的增加, 你可能导出都存在这种代码,对于EmployeeService来说它就依赖EmployeeDAO.如果采用这种方式,可想而知,项目中会存在很多复杂的依赖,而且这些依赖有的又是重复引用的.这会导致,其中一个依赖要是存了问题,整个软件系统就无法正常的运行。
如下图,是我在网络上找的一张图,传统软件开发思维如下。导致的问题可想而知。齿轮之间相互耦合太强,牵一发而动全身。

IoC反转控制思想

传统的软件设计是我们需要什么对象需要我们自己去创造,如上分析这随着软件复杂度增加,对于系统的维护和变更是十分困难的,因为存在太多耦合。
IoC的思想
IoC的思想是能不能把这种主动的控制权转交出来,当我们需要某个对象的时候才让"第三方"帮我们注入我们需要的对象。这就解除了类之间的耦合关系,类于类之前的联系需要通过这个中间容器即IoC容器来桥接。

如下图, 只要遵守这个"第三方"的规则,所有的齿轮都能够转动。这个第三方就相当于一个粘合剂,将所有齿轮"粘合"起来,保证整个系统的运转。

这种控制权颠倒过来的设计, 我们就是把它称为“控制反转”。通过这种设计,我们可以在Spring中很方便的扩充功能,而且还能让类之间解除耦合。

说了那么多我们先来看下Spring中IoC容器容器如何创建BeanBean如何实例化Bean的作用域已经Bean的初始化和销毁方法

3.2 IoC容器

获取IoC容器的方式有两种。一种是BeanFactory提供了最基本的IoC功能。一种是ApplicatioinContext, 基于BeanFactory扩展了更强的功能.

  • 使用实现BeanFacory接口的实现类

    BeanFacory是Spring底层的接口,只提供了简单的IoC功能, 负责配置创建管理bean。应用中一般不实用BeanFactory, 而推荐使用ApplicationContext。

  • 使用实现ApplicationContext接口的实现类

    一般开发中都使用ApplicationContext来代表容器.ApplicationContext是BeanFactory子接口,除了继承IoC基本功能,其还提供了更多的功能。

    • 消息机制
    • 国际化
    • 统一的资源加载
    • AOP功能

3.2.1 基于ApplicationContext创建IoC容器(Spring容器)

基于BeanFactory的方式在HelloWorld中已经介绍过, 我们就简单介绍下,基于ApplicatioinContext创建Spring容器的方式。由于ApplicationContext也是一个接口,所以我们只能找其实现类。

通过查看类继承结构如下图,可以发现有如下两个类。

3.3 Bean创建时机研究

ApplicatioinContext和BeanFactory对bean的创建时机并不是相同的,我们来研究下他们有什么区别。之所以以下代码不采用Spring提供的Test来自动装配IoC容器,是因为如果使用SpringTest,它会自动加载所有Bean.就看不出ApplicationContext创建Bean的时机和BeanFactory的区别。

以下代码省略了转交控制权的步骤,需要配置xml,可以参考上面的配置步骤。

3.3.1 基于BeanFactory的Bean创建时机研究

  • testCreateWithBeanFactory用例,是创建IoC容器,并获取Bean
  • testCreateWithBeanFactory2用例, 是只创建IoC容器
    @Test
    public void testCreateWithBeanFactory() {
        Resource resource = new ClassPathResource("com/sweetcs/_03ioc/BeanCreateTest-context.xml");
        BeanFactory ioc = new XmlBeanFactory(resource);
        User user = ioc.getBean("user", User.class);
        System.out.println(user);
        /*  输出
            User 构造器被调用,正在初始化User对象
            User [name=SweetCS]
         */
    }
    
    @Test
    public void testCreateWithBeanFactory2() {
        Resource resource = new ClassPathResource("com/sweetcs/_03ioc/BeanCreateTest-context.xml");
        BeanFactory ioc = new XmlBeanFactory(resource);
        /*
         * 无任何输出
         */
    }

使用BeanFacory方式,Bean只会在需要的时候才创建

3.3.2 基于ApplicationContext的Bean创建时机研究

  • testCreateWithApplicationContext用例,是创建IoC容器,并获取Bean
  • testCreateWithApplicationContext2用例, 是只创建IoC容器
    
    @Test
    public void testCreateWithApplicationContext() {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("com/sweetcs/_03ioc/BeanCreateTest-context.xml");
        User bean = ctx.getBean("user", User.class);
        System.out.println(bean);
        /*  输出
            User 构造器被调用,正在初始化User对象
            User [name=SweetCS]

         */
    }
    
    @Test
    public void testCreateWithApplicationContext2() {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("com/sweetcs/_03ioc/BeanCreateTest-context.xml");
        // 输出 User 构造器被调用,正在初始化User对象
    }   

运行程序后使用ApplicationContext的方式,无论有没有取获取Bean,Bean都会在容器启动的时候被创建

BeanFactory和ApplicationContext的应用场景

  • ApplicatioinContext会一次性将所有Bean的创建出来,主要是为了优化服务端的性能,主用应用于Web后端开发
  • BeanFacory只会在需要使用Bean的时候在创建, 主要用在客户端开发。因为客户端直接面向用户,关注用户体验。不能再一启动创建大量对象,让应用卡顿半天。

3.3.3 能否配置延迟创建

其实这个需求,一般不回用到。对于我们做后端开发的来说, 最常用的是ApplicationContext, 如果要让其延迟加载Bean也是可以。有两种配置方式。

  • 全局配置,对所有Bean都生效。<beans default-lazy-init="true"> </beans>
  • 局部配置,指对指定Bean生效。<bean lazy-init="true"></bean>
    任选一种做测试,会发现在ApplicationContext下的Bean,如果没获取也不会创建.

3.4 Bean的实例化方式

之所以要聊Bean的实例化方式,是因为我们可能会在不同的情况下需要使用不同的方式,让Spring帮我们注入对象。所以我们需要来研究下让Spring帮我们注入对象的四种方式, 其对应的就是Bean的实例化方式。主要有以下四种方式

通过无参构造器方式,最标准, 使用最多(重点)

转交控制权

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

<!-- 1.最标准的方式,通过无参够照器方式创建 -->
    <bean id="user" class="com.sweetcs._04beaninstance.construtor.User"></bean>
</beans>
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class BeanInstanceTest {
    
    @Autowired
    ApplicationContext ctx;
    
    @Test
    public void testInstanceBeanWithNoArgConstrutor() {
        User bean = ctx.getBean("user", User.class);
        System.out.println(bean);
    }   
}

输出

通过静态工厂实例化方式

其主要解决一些系统留下来的问题。例如有可能你接触的项目并不是通过最标准的创建Bean的方式创建,它可能都是使用静态工程创建,那我们就应该在接入Spring的时候,为了兼容性,使用静态工厂实例化方式。
SomeBean2StaticFactory
配置bean,id, class ,factory-method

    
    @Test
    public void testInstanceBeanWithStaticFactory() {
        SomeBean someBean = ctx.getBean("someBean", SomeBean.class);
        System.out.println(someBean);
    }

输出

通过实例工厂实例化方式

SomeBean2

package com.sweetcs._04beaninstance.instance_factory;
public class SomeBean2 {

}

SomeBean2Factory

package com.sweetcs._04beaninstance.instance_factory;
public class SomeBean2Factory {
    public SomeBean2 getSomeBean2() {
        return new SomeBean2();
    }
}

转交控制权

    <!-- 3.通过实例工厂实例化 -->
    <bean id="someBean2Factory" class="com.sweetcs._04beaninstance.instance_factory.SomeBean2Factory"></bean>
    <bean id="someBean2" factory-bean="someBean2Factory" factory-method="getSomeBean2"></bean>

测试用例

    @Test
    public void testInstanceBeanWithInstanceFactory() {
        SomeBean2 someBean2 = ctx.getBean("someBean2",SomeBean2.class);
        System.out.println(someBean2);
    }

需要new出这个工厂
先要转交实例工厂对象创建权
再要转交Bean对象创建权

实现FacotryBean接口实例化。实例工厂的变种,主要为了集成其他框架。

如果发现了这个对象是实现这个接口,Spring容器会自动条用getObject方法返回创建的对象。

package com.sweetcs._04beaninstance.facotry_bean;

import org.springframework.beans.factory.FactoryBean;

public class MyBean {
}

package com.sweetcs._04beaninstance.facotry_bean;

import org.springframework.beans.factory.FactoryBean;

public class MyBeanFactory implements FactoryBean<MyBean>{

    @Override
    public MyBean getObject() throws Exception {
        // TODO Auto-generated method stub
        return new MyBean();
    }

    @Override
    public Class<?> getObjectType() {
        // TODO Auto-generated method stub
        return MyBean.class;
    }

    @Override
    public boolean isSingleton() {
        // TODO Auto-generated method stub
        return false;
    }
    
}

测试用例

    @Test
    public void testInstanceBeanWithImplementsFactoryBean() {
        MyBean myBean = ctx.getBean("myBean", MyBean.class);
        System.out.println(myBean);
    }

输出

3.5 Bean的作用域

Bean的作用域研究表示Bean可以存活多久, Bean的标签里有一个scope属性,要来表示Bean的作用域。这边只介绍用得最多的两种方式singleton(单例)和prototype(多例)。

默认方式

<?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="myBean" class="com.sweetcs._05beanscope.MyBean"></bean>
    
</beans>
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class MyBeanTest {
    
    @Autowired
    ApplicationContext ctx;
    
    @Test
    public void testDefaultScope() {
        
        MyBean myBean = ctx.getBean("myBean", MyBean.class);
        MyBean myBean2 = ctx.getBean("myBean", MyBean.class);
        MyBean myBean3 = ctx.getBean("myBean", MyBean.class);
        
        System.out.println(myBean + "\n" + myBean2 + "\n" + myBean3);
    }
}

输出

Singleton方式

更改bean的配置的,添加scope属性为singleton

    <bean id="myBean" class="com.sweetcs._05beanscope.MyBean" scope="singleton"></bean>

再运行刚才的用例,输出

prototype方式

更该bean的配置为prototype

    <bean id="myBean" class="com.sweetcs._05beanscope.MyBean" scope="prototype"></bean>

所有作用域的总结

最常用的是singleton和prototype作用域。其他的可以参考以下

作用域 描述
singleton 该作用域将 bean 的定义的限制在每一个 Spring IoC 容器中的一个单一实例(默认)。
prototype 该作用域将单一 bean 的定义限制在任意数量的对象实例。
request 该作用域将 bean 的定义限制为 HTTP 请求。只在 web-aware Spring ApplicationContext 的上下文中有效。
session 该作用域将 bean 的定义限制为 HTTP 会话。 只在web-aware Spring ApplicationContext的上下文中有效。
global-session 该作用域将 bean 的定义限制为全局 HTTP 会话。只在 web-aware Spring ApplicationContext 的上下文中有效。

3.6 Bean的初始化和销毁方法

bean标签中提供了init-method属性和destory-method属性。分别会在bean创建之前调用,在bean销毁之前做收尾操作。

3.6.1 DataSource需要回收

比如DataSource最终都需要关闭资源, 在Bean销毁之前,都要调用close方法。

MyDataSourceBean

package com.sweetcs._06bean_init_destory;

public class MyDataSourceBean {
    
    public void doingSomeWork() {
        
        System.out.println("处理业务逻辑");
    
    }
    
    
    public void init() {
        
        System.out.println("初始化配置,准备连接数据库");
    }
    
    
    public void close() { 
        System.out.println("释放connection");
    }
    
}

转交控制权给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">
    <bean id="myDataSource" class="com.sweetcs._06bean_init_destory.MyDataSourceBean" 
    init-method="init" destroy-method="close"></bean>
</beans>

MyDataSourceBeanTest

@RunWith(SpringJUnit4ClassRunner.class)

@ContextConfiguration
public class MyDataSourceBeanTest {
    
    @Autowired
    ApplicationContext ctx;
    
    @Test
    public void testCreateMyDataSourceBean() {
        MyDataSourceBean dataSource = ctx.getBean("myDataSource", MyDataSourceBean.class);
        dataSource.doingSomeWork();
    }
}

输出

3.6.2 对于多例无效

如果Bean标签的scope属性配置成scope="prototype"。则容器不负责销毁控制,而且这个bean也不会放入Spring容器里取管理, 而是一创建就交给程序员,让程序员自己去负责。

    <bean id="myDataSource" class="com.sweetcs._06bean_init_destory.MyDataSourceBean" 
    init-method="init" destroy-method="close" 
    scope="prototype">
        
    </bean>

4.DI

DI又称为依赖注入.DI是IoC思想的一种实现,在Spring容器中就采用了DI技术来实现IoC, 即采用DI实现了反转控制。所以我们要来探讨下如何注入,注入的几种方式。记下来我们主要来研究自动装配属性注入构造器注入属性占位符的使用

4.1 自动装配

自动装配Spring中主要提供了两种方式。如下

  • 基于XML配置的自动装配.Bean标签中配置 autowire属性
  • 基于注解配置的自动装配.@Autowirsed注解

基于XML的自动装配

我们创建两个类分别是ABean和CBean,让ABean依赖于CBean.代码如下

<?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="aBean" class="com.sweetcs._07di.autowise_xml.ABean" autowire="byType"></bean>
    <bean id="cBean" class="com.sweetcs._07di.autowise_xml.CBean"></bean>
</beans>

ABean


public class ABean {
    
    private CBean cBean;
    
    public void setcBean(CBean cBean) {
        this.cBean = cBean;
    }

    @Override
    public String toString() {
        return "ABean [cBean=" + cBean + "]";
    }   
}

CBean

public class CBean {

}

测试代码

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class AutoWiseWithXMLTest {
    
    @Autowired
    ApplicationContext ctx;
    
    @Test
    public void testAutoWiseWithXML() {
        
        ABean bean = ctx.getBean("aBean", ABean.class);
        System.out.println(bean);
        
    }
}

输出

如上输出, 基于XML方式自动装配.自动将CBean注入到了ABean当中。使用的是byType的方式,其实还可以使用byName的方式。下面说下二者区别

byName和byType的区别

  • byName是按照属性名去容器中找id为这个属性名的对象,如果找不到则抛出异常。
  • byType是会根据对应要注入的字段的类型,去容器里找。如果找到一个匹配类型的对象则直接注入。如果找到多个则抛出异常。

4.2 注入值

4.2.1 setter注入

一般我们不会在xml中使用自动装配,应用这样很难看出这个bean有哪些属性。取而代之的是有两种方法

  • 使用属性注入方式。可以很清晰的看出bean的属性。
  • 使用注解装配方式。

注入简单类型-使用字符串

    <bean id="employee" class="com.sweetcs._07di.property.Employee">
        <property name="name" value="SweetCS"></property>
        <property name="url"  value="http://www.baidu.com"></property>
        <property name="age" value="1"></property>
    </bean>

测试用例

public class Employee {
    

    private String name;
    private URL url;
    private Integer age;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public URL getUrl() {
        return url;
    }
    public void setUrl(URL url) {
        this.url = url;
    }
    public Integer getAge() {
        return age;
    }
    public void setAge(Integer age) {
        this.age = age;
    }   
    

    @Override
    public String toString() {
        return "Employee [name=" + name + ", url=" + url + ", age=" + age + "]";
    }
}
    @Test
    public void testDIWithProperyMethod() {
        
        Employee employee = ctx.getBean("employee", Employee.class);
        System.out.println(employee);
        
    }

注入引用类型

public class EmployeeDAO {
    public void save() {
        System.out.println("开始持久化数据到数据库");
    }
}

public class EmployeeService {
    private EmployeeDAO employeeDAO;

    public void setEmployeeDAO(EmployeeDAO employeeDAO) {
        this.employeeDAO = employeeDAO;
    }

    @Override
    public String toString() {
        return "EmployeeDAO [employeeDAO=" + employeeDAO + "]";
    }
    
    public void save() {
        employeeDAO.save();
    }
}

xml配置

    <bean id="employeeService" class="com.sweetcs._07di.property.EmployeeService" autowire="byType"></bean>
    <bean id="employeeDAO" class="com.sweetcs._07di.property.EmployeeDAO"></bean>

注入集合类型


public class CollectionBean {
    
    private Set set;
    private List list;
    private String[] array;
    private Properties properties;
    private Map map;
    
    
    public void setArray(String[] array) {
        this.array = array;
    }


    public void setProperties(Properties p) {
        this.properties =p;
    }
    

    public void setSet(Set set) {
        this.set = set;
    }
    public void setList(List list) {
        this.list = list;
    }

    public void setMap(Map map) {
        this.map = map;
    }
    @Override
    public String toString() {
        return "CollectionBean [set=" + set + ", list=" + list + ", arrayList=" + array + ", map=" + map + "]";
    }
    
}

xml配置

    
    <bean id="collectionBean" class="com.sweetcs._07di.property.CollectionBean">
        <property name="set">
            <set>
                <value>"1"</value>
                <value>"2"</value>
                <value>3</value>
            </set>
        </property>
        
        <property name="list">
            <list>
                <value>"1"</value>
                <value>"2"</value>
                <value>3</value>
            </list>
        </property>
        
        <property name="array">
            <array>
                <value>"1"</value>
                <value>"2"</value>
                <value>"3"</value>
            </array>
        </property>
        
        <property name="properties">
            <value>
                key1=value1
                key2=value2
                key3=value3
            </value>
        </property>
        
        <property name="map">
            <map>
                <entry key="key1" value="value1"></entry>
                <entry key="key2" value="value2"></entry>
                <entry key="key3" value="value3"></entry>
            </map>
        </property>
        
    </bean>

4.2.2 构造器注入

构造器注入其实和属性注入差不多, 主要有两个差别,具体就不演示了。

  • 需要为相应的Bean类提供有参的构造方法(无参构造也要提供)
  • 需要使用constructor-arg标签替换property标签。

4.3 property place holder(配置数据库连接池)

属性占位符是十分有用的一个功能。开发中一般我们都将有关数据库的配置做成一个配置文件,方便和运维人员解耦,运维人员只要拿着配置文件取做部署,无需关心程序员的代码实现细节。

步骤

  • 使用属性占位符初始化上下文的环境变量。<context:property-placeholder location="classpath:配置文件位置" />
  • 使用属性注入方式,配置相关信息。

XML配置, 转交控制权

    <context:property-placeholder location="classpath:config.properties"/>
    <bean id="ds" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
        <property name="username" value="${jdbc.username}"></property>
        <property name="password" value="${jdbc.password}"></property>
        <property name="driverClassName" value="${jdbc.driverClassName}"></property>
        <property name="url" value="${jdbc.url}"></property>
        <property name="initialSize" value="${jdbc.initialSize}"></property>
        <property name="maxActive" value="${jdbc.maxActive}"></property>
        <property name="minIdle" value="${jdbc.minIdle}"></property> 
    </bean>

测试用例

@RunWith(SpringJUnit4ClassRunner.class)

@ContextConfiguration
public class DruidCreateTest {
    
    @Autowired
    ApplicationContext ctx;
    
    @Test
    public void testGetConnWithDruid()  {
        
        DruidDataSource ds = ctx.getBean("ds", DruidDataSource.class);
        Connection connection = null;
        try {
            connection = ds.getConnection();
        } catch (SQLException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        System.out.println(connection);
    }
}

输出

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342