前言
如果你稍微留意下commit就會這個項目早在好2個月前就創建了,我也一直想寫下,但是確實太懶了就一直拖着着。這幾天由於沒有太多開發任務就有想寫了。
依賴環境
-
jdk:1.8
-
tomcat8.5
-
servlet3.1.0
-
maven3.6.0
正文
1. 這個項目又兩個分支:webxml
和nowebxml
,我開始是基於servlet2.5寫的,後面我又想用servlet3.x了於是就兩個分支了(後面主要代碼都在nowebxml上)。
2. Servlet容器啓動會掃描,當前應用裏面每一個jar包的ServletContainerInitializer
的實現,然後就注入一個DispatcherServlet
就可以了,跟在web.xml配置一樣,只是這裏都在java代碼裏面了(看過spring代碼的同學可能一眼就看出了它也是有個SpringServletContainerInitializer
類作爲人口的)
public class SpringxServletContainerInitializer implements ServletContainerInitializer {
@Override
public void onStartup(Set<Class<?>> c, ServletContext ctx) {
ServletRegistration.Dynamic dispatcherServlet = ctx.addServlet("dispatcherServlet", DispatcherServlet.class);
dispatcherServlet.setInitParameter("scanPackage", "com.sunny.springx.example");
dispatcherServlet.addMapping("/*");
dispatcherServlet.setLoadOnStartup(1);
}
}
3.上面代碼可以看出有個scanPackage
參數,這個是告訴springx從那裏開始工作。DispatcherServlet
繼承HttpServlet
所以開始會調用init
方法,然後就是大概四步就可以完成一個簡單的springx
@Override
public void init(ServletConfig config) {
Instant start = Instant.now();
// 1.掃描相關類
doScanner(config.getInitParameter("scanPackage"));
// 2.初始化類
doInstance();
// 3. 注入
doAutoWried();
// 4. 初始化url
initHandlerMapping();
Instant end = Instant.now();
System.out.println("springx init in " + Duration.between(start, end).toMillis() + " ms");
}
4. 主要邏輯都在DispatcherServlet
這裏面
public class DispatcherServlet extends HttpServlet {
//存放class name
private List<String> classNames = new ArrayList<>();
// ioc 容器
private Map<String, Object> ioc = new HashMap<>();
//handMapping
private List<Handler> handlerMapping = new ArrayList<>();
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
try {
doDispatch(req, resp);
} catch (Exception e) {
resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
resp.getWriter().write("500 " + e.getMessage());
}
}
private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws IOException, InvocationTargetException, IllegalAccessException {
String url = req.getRequestURI();
String contextPath = req.getContextPath();
url = url.replace(contextPath, "").replaceAll("/+", "/");
Handler handler = getHandler(url);
//url 不存在404
if (Objects.isNull(handler)) {
resp.setStatus(HttpServletResponse.SC_NOT_FOUND);
resp.getWriter().write("not found url : " + url);
return;
}
// 獲取方法
Object invoke = handler.method.invoke(handler.controller, null);
resp.setStatus(HttpServletResponse.SC_OK);
resp.getWriter().write(invoke.toString());
}
@Override
public void init(ServletConfig config) {
Instant start = Instant.now();
// 1.掃描相關類
doScanner(config.getInitParameter("scanPackage"));
// 2.初始化類
doInstance();
// 3. 注入
doAutoWried();
// 4. 初始化url
initHandlerMapping();
Instant end = Instant.now();
System.out.println("springx init in " + Duration.between(start, end).toMillis() + " ms");
}
/**
* 獲取 handler
*
* @param url 請求url
* @return
*/
private Handler getHandler(String url) {
if (handlerMapping.isEmpty()) return null;
for (Handler handler : handlerMapping) {
Matcher matcher = handler.pattern.matcher(url);
if (!matcher.matches()) continue;
return handler;
}
return null;
}
private void initHandlerMapping() {
if (ioc.isEmpty()) return;
for (Map.Entry<String, Object> entry : ioc.entrySet()) {
// 獲取類
Class<?> clazz = entry.getValue().getClass();
//沒有controller註解的跳過
if (!clazz.isAnnotationPresent(Controller.class)) continue;
//根url
String rootUrl = "";
if (clazz.isAnnotationPresent(RequestMapping.class)) {
RequestMapping requestMapping = clazz.getAnnotation(RequestMapping.class);
rootUrl = requestMapping.value();
}
//掃描類全部方法
for (Method method : clazz.getMethods()) {
//只處理RequestMapping註解
if (!method.isAnnotationPresent(RequestMapping.class)) continue;
RequestMapping requestMapping = method.getAnnotation(RequestMapping.class);
// 拼接rootUrl 全局替換避免兩個斜槓 正則
String reg = ("/" + rootUrl + requestMapping.value()).replaceAll("/+", "/");
handlerMapping.add(new Handler(method, entry.getValue(), Pattern.compile(reg)));
System.out.println("mapping:" + reg + "," + method);
}
}
}
private void doAutoWried() {
if (ioc.isEmpty()) return;
for (Map.Entry<String, Object> entry : ioc.entrySet()) {
// 獲取類中全部的字段
Field[] fields = entry.getValue().getClass().getDeclaredFields();
for (Field field : fields) {
// 不包含autowried註解的跳過
if (!field.isAnnotationPresent(Autowried.class)) continue;
Autowried autowried = field.getAnnotation(Autowried.class);
String beanId = autowried.value().trim();
if (StringUtils.isBlank(beanId)) {
beanId = field.getName();
}
//設置授權
field.setAccessible(true);
try {
// 字段賦值
field.set(entry.getValue(), ioc.get(beanId));
} catch (IllegalAccessException ex) {
ex.printStackTrace();
}
}
}
}
private void doInstance() {
if (classNames.isEmpty()) return;
try {
for (String className : classNames) {
//根據名稱實例化加了controller和service註解的類
Class<?> clazz = Class.forName(className);
//beanId 默認類小寫
String beanId;
if (clazz.isAnnotationPresent(Controller.class)) {// 處理Controller註解
Controller controller = clazz.getAnnotation(Controller.class);
beanId = controller.value();
if (StringUtils.isBlank(beanId)) {
beanId = StringUtils.lowerFirstCase(clazz.getSimpleName());
}
ioc.put(beanId, clazz.newInstance());
} else if (clazz.isAnnotationPresent(Service.class)) { // 處理Service 註解
// 獲取註解
Service service = clazz.getAnnotation(Service.class);
beanId = service.value();
if (StringUtils.isBlank(beanId)) {
beanId = StringUtils.lowerFirstCase(clazz.getSimpleName());
}
Object instance = clazz.newInstance();
ioc.put(beanId, instance);
// 獲取類的實現
for (Class<?> anInterface : clazz.getInterfaces()) {
ioc.put(StringUtils.lowerFirstCase(anInterface.getSimpleName()), instance);
}
}
}
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
e.printStackTrace();
}
}
/**
* 加載路徑下的全部class name
*
* @param scanPackage 包開始掃描路徑
*/
private void doScanner(String scanPackage) {
// com.sunny.springx.example 路徑替換文件路徑
URL url = getClassLoader().getResource("/" + scanPackage.replaceAll("\\.", "/"));
assert url != null;
File classDir = new File(url.getFile());
for (File file : Objects.requireNonNull(classDir.listFiles())) {
//如果是文件夾繼續掃描
if (file.isDirectory()) {
doScanner(scanPackage + "." + file.getName());
} else {
String className = scanPackage + "." + file.getName().replaceAll(".class", "");
classNames.add(className);
}
}
}
private ClassLoader getClassLoader() {
return this.getClass().getClassLoader();
}
private class Handler {
Method method;
//方法對象實力
Object controller;
//url正則
Pattern pattern;
Handler(Method method, Object controller, Pattern pattern) {
this.method = method;
this.controller = controller;
this.pattern = pattern;
}
}
}
結尾
這個項目寫的比較粗糙,後面有時間我打算把spring的能力都在這個項目裏面體現下。比如參數綁定我就沒有實現,只是定義了註解還沒有實現。當然你也可以fork
去寫這玩玩,感謝圍觀
關注公衆號一起寫bug,一起嘻嘻哈哈