南昌簡單做網(wǎng)站/我想找一個營銷團隊
依賴包
新建一個工程,包含兩個 module:
springboot 模塊,表示 springboot 源碼實現(xiàn);
user 模塊,表示業(yè)務系統(tǒng),使用 springboot 模塊;
依賴包:Spring、SpringMVC、Tomcat 等,引入依賴如下:
<dependencies><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.3.18</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-web</artifactId><version>5.3.18</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-webmvc</artifactId><version>5.3.18</version></dependency><dependency><groupId>javax.servlet</groupId><artifactId>javax.servlet-api</artifactId><version>4.0.1</version></dependency><dependency><groupId>org.apache.tomcat.embed</groupId><artifactId>tomcat-embed-core</artifactId><version>9.0.60</version></dependency>
</dependencies>
在 user 模塊下引入依賴:
<dependencies><dependency><groupId>org.example</groupId><artifactId>springboot</artifactId><version>1.0-SNAPSHOT</version></dependency>
</dependencies>
定義對應的 controller 和 service:
@RestController
public class UserController {@Autowiredprivate UserService userService;@GetMapping("test")public String test(){return userService.test();}
}
最終希望通過啟動 MyApplication 的 main 方法,啟動項目,能訪問到 UserController。
核心注解和核心類
SpringBoot 的核心類和注解:
@SpringBootApplication,這個注解是加在應用啟動類上的,也就是 main 方法所在的類;
SpringApplication,這個類中有個 run() 方法,用來啟動 SpringBoot 應用的;
所以,自定義類和注解以實現(xiàn)上面的功能。
@FireSpringBootApplication 注解:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Configuration
@ComponentScan
public @interface FireSpringBootApplication {
}
FireSpringApplication 啟動類:
public class FireSpringApplication {public static void run(Class clazz){}
}
在 MyApplication 中使用:
@FireSpringBootApplication
public class MyApplication {public static void main(String[] args) {FireSpringApplication.run(MyApplication.class);}
}
run 方法
需要在 run 方法中啟動 tomcat,通過 tomcat 接收請求;
DispatchServlet 綁定 spring 容器,DispatchServlet 接收到請求后需要在 spring 容器中找到一個 controller 中對應的方法;
run 方法中需要實現(xiàn)的邏輯:
- 創(chuàng)建一個 Spring 容器
- 創(chuàng)建 Tomcat 對象
- 生成 DispatcherServlet 對象,并且和前面創(chuàng)建出來的 Spring 容器進行綁定
- 將 DispatcherServlet 添加到 Tomcat 中
- 啟動 Tomcat
創(chuàng)建 Spring 容器
public class FireSpringApplication {public static void run(Class clazz){AnnotationConfigWebApplicationContext applicationContext = new AnnotationConfigWebApplicationContext();applicationContext.register(clazz);applicationContext.refresh();}
}
run 方法中傳入的即使 MyApplication 類,被解析為 Spring 容器的配置類;
默認會將 MyApplication 所在的包作為掃描路徑,從而掃描到 UserController 和 UserService,所以在 spring 容器啟動后就會存在兩個 bean 了;
啟動 Tomcat
使用內(nèi)嵌的 Tomact,即 Embed-Tomcat,啟動代碼如下:
public static void startTomcat(WebApplicationContext applicationContext){Tomcat tomcat = new Tomcat();Server server = tomcat.getServer();Service service = server.findService("Tomcat");Connector connector = new Connector();// 綁定端口connector.setPort(8081);Engine engine = new StandardEngine();engine.setDefaultHost("localhost");Host host = new StandardHost();host.setName("localhost");String contextPath = "";Context context = new StandardContext();context.setPath(contextPath);context.addLifecycleListener(new Tomcat.FixContextListener());host.addChild(context);engine.addChild(host);service.setContainer(engine);service.addConnector(connector);// 添加DispatcherServlet,并且綁定一個Spring容器tomcat.addServlet(contextPath, "dispatcher", new DispatcherServlet(applicationContext));// 設置Mapping關系context.addServletMappingDecoded("/*", "dispatcher");try {tomcat.start();} catch (LifecycleException e) {e.printStackTrace();}}
在 run 方法中調(diào)用 startTomcat 方法啟動 tomcat:
public static void run(Class clazz){AnnotationConfigWebApplicationContext applicationContext = new AnnotationConfigWebApplicationContext();applicationContext.register(clazz);applicationContext.refresh();// 啟動tomcatstartTomcat(applicationContext);}
到此,一個簡單的 SpringBoot 就寫出來了,運行 MyApplication 正常啟動項目,通過瀏覽器就可以訪問 UserController 了。
實現(xiàn) Tomcat 和 Jetty 的切換
前面代碼中默認啟動的是 Tomcat,現(xiàn)在想改成這樣子:
- 如果項目中有 Tomcat 的依賴,那就啟動 Tomcat
- 如果項目中有 Jetty的依賴就啟動 Jetty
- 如果兩者都沒有則報錯
- 如果兩者都有也報錯
這個邏輯希望 SpringBoot 自動實現(xiàn),對于程序員用戶而言,只要在 Pom 文件中添加相關依賴就可以了,想用 Tomcat 就加 Tomcat 依賴,想用 Jetty 就加 Jetty 依賴。
Tomcat 和 Jetty 都是應用服務器,或者是 Servlet 容器,可以定義接口來表示它們,這個接口交 WebServer(SpringBoot 源碼中也叫這個)。
定義接口如下:
public interface WebServer {public void start();}
Tomcat 實現(xiàn)類:
public class TomcatWebServer implements WebServer{@Overridepublic void start() {System.out.println("啟動Tomcat");}
}
Jetty 實現(xiàn)類:
public class JettyWebServer implements WebServer{@Overridepublic void start() {System.out.println("啟動Jetty");}
}
在 FireSpringApplication 中的 run 方法中,去獲取對應的 WebServer,然后啟動對應的 webServer。
代碼如下:
public static void run(Class clazz){AnnotationConfigWebApplicationContext applicationContext = new AnnotationConfigWebApplicationContext();applicationContext.register(clazz);applicationContext.refresh();// 自動獲取配置的Tomcat或者Jetty容器WebServer webServer = getWebServer(applicationContext);webServer.start();}public static WebServer getWebServer(ApplicationContext applicationContext){return null;
}
模擬實現(xiàn)條件注解
首先實現(xiàn)一個條件注解@FireConditionalOnClass
,對應代碼如下:
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Conditional(FireOnClassCondition.class)
public @interface FireConditionalOnClass {String value() default "";
}
注意核心為@Conditional(FireOnClassCondition.class)
中的 FireOnClassCondition,因為它才是真正得條件邏輯:
public class FireOnClassCondition implements Condition {@Overridepublic boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {Map<String, Object> annotationAttributes = metadata.getAnnotationAttributes(FireConditionalOnClass.class.getName());String className = (String) annotationAttributes.get("value");try {context.getClassLoader().loadClass(className);return true;} catch (ClassNotFoundException e) {return false;}}
}
具體邏輯為,拿到@FireConditionalOnClass
中的 value 屬性,然后用類加載器進行加載,如果加載到了所指定的這個類,那就表示符合條件,如果加載不到,則表示不符合條件。
模擬實現(xiàn)自動配置類
配置類代碼如下:
@Configuration
public class WebServiceAutoConfiguration {@Bean@FireConditionalOnClass("org.apache.catalina.startup.Tomcat")public TomcatWebServer tomcatWebServer(){return new TomcatWebServer();}@Bean@FireConditionalOnClass("org.eclipse.jetty.server.Server")public JettyWebServer jettyWebServer(){return new JettyWebServer();}
}
表示org.apache.catalina.startup.Tomcat
存在,則有 tomcatWebServer 這個bean;
表示org.eclipse.jetty.server.Server
存在,則有 jettyWebServer 這個bean;
FireSpringApplication#getWebServer()方法實現(xiàn):
public static WebServer getWebServer(ApplicationContext applicationContext){// key為beanName, value為Bean對象Map<String, WebServer> webServers = applicationContext.getBeansOfType(WebServer.class);if (webServers.isEmpty()) {throw new NullPointerException();}if (webServers.size() > 1) {throw new IllegalStateException();}// 返回唯一的一個return webServers.values().stream().findFirst().get();
}
這樣整體 SpringBoot 啟動邏輯就是這樣的:
- 創(chuàng)建一個 AnnotationConfigWebApplicationContext 容器
- 解析 MyApplication 類,然后進行掃描
- 通過 getWebServer 方法從 Spring 容器中獲取 WebServer 類型的 Bean
- 調(diào)用 WebServer 對象的 start 方法
發(fā)現(xiàn)自動配置類
WebServiceAutoConfiguration 需要被 SpringBoot 發(fā)現(xiàn),可以通過 SPI 機制實現(xiàn),比較 JDK 自帶的 SPI 來實現(xiàn)。
在 springboot 項目中的 resources 目錄下添加目錄META-INF/services
和文件 org.example.springboot.AutoConfiguration
,文件內(nèi)容為org.example.springboot.WebServiceAutoConfiguration
。
接口:
public interface AutoConfiguration {
}
WebServiceAutoConfiguration 實現(xiàn)該接口:
@Configuration
public class WebServiceAutoConfiguration implements AutoConfiguration {@Bean@FireConditionalOnClass("org.apache.catalina.startup.Tomcat")public TomcatWebServer tomcatWebServer(){return new TomcatWebServer();}@Bean@FireConditionalOnClass("org.eclipse.jetty.server.Server")public JettyWebServer jettyWebServer(){return new JettyWebServer();}
}
再利用 spring 中的@Import
技術來導入這些配置類,我們在@FireSpringBootApplication
的定義上增加如下代碼:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Configuration
@ComponentScan
@Import(FireImportSelect.class)
public @interface FireSpringBootApplication {
}
FireImportSelect:
public class FireImportSelect implements DeferredImportSelector {@Overridepublic String[] selectImports(AnnotationMetadata importingClassMetadata) {ServiceLoader<AutoConfiguration> serviceLoader = ServiceLoader.load(AutoConfiguration.class);List<String> list = new ArrayList<>();for (AutoConfiguration autoConfiguration : serviceLoader) {list.add(autoConfiguration.getClass().getName());}return list.toArray(new String[0]);}
}
如此,Spring 容器可以裝載 WebServiceAutoConfiguration 配置類了,對于 user 模塊而言,不需要修改代碼就可以自動識別 Tomcat 和 Jetty 了。
總結(jié)
到此,實現(xiàn)了一個簡單版本的 SpringBoot,因為 SpringBoot 首先是基于 Spring 的,而且提供的功能也更加強大,后面會對這些功能進行更深入的剖析。