实战:自定义简易版SpringBoot
一、功能要求
自定义简易版SpringBoot,实现SpringBoot MVC及内嵌Tomcat启动、DispatcherServlet注册和组件扫描功能
程序通过main方法启动,可以自动启动tomcat服务器
可以自动创建和加载DispatcherServlet组件到ServletContext中
可以自动通过@ComponentScan扫描Controller等组件
Controller组件可以处理浏览器请求,返回响应结果
二、实现思路分析
传统SpringMVC框架web.xml的配置内容:
<web-app>
<!-- 初始化Spring上下文 -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- 指定Spring的配置文件 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/app-context.xml</param-value>
</context-param>
<!-- 初始化DispatcherServlet -->
<servlet>
<servlet-name>app</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value></param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>app</servlet-name>
<url-pattern>/app/*</url-pattern>
</servlet-mapping>
</web-app>
Spring官方文档中给出了基于Servlet3.0规范如何使用Java代码实现web.xml配置的example
public class MyWebApplicationInitializer implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext servletCxt) {
// Load Spring web application configuration
//通过注解的方式初始化Spring的上下文
AnnotationConfigWebApplicationContext ac = new AnnotationConfigWebApplicationContext();
//注册spring的配置类(替代传统项目中xml的configuration)
ac.register(AppConfig.class);
ac.refresh();
// Create and register the DispatcherServlet
//基于java代码的方式初始化DispatcherServlet
DispatcherServlet servlet = new DispatcherServlet(ac);
ServletRegistration.Dynamic registration = servletContext.addServlet("app", servlet);
registration.setLoadOnStartup(1);
registration.addMapping("/app/*");
}
}
- 当实现了Servlet3.0规范的容器(比如Tomcat7及以上版本)启动时
-
会通过SPI扩展机制自动扫描所有jar包里META-INF/services/javax.servlet.ServletContainerInitializer文件中指定的全路径类(该类需实现ServletContainerInitializer接口)
-
Servlet3.0+容器启动时将自动扫描类路径,以查找Spring的WebApplicationInitializer接口的所有实现类
-
将实现类放置到一个set集合中,提供给ServletContainerInitializer中onStartup的第一个参数使用
-
- 实例化ServletContainerInitializer实现类
- 回调类中的onStartup方法
- 在onStartup方法中(形参webAppInitializerClasses 已经封装了WebApplicationInitializer接口的所有实现类),可以回调WebApplicaitonInitializer的onStartup方法
- 在WebApplicaitonInitializer的onStartup方法中 进行传统SpringMVC框架web.xml的 内容配置
三、编码(实现方式一:使用框架的SpringServletContainerInitializer)
(1)创建maven工程,导入以下依赖:
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.0.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
<version>8.5.32</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
<version>8.5.32</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.0.8.RELEASE</version>
</dependency>
</dependencies>
(2)创建SpringApplication类,编写run方法(方法中要求完成tomcat的创建及启动)
public class SpringApplication {
public static void run(){
//创建tomcat实例
Tomcat tomcat = new Tomcat();
//设置tomcat端口
tomcat.setPort(8888);
try {
//设置项目文件路径(完成一些自动配置,否则SpringServletContainerInitializer的onStartup方法可能不执行)
tomcat.addWebapp("/", "G:\\");
//启动tomcat
tomcat.start();
// 监听关闭端口,阻塞式。没有这一句,方法执行完会直接结束
tomcat.getServer().await();
} catch (LifecycleException e) {
e.printStackTrace();
} catch (ServletException e) {
e.printStackTrace();
}
}
}
(3)创建spring的配置类 AppConfig该类上要通过@ComponentScan来进行包扫描
@Configuration
@ComponentScan("com.dabing")
public class AppConfig {
{
System.out.println("ComponentScan...........");
}
}
(4)创建MyWebApplicationInitializer实现WebApplicationInitializer接口,重写onstartup方法(WebApplicationInitializer实现web.xml的配置)
public class MyWebApplicationInitializer implements WebApplicationInitializer {
public void onStartup(ServletContext servletContext) throws ServletException {
System.out.println("初始化 MyWebApplicationInitializer");
//通过注解的方式初始化Spring的上下文
AnnotationConfigWebApplicationContext ac = new AnnotationConfigWebApplicationContext();
//注册spring的配置类(替代传统项目中xml的configuration)
ac.register(AppConfig.class);
// Create and register the DispatcherServlet
//基于java代码的方式初始化DispatcherServlet
DispatcherServlet servlet = new DispatcherServlet(ac);
ServletRegistration.Dynamic registration = servletContext.addServlet("/", servlet);
registration.setLoadOnStartup(1);
registration.addMapping("/*");
}
}
(5)编写一个Controller测试类及目标方法,响应输出“hello”即可
@RestController
public class TestController {
@RequestMapping("/test/hello")
public String test(){
System.out.println("--- hello ---");
return "hello";
}
}
(6)编写一个启动类Main,通过执行main方法启动服务
public class Main {
public static void main(String[] args) {
SpringApplication.run();
}
}
(7)通过浏览器对目标方法进行方法
四、扩展:验证当实现了Servlet3.0规范的容器启动时 会通过SPI扩展机制 自动扫描 所有jar包里 META-INF/services/javax.servlet.ServletContainerInitializer 文件中指定的全路径类
自定义MySpringServletContainerInitializer 进行验证
(1)创建maven工程,导入以下依赖:
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.0.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
<version>8.5.32</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
<version>8.5.32</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.0.8.RELEASE</version>
</dependency>
</dependencies>
(2)创建SpringApplication 类,编写run方法(方法中要求完成tomcat的创建及启动)
public class SpringApplication {
public static void run(){
//创建tomcat实例
Tomcat tomcat = new Tomcat();
//设置tomcat端口
tomcat.setPort(8888);
try {
//设置项目文件路径
tomcat.addWebapp("/", "G:\\");
//启动tomcat
tomcat.start();
// 监听关闭端口,阻塞式。没有这一句,方法执行完会直接结束
tomcat.getServer().await();
} catch (LifecycleException e) {
e.printStackTrace();
} catch (ServletException e) {
e.printStackTrace();
}
}
}
(3)创建spring的配置类 AppConfig该类上要通过@ComponentScan来进行包扫描
@Configuration
@ComponentScan("com.dabing")
public class AppConfig {
{
System.out.println("ComponentScan...........");
}
}
(4)创建MySpringServletContainerInitializer,实现ServletContainerInitializer接口,重写onstartup方法,方法中调用第4步中MyWebApplicationInitializer的onstartup方法
@HandlesTypes(WebApplicationInitializer.class)
public class MySpringServletContainerInitializer implements ServletContainerInitializer {
public void onStartup(Set<Class<?>> webAppInitializerClasses, ServletContext servletContext) throws ServletException {
System.out.println("初始化 MySpringServletContainerInitializer");
List<WebApplicationInitializer> initializers = new LinkedList<WebApplicationInitializer>();
if (webAppInitializerClasses != null) {
for (Class<?> waiClass : webAppInitializerClasses) {
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);
for (WebApplicationInitializer initializer : initializers) {
System.out.println("for--------------");
initializer.onStartup(servletContext);
}
}
}
(5)创建文件:META-INF/services/javax.servlet.ServletContainerInitializer,在该文件中配置ServletContainerInitializer的实现类MySpringServletContainerInitializer
com.dabing.web.MySpringServletContainerInitializer
(6)创建MyWebApplicationInitializer实现WebApplicationInitializer接口,重写onstartup方法(WebApplicationInitializer实现web.xml的配置)
public class MyWebApplicationInitializer implements WebApplicationInitializer {
public void onStartup(ServletContext servletContext) throws ServletException {
System.out.println("初始化 MyWebApplicationInitializer");
//通过注解的方式初始化Spring的上下文
AnnotationConfigWebApplicationContext ac = new AnnotationConfigWebApplicationContext();
//注册spring的配置类(替代传统项目中xml的configuration)
ac.register(AppConfig.class);
// Create and register the DispatcherServlet
//基于java代码的方式初始化DispatcherServlet
DispatcherServlet servlet = new DispatcherServlet(ac);
ServletRegistration.Dynamic registration = servletContext.addServlet("/", servlet);
if(registration==null) return;
registration.setLoadOnStartup(1);
registration.addMapping("/*");
}
}
(7)编写一个Controller测试类及目标方法,响应输出“hello”即可
@RestController
public class TestController {
@RequestMapping("/test/hello")
public String test(){
System.out.println("--- hello ---");
return "hello";
}
}
(8)编写一个启动类Main,通过执行main方法启动服务
public class Main {
public static void main(String[] args) {
SpringApplication.run();
}
}
(9)通过浏览器对目标方法进行方法