Spring学习笔记(四)——IoC进阶篇
前提
这篇博文是这套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容器根据各种配置信息在容器内构建并维护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文件的格式造成破坏(比如提前关闭标签等)。以下是它们对应的转义符表:
特殊字符 | 转义符 |
---|---|
< | <; |
> | >; |
& | &; |
" | "; |
' | &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的配置和基于注解的配置两种方式配合使用即可。
上一篇: Spring基础知识