欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Spring学习笔记(四)——IoC进阶篇

程序员文章站 2022-07-12 13:09:59
...

前提

这篇博文是这套Spring学习笔记的第四篇——IoC进阶篇,在之前的综述篇和配置篇中我们大体上知道了IoC的概念及其简单的编码应用。这一篇的主要内容是关于IoC我们之前没有涉猎到的进阶知识。如果需要了解有关Spring的综述信息或博文的索引信息,请移步:
《综述篇》


资源访问机制

Spring中提供了强大的资源访问机制,使我们在开发中可以访问到任何想要访问的文件。

地址前缀 示例 说明
classpath: classpath:com/implementist/MyFirstWebApp/configure.xml 从类路径中加载资源
file: file:applicationContext.xml 从文件系统中加载资源,支持绝对路径和相对路径
http:// http://myfirstwebapp.com/images/icon.jpg 从Web服务器中加载资源
ftp:// ftp://myfirstwebapp.com/images/icon.jpg 从FTP服务器中加载资源

说明:“类路径”即WEB-INF/classes文件夹,在工程中即默认包

关于其使用场景,比如在《配置篇》中,需要让web.xml加载applicationContext.xml以启动Spring容器时:

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:applicationContext.xml</param-value>
    </context-param>

这里就用到了classpath这种加载方式。


Spring的高层视图

Spring学习笔记(四)——IoC进阶篇

Spring容器根据各种配置信息在容器内构建并维护Bean定义注册表,然后根据注册表加载、实例化Bean、建立Bean与Bean之间的依赖关系,然后将准备就绪的Bean放到Bean缓存池中,以供外层的应用程序进行调用。


Bean的装配

在Spring中,Bean的装配有三种方式:基于XML的配置、基于注解的配置和基于Java类的装配。

以一个最简单的Bean的装配为例:

    <bean id="user" class="com.implementist.MyFirstWebApp.domain.User"/>

解释:
①与HTML标签类似,bean可以有id和name两种标识,但是因为name存在可以重名的情况,我们只用id;
②id指定了bean的唯一标识,它必须以字母开头,不得以逗号或空格这些非完整结束符结束;
③class指定了bean的实现类,即当前的bean将会作为哪个类的实例;
④工程中,任意对User类型,名称为“user”的bean的引用都会指向当前这个bean。


基于XML的装配

依赖注入

在装配一个bean的时候,我们希望初始化的时候对bean的一些字段赋以初值,此时共有三种方式供我们选择:属性注入、构造函数注入和工厂方法注入

1.属性注入

    <bean id="user" class="com.implementist.MyFirstWebApp.domain.User">
        <property name="username"><value>Jackson</value></property>
        <property name="password"><value>1234567890</value></property>
    </bean>

解释:
①属性以键值对的形式设置;
②使用这样的方式为bean注入属性值需要对应的User类中声明了username和password这两个字段和它们对应的Getter,Setter函数。

2.构造方法注入

如果我们的User类中有如下的构造函数:

    public void User(String username, int age){
        ......
    }

我们就可以使用构造方法注入的方式:

    <bean id="user" class="com.implementist.MyFirstWebApp.domain.User">
        <constructor-arg type="java.lang.String" value="Jackson"/>
        <constructor-arg type="int" value="20"/>
    </bean>

但是问题来了,如果User类还有一个构造函数,还需要设置密码:

    public void User(String username, String password, int age){
        ......
    }

前两个字段都是String型的,IoC就不能判断把哪个值给哪个字段了。这时,我们可以给constructor-arg加上索引来指定值分配给第几个参数:

    <bean id="user" class="com.implementist.MyFirstWebApp.domain.User">
        <constructor-arg index="0" value="Jackson"/>
        <constructor-arg index="1" value="1234567890"/>
        <constructor-arg index="2" value="20"/>
    </bean>

问题又来,如果这时User中还有一个构造函数中包含了对double型的体重字段的赋值:

    public void User(String username, String password, double weight){
        ......
    }

因为double是兼容int的,因此,单凭字段类型或索引都不能判定应该调用哪个构造函数。这时,可以将字段类型和索引联合起来使用:

    <bean id="user" class="com.implementist.MyFirstWebApp.domain.User">
        <constructor-arg index="0" value="Jackson"/>
        <constructor-arg index="1" value="1234567890"/>
        <constructor-arg index="2" type="double" value="20"/>
    </bean>

因为前两个属性通过index已经可以精确指定了,因此只需对第三个会发生歧义的属性联合使用index和type即可。

3.工厂方法注入
工厂方法注入需要额外创建bean的工厂,如UserFactory:

    public class UserFactory{
        public static User createZhangSan(){
            ......
        }
        public static User createLiSi(){
            ......
        }
    }

这样,我们就可以通过指定工厂类和方法来为user注入属性了:

    <bean id="user" class="com.implementist.MyFirstWebApp.factory.UserFactory" factory-method="ZhangSan"/>

说明:因为工厂方法注入需要额外的工厂类,且灵活性较差,因此在开发中通常只使用属性注入和构造方法注入。

XML标签中的各种情形

①需要转义的字符
XML中有五个特殊的字符< > & " ',在需要将它们作为参数是不能直接输入这些字符,而是需要用转义符来转义,避免他们对XML文件的格式造成破坏(比如提前关闭标签等)。以下是它们对应的转义符表:

特殊字符 转义符
< &lt;
> &gt;
& &amp;
" &quot;
' &apos;

②内部Bean
与Java的匿名内部类类似,bean内部可以嵌套匿名的内部bean来作为其字段的引用,这个内部bean无法被外部的其他bean引用,如上述的jdbcTemplate与dataSource的关系就可以写成:

    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource">
            <bean class="org.apache.commons.dbcp2.BasicDataSource"
                destroy-method="close" 
                p:driverClassName="com.mysql.jdbc.Driver"
                p:url="jdbc:mysql://localhost:3306/myfirstapp?characterEncoding=utf8" 
                p:username="root"
                p:password="" />
        </property>
    </bean>

③null
为一个属性注入一个null值需要使用<null/>标签,如:

    <property name="username"><null/></property>

④数据集
可以为一个属性注入一个数据集,以Set为例:

    <bean id="user" class="com.implementist.MyFirstWebApp.domain.User">
        <property name="favorites">
            <set>
                <value>Soccer</value>
                <value>Jogging</value>
                <value>Reading</value>
            </set>
        </property>
    </bean>

Bean与Bean间的关系

①继承
与Java中的继承类似,beanB可以通过设置parent="beanA"来指定beanA为自己的父级bean。子bean将继承父bean的配置信息,并且可以覆盖父bean提供的配置信息。

    <bean id="beanA" class="com.implementist.MyFirstWebApp.domain.User"
        abstract="true"
        p:username="Jackson"
        p:age="20"/>
        
    <bean id="beanB" parent="beanA"/>

解释:如果设置abstract=“true”,则IoC容器不会真的创建一个beanA

②依赖
beanA可以通过设置depends-on="beanB"来指定其依赖于beanB

    <bean id="beanA" class="com.implementist.MyFirstWebApp.dao.UserDAO" depends-on="beanB"/>   
    <bean id="beanB" class="com.implementist.MyFirstWebApp.domain.User"/>

解释:指定了依赖关系后,beanA会在beanB实例化之后才被实例化。

③引用
还记得在applicationContext.xml中,jdbcTemplate引用dataSource是怎么写的吗?

    <!-- 配置数据源 -->
    <bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource"
          destroy-method="close" 
          p:driverClassName="com.mysql.jdbc.Driver"
          p:url="jdbc:mysql://localhost:3306/myfirstapp?characterEncoding=utf8" 
          p:username="root"
          p:password="" />

    <!-- 配置Jdbc模板  -->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"
          p:dataSource-ref="dataSource" />

解释:
1)p:dataSource-ref="dataSource"这一行就是jdbcTemplate引用dataSource作为其属性;
2)bean开始标签中的p:XXX-ref="YYY"的作用同
<property name=“XXX”/>
  <ref bean=“YYY”/>
</property>
Spring 添加了这种写法是想尽量减少开发人员必要的代码量;
3)使用p:之前需要在<beans>开始标签中添加p命名空间的声明:
xmlns:p="http://www.springframework.org/schema/p"

合并多个配置文件中的配置信息

在配置文件configure2.xml中可以导入configure1.xml中的配置信息,这样configure2中的bean就可以引用configure1中的bean

    <import resource="classpath:configure1.xml"/>

Bean的作用域

作用域会对bean的生命周期、创建及维护方式产生影响,最初只有singleton和prototype两种,后来Web应用上下文添加了三个新的作用域:

作用域 说明
singleton 单例模式,IoC容器对该类的每一次引用注入同一个的bean。
prototype 原型模式,IoC容器对该类的每一次引用注入一个新的bean。
request 请求模式,每次Http请求都会创建一个新的bean。
session 会话模式,同一个Http Session共享一个bean。
globalSession 全局会话模式,同一个全局Session共享一个bean,多应用于Portlet中。

基于注解的配置

@Component

关于注解的定义以及@Repository、@Service和@Controller三个注解,我在配置篇中已经说过了。另外还有一个@Component注解也上述三个类似,它可以与这三个注解相互替换,也可以用来注解既不属于Repository层、Service层,也不属于Controller层的bean。

组件扫描

在配置篇中,我们在applicationContext.xml和spring-mvc.xml中,我们分别对dao、service和controller三个包进行了扫描:

    <context:component-scan base-package="com.implementist.MyFirstWebApp.dao"/>
    <context:component-scan base-package="com.implementist.MyFirstWebApp.service"/>
    <context:component-scan base-package="com.implementist.MyFirstWebApp.controller"/>

Spring容器会扫描这些包里的所有类,并自动从注解信息中获取bean的定义。

@Autowired

同样在配置篇中我说过,IoC容器会自动装配被冠以@Autowired注解的bean;
它还可以与@Qualifier注解配合使用,@Qualifier注解可以指定装配的bean的名称:

@Autowired
@Qualifier("zhangSan")
private User user;

指定Bean的作用域

@Scope注解可以配合@Component、@Repository、@Service和@Controller四个注解来指定bean的作用域,如:

@Component
@Scope("singleton")
public class User{
    ......
}

基于Java类的配置

被冠以@Configuration注解的类将被视为配置类,IoC容器会根据该类中的配置信息初始化bean,如:

@Configuration
public class AppConfig{
    @Bean
    public User user(){
        return new User();
    }

    @Bean
    public UserDAO userDAO(){
        return new UserDAO();
    }

    @Bean
    public UserService userService(){
        UserService userService = new UserService();
        userService.setUser(user());
        userService.setUserDAO(userDAO());
        return userService;
        }
}

解释:
①这段代码先定义了三个Bean:user和userDAO、userService,及其各自的实例化方法;
②bean的名称若未指定@Bean(name=“XXX”),则默认与方法名相同;
③其效果等同于:
<bean id=“user” class=“com.implementist.MyFirstWebApp.domain.User”/>
<bean id=“userDAO” class=“com.implementist.MyFirstWebApp.dao.UserDAO”/>
<bean id=“userService” class="com.implementist.MyFirstWebApp.service.UserService"
  p:user-ref="user"
  p:userDAO-ref=“userDAO”/>


后记

基于Java类的配置灵活性略差,且需要额外的Java类来手动定义bean的配置。因此平时将基于XML的配置和基于注解的配置两种方式配合使用即可。

相关标签: IoC Spring