总结:先聊聊 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 就写完了,是不是 有点小收货