从Servlet 到 springmvc,简单聊聊
总结:先聊聊 servlet 其实是一套规范具体实现都是交给容器,如 tomcat,jetty 具体实现,
接口如下:
public interface Servlet { void init(ServletConfig var1) throws ServletException; ServletConfig getServletConfig(); void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException; String getServletInfo(); void destroy(); }
tip:
load-on-startup
元素标记容器是否应该在web应用程序启动的时候就加载这个Servlet,(实例化并调用其init()方法)。
它的值必须是一个整数,表示Servlet被加载的先后顺序。
如果该元素的值为负数或者没有设置,则容器会当Servlet被请求时再加载。
如果值为正整数或者0时,表示容器在应用启动时就加载并初始化这个Servlet,值越小,Servlet的优先级越高,就越先被加载。值相同时,容器就会自己选择顺序来加载。
生命周期如下:
- init () 方法进行加载资源等初始化。
- service() 方法来处理客户端的请求。
- destroy() 方法终止(结束)。
- 最后,Servlet 是由 JVM 的垃圾回收器进行垃圾回收的。
sevlert 单例 构造器 初始化函数 只会调用一次,在次请求 都是只 service()方法 不会再初始对象
经过上面简单复习,小伙伴是不是对servlet 有了简单的认识
下面教大家哦 实现 一个简单版mvc
web.xml 配置
<web-app> <display-name>Archetype Created Web Application</display-name> <servlet> <servlet-name>MySpringMvc</servlet-name> <servlet-class>com.lyc.framework.servlet.MyDispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>application.properties</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>MySpringMvc</servlet-name> <url-pattern>*.json</url-pattern> </servlet-mapping> </web-app>
初始化过程:我这里只是简单实现,看过源码的都知道 比我这多
this.initMultipartResolver(context); this.initLocaleResolver(context); this.initThemeResolver(context); this.initHandlerMappings(context); this.initHandlerAdapters(context); this.initHandlerExceptionResolvers(context); this.initRequestToViewNameTranslator(context); this.initViewResolvers(context); this.initFlashMapManager(context);
@Override public void init(ServletConfig config) throws ServletException { logger.info("MyDispatcherServlet init"); String configLocation = config.getInitParameter(localtion); // 初始化ioc容器 di 依赖注入 MyApplicationContext context = new MyApplicationContext(configLocation); // url 对应 实例化类 和 方法 为了 method.invoke(obj, args); initHandlerMappings(context); // 适配 参数,类型转换 反射 method.invoke(obj, args); initHandlerAdapters(context); //拿到结果 视图解析 initViewResolvers(context); }
下面讲一下 具体实现
第一步:扫包 获取全路径 以便class.forName
private void doRegister(String packageName) { URL url = this.getClass().getClassLoader().getResource("/"+packageName.replaceAll("\\.","/")); File dir = new File(url.getFile()); for (File file:dir.listFiles()) { if(file.isDirectory()){ doRegister(packageName+"/"+file.getName()); }else{ classCache.add(packageName.replace("/",".")+"."+file.getName().replace(".class","")); } } }
第二步 实例化 这里就是简单处理 类名小写 对应 实例化对象
private void doCreateBean() {
if(classCache.isEmpty()){
return;
}
try {
for (String className:classCache) {
Class<?> clazz = Class.forName(className);
if(clazz.isAnnotationPresent(MyController.class)){
instanceMapping.put(lowerFirstName(clazz.getSimpleName()),clazz.newInstance());
}else if(clazz.isAnnotationPresent(MyService.class)){
MyService myService = clazz.getAnnotation(MyService.class);
String id = myService.value();
if(!"".equals(id.trim())){
instanceMapping.put(id,clazz.newInstance());
}
Class<?>[] interfaces = clazz.getInterfaces();
for(Class i:interfaces){
instanceMapping.put(i.getName(),clazz.newInstance());
}
}
}
}catch (Exception e){
e.printStackTrace();
}
}
第三步 依赖注入 di
private void populate() {
if(classCache.isEmpty()){
return;
}
for(Map.Entry<String,Object> entry:instanceMapping.entrySet()){
Field[] fields = entry.getValue().getClass().getDeclaredFields();
for(Field field :fields){
if(!field.isAnnotationPresent(MyAutoWired.class)){
continue;
}
MyAutoWired myAutoWired = field.getAnnotation(MyAutoWired.class);
String id = myAutoWired.value().trim();
if("".equals(id)){
id = field.getType().getName();
}
field.setAccessible(true);
try {
field.set(entry.getValue(),instanceMapping.get(id));
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
经过上面的步骤已经 完成了 ioc di 的初始化 操作。
下面就来 简单的 url 匹配 对应的类#方法
代码如下
private void initHandlerMappings(MyApplicationContext context) {
Map<String, Object> ioc = context.findAll();
if (ioc.isEmpty()) {
return;
}
for (Map.Entry<String, Object> entry : ioc.entrySet()) {
Class<?> clazz = entry.getValue().getClass();
if (!clazz.isAnnotationPresent(MyController.class)) {
return;
}
String url = "";
if (clazz.isAnnotationPresent(MyRequestMapping.class)) {
MyRequestMapping myRequestMapping = clazz.getAnnotation(MyRequestMapping.class);
url += myRequestMapping.value();
}
Method[] methods = clazz.getMethods();
for (Method method : methods) {
if (!method.isAnnotationPresent(MyRequestMapping.class)) {
continue;
}
MyRequestMapping myRequestMapping = method.getAnnotation(MyRequestMapping.class);
String mappingUrl = url + myRequestMapping.value();
handlerMapping.put(mappingUrl, new Handler(entry.getValue(), method));
}
}
}
找到url 对应的类 实例 和方法 了就要解析参数了 代码如下:
找到 参数类型 位置
private void initHandlerAdapters(MyApplicationContext context) {
if (handlerMapping.isEmpty()) {
return;
}
// 参数的类型为key index为值
Map<String, Integer> paramMapping = new HashMap<>();
for (Map.Entry<String, Handler> entry : handlerMapping.entrySet()) {
Handler handler = entry.getValue();
Class<?>[] paramsTypes = handler.method.getParameterTypes();
// 参数有顺序 反射无法拿到参数的名字
for (int i = 0; i < paramsTypes.length; i++) {
Class<?> type = paramsTypes[i];
if (type == HttpServletRequest.class || type == HttpServletResponse.class) {
paramMapping.put(type.getName(), i);
continue;
}
}
Annotation[][] annos = handler.method.getParameterAnnotations();
for (int i = 0; i < annos.length; i++) {
for (Annotation a : annos[i]) {
if (a instanceof MyRequestParam) {
String param = ((MyRequestParam) a).value();
if (!"".equals(param)) {
paramMapping.put(param, i);
}
}
}
}
adapterMap.put(handler, new HandlerAdapter(paramMapping));
}
}
参数适配
class HandlerAdapter { private Map<String, Integer> paramMapping; public HandlerAdapter(Map<String, Integer> paramMapping) { this.paramMapping = paramMapping; } public ModelAndView handle(HttpServletRequest req, HttpServletResponse resp, Handler handler) throws InvocationTargetException, IllegalAccessException { Class<?>[] paramTypes = handler.method.getParameterTypes(); Object[] paramValues = new Object[paramTypes.length]; Map<String, String[]> map = req.getParameterMap(); for (Map.Entry<String, String[]> param : map.entrySet()) { String value = Arrays.toString(param.getValue()).replaceAll("\\[|\\]", "") .replaceAll(",\\s", ","); if (!this.paramMapping.containsKey(param.getKey())) { continue; } int index = this.paramMapping.get(param.getKey()); paramValues[index] = castStringValue(value, paramTypes[index]); } if (this.paramMapping.containsKey(HttpServletRequest.class.getName())) { int reqIndex = this.paramMapping.get(HttpServletRequest.class.getName()); paramValues[reqIndex] = req; } if (this.paramMapping.containsKey(HttpServletResponse.class.getName())) { int respIndex = this.paramMapping.get(HttpServletResponse.class.getName()); paramValues[respIndex] = resp; } Class returnType = handler.method.getReturnType(); boolean isModelAndView = returnType == ModelAndView.class; Object result = handler.method.invoke(handler.controller, paramValues); if (isModelAndView) { return (ModelAndView) result; } return null; } }
类型转换 贴一点 意思一下
private Object castStringValue(String value, Class<?> clazz) { if (clazz == String.class) { return value; } else if (clazz == Integer.class) { return Integer.parseInt(value); } else if (clazz == Boolean.class) { return Boolean.parseBoolean(value);
使用
post 里面 调用
private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws IOException, InvocationTargetException, IllegalAccessException { Handler handler = getHandler(req); if (handler == null) { resp.getWriter().write("404 Not Found"); } HandlerAdapter handlerAdapter = getHandlerAdapter(handler); ModelAndView modelAndView = handlerAdapter.handle(req, resp, handler); applyDefaultView(resp, modelAndView); }
简单的 mvc 就写完了,是不是 有点小收货
elk