南昌簡(jiǎn)單做網(wǎng)站/我想找一個(gè)營(yíng)銷團(tuán)隊(duì)
依賴包
新建一個(gè)工程,包含兩個(gè) module:
springboot 模塊,表示 springboot 源碼實(shí)現(xiàn);
user 模塊,表示業(yè)務(wù)系統(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>
定義對(duì)應(yīng)的 controller 和 service:
@RestController
public class UserController {@Autowiredprivate UserService userService;@GetMapping("test")public String test(){return userService.test();}
}
最終希望通過(guò)啟動(dòng) MyApplication 的 main 方法,啟動(dòng)項(xiàng)目,能訪問(wèn)到 UserController。
核心注解和核心類
SpringBoot 的核心類和注解:
@SpringBootApplication,這個(gè)注解是加在應(yīng)用啟動(dòng)類上的,也就是 main 方法所在的類;
SpringApplication,這個(gè)類中有個(gè) run() 方法,用來(lái)啟動(dòng) SpringBoot 應(yīng)用的;
所以,自定義類和注解以實(shí)現(xiàn)上面的功能。
@FireSpringBootApplication 注解:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Configuration
@ComponentScan
public @interface FireSpringBootApplication {
}
FireSpringApplication 啟動(dòng)類:
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 方法中啟動(dòng) tomcat,通過(guò) tomcat 接收請(qǐng)求;
DispatchServlet 綁定 spring 容器,DispatchServlet 接收到請(qǐng)求后需要在 spring 容器中找到一個(gè) controller 中對(duì)應(yīng)的方法;
run 方法中需要實(shí)現(xiàn)的邏輯:
- 創(chuàng)建一個(gè) Spring 容器
- 創(chuàng)建 Tomcat 對(duì)象
- 生成 DispatcherServlet 對(duì)象,并且和前面創(chuàng)建出來(lái)的 Spring 容器進(jìn)行綁定
- 將 DispatcherServlet 添加到 Tomcat 中
- 啟動(dòng) 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 容器的配置類;
默認(rèn)會(huì)將 MyApplication 所在的包作為掃描路徑,從而掃描到 UserController 和 UserService,所以在 spring 容器啟動(dòng)后就會(huì)存在兩個(gè) bean 了;
啟動(dòng) Tomcat
使用內(nèi)嵌的 Tomact,即 Embed-Tomcat,啟動(dòng)代碼如下:
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,并且綁定一個(gè)Spring容器tomcat.addServlet(contextPath, "dispatcher", new DispatcherServlet(applicationContext));// 設(shè)置Mapping關(guān)系context.addServletMappingDecoded("/*", "dispatcher");try {tomcat.start();} catch (LifecycleException e) {e.printStackTrace();}}
在 run 方法中調(diào)用 startTomcat 方法啟動(dòng) tomcat:
public static void run(Class clazz){AnnotationConfigWebApplicationContext applicationContext = new AnnotationConfigWebApplicationContext();applicationContext.register(clazz);applicationContext.refresh();// 啟動(dòng)tomcatstartTomcat(applicationContext);}
到此,一個(gè)簡(jiǎn)單的 SpringBoot 就寫(xiě)出來(lái)了,運(yùn)行 MyApplication 正常啟動(dòng)項(xiàng)目,通過(guò)瀏覽器就可以訪問(wèn) UserController 了。
實(shí)現(xiàn) Tomcat 和 Jetty 的切換
前面代碼中默認(rèn)啟動(dòng)的是 Tomcat,現(xiàn)在想改成這樣子:
- 如果項(xiàng)目中有 Tomcat 的依賴,那就啟動(dòng) Tomcat
- 如果項(xiàng)目中有 Jetty的依賴就啟動(dòng) Jetty
- 如果兩者都沒(méi)有則報(bào)錯(cuò)
- 如果兩者都有也報(bào)錯(cuò)
這個(gè)邏輯希望 SpringBoot 自動(dòng)實(shí)現(xiàn),對(duì)于程序員用戶而言,只要在 Pom 文件中添加相關(guān)依賴就可以了,想用 Tomcat 就加 Tomcat 依賴,想用 Jetty 就加 Jetty 依賴。
Tomcat 和 Jetty 都是應(yīng)用服務(wù)器,或者是 Servlet 容器,可以定義接口來(lái)表示它們,這個(gè)接口交 WebServer(SpringBoot 源碼中也叫這個(gè))。
定義接口如下:
public interface WebServer {public void start();}
Tomcat 實(shí)現(xiàn)類:
public class TomcatWebServer implements WebServer{@Overridepublic void start() {System.out.println("啟動(dòng)Tomcat");}
}
Jetty 實(shí)現(xiàn)類:
public class JettyWebServer implements WebServer{@Overridepublic void start() {System.out.println("啟動(dòng)Jetty");}
}
在 FireSpringApplication 中的 run 方法中,去獲取對(duì)應(yīng)的 WebServer,然后啟動(dòng)對(duì)應(yīng)的 webServer。
代碼如下:
public static void run(Class clazz){AnnotationConfigWebApplicationContext applicationContext = new AnnotationConfigWebApplicationContext();applicationContext.register(clazz);applicationContext.refresh();// 自動(dòng)獲取配置的Tomcat或者Jetty容器WebServer webServer = getWebServer(applicationContext);webServer.start();}public static WebServer getWebServer(ApplicationContext applicationContext){return null;
}
模擬實(shí)現(xiàn)條件注解
首先實(shí)現(xiàn)一個(gè)條件注解@FireConditionalOnClass
,對(duì)應(yīng)代碼如下:
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Conditional(FireOnClassCondition.class)
public @interface FireConditionalOnClass {String value() default "";
}
注意核心為@Conditional(FireOnClassCondition.class)
中的 FireOnClassCondition,因?yàn)樗攀钦嬲脳l件邏輯:
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 屬性,然后用類加載器進(jìn)行加載,如果加載到了所指定的這個(gè)類,那就表示符合條件,如果加載不到,則表示不符合條件。
模擬實(shí)現(xiàn)自動(dòng)配置類
配置類代碼如下:
@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 這個(gè)bean;
表示org.eclipse.jetty.server.Server
存在,則有 jettyWebServer 這個(gè)bean;
FireSpringApplication#getWebServer()方法實(shí)現(xiàn):
public static WebServer getWebServer(ApplicationContext applicationContext){// key為beanName, value為Bean對(duì)象Map<String, WebServer> webServers = applicationContext.getBeansOfType(WebServer.class);if (webServers.isEmpty()) {throw new NullPointerException();}if (webServers.size() > 1) {throw new IllegalStateException();}// 返回唯一的一個(gè)return webServers.values().stream().findFirst().get();
}
這樣整體 SpringBoot 啟動(dòng)邏輯就是這樣的:
- 創(chuàng)建一個(gè) AnnotationConfigWebApplicationContext 容器
- 解析 MyApplication 類,然后進(jìn)行掃描
- 通過(guò) getWebServer 方法從 Spring 容器中獲取 WebServer 類型的 Bean
- 調(diào)用 WebServer 對(duì)象的 start 方法
發(fā)現(xiàn)自動(dòng)配置類
WebServiceAutoConfiguration 需要被 SpringBoot 發(fā)現(xiàn),可以通過(guò) SPI 機(jī)制實(shí)現(xiàn),比較 JDK 自帶的 SPI 來(lái)實(shí)現(xiàn)。
在 springboot 項(xiàng)目中的 resources 目錄下添加目錄META-INF/services
和文件 org.example.springboot.AutoConfiguration
,文件內(nèi)容為org.example.springboot.WebServiceAutoConfiguration
。
接口:
public interface AutoConfiguration {
}
WebServiceAutoConfiguration 實(shí)現(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
技術(shù)來(lái)導(dǎo)入這些配置類,我們?cè)?code>@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 配置類了,對(duì)于 user 模塊而言,不需要修改代碼就可以自動(dòng)識(shí)別 Tomcat 和 Jetty 了。
總結(jié)
到此,實(shí)現(xiàn)了一個(gè)簡(jiǎn)單版本的 SpringBoot,因?yàn)?SpringBoot 首先是基于 Spring 的,而且提供的功能也更加強(qiáng)大,后面會(huì)對(duì)這些功能進(jìn)行更深入的剖析。