【Spring】Spring IoC And DI

 

写在前面

  • 关于文章

    本篇文章主要学习了Java Spring框架的入门知识,包括Spring简介、快速入门、配置文件以及相关API,文章整理自Spring 官网!

Spring 简介

Spring是分层的 Java SE/EE应用 full-stack 轻量级开源框架,以IoC(Inverse Of Control:反转控制)AOP(Aspect Oriented Programming:面向切面编程)为内核。

提供了展现层 SpringMVC持久层 Spring JDBCTemplate 以及业务层事务管理等众多的企业级应用技术 ,还能整合开源世界众多著名的第三方框架和类库,逐渐成为使用最多的Java EE 企业应用开源框架。

Spring 优势

  • 方便解耦,简化开发

    通过 Spring 提供的 IoC容器,可以将对象间的依赖关系交由 Spring 进行控制,避免硬编码所造成的过度耦合。 用户也不必再为单例模式类、属性文件解析等这些很底层的需求编写代码,可以更专注于上层的应用。

  • AOP 编程的支持

    通过 Spring的 AOP 功能,方便进行面向切面编程,许多不容易用传统 OOP 实现的功能可以通过 AOP 轻松实现。

  • 声明式事务的支持

    可以将我们从单调烦闷的事务管理代码中解脱出来,通过声明式方式灵活的进行事务管理,提高开发效率和质量。

  • 方便程序的测试

    可以用非容器依赖的编程方式进行几乎所有的测试工作,测试不再是昂贵的操作,而是随手可做的事情。

  • 方便集成各种优秀框架

    Spring对各种优秀框架(Struts、Hibernate、Hessian、Quartz等)的支持。

  • 降低 JavaEE API 的使用难度

    Spring对 JavaEE API(如 JDBC、JavaMail、远程调用等)进行了薄薄的封装层,使这些 API 的使用难度大为降低。

  • Java 源码是经典学习范例

    Spring的源代码设计精妙、结构清晰、匠心独用,处处体现着大师对Java 设计模式灵活运用以及对 Java技术的高深 造诣。它的源代码无意是 Java 技术的最佳实践的范例。

 

前置知识

在学习IoC之前首先需要了解一些常见的名词:

容器:可以管理对象的生命周期、对象与对象之间的依赖关系。

POJO :POJO(Plain Old Java Object)这种叫法是Martin Fowler、Rebecca Parsons和Josh MacKenzie在2000年的一次演讲的时候提出来的。按照Martin Fowler的解释是“Plain Old Java Object”,从字面上翻译为“纯洁老式的Java对象”,但大家都使用“简单java对象”来称呼它。POJO的内在含义是指:那些没有继承任何类、也没有实现任何接口,更没有被其它框架侵入的java对象。不允许有业务方法,也不能携带connection之类的方法,实际就是普通JavaBeans

JavaBean: JavaBean是一种JAVA语言写成的可重用组件。JavaBean符合一定规范编写的Java类,不是一种技术,而是一种规范。大家针对这种规范,总结了很多开发技巧、工具函数。符合这种规范的类,可以被其它的程序员或者框架使用。它的方法命名,构造及行为必须符合特定的约定:

  • 1、所有属性为private。
  • 2、这个类必须有一个公共的缺省构造函数。即是提供无参数的构造器。
  • 3、这个类的属性使用getter和setter来访问,其他方法遵从标准命名规范。
  • 4、这个类应是可序列化的。实现serializable接口。

因为这些要求主要是靠约定而不是靠实现接口,所以许多开发者把JavaBean看作遵从特定命名约定的POJO。

POJO与Java Bean的区别

POJO JAVABean
除了Java语言强加的限制外,它没有其他特殊限制。 这是一个特殊的POJO,它有一些限制。
它没有对成员提供太多控制。 它提供对成员的完全控制。
它可以实现Serializable接口。 它应该实现可序列化的接口。
可以通过字段名称访问字段。 字段只能由getter和setter访问。
字段可以具有任何可见性。 字段只有私人可见性。
可能/可能没有no-arg构造函数。 它必须具有无参数构造函数。
当您不想限制成员并让用户完全访问您的实体时使用它 当您要向用户提供您的实体,但仅向实体的一部分提供服务时,将使用它。

SpringBean: SpringBean是受Spring管理的对象,所有能受Spring容器管理的对象都可以成为SpringBean。Spring中的bean,是通过配置文件、javaconfig等的设置,由Spring自动实例化,用完后自动销毁的对象。

SpringBean和JavaBean的区别

  • 1、用处不同:传统javabean更多地作为值传递参数,而spring中的bean用处几乎无处不在,任何组件都可以被称为bean。
  • 2、写法不同:传统javabean作为值对象,要求每个属性都提供getter和setter方法;但spring中的bean只需为接受设值注入的属性提供setter方法。
  • 3、生命周期不同:传统javabean作为值对象传递,不接受任何容器管理其生命周期;spring中的bean有spring管理其生命周期行为。

Entity Bean: Entity Bean是域模型对象,用于实现O/R映射,负责将数据库中的表记录映射为内存中的Entity对象,事实上,创建一个Entity Bean对象相当于新建一条记录,删除一个 Entity Bean会同时从数据库中删除对应记录,修改一个Entity Bean时,容器会自动将Entity Bean的状态和数据库同步。

Spring快速入门

Spring程序开发步骤

① 导入 Spring 开发的基本包坐标

② 编写 Dao 接口和实现类

③ 创建 Spring 核心配置文件

④ 在 Spring 配置文件中配置 UserDaoImpl

⑤ 使用 Spring 的 API 获得 Bean 实例

①导入 Spring 基本包坐标

<!--1.导入Spring开发的基本包坐标-->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.0.5.RELEASE</version>
</dependency>

② 编写 Dao 接口和实现类

// 2.编写Dao接口和实现类
public interface UserDao {
    public void save();
}
public class UserDaoImpl implements UserDao {
    @Override
    public void save() {
        System.out.println("saving....");
    }
}

③ 创建 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">
    <!--3.创建Spring核心配置文件-->
    
</beans>

④ 配置 UserDaoImpl

<?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">
    <!--3.创建Spring核心配置文件-->
    <!--4.在Spring配置文件中配置UserDaoImpl-->
    <bean id="userDao" class="cn.imyjs.dao.impl.UserDaoImpl"></bean>
</beans>

⑤ 使用Spring的API获得实例

@Test
public void test1(){
    // 5.使用Spring的API获得Bean实例
    ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
    UserDaoImpl userDao = (UserDaoImpl) app.getBean("userDao");
    userDao.save();
}

Spring配置文件

Bean标签基本配置

用于配置对象交由Spring 来创建。

默认情况下它调用的是类中的无参构造函数,如果没有无参构造函数则不能创建成功

基本属性:id:Bean实例在Spring容器中的唯一标识 class:Bean的全限定名称

bean的命名

每个bean都有一个或多个标识符。 这些标识符在承载bean的容器(ioc容器)中必须是唯一的。 bean通常只有一个标识符。 但是,如果需要多个,则可以考虑使用别名。

基于xml的配置元数据中,可以使用id 属性name属性两者同时使用,来指定bean的标识符。id属性允许您指定一个id,通常,这些名称是字母数字('myBean', 'someService'等),但它们也可以包含特殊字符。 如果想为bean引入其他别名(一个或者多个都可以),还可以在name属性中指定它们,由逗号(',')、分号(';')或空格分隔。

您甚至不需要为bean提供nameid。 如果您没有显式地提供' name '或' id ',容器将为该bean生成唯一的名称。 但是,如果您想通过名称引用该bean,则必须通过使用' ref '元素来提供名称。 xml中默认的名字是权限定名称#数字

bean命名约定

在命名bean时,bean名称以小写字母开头,并从那里开始采用驼峰式大小写。 这类名称的例子包括' accountManager '、' accountService '、' userDao '、' loginController '等等。

一致地命名bean可以使您的配置更容易阅读和理解。

bean的别名

在bean的定义中,可以为bean提供多个名称,方法是使用' id '属性指定的最多一个名称和' name '属性中任意数量的其他名称的组合。 这些名称可以是相同bean的等效别名,在某些情况下很有用,例如允许应用程序中的每个组件使用特定于该组件本身的bean名称来引用公共依赖项。

然而,在实际定义bean的地方指定所有别名并不一定能满足所有需求,有时需要为别处定义的bean(比如引入的jar包)引入别名。 这种情况在大型系统中很常见,其中配置在每个子系统之间被分割,每个子系统都有自己的一组对象定义。 在基于xml的配置元数据中,可以使用<alias/>元素来实现这一点。 下面的例子展示了如何做到这一点:

<alias name="fromName" alias="toName"/>

在这种情况下,一个名为【fromName】的bean被定义了一个新的别名【toName】。

Bean标签范围配置

scope:指对象的作用范围,取值如下:

scope 描述
singleton 每个bean在ioc容器中都是独一无二的单例形式。
prototype 将单个beanDifination定义为,spring容器可以【实例化任意数量】的对象实例。
request 将单个beanDifination限定为单个HTTP请求的生命周期。 也就是说,每个HTTP请求都有自己的bean实例,它是在单个beanDifination的后面创建的。 仅在web环境中的Spring【ApplicationContext】的上下文中有效。
session 将单个beanDifination定义为HTTP 【Session】的生命周期。 仅在web环境中的Spring 【ApplicationContext】的上下文中有效。
application 将单个beanDifination定义为【ServletContext】的生命周期。 仅在web环境中的Spring 【ApplicationContext】的上下文中有效。
websocket 将单个beanDifination作用域定义为【WebSocket】的生命周期。 仅在web环境中的Spring【ApplicationContext】的上下文中有效。

Bean生命周期配置

init-method:指定类中的初始化方法名称

destroy-method:指定类中销毁方法名称

  • 在UserDaoImpl实现类中提前定义好两个方法
    public void init(){
            System.out.println("userDao初始化...");
        }
    
    public void destroy(){
        System.out.println("userDao销毁...");
    }
    
  • 编写配置文件
    <bean id="userDao" class="cn.imyjs.dao.impl.UserDaoImpl" init-method="init" destroy-method="destroy"></bean>
    

Bean实例化三种方式

无参构造方法实例化

它会根据默认无参构造方法来创建类对象,如果bean中没有默认无参构造函数,将会创建失败

使用基于xml的配置元数据,可以使用如下方法,指定bean类:

<bean id="exampleBean" class="examples.ExampleBean"/>
<bean name="anotherExample" class="examples.ExampleBeanTwo"/>

静态工厂方法实例化

在使静态工厂方法创建的bean时,使用class属性指定包含一个静态工厂方法的类,并使用名为factory-method的属性指定工厂方法本身的名称。 我们应该能够调用这个方法并返回一个对象实例。

public class ClientService {
    private static ClientService clientService = new ClientService();
    private ClientService() {}

    public static ClientService createInstance() {
        return clientService;
    }
}
<bean id="clientService" class="examples.ClientService" factory-method="createInstance"/>

在这个例子中,createInstance()方法必须是一个静态方法

实例工厂方法实例化

该方法类似于通过(静态工厂方法)实例化所需的bean,容器同样可以使用实例工厂方法调用非静态方法创建一个新的bean。 要使用这种机制,请将class属性保留为空,并在factory-bean属性中指定当前容器中包含要调用的实例方法的bean的名称。 使用factory-method属性设置工厂方法本身的名称。

public class DynamicFactory {
    public UserDaoImpl getUserDao(){
        return new UserDaoImpl();
    }
}
<bean id="userDao" class="cn.imyjs.dao.impl.UserDaoImpl"></bean>

<bean id="factoryBean" class="cn.imyjs.factory.DynamicFactory"></bean>
<bean id="userDao" factory-bean="factoryBean" factory-method="getUserDao"></bean>

Bean的依赖注入

依赖注入(DI)是一个过程,在此过程中,对象仅通过构造函数参数工厂方法参数等来确定它们的依赖项。 然后容器在创建bean时注入这些依赖项。 从根本上说,这个过程与bean本身相反(因此得名“控制反转”)。

使用依赖注入的代码更清晰,并且在向对象提供依赖时解耦更有效

DI主要有以下两种方式:

  • Constructor-based依赖注入,基于构造器的依赖注入,本质上是使用构造器给成员变量赋值。
  • Setter-based依赖注入,基于setter方法的依赖注入,本质上是使用set方法给成员变量赋值。

基于构造函数的依赖注入

基于构造器的依赖注入是通过容器调用带有许多参数的构造器来实现的,每个参数表示一个依赖项:

public class SimpleMovieLister {

    // the SimpleMovieLister has a dependency on a MovieFinder
    private final MovieFinder movieFinder;

    // a constructor so that the Spring container can inject a MovieFinder
    public SimpleMovieLister(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // business logic that actually uses the injected MovieFinder is omitted...
}

注意,这个类没有什么特别之处。 它是一个POJO,不依赖于容器特定的接口、基类或注解。

1、使用参数的顺序实现

如果beanDifination的构造函数参数中不存在【潜在的歧义】,那么在beanDifination中定义【构造函数参数的顺序】就是在实例化bean时将这些参数提供给适当构造函数的顺序,我们可以看一下下边这个类:

package x.y;

public class ThingOne {

    public ThingOne(ThingTwo thingTwo,ThingThree thingThree) {
        // ...
    }
}

假设【ThingTwo】和【ThingThree】类没有继承关系,就不存在潜在的歧义。 因此,下面的配置工作正常,并且您不需要在<constructor-arg/>元素中显式指定【构造函数参数索引或类型】。

<beans>
    <bean id="beanOne" class="x.y.ThingOne">
        <!-- 直接写就可以 -->
        <constructor-arg ref="beanTwo"/>
        <constructor-arg ref="beanThree"/>
    </bean>

    <bean id="beanTwo" class="x.y.ThingTwo"/>
    <bean id="beanThree" class="x.y.ThingThree"/>
</beans>
2、构造函数参数类型匹配

当引用另一个bean时,类型是已知的,可以进行匹配(如上例所示)。 当使用简单类型时,例如<value>true</value>, Spring无法确定值的类型,因此在没有帮助的情况下无法按类型匹配。 考虑以下官网提供的类:

在前面的场景中,如果你通过使用【type】属性显式指定构造函数参数的类型,容器可以使用与简单类型匹配的类型,如下面的示例所示:

<bean id="exampleBean" class="examples.ExampleBean">
    <constructor-arg type="int" value="7500000"/>
    <constructor-arg type="java.lang.String" value="42"/>
</bean>

在前面的场景中,如果你通过使用【type】属性显式指定构造函数参数的类型,容器可以使用与简单类型匹配的类型,如下面的示例所示:

<bean id="exampleBean" class="examples.ExampleBean">
    <constructor-arg type="int" value="7500000"/>
    <constructor-arg type="java.lang.String" value="42"/>
</bean>
3、按照构造函数参数下标匹配

你可以使用【index】属性显式指定构造函数参数的索引,如下例所示:

<bean id="exampleBean" class="examples.ExampleBean">
    <constructor-arg index="0" value="7500000"/>
    <constructor-arg index="1" value="42"/>
</bean>

除了解决多个简单值的歧义之外,指定索引还解决构造函数具有相同类型的两个参数的歧义。

4、按照构造函数参数的名字匹配

还可以使用构造函数参数名来消除值的歧义,如下面的示例所示:

<bean id="exampleBean" class="examples.ExampleBean">
    <constructor-arg name="years" value="7500000"/>
    <constructor-arg name="ultimateAnswer" value="42"/>
</bean>

基于setter的注入

基于setter的DI是通过容器在【调用无参数构造函数】或【无参数“静态”工厂方法】实例化bean后调用bean上的setter方法来实现的。

<bean id="userService" class="cn.imyjs.service.impl.UserServiceImpl">
        <property name="userDao" ref="userDao"/>  <!-- 注意:这里name是set方法名称去掉‘set’ -->
</bean>

如何选择?

基于构造函数还是基于setter的依赖注入?

由于可以混合使用基于构造函数和基于setter的DI,一般情况下,我们对于强制性依赖项使用构造函数,对于可选依赖项使用setter方法注入,这是一个很好的经验法则。 注意,在setter方法上使用@Required注解可以使属性成为必需依赖项。

Spring团队通常提倡构造函数注入,因为它允许你将应用程序组件实现为不可变的对象,并确保所需的依赖项不是”空“的。 而且,构造函数注入的组件总是以完全初始化的状态返回给客户端(调用)代码。

Setter注入主要应该只用于可选依赖项,这些依赖项可以在类中分配合理的默认值。 setter注入的一个好处是,setter方法使该类的对象能够在稍后进行重新配置或重新注入。

有时,在处理您没有源代码的第三方类时,您可以自行选择。 例如,如果第三方类不公开任何setter方法,那么构造函数注入可能是DI的唯一可用形式。

依赖关系和配置细节

Spring基于xml配置的元数据应该为其<property/><constructor-arg/>元素中支持多样的元素类型。

1.直接值(原语、字符串等)

<property/>元素的value属性将属性或构造函数参数指定为人类可读的字符串表示形式。 Spring的类型转化器用于将这些值从' String '转换为属性或参数的实际类型(比如数字类型,甚至是对象)。

<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    <!-- results in a setDriverClassName(String) call -->
    <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://localhost:3306/mydb"/>
    <property name="username" value="root"/>
    <property name="password" value="misterkaoli"/>
</bean>
2.idref元素

idref元素只是将容器中另一个bean的id 字符串值-不是引用传递给·<constructor-arg/><property/>元素的一种防错误方法。 下面的例子展示了如何使用它:

<bean id="theTargetBean" class="..."/>

<bean id="theClientBean" class="...">
    <property name="targetName">
        <idref bean="theTargetBean"/>
    </property>
</bean>

前面的beanDifination代码段(在运行时)与下面的代码段完全相同:

<bean id="theTargetBean" class="..." />

<bean id="client" class="...">
    <property name="targetName" value="theTargetBean"/>
</bean>

第一种形式比第二种形式更可取,因为使用' idref '标记可以让容器在部署时【验证所引用的已命名bean是否实际存在】。 在第二个变体中,没有对传递给"theClientBean"的【targetName】属性的值执行验证。 只有在实际实例化【theClientBean】时才会发现拼写错误(很可能导致致命的结果)。

3.对其他bean的引用

ref元素是<constructor-arg/><property/>定义元素中的最后一个元素。 在这里,将bean的指定属性的值设置为容器管理的另一个bean(合作者bean)的引用。 被引用的bean是要设置其属性的bean的依赖项,并且在设置属性之前根据需要初始化它。

通过<ref/>标记的bean属性指定目标bean是最常用的一种形式,它允许创建同一容器中的任何bean的引用,而不管它是否在同一XML文件中。 bean属性的值可以与目标bean的id属性相同,也可以与目标bean的name属性中的一个值相同。 下面的例子展示了如何使用ref元素:

<bean id="accountService" class="com.something.SimpleAccountService">
    <!-- insert dependencies as required here -->
</bean>

<bean id="accountService" <!-- bean name is the same as the parent bean -->
    class="org.springframework.aop.framework.ProxyFactoryBean">
    <property name="target">
        <ref bean="accountService"/> <!-- notice how we refer to the parent bean -->
    </property>
    <!-- insert other configuration and dependencies as required here -->
</bean>
4.内部bean

<property/><constructor-arg/>元素内部的'<bean/>元素定义了一个内部bean,如下面的例子所示:

<bean id="outer" class="...">
    <!-- instead of using a reference to a target bean, simply define the target bean inline -->
    <property name="target">
        <bean class="com.example.Person"> <!-- this is the inner bean -->
            <property name="name" value="Fiona Apple"/>
            <property name="age" value="25"/>
        </bean>
    </property>
</bean>

内部bean总是匿名的,并且总是与外部bean一起创建的。 不可能独立地访问内部bean,也不可能将它们注入到外围bean之外的协作bean中。

5.集合

<list/><set/><map/>, 和 <props/> 元素分别设置 Java Collection 类型 ListSetMap,和 Properties的属性和参数。 下面的例子展示了如何使用它们:

<bean id="moreComplexObject" class="example.ComplexObject">
    <!-- results in a setAdminEmails(java.util.Properties) call -->
    <property name="adminEmails">
        <props>
            <prop key="administrator">administrator@example.org</prop>
            <prop key="support">support@example.org</prop>
            <prop key="development">development@example.org</prop>
        </props>
    </property>
    <!-- results in a setSomeList(java.util.List) call -->
    <property name="someList">
        <list>
            <value>a list element followed by a reference</value>
            <ref bean="myDataSource" />
        </list>
    </property>
    <!-- results in a setSomeMap(java.util.Map) call -->
    <property name="someMap">
        <map>
            <entry key="an entry" value="just some string"/>
            <entry key="a ref" value-ref="myDataSource"/>
        </map>
    </property>
    <!-- results in a setSomeSet(java.util.Set) call -->
    <property name="someSet">
        <set>
            <value>just some string</value>
            <ref bean="myDataSource" />
        </set>
    </property>
</bean>

映射键或值或集合值的值也可以是以下元素中的任何一个:

bean | ref | idref | list | set | map | props | value | null
6.null值和空字符串

Spring将属性的【空参数】等作为空字符串处理,以下基于xml的配置元数据片段将' email '属性设置为空字符("")。

<bean class="ExampleBean">
    <property name="email" value=""/>
</bean>

上面的例子等价于下面的Java代码:

exampleBean.setEmail("");

<null/>元素处理 null值。 下面的例子显示了一个示例:

<bean class="ExampleBean">
    <property name="email">
        <null/>
    </property>
</bean>

上述配置相当于以下Java代码:

exampleBean.setEmail(null);
7.带有【p命名空间】的XML配置方式

【p-名称空间】允许您使用【bean元素的属性】(而不是嵌套的<property/>元素)来描述协作bean的属性值,或者两者都使用。说的简单一点就是另外一种写法。

下面的示例显示了两个XML片段(第一个使用标准XML格式,第二个使用p-名称空间),它们解析相同的结果:

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:p="http://www.springframework.org/schema/p"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean name="classic" class="com.example.ExampleBean">
        <property name="email" value="someone@somewhere.com"/>
    </bean>

    <bean name="p-namespace" class="com.example.ExampleBean"
        p:email="someone@somewhere.com"/>
</beans>

引入其他配置文件

实际开发中,Spring的配置内容非常多,这就导致Spring配置很繁杂且体积很大,所以,可以将部分配置拆解到其他 配置文件中,而在Spring主配置文件通过import标签进行加载:

<import resource="applicationContext-xxx.xml"/>

Spring相关API

applicationContext:接口类型,代表应用上下文,可以通过其实例获得 Spring 容器中的 Bean 对象

ApplicationContext的实现类

1)ClassPathXmlApplicationContext 它是从类的根路径下加载配置文件 推荐使用这种

2)FileSystemXmlApplicationContext 它是从磁盘路径上加载配置文件,配置文件可以在磁盘的任意位置。

3)AnnotationConfigApplicationContext 当使用注解配置容器对象时,需要使用此类来创建 spring 容器。它用来读取注解。

getBean()方法使用

ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService1 = (UserService)applicationContext.getBean("userService");
UserService userService2 = applicationContext.getBean(UserService.class);

微信关注

WeChat

 

阅读剩余
THE END