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

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Mr_Megamind/article/details/81004218

前提

这篇博文是这套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高层视图

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的配置和基于注解的配置两种方式配合使用即可。

猜你喜欢

转载自blog.csdn.net/Mr_Megamind/article/details/81004218