0.環境
Java : JDK 1.8
IDE : IDEA 2019
構建工具 : Gradle
複製代碼
1.總體思路
1.1 一些點
- 使用DispatcherServlet統一接收請求
- 自定義@Controller、@RequestMapping、@RequestParam註解來實現對應不一樣URI的方法調用
- 使用反射用HandlerMapping調用對應的方法
- 使用tomcat-embed-core內嵌web容器Tomcat.
- 自定義簡單的BeanFactory實現依賴注入DI,實現@Bean註解和@Controller註解的Bean管理
1.2 總體調用圖
1.3 啓動加載順序
2.具體實現
2.1 項目總體工程目錄
- 建立項目就不說了,IDEA自行建立gradle項目就好。
2.2 具體實現
- 在web.server下建立TomcatServer類
- 簡單來講就是實例化一個tomcat服務,並實例化一個DispatcherServlet加入到context中,設置支持異步,處理全部的請求
public class TomcatServer {
private Tomcat tomcat;
private String[] args;
public TomcatServer(String[] args) {
this.args = args;
}
public void startServer() throws LifecycleException {
tomcat = new Tomcat();
tomcat.setPort(6699);
tomcat.start();
Context context = new StandardContext();
context.setPath("");
context.addLifecycleListener(new Tomcat.FixContextListener());
DispatcherServlet dispatcherServlet = new DispatcherServlet();
Tomcat.addServlet(context, "dispatcherServlet", dispatcherServlet).setAsyncSupported(true);
context.addServletMappingDecoded("/", "dispatcherServlet");
tomcat.getHost().addChild(context);
Thread awaitThread = new Thread(() -> TomcatServer.this.tomcat.getServer().await(), "tomcat_await_thread");
awaitThread.setDaemon(false);
awaitThread.start();
}
}
複製代碼
- 在web.servlet中新建DispatcherServlet實現Servlet接口.
- 由於是作一個簡單的MVC,這裏我直接處理全部請求,不分GET和POST,能夠自行改進。
- 處理全部請求 只須要在service方法中處理便可。
- 簡單的思路是,用HandlerManager經過URI在Map對象中獲取到對應MappingHandler對象,而後調用handle方法。
@Override
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
try {
MappingHandler mappingHandler = HandlerManager.getMappingHandlerByURI(((HttpServletRequest) req).getRequestURI());
if (mappingHandler.handle(req, res)) {
return;
}
} catch (IllegalAccessException | InstantiationException | InvocationTargetException | ClassNotFoundException e) {
e.printStackTrace();
}
}
複製代碼
- 在web.handler中分別新建MappingHandler和HandlerManager兩個類。
- MappingHandler用來存儲URI調用信息,像URI、Method 和 調用參數 這些。以下
public class MappingHandler {
private String uri;
private Method method;
private Class<?> controller;
private String[] args;
public MappingHandler(String uri, Method method, Class<?> controller, String[] args) {
this.uri = uri;
this.method = method;
this.controller = controller;
this.args = args;
}
}
複製代碼
- 而HandlerManager則是負責把對應的URI和處理的MappingHandler對應起來
- 實現就是用本身定義的類掃描器把全部掃描到的類傳進來遍歷,找出帶有Controller註解的類
- 而後針對每一個Controller中含有RequestMapping註解的方法信息構建MappingHandler對象進行註冊,放入Map中。
public class HandlerManager {
public static Map<String, MappingHandler> handleMap = new HashMap<>();
public static void resolveMappingHandler(List<Class<?>> classList) {
for (Class<?> cls : classList) {
if (cls.isAnnotationPresent(Controller.class)) {
parseHandlerFromController(cls);
}
}
}
private static void parseHandlerFromController(Class<?> cls) {
Method[] methods = cls.getDeclaredMethods();
for (Method method : methods) {
if (!method.isAnnotationPresent(RequestMapping.class)) {
continue;
}
String uri = method.getDeclaredAnnotation(RequestMapping.class).value();
List<String> paramNameList = new ArrayList<>();
for (Parameter parameter : method.getParameters()) {
if (parameter.isAnnotationPresent(RequestParam.class)) {
paramNameList.add(parameter.getDeclaredAnnotation(RequestParam.class).value());
}
}
String[] params = paramNameList.toArray(new String[paramNameList.size()]);
MappingHandler mappingHandler = new MappingHandler(uri, method, cls, params);
HandlerManager.handleMap.put(uri, mappingHandler);
}
}
public static MappingHandler getMappingHandlerByURI(String uri) throws ClassNotFoundException {
MappingHandler handler = handleMap.get(uri);
if (null == handler) {
throw new ClassNotFoundException("MappingHandler was not exist!");
} else {
return handler;
}
}
}
複製代碼
- 而後在MappingHandler中加入handle方法,對請求進行處理。
public boolean handle(ServletRequest req, ServletResponse res) throws IllegalAccessException, InstantiationException, InvocationTargetException, IOException {
String requestUri = ((HttpServletRequest) req).getRequestURI();
if (!uri.equals(requestUri)) {
return false;
}
Object[] parameters = new Object[args.length];
for (int i = 0; i < args.length; i++) {
parameters[i] = req.getParameter(args[i]);
}
Object ctl = BeanFactory.getBean(controller);
Object response = method.invoke(ctl, parameters);
res.getWriter().println(response.toString());
return true;
}
複製代碼
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Controller {
}
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface RequestMapping {
String value();
}
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface RequestParam {
String value();
}
複製代碼
- 建立類掃描器ClassScanner
- 思路也簡單,用Java的類加載器,把類信息讀入,放到一個List中返回便可。
- 我只處理了jar包類型。
public class ClassScanner {
public static List<Class<?>> scanClasses(String packageName) throws IOException, ClassNotFoundException {
List<Class<?>> classList = new ArrayList<>();
String path = packageName.replace(".", "/");
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
Enumeration<URL> resources = classLoader.getResources(path);
while (resources.hasMoreElements()) {
URL resource = resources.nextElement();
if (resource.getProtocol().contains("jar")) {
JarURLConnection jarURLConnection = (JarURLConnection) resource.openConnection();
String jarFilePath = jarURLConnection.getJarFile().getName();
classList.addAll(getClassesFromJar(jarFilePath, path));
} else {
}
}
return classList;
}
private static List<Class<?>> getClassesFromJar(String jarFilePath, String path) throws IOException, ClassNotFoundException {
List<Class<?>> classes = new ArrayList<>();
JarFile jarFile = new JarFile(jarFilePath);
Enumeration<JarEntry> jarEntries = jarFile.entries();
while (jarEntries.hasMoreElements()) {
JarEntry jarEntry = jarEntries.nextElement();
String entryName = jarEntry.getName();
if (entryName.startsWith(path) && entryName.endsWith(".class")) {
String classFullName = entryName.replace("/", ".").substring(0, entryName.length() - 6);
classes.add(Class.forName(classFullName));
}
}
return classes;
}
}
複製代碼
- 在beans包下建立BeanFactory類和@Autowired @Bean註解
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Autowired {
}
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Bean {
}
複製代碼
- 相信細心的必定看到了我MappingHandler裏面的handle方法實際上是用BeanFactory調用的getBean。
- BeanFactory的實現其實也很簡單。就是把類掃描器掃描到的類傳進來,吧帶有Controller和Bean註解的類放入map中,若是內部用Autowired註解就用內部依賴注入。只有單例模式。
public class BeanFactory {
private static Map<Class<?>, Object> classToBean = new ConcurrentHashMap<>();
public static Object getBean(Class<?> cls) {
return classToBean.get(cls);
}
public static void initBean(List<Class<?>> classList) throws Exception {
List<Class<?>> toCreate = new ArrayList<>(classList);
while (toCreate.size() != 0) {
int remainSize = toCreate.size();
for (int i = 0; i < toCreate.size(); i++) {
if (finishCreate(toCreate.get(i))) {
toCreate.remove(i);
}
}
if (toCreate.size() == remainSize) {
throw new Exception("cycle dependency!");
}
}
}
private static boolean finishCreate(Class<?> cls) throws IllegalAccessException, InstantiationException {
if (!cls.isAnnotationPresent(Bean.class) && !cls.isAnnotationPresent(Controller.class)) {
return true;
}
Object bean = cls.newInstance();
for (Field field : cls.getDeclaredFields()) {
if (field.isAnnotationPresent(Autowired.class)) {
Class<?> fieldType = field.getType();
Object reliantBean = BeanFactory.getBean(fieldType);
if (null == reliantBean) {
return false;
}
field.setAccessible(true);
field.set(bean, reliantBean);
}
}
classToBean.put(cls, bean);
return true;
}
}
複製代碼
- 啓動類
public class IlssApplication {
public static void run(Class<?> cls, String[] args) {
TomcatServer tomcatServer = new TomcatServer(args);
try {
tomcatServer.startServer();
List<Class<?>> classList = ClassScanner.scanClasses(cls.getPackage().getName());
BeanFactory.initBean(classList);
HandlerManager.resolveMappingHandler(classList);
} catch (Exception e) {
e.printStackTrace();
}
}
}
複製代碼
2.3 關於測試模塊 test
- 作測試 內部打包的時候須要在test項目中的build.gradle加入下面配置
jar {
manifest {
attributes "Main-Class": "io.ilss.framework.Application"
}
from {
configurations.compile.collect {
it.isDirectory() ? it : zipTree(it)
}
}
}
複製代碼
- 建立Service類
@Bean
public class NumberService {
public Integer calNumber(Integer num) {
return num;
}
}
複製代碼
- 建立Controller類
@Controller
public class TestController {
@Autowired
private NumberService numberService;
@RequestMapping("/getNumber")
public String getSalary(@RequestParam("name") String name, @RequestParam("num") String num) {
return numberService.calNumber(11111) + name + num ;
}
}
複製代碼
- 建立Application啓動類
public class Application {
public static void main(String[] args) {
IlssApplication.run(Application.class, args);
}
}
複製代碼
- 控制檯
gradle clean install
java -jar mvc-test/build/libs/mvc-test-1.0-SNAPSHOT.jar
複製代碼
- 訪問網址
http://localhost:6699/getNumber?name=aaa&num=123
待改進的一些點
- 異常處理,框架裏面的異常我不少都是直接答應堆棧信息,並無處理。
- BeanFactory很簡陋,由於是簡易,因此真的很簡易。不支持多例。你們能夠試試加
- 擴展性不好,小弟能力有限,但願大佬輕噴。
- .......
寫在最後
關於我
- 座標杭州,普通本科在讀,計算機科學與技術專業,20年畢業,目前處於實習階段。
- 主要作Java開發,會寫點Golang、Shell。對微服務、大數據比較感興趣,預備作這個方向。
- 目前處於菜鳥階段,各位大佬輕噴,小弟正在瘋狂學習。
- 歡迎你們和我交流鴨!!!