介绍
前面这篇文章和这篇文章里面,Spring MVC整合的都是基于XML来获取配置元数据的Spring IoC容器,但很多情况下我们想使用基于注解和Java来获取配置元数据的Spring IoC容器,没关系,Spring MVC也为我们提供了这种整合所需要的组件。
由于有了前面的基础,本篇文章不再把整个工程的结构以及所有代码都展示出来,只把有关的部分代码展示出来,因为编写方式都是类似的,读者可以尝试自己在原来的代码基础上进行修改和验证。
思路
如何整合呢?我们先来看看原来在基于Java配置DispatcherServlet
的代码中是如何整合Spring IoC容器的:
XmlWebApplicationContext appBContext = new XmlWebApplicationContext();
appBContext.setConfigLocation("/WEB-INF/appB-context.xml");
关键在于XmlWebApplicationContext
这个类的前缀是Xml
,那我们是不是可以猜想有类似于JavaWebApplicationContext
或者AnnotationWebApplicationContext
这样的类呢?然后不就可以生成它们的实例,传入配置元数据的位置了吗?
不过根据这篇文章,传入的配置元数据的位置就不是XML文件的目录了,而是一个带有@Configuration
注解的一个配置类了。可能方法名称还是setConfigLocation
,但也可能是别的。
现在问题是确定有没有JavaWebApplicationContext
或者AnnotationWebApplicationContext
这样的类。我们先看XmlWebApplicationContext
所在的包是哪:
import org.springframework.web.context.support.XmlWebApplicationContext;
可以看到,它在org.springframework.web
包下,于是很自然的想到可以在 spring-web-5.1.7.RELEASE.jar 包中去找。我们在Eclipse的 Project Explorer 视图中打开所依赖的该JAR包,一级一级往下找:
继续展开context节点及其子节点support:
可以看到,除了我们已经使用过的XmlWebApplicationContext
,还真的有一个类似的AnnotationConfigWebApplicationContext
,跟我们猜想的类名差不多。
剩下的工作就是使用它来编写代码了,然后使用某个方法来把基于Java的配置元数据传进去,一个方法是直接在Eclipse中使用代码补全功能以及Javadoc查看该类是使用哪个方法来设置基于Java的配置元数据的。当然,这样可能会比较费时间。
另一个方法,就是借助万能的某度搜索引擎了,搜索关键字已经很明确了。
在初始化器中进行整合
假设我们将appA应用的Servlet IoC容器修改为使用基于注解和Java的配置元数据。
首先,当然是要编写基于注解和Java的配置元数据,我在原来的config包下新增了一个配置类(参考这篇文章),AppAConfig.java:
package test.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan({ "test.appa" })
public class AppAConfig {
}
这个没什么好说的,参考文章已经介绍得很详细了。唯一要关注的是@ComponentScan
注解,它其实就类似于基于XML的<component-scan>
标记,用来开启该IoC容器的自动扫描和配置,也有一个扫描范围的参数,这个参数是一个字符串数组,即可以传入多个包,用逗号分隔即可。
当然,你也可以在配置类中使用@Bean
注解来配置Bean。
然后,就可以在appA的初始化器AppAInitializer中将原来整合基于XML的IoC容器修改为基于注解和Java的IoC容器:
AnnotationConfigWebApplicationContext appAContext = new AnnotationConfigWebApplicationContext();
appAContext.register(AppAConfig.class);
可以看到,原来是使用register
方法来传入配置元数据的位置,这个位置就是我们编写的配置类,直接使用AppAConfig.class
,以后再讨论.class
这种用法。
是不是跟整合基于XML的Spring IoC容器很类似,这就是充分利用了举一反三触类旁通的思想。当然,Spring的设计者也是有意这样设计的,因为这样使用者用起来才容易啊,我们也应该尽量把自己的软件设计成这样。
上面整合的是appA的Servlet IoC容器,appB的Servlet IoC容器以及Root IoC容器也是一样的方式。
在web.xml中进行整合
现在假设我们要将appB应用的Servlet IoC容器修改为使用基于注解和Java的配置元数据,但我们不采用初始化器的方式,而是在web.xml中配置appB应用的DispatcherServlet
和Servlet IoC容器。
先编写AppBConfig.java:
package test.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan({ "test.appb" })
public class AppBConfig {
}
web.xml中原来的appB应用的配置是这样的:
<servlet>
<servlet-name>appB</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/appB-context.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>appB</servlet-name>
<url-pattern>/appB/*</url-pattern>
</servlet-mapping>
是不是只要把<param-value>/WEB-INF/appB-context.xml</param-value>
替换成<param-value>test.config.AppBConfig</param-value>
就可以了呢?
如果是这样,那DispatcherServlet
如何知道使用哪种IoC容器(即WebApplicationContext
)呢?除非我们期望Spring MVC的DispatcherServlet
足够智能,它能根据contextConfigLocation
参数的值判断出是使用基于XML的还是基于注解和Java的配置元数据。
现实可没那么理想,大家可以自己尝试一下,这样修改后会出现错误:
parsing XML document from ServletContext resource [/test.config.AppBConfig];
显然,DispatcherServlet
仍然使用的是XmlWebApplicationContext
这种基于XML读取配置元数据的IoC容器,我们可以大胆猜想这个是DispatcherServlet
的默认值。
那么必然可以向DispatcherServlet
传递另外一个指示使用哪种IoC容器的参数,这个参数的值自然是前面我们已经用过的能读取基于注解和Java的配置元数据的IoC容器org.springframework.web.context.support.AnnotationConfigWebApplicationContext
,问题就是参数的名字是什么:
<servlet>
<servlet-name>appB</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>?????????????</param-name>
<param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
</init-param>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>test.config.AppBConfig</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
这个参数的名字我是在AnnotationConfigWebApplicationContext
的Javadoc中找到的:
所以,以后一定要多看Javadoc。
现在,把?????????????
替换成contextClass
即可,这样appB的Servlet IoC容器就配置好了,经过验证无误(注意要把原来appB的初始化器去掉)。
剩下的Root IoC容器的配置也可以修改成:
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextClass</param-name>
<param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
</context-param>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>test.config.RootConfig</param-value>
</context-param>
添加一个contextClass
的参数,并将contextConfigLocation
参数的值改为相应的配置类。
RootConfig.java:
package test.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan({ "test.common" })
public class RootConfig {
}
总结
- 要学会举一反三、触类旁通;
- 要多看Javadoc。