description = "Spring Web MVC"
apply plugin: "kotlin"
dependencies {
compile(project(":spring-webmvc"))
compile(group: "org.apache.tomcat.embed",name: "tomcat-embed-core",version: "8.5.64")
compile(group: "org.apache.tomcat",name: "tomcat-jasper",version: "9.0.50")
compile group: 'javax.servlet', name: 'javax.servlet-api', version: '3.1.0'
}
/**
* 启动这个 Tomcat 的时候注意版本,有的版本可能半天都跑不起来哦
*/
public class SpringApplication {
public static void run() {
try {
Tomcat tomcat = new Tomcat();
tomcat.setPort(8888);
tomcat.setBaseDir("IdeaProjects/spring-framework/spring-test-boot/src/main");
tomcat.addWebapp("/boot", "IdeaProjects/spring-framework/spring-test-boot/src/main");
// 第一种方法:给 Tomcat 中注册一个 Servlet 和 ServletMapping
// tomcat.addServlet("/boot", "myHttpServlet", new MyHttpServlet()).addMapping("/hello");
// 第二种方法: 直接给利用 Tomcat SPI 机制,实现父子容器加载,这种方式直接可以写 Controller 了
// 就不用上面这么麻烦了还有自己注册 Servlet 参考 QuickStartWebApplicationInitializer 这个类
tomcat.start();
tomcat.getServer().await();
} catch (LifecycleException e) {
e.printStackTrace();
}
}
}
可以将 MyHttpServlet 注册到 Tomcat 中,然后访问测试下看效果。
public class MyHttpServlet extends HttpServlet implements Serializable {
/** use serialVersionUID from Spring 1.2 for interoperability. */
private static final long serialVersionUID = 7099057708183571937L;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req,resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("doGet........");
resp.getWriter().write("Hello Tomcat.....
");
}
}
通过 Tomcat 中的第一种方式就可以把 Servlet 注册到 Tomcat 中,然后进行访问,效果如下:
但是第一种注册 Servlet 太麻烦了,第二种方式通过 Tomcat SPI 机制加载配置类来启动 Spring IOC 容器。
// mvc_tag: 表示我不扫描标有 web 注解的组件
// 这个就是父容器配置类,父子容器就是这么来的
@ComponentScan(value = "com.gwm.springboot",excludeFilters = {
@ComponentScan.Filter(type = FilterType.ANNOTATION,value = Controller.class)
})
@Configuration
public class SpringConfig {
}
// mvc_tag: 表示只扫描 SpringMVC 组件,注意要禁用默认过滤行为
// 这个就是子容器配置类,父子容器就是这么来的
@ComponentScan(value = "com.gwm.springboot",includeFilters = {
@ComponentScan.Filter(type = FilterType.ANNOTATION,value = Controller.class)
}, useDefaultFilters = false)
@Configuration
public class SpringMVCConfig {
}
Tomcat SPI 机制中暴露了一个接口 ServletContainerInitializer,在 Tomcat 启动生命周期过程中会去加载加载该接口的所有实现类,并且还会解析 @HandlersTypes 注解,解析到的结果存放到一个 Set
public interface ServletContainerInitializer {
void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException;
}
其中 spring-web 模块就去实现了这个 ServletContainerInitializer 接口,如图所示:
然后进入到这个实现类如下:
@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
/**
* 此处的 onStartup() 方法会被 tomcat 服务启动调用
* 这个方法主要是为 Web 容器
* 1、添加 WebServlet 请求
* 2、添加 WebFilter 过滤器
* 3、添加 WebListener 监听器
* @Param webAppInitializerClasses 表示加载所有被注解:@HandlesTypes(WebApplicationInitializer.class) 修饰的类装载进容器
* @Param servletContext Web 容器上下文
* @throws ServletException
*/
@Override
public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
throws ServletException {
// ...省略
for (WebApplicationInitializer initializer : initializers) {
initializer.onStartup(servletContext);
}
}
}
Tomcat 启动过程中会调用 onStartup() 方法,然后被注解 @HandlesType 修饰的子类都会被添加到 onStartup() 方法中的第一个参数 Set 集合中,进行挨个执行 onStartup() 方法。
AbstractAnnotationConfigDispatcherServletInitializer 类是 WebApplicationInitializer 的子类,所以最终 Tomcat 也会调用 onStartup() 方法执行里面的逻辑。
而且实现这个类对于父子容器理解非常清晰,代码如下:
public class QuickStartWebApplicationInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
/**
* mvc_tag: 获取 Spring 配置文件 在 mvc_fun_call1 被回调
*/
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class<?>[]{SpringConfig.class};
}
/**
* mvc_tag: 获取 SpringMVC 配置文件 在 mvc_fun_call2 被回调
*/
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class<?>[]{SpringMVCConfig.class};
}
/**
* mvc_tag: 获取 SpringMVC 根路径 在 mvc_fun_call3 被回调
*/
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
}
写个 Controller 类测试即可,代码如下:
@RestController
public class HelloController {
@Autowired
private HelloService helloService;
@RequestMapping("/hello")
public String hello() {
System.out.println("hellow -----------------");
helloService.sayHello();
return "hellow";
}
}
@Service
public class HelloServiceImpl implements HelloService {
@Override
public void sayHello() {
System.out.println("HelloServiceImpl execute....");
}
}
前端效果如下:
后端效果如下: