刚开始使用springmvc项目,要配置web.xml 、spring.xml、spring-dispatcher.xml配置文件挺烦人的,学习享学课堂Jack老师无XML启动springmvc收获很多,下面我把我收获到的东西分享给大家,希望对大家有帮助。
1. Servlet容器启动
在编写代码之前,我们简单了解一下Servlet容器启动一些知识。Servlet容器启动的时候,它会去收集所有实现ServletContainerInitializer接口的类实例,然后调用接口实例onStartup方法。我们先去看看spring-web工程是如何实现的 ?
spring-web工程利用SPI机制暴露 ServletContainerInitializer 接口实例 SpringServletContainerInitializer 。 当Web容器启动的时根据SPI技术,会调用ServletContainerInitializer接口实例的onStartup() 方法。 我们看看 SpringServletContainerInitializer 类中的代码
@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
/**
* onStartup方法里面有两个参数 webAppInitializerClasses、servletContext
* webAppInitializerClasses:收集实现了WebApplicationInitializer接口的反射实例对象
* servletContext:Servlet上下执行文
*/
@Override
public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
throws ServletException {
List<WebApplicationInitializer> initializers = new LinkedList<>();
if (webAppInitializerClasses != null) {
for (Class<?> waiClass : webAppInitializerClasses) {
// Be defensive: Some servlet containers provide us with invalid classes,
// no matter what @HandlesTypes says...
if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
try {
initializers.add((WebApplicationInitializer)
ReflectionUtils.accessibleConstructor(waiClass).newInstance());
}
catch (Throwable ex) {
throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);
}
}
}
}
if (initializers.isEmpty()) {
servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
return;
}
servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
AnnotationAwareOrderComparator.sort(initializers);
//在这里会去调用所有WebApplicationInitializer实例的onStartup方法
for (WebApplicationInitializer initializer : initializers) {
//调用onStartup方法
initializer.onStartup(servletContext);
}
}
}
onStartup方法里面有两个参数 webAppInitializerClasses、servletContext
1:参数webAppInitializerClasses:收集实现了WebApplicationInitializer接口的反射实例对象。(WebApplicationInitializer 接口就是@HandlesTypes 这里面申明的接口 )
2:servletContext:Servlet上下执行文
2.自定义ServletContainerInitializer启动类
按照上面说的Servlet容器启动的知识点,我们也来自定义ServletContainerInitializer启动类,我们创建一个Maven工程。我们利用SPI技术暴露ServletContainerInitializer接口实例,实例名称TomcatServletContainerInitializer
接下来我们来编写 TomcatServletContainerInitializer 类里面的东西,代码如下
@HandlesTypes(TomcatWebApplicationInitializer.class)
public class TomcatServletContainerInitializer implements ServletContainerInitializer {
@Override
public void onStartup(Set<Class<?>> set, ServletContext ctx) throws ServletException {
if (set != null) {
Iterator<Class<?>> iterator = set.iterator();
while (iterator.hasNext()) {
Class<?> clazz = (Class<?>) iterator.next();
if (!clazz.isInterface() && !Modifier.isAbstract(clazz.getModifiers())
&& TomcatWebApplicationInitializer.class.isAssignableFrom(clazz)) {
try {
// 1.调用TomcatWebApplicationInitializer 接口实例的 onStartup() 方法
((TomcatWebApplicationInitializer) clazz.newInstance()).onStartup(ctx);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
}
TomcatServletContainerInitializer 类实现了 ServletContainerInitializer 接口,然后我们 TomcatServletContainerInitializer 类上面需要添加 @HandlesTypes(TomcatWebApplicationInitializer.class) 。这个参数作用就是告诉Servlet容器去收集TomcatWebApplicationInitializer 接口实例,然后已参数形式传递给 onStartup()方法。
在TomcatServletContainerInitializer类中的onStartup方法里面,它会去循环set集合(因为集合里面全部是TomcatWebApplicationInitializer 接口实例对象),然后调用TomcatWebApplicationInitializer 接口实例对象的 onStartup 方法
备注:TomcatWebApplicationInitializer 接口方法我没定义好(写博客之前做好的案例,所以懒得去修改了),大家注意一下
我们接下来定义TomcatWebApplicationInitializer接口实例,代码如下
public interface TomcatWebApplicationInitializer {
void onStartup(ServletContext servletContext);
}
TomcatWebApplicationInitializer 接口实例定义的很简单,我们接下来定义几个类去实现 TomcatWebApplicationInitializer接口,不然效果就不明显了。
接下来我们定义一个类TomcatServletInitializer 去实现TomcatWebApplicationInitializer 接口 ,既然Servlet容器启动的时候会收集 TomcatServletInitializer 接口实例,然后调用到onStartup() 方法。我们就可以自定义Servlet 将其添加到Servlet容器中 。
// 我们先定义一个Servlet实现类
public class UserHttpServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("=====UserServlet doGet===");
PrintWriter writer = resp.getWriter();
writer.print("<h1>UserServlet</h1>");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
super.doPost(req, resp);
}
@Override
public void init(ServletConfig config) throws ServletException {
super.init(config);
}
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
super.service(req, resp);
}
}
// 实现 TomcatWebApplicationInitializer ,这个接口就是 @HandlesTypes() 里面定义的接口
// Servlet容器启动的时候,会调用到这里面的onStartup 方法
public class TomcatServletInitializer implements TomcatWebApplicationInitializer {
@Override
public void onStartup(ServletContext servletContext) {
ServletRegistration.Dynamic dynamic = servletContext.addServlet(UserHttpServlet.class.getName(), UserHttpServlet.class);
// 如果值为整数或则0,表示容器在应用启动时候加载并且初始化这个Servlet
// 值越小,servlet的优先级越高,就越先被加载
dynamic.setLoadOnStartup(1);
dynamic.addMapping("/user");
}
}
3.Tomcat 容器启动
上述代码编写好后,接下来我们编写Tomcat启动的代码,代码如下
public class TomcatApplication {
public static void main(String[] args) {
try {
// 创建Tomcat容器
Tomcat tomcatServer = new Tomcat();
// 端口号设置
tomcatServer.setPort(9000);
// 读取项目路径 加载静态资源
StandardContext ctx = (StandardContext) tomcatServer.addWebapp("/", new File("src/main").getAbsolutePath());
// 禁止重新载入
ctx.setReloadable(false);
// class文件读取地址
File additionWebInfClasses = new File("target/classes");
// 创建WebRoot
WebResourceRoot resources = new StandardRoot(ctx);
// tomcat内部读取Class执行
resources.addPreResources(
new DirResourceSet(resources, "/WEB-INF/classes", additionWebInfClasses.getAbsolutePath(), "/"));
tomcatServer.start();
// 异步等待请求执行
tomcatServer.getServer().await();
} catch (LifecycleException | ServletException e) {
e.printStackTrace();
}
}
}
代码写完后,我们直接运行TomcatApplication 类中的main 方法,就能看到效果了 。我们也打一个断点
看看效果是不是跟我们上面所述的效果是一样的,代码准备就绪 GO GO GO!
断点进来了,效果跟我们上面所述理论东西相符合 。我们项目中只有一个 TomcatWebApplicationInitializer 接口实例,所以这个set 集合的size属性 = 1,项目工程启动完成后我们输入http://localhost:9000/user 能不能正常访问 。
4.启动springmvc项目
如果上面的成功运行,我们接下来把spring-mvc工程整合进来玩玩,我们在pom文件里面引入spring-mvc工程
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.1.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.1.3.RELEASE</version>
</dependency>
我们看下spring-web工程的源代码去,我们也发现spring-web 工程也利用SPI技术暴露了ServletContainerInitializer 接口实例
根据我们上述的思路,我们也定义一个类去实现 WebApplicationInitializer 接口就OK, spring-web工程提供了 AbstractAnnotationConfigDispatcherServletInitializer类,这个类父类实现类WebApplicationInitializer接口实的。所以我们定义一个类继承AbstractAnnotationConfigDispatcherServletInitializer。
AbstractAnnotationConfigDispatcherServletInitializer 大家可以去看看,它的父类 AbstractDispatcherServletInitializer 。在这个类里面它会去创建 ContextLoaderListener 和 DispatcherServlet 实例
然后我们再去看下我们传统springmvc项目的web.xml配置文件
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
<!--加载spring配置-->
classpath:spring.xml
</param-value>
</context-param>
<context-param>
<param-name>webAppRootKey</param-name>
<param-value>ServicePlatform.root</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet>
<servlet-name>spring-dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<!--springmvc的配置文件-->
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-dispatcher.xml</param-value>
</init-param>
<load-on-startup>0</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>spring-dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
仔细去看,你会发现web.xml 里面配置 ContextLoaderListener 、DispatcherServlet
指向的是同一个类,自己去看看就知道我说的再多都没用。 那我们创建一个 WebAppInitializer 去继承 AbstractAnnotationConfigDispatcherServletInitializer
// AbstractAnnotationConfigDispatcherServletInitializer 这个类实现了 WebApplicationInitializer
public class WebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
/**
* 父容器 spring容器
*
* @return
*/
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[]{SpringContainer.class};
}
/**
* 子容器 dispatcher容器
*
* @return
*/
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[]{MvcContainer.class};
}
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
}
WebAppInitializer 既然继承 AbstractAnnotationConfigDispatcherServletInitializer ,而AbstractAnnotationConfigDispatcherServletInitializer 这个类会去创建ContextLoaderListener 和 DispatcherServlet实例(代替传统的web.xml配置文件)
1:创建ContextLoaderListener 时候会调用getRootConfigClasses()等钩子方法。也就是说调用到了WebAppInitializer 类中的getRootConfigClasses()方法
2:创建DispatcherServlet 时候会调用到getServletConfigClasses()等钩子方法,同样也会调用到WebAppInitializer getServletConfigClasses()、getServletMappings()等方法。
@ComponentScan(value = "com.autumn.sample",excludeFilters = {
@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class})
})
public class SpringContainer {
}
@ComponentScan(value = "com.autumn.sample",includeFilters = {
@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class})
},useDefaultFilters = false)
public class MvcContainer {
}
接下来只要创建相应的Controller 和 Service类 就可以了,我这里就不细说了 ,我随便截一个我本地写的案例
上述工作都准备好后,我们接下来启动程序看效果了 。同样我们启动TomcatApplication 类中的main方法
启动成功了,我们看看我们定义申明@Controller 是否生效了 ,我们输入 http://localhost:9000/user/query
5.总结
本人也是一个小白,也是看到很多资料才领悟到的,但希望对大家有帮助。如有需要案例的请留言 。。。