前提:
spring對于java程式員來說,無疑就是吃飯到筷子。在每次程式設計工作到時候,我們幾乎都離不開它,相信無論過去,還是現在或是未來到一段時間,它仍會扮演着重要到角色。自己對spring有一定的自我見解,是以參考網上的視訊和文章,整理出一套簡單的SpirngMVC。
内容較多,項目位址先貼出來,接下來大概講下流程。
手寫簡單的SpringMvc架構。
主要分為幾個步驟:
- 掃描包下面的檔案。
- 根據掃描到到檔案,初始化bean工廠。
- 根據@Controller @RequestMapping 注解,處理映射關系。
- 啟動tomcat。
1. 項目基于maven,framework子產品是主要對SpringMVC架構,test隻是為了友善測試,單獨引用framework架構進行測試 。
2.接下來講下主要framework子產品。圖如下:
- (1)MiniApplication為架構的入口類。其主要有以下四個功能。
package com.chan.starter;
import com.chan.beans.BeanFactory;
import com.chan.core.ClassScanner;
import com.chan.web.handler.HandlerManage;
import com.chan.web.server.TomcatServer;
import org.apache.catalina.LifecycleException;
import java.io.IOException;
import java.util.List;
/**
* @description: 項目的入口
* @author: Chen
* @create: 2019-06-23 14:22
**/
public class MiniApplication {
public static void run(Class<?> clz,String[] agrs){
try {
//根據傳進來的clz所在的包取掃描包下的資料
List<Class<?>> classList = ClassScanner.scanClasses(clz.getPackage().getName());
try {
//根據掃描到到檔案,初始化bean工廠。
BeanFactory.init(classList);
//根據@Controller @RequestMapping 注解,處理映射關系。
HandlerManage.resolveMappingHandleList(classList);
} catch (Exception e) {
e.printStackTrace();
}
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
try {
//啟動tomcat
TomcatServer tomcatServer = new TomcatServer(agrs);
tomcatServer.startServer();
} catch (LifecycleException e) {
e.printStackTrace();
}
}
}
- (2)功能一 掃描包下的檔案。
package com.chan.core;
import java.io.File;
import java.io.IOException;
import java.net.JarURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
/**
* @description:類掃描器
* @author: Chen
* @create: 2019-07-02 22:29
**/
public class ClassScanner {
/**
* 掃描包下的檔案并傳回。
* 如果是jar包,則取jar的檔案。如果是檔案夾,這遞歸取
* @param packegeName
* @return
* @throws IOException
* @throws ClassNotFoundException
*/
public static List<Class<?>> scanClasses(String packegeName) throws IOException, ClassNotFoundException {
List<Class<?>> classList = new ArrayList<>();
String path = packegeName.replace(".","/");
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
Enumeration<URL> resources = classLoader.getResources(path);
while (resources.hasMoreElements()){
URL resource = resources.nextElement();
//如果資源是jar包,那麼周遊jar裡面到檔案
if (resource.getProtocol().contains("jar")){
JarURLConnection jarURLConnection = (JarURLConnection) resource.openConnection();
String jarFilePath = jarURLConnection.getJarFile().getName();
classList.addAll(getClassesFromJar(path,jarFilePath));
}
//是檔案的話,遞歸取的檔案
else if (resource.getProtocol().contains("file")){
File dir = new File(resource.getFile());
for (File file:dir.listFiles()){
if (file.isDirectory()){
classList.addAll(scanClasses(packegeName + "." + file.getName()));
}else {
String className =packegeName +"." +file.getName().replace(".class", "");
classList.add(Class.forName(className));
}
}
}
}
return classList;
}
private static List<Class<?>> getClassesFromJar(String path, String jarFilePath) throws IOException, ClassNotFoundException {
List<Class<?>> classList = new ArrayList<>();
JarFile jarFile = new JarFile(jarFilePath);
Enumeration<JarEntry> jarEntrys = jarFile.entries();
while (jarEntrys.hasMoreElements()){
JarEntry jarEntry = jarEntrys.nextElement();
String name = jarEntry.getName();
if (name.startsWith(path)&&name.endsWith(".class")){
String classFullName = name.replace("/", ".").substring(0, name.length() - 6);
classList.add(Class.forName(classFullName));
}
}
return classList;
}
}
(3) 根據掃描到到檔案,初始化bean工廠。
package com.chan.beans;
import com.chan.web.mvc.Controller;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* @description: bean 工廠
* @author: Chen
* @create: 2019-07-03 00:03
**/
public class BeanFactory {
/**
* 配置bean容器 存放bean
*/
private static Map<Class<?>,Object> beanMap = new ConcurrentHashMap<>();
/**
* 根據類 擷取bean
* @param clz
* @return
*/
public static Object getBean(Class<?> clz){return beanMap.get(clz);}
/**
* 根據掃描到到類 依次按照規則注入到bean容器中
* @param classList
* @throws Exception
*/
public static void init(List<Class<?>> classList) throws Exception {
List<Class<?>> toCreate = new ArrayList<>(classList);
/**
* 将滿足注入條件循環注入bean容器
*/
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 (remainSize==toCreate.size()){
throw new Exception("cycle dependency");
}
}
}
/**
* 判斷是否是需要注入到bean
* 如果不是 直接傳回true 并且添加到bean容器
* 如果是,但是當時不滿足注入條件 傳回false 等到下次循環再調用
* 直至滿足條件,添加到bean容器中
* @param clz
* @return
* @throws IllegalAccessException
* @throws InstantiationException
*/
private static boolean finishCreate(Class<?> clz) throws IllegalAccessException, InstantiationException {
if (!clz.isAnnotationPresent(Controller.class)&&!clz.isAnnotationPresent(Bean.class)){
return true;
}
Object bean = clz.newInstance();
for (Field field : clz.getDeclaredFields()){
if (field.isAnnotationPresent(AutoWired.class)){
Class<?> fieldType = field.getType();
Object filedTypeObj = BeanFactory.getBean(fieldType);
if (filedTypeObj==null){
return false;
}
field.setAccessible(true);
field.set(bean,filedTypeObj);
}
}
beanMap.put(clz,bean);
return true;
}
}
(4)根據@Controller @RequestMapping 注解,處理映射關系。
package com.chan.web.handler;
import com.chan.web.mvc.Controller;
import com.chan.web.mvc.RequestMapping;
import com.chan.web.mvc.RequestParam;
import com.sun.glass.events.mac.NpapiEvent;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.List;
/**
* @description: 映射處理管理中心
* @author: Chen
* @create: 2019-07-03 00:34
**/
public class HandlerManage {
/**
* 儲存需要映射的清單
*/
public static List<MappingHandle> mappingHandleList = new ArrayList<>();
public static void resolveMappingHandleList(List<Class<?>> classList){
for (Class<?> clz :classList){
if (clz.isAnnotationPresent(Controller.class)){
parseHandlerFromController(clz);
}
}
}
private static void parseHandlerFromController(Class<?> clz) {
Method[] methods = clz.getMethods();
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[] args = paramNameList.toArray(new String[paramNameList.size()]);
MappingHandle mappingHandle = new MappingHandle(uri,method,clz,args);
HandlerManage.mappingHandleList.add(mappingHandle);
}
}
}
(5)配置DispatcherServlet啟動tomcat。
package com.chan.web.server;
import com.chan.util.YmlUtil;
import com.chan.web.servlet.DispatcherServlet;
import org.apache.catalina.Context;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.startup.Tomcat;
import javax.servlet.Servlet;
/**
* @description:根據 tomcat 的包進行處理
* @author: Chen
* @create: 2019-06-23 14:58
**/
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(YmlUtil.get("server.port"));
tomcat.start();
Context context = new StandardContext();
context.setPath("");
context.addLifecycleListener(new Tomcat.FixContextListener());
DispatcherServlet servlet = new DispatcherServlet();
Tomcat.addServlet(context, "dispatcherServlet", servlet).setAsyncSupported(true);
context.addServletMappingDecoded("/", "dispatcherServlet");
tomcat.getHost().addChild(context);
Thread awaitThread = new Thread("tomcar-await-thread"){
@Override
public void run() {
TomcatServer.this.tomcat.getServer().await();
}
};
awaitThread.setDaemon(false);
awaitThread.start();
}
}
package com.chan.web.servlet;
import com.chan.web.handler.HandlerManage;
import com.chan.web.handler.MappingHandle;
import javax.servlet.*;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
/**
* @description: serlet擴充卡,所有的url都會進入到此處,
* 在此處根據@RequestMaping所映射到方法進行操作。
* @author: Chen
* @create: 2019-06-23 15:15
**/
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 {
System.out.println("HandlerManage.mappingHandleList:"+HandlerManage.mappingHandleList.size());
for (MappingHandle mappingHandle : HandlerManage.mappingHandleList){
try {
if (mappingHandle.handle(req,res)){
return;
}else {
System.out.println("false");
}
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
@Override
public String getServletInfo() {
return null;
}
@Override
public void destroy() {
}
}
package com.chan.web.handler;
import com.chan.beans.BeanFactory;
import org.apache.catalina.servlet4preview.http.HttpServletRequest;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* @description: 映射處理類
* @author: Chen
* @create: 2019-07-03 00:35
**/
public class MappingHandle {
String uri;
Method method;
Class<?> controller;
String[] agrs;
public MappingHandle(String uri,Method method,Class<?> controller,String[] agrs){
this.uri = uri;
this.method = method;
this.controller = controller;
this.agrs = agrs;
}
/**
* 将掃描到到RequestMapping的路徑 進行處理
* 通過反射調用 調用該方法
* @param req
* @param res
* @return
* @throws InvocationTargetException
* @throws IllegalAccessException
* @throws IOException
*/
public boolean handle(ServletRequest req, ServletResponse res) throws InvocationTargetException, IllegalAccessException, IOException {
String requestURI = ((HttpServletRequest)req).getRequestURI();
System.out.println("uri:"+uri);
System.out.println("requestURI:"+requestURI.substring(1));
if (!uri.equals(requestURI.substring(1))){
return false;
}
String[] params = new String[agrs.length];
for (int i =0;i< params.length;i++){
params[i] = req.getParameter(agrs[i]);
}
Object obj = BeanFactory.getBean(controller);
System.out.println(obj);
if (obj==null){
return false;
}
Object ret = method.invoke(obj,params);
System.out.println("ret:"+ret.toString());
res.getWriter().println(ret.toString());
return true;
}
}
3.測試framework子產品。
- application.yml 配置項目啟動的端口号。
- Application項目入口。
- controller 控制層
- service 業務層,在這裡注入類bean。
這裡和springboot類似,這隻是簡單Spirng架構的大概思路流程。
在尾巴處再次貼上項目手寫簡單的SpringMvc架構。
如果文章對您有幫助,github給個start吧。