零XML配置启动springmvc项目

刚开始使用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.总结

本人也是一个小白,也是看到很多资料才领悟到的,但希望对大家有帮助。如有需要案例的请留言 。。。

发布了16 篇原创文章 · 获赞 10 · 访问量 7945

猜你喜欢

转载自blog.csdn.net/mnicsm/article/details/104136468