1. 测试案例
1.1 引入依赖
<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.5</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.tomcat.embed/tomcat-embed-core -->
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
<version>8.5.64</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.tomcat.embed/tomcat-embed-jasper -->
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
<version>8.5.64</version>
</dependency>
1.2 Servlet处理类
public class HelloServlet extends HttpServlet {
@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 {
resp.getWriter().write("hello tomcat");
}
}
1.3 启动类
public class Main {
public static void main(String[] args) throws LifecycleException {
//自己写Tomcat的启动源码
Tomcat tomcat = new Tomcat();
tomcat.setPort(8888);
tomcat.setHostname("localhost");
tomcat.setBaseDir(".");
Context context = tomcat.addWebapp("/boot", System.getProperty("user.dir") + "/src/main");
//给Tomcat里面添加一个Servlet
Wrapper hello = tomcat.addServlet("/boot", "hello", new HelloServlet());
hello.addMapping("/66"); //指定处理的请求
tomcat.start(); //启动tomcat 注解版MVC利用Tomcat SPI机制
tomcat.getServer().await(); //服务器等待
}
}
直接运行main方法,在浏览器中输入 http://localhost:8888/boot/66 ,输出为:
以上只是一个Tomcat
的启动并使用HelloServlet
处理一个请求的案例。下面我们结合SpringMVC,看如何优雅的启动。
2. 嵌入式Tomcat启动SpringMVC
2.1 最简单的方法
public class Main {
public static void main(String[] args) throws LifecycleException {
//自己写Tomcat的启动源码
Tomcat tomcat = new Tomcat();
tomcat.setPort(8888);
tomcat.setHostname("localhost");
tomcat.setBaseDir(".");
Context context = tomcat.addWebapp("/boot", System.getProperty("user.dir") + "/src/main");
//自己创建 DispatcherServlet 对象,并且创建ioc容器,DispatcherServlet里面有ioc容器
DispatcherServlet servlet = new DispatcherServlet();
Wrapper hello = tomcat.addServlet("/boot", "hello", servlet);
tomcat.start(); //启动tomcat 注解版MVC利用Tomcat SPI机制
tomcat.getServer().await(); //服务器等待
}
}
当然,如果使用这种方法启动,需要自己往容器中注入DispatcherServlet
的各种初始化(九大组件等),所以这种方式比较麻烦,我们不采用这种方式。
2.2 Tomcat的SPI机制启动
利用tomcat的SPI启动机制,SPI机制下 QuickAppStarter
生效创建 ioc容器配置DispatcherServlet
等各种组件。代码如下:
Main.java:
public class Main {
public static void main(String[] args) throws LifecycleException {
//自己写Tomcat的启动源码
Tomcat tomcat = new Tomcat();
tomcat.setPort(8888);
tomcat.setHostname("localhost");
tomcat.setBaseDir(".");
Context context = tomcat.addWebapp("/boot", System.getProperty("user.dir") + "/src/main");
tomcat.start(); //启动tomcat 注解版MVC利用Tomcat SPI机制
tomcat.getServer().await(); //服务器等待
}
}
QuickAppStarter类:
/**
* 最快速的整合注解版SpringMVC和Spring的
*/
public class QuickAppStarter extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override //根容器的配置(Spring的配置文件===Spring的配置类)
protected Class<?>[] getRootConfigClasses() {
return new Class<?>[]{SpringConfig.class};
}
@Override //web容器的配置(SpringMVC的配置文件===SpringMVC的配置类)
protected Class<?>[] getServletConfigClasses() {
return new Class<?>[]{SpringMVCConfig.class};
}
@Override //Servlet的映射,DispatcherServlet的映射路径
protected String[] getServletMappings() {
return new String[]{"/"};
}
@Override
protected void customizeRegistration(ServletRegistration.Dynamic registration) {
// super.customizeRegistration(registration);
// registration.addMapping("");//
}
}
启动后,在浏览器访问http://localhost:8888/boot/hello66 ,输出为:
SpringBoot就是采用上面的这种方式来启动Tomcat的。SpringBoot封装了功能的自动配置,导入各种starter依赖,SpringBoot封装了很多的自动配置,帮我们给容器中放了很多组件。
3. @SpringBootApplication 注解原理
1.@SpringBootApplication注解
对于Springboot项目,它有一个启动类。一般如下(此处以Springboot演示案例为例):
@SpringBootApplication
public class DemoSpringbootApplication{
public static void main(String[] args) {
SpringApplication.run(DemoSpringbootApplication.class, args);
}
}
从上述代码里看出,启动类引用了一个注解 @SpringBootApplication,而
@SpringBootApplication实际上是一个复合注解,它的类定义如下所示:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration // 继承了Configuration,表示当前是注解类
@EnableAutoConfiguration // 开启自动配置
@ComponentScan(excludeFilters = { // 扫描组件
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
...
}
实际上 @SpringBootApplication内的核心注解有三个: @SpringBootConfiguration, @EnableAutoConfiguration, @ComponentScan

3.1 @SpringBootConfiguration
@SpringBootConfiguration
实际上是引入的@Configuration
,而@Configuration
是Spring的注解类,用于定义配置类,并会将当前类内声明的一个或多个以@Bean
注解标记的方法的实例纳入到srping容器中,并且实例名就是方法名,等同于spring的XML配置文件。
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
}
@Configuration与XML配置的简单对比如下:
@Configuration
public class SpringDemo{
}
等价于XML形式的
:
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
3.2 @EnableAutoConfiguration
@EnableAutoConfiguration
是借助@Import
的帮助,将所有符合自动配置条件的bean定义加载到IoC容器,它也是复合注解,定义如下所示:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage //自动配置包
@Import({AutoConfigurationImportSelector.class}) //借助AutoConfigurationImportSelector自动配置类
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class<?>[] exclude() default {};
String[] excludeName() default {};
}
@EnableAutoConfiguration
里重要的注解分别是@AutoConfigurationPackage
和@Import(AutoConfigurationImportSelector.class)
,看名知意,@AutoConfigurationPackage
:自动配置包,AutoConfigurationImportSelector
:自动配置组件的导入。
3.2.1 @AutoConfigurationPackage
@AutoConfigurationPackage
具体定义如下所示:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({Registrar.class})
public @interface AutoConfigurationPackage {
}
由@AutoConfigurationPackage
定义可以看出,实际是借助@Import
导入了Registrar
,而Registrar
中主要调用了registerBeanDefinition
方法来进行bean定义的注册。Registrar类定义如下:
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
Registrar() {
}
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
//此处是注册了一个Bean的定义。
//getPackageName()其实返回了当前主程序类的 同级以及子级的包组件。
AutoConfigurationPackages.register(registry, (new AutoConfigurationPackages.PackageImport(metadata)).getPackageName());
}
public Set<Object> determineImports(AnnotationMetadata metadata) {
return Collections.singleton(new AutoConfigurationPackages.PackageImport(metadata));
}
}