由於Spring官方就是選擇gradle作爲自動化構建工具,所以我們在本次嘗試中就按照spring的選擇也是用gradle
在整個項目中,我們一共包含兩個模塊framework模塊用於首先實現我們springmvc的常見功能,test模塊則是用來測試我們手寫的模塊是否正確
項目鏈接:https://github.com/ZhangJia97/Mini-Spring
下面是項目結構,只保留了我們用到的文件結構
├── build.gradle
├── framework
│ ├── build.gradle
│ └── src
│ ├── main
│ ├── java
│ └── xyz
│ └── suiwo
│ └── imooc
│ ├── beans
│ │ ├── Autowired.java
│ │ ├── Bean.java
│ │ └── BeanFactory.java
│ ├── core
│ │ └── ClassScanner.java
│ ├── starter
│ │ └── MiniApplication.java
│ └── web
│ ├── handler
│ │ ├── HandlerManager.java
│ │ └── MappingHandler.java
│ ├── mvc
│ │ ├── Controller.java
│ │ ├── RequestMapping.java
│ │ └── RequestParam.java
│ ├── server
│ │ └── TomcatServer.java
│ └── servlet
│ └── DispatcherServlet.java
└── test
├── build.gradle
└── src
├── main
├── java
└── xyz
└── suiwo
└── imooc
├── Application.java
├── controller
│ └── SalaryController.java
└── service
└── SalaryService.java
首先我們需要在framework的依賴中添加tomcat的依賴,因爲springboot就是通過加入tomcat依賴來實現的
dependencies {
testCompile group: 'junit', name: 'junit', version: '4.12'
// https://mvnrepository.com/artifact/org.apache.tomcat.embed/tomcat-embed-core
compile group: 'org.apache.tomcat.embed', name: 'tomcat-embed-core', version: '8.5.23'
}
接下來讓我們看看如何去創建一個tomcat服務
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(8080);
Context context = new StandardContext();
context.setPath("");
context.addLifecycleListener(new Tomcat.FixContextListener());
DispatcherServlet dispatcherServlet = new DispatcherServlet();
// servlet註冊到tomcat容器內並開啓異步支持
Tomcat.addServlet(context, "dispatcherServlet", dispatcherServlet).setAsyncSupported(true);
context.addServletMappingDecoded("/", "dispatcherServlet");
// 註冊到默認host容器
tomcat.getHost().addChild(context);
tomcat.start();
Thread awaitThread = new Thread("tomcat_await_thread"){
@Override
public void run() {
TomcatServer.this.tomcat.getServer().await();
}
};
//設置成非守護線程
awaitThread.setDaemon(false);
awaitThread.start();
}
}
然後我們可以看到上述代碼向tomcat中set了一個dispatchServlet用於處理請求,我們看看DispatchServlet如何去處理請求
public class DispatcherServlet implements Servlet {
@Override
public void init(ServletConfig config) throws ServletException {
}
@Override
public ServletConfig getServletConfig() {
return null;
}
@Override
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
for(MappingHandler mappingHandler : HandlerManager.mappingHandlerList){
try {
if(mappingHandler.handle(req, res)){
return;
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
@Override
public String getServletInfo() {
return null;
}
@Override
public void destroy() {
}
}
我們現在已經成功創建了一個Tomcat的服務類,下面我們就可以在主類中啓動tomcat服務了
然後我們看一些framework的主類
public class MiniApplication {
public static void run(Class<?> cls, String[] args){
System.out.println("Hello Mini-Spring!");
// 創建一個Tomcat服務
TomcatServer tomcatServer = new TomcatServer(args);
try {
// 啓動tomcat
tomcatServer.startServer();
// 掃描項目中當前cls目錄下的所有包
List<Class<?>> classList = ClassScanner.scannerClass(cls.getPackage().getName());
// 初始化所有bean
BeanFactory.init(classList);
// 初始化所有的MappingHandler
HandlerManager.resolveMappingHandler(classList);
} catch (Exception e) {
e.printStackTrace();
}
}
}
我們再創建三個mvc相關的註解
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Controller {
}
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface RequestMapping {
String value() default "";
}
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface RequestParam {
String value() default "";
}
然後我們看一下ClassScanner類,這個類主要用於掃描包
public class ClassScanner {
public static List<Class<?>> scannerClass(String packageName) throws IOException, ClassNotFoundException {
List<Class<?>> classList= new ArrayList<>();
String path = packageName.replaceAll("\\.", "/");
// 獲取默認類加載器
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
// 獲取資源文件的路徑
Enumeration<URL> resources = classLoader.getResources(path);
while(resources.hasMoreElements()){
URL resource = resources.nextElement();
// 判斷資源類型
if(resource.getProtocol().contains("jar")){
// 如果資源類型是jar包,則我們先獲取jar包的絕對路徑
JarURLConnection jarURLConnection = (JarURLConnection) resource.openConnection();
String jarFilePath = jarURLConnection.getJarFile().getName();
// 獲取這個jar包下所有的類
classList.addAll(getClassesFromJar(jarFilePath, path));
}else {
// todo 處理非jar包的情況
}
}
return classList;
}
private static List<Class<?>> getClassesFromJar(String jarFilePath, String path) throws IOException, ClassNotFoundException {
//初始化一個容器用於存儲類
List<Class<?>> classes = new ArrayList<>();
// 通過路徑獲取JarFile實例
JarFile jarFile = new JarFile(jarFilePath);
// 遍歷jar包,每個jarEntry都是jar包裏的一個文件
Enumeration<JarEntry> jarEntryEnumeration = jarFile.entries();
while(jarEntryEnumeration.hasMoreElements()){
JarEntry jarEntry = jarEntryEnumeration.nextElement();
String entryName = jarEntry.getName(); // xyz/suiwo/imooc/test/Test.class
if(entryName.startsWith(path) && entryName.endsWith(".class")){
// 把分隔符換成點,並去除.class後綴
String classFullName = entryName.replace("/", ".").substring(0, entryName.length() - 6);
classes.add(Class.forName(classFullName));
}
}
return classes;
}
}
作爲spring的經典ioc思想,初始化創建bean是重中之重,下面讓我們看看如何實現吧
對於常見與Bean相關的註解就是@Bean
還有@Autowired
所以我們首先創建兩個註解
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Autowired {
String value() default "";
}
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Bean {
String value() default "";
}
下面我們看看如何去初始化bean吧
public class BeanFactory {
private static Map<Class<?>, Object> classToBean = new ConcurrentHashMap<>();
public static Object getBean(Class<?> cls){
return classToBean.get(cls);
}
public static void init(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++){
// 返回true則說明創建成功或者說當前類不是一個bean
// 返回false則此時可能存存在當前需要創建的bean的依賴還沒有創建所以暫時先跳過
if(finishCreate(toCreate.get(i))){
toCreate.remove(i);
}
}
// 如果數量沒有改變則說明出現了死循環,拋出異常
if(toCreate.size() == remainSize){
throw new Exception("死循環");
}
}
}
private static boolean finishCreate(Class<?> cls) throws IllegalAccessException, InstantiationException {
// 如果沒有滿足的註解,則直接返回true
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);
// 如果爲空,則說明當前類中的字段所依賴的類還沒有注入,所以返回false,先跳過,等到所需要依賴注入之後再創建
if(reliantBean == null){
return false;
}
field.setAccessible(true);
field.set(bean, reliantBean);
}
}
// 將創建好的bean放入容器中
classToBean.put(cls, bean);
return true;
}
}
然後我們來看一下控制器,每一個MappingHandler都是一個請求映射器
public class MappingHandler {
// 需要處理的uri
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;
}
// 若與MappingHandler匹配成功,執行方法
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;
}
}
我們在創建一個管理器去管理這些MappingHandler
public class HandlerManager {
public static List<MappingHandler> mappingHandlerList = new ArrayList<>();
// 把Controller類挑選出來,並將類中的帶有@RequestMapping方法初始化成MappingHandler
public static void resolveMappingHandler(List<Class<?>> classList){
for(Class<?> cls : classList){
if(cls.isAnnotationPresent(Controller.class)){
parseHandlerFromController(cls);
}
}
}
// 解析controller類
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.mappingHandlerList.add(mappingHandler);
}
}
}
至此,我們就已經成功的將整個框架大致完成了,對於test模塊中的代碼,我就不在這裏在書寫了,因爲和我們日常寫springboot業務相同只是爲了測試我們手寫框架的幾個功能。