模拟Spring MVC实现web框架
Tomcat
前一篇笔记中介绍了Servlet以及Tomcat,servlet是一个行业的标准,tomcat在sevlet的标准上实现了它的标准,在tomcat中就是一堆的容器,其中servlet容器就是context容器,我们看下tomcat的sever.xml配置文件就可以知道一般我们不熟的应用都在context下面,context下面部署的就是我们的web应用,也就是sevlet容器,tomcat中的容器是这样分类的:
server容器—>Service容器–>Connector容器、Engine容器
Engine容器又有许多servlet容器也就是context容器,所以tomcat的启动过程是:
1.server容器;
2.Service容器;
3.Conector容器(BIO、NIO、NIO2);
4.context容器;
比如我们来看一段代码:
public static void main(String[] args) throws LifecycleException {
Tomcat tomcat = new Tomcat();
tomcat.setPort(8080);
Context context = tomcat.addWebapp("/", "D:\\study\\idea\\dev-project\\dev-springmvvc");
// AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(Test.class);
// Tomcat.addServlet(context, "dispatcherServlet", new DispatcherServlet(ac));
// context.addServletMapping("/*", "dispatcherServlet");
tomcat.start();
tomcat.getServer().await();
}
addWeApp
public Context addWebapp(String contextPath, String docBase) {
//添加一个conext容器到tomcat中,这里是使用的内嵌版本的tomcat
return this.addWebapp(this.getHost(), contextPath, docBase);
}
public Context addWebapp(Host host, String contextPath, String docBase) {
LifecycleListener listener = null;
try {
//得到一个tomat非常重要的监听器,ConfigClass
Class<?> clazz = Class.forName(this.getHost().getConfigClass());
listener = (LifecycleListener)clazz.getConstructor().newInstance();
} catch (ReflectiveOperationException var6) {
throw new IllegalArgumentException(var6);
}
//添加context(servlet)容器
return this.addWebapp(host, contextPath, docBase, listener);
}
public Context addWebapp(Host host, String contextPath, String docBase, LifecycleListener config) {
this.silence(host, contextPath);
//创建一个Context容器
Context ctx = this.createContext(host, contextPath);
//设置路径
ctx.setPath(contextPath);
ctx.setDocBase(docBase);
if (this.addDefaultWebXmlToWebapp) {
ctx.addLifecycleListener(this.getDefaultWebXmlListener());
}
ctx.setConfigFile(this.getWebappConfigFile(docBase, contextPath));
//将刚刚反射得到的监听器添加到context容器中
ctx.addLifecycleListener(config);
if (this.addDefaultWebXmlToWebapp && config instanceof ContextConfig) {
((ContextConfig)config).setDefaultWebXml(this.noDefaultWebXmlPath());
}
//下面就表示context的上层容器是否是空的,如果是空的,这里先要去添加上层容器,再添加context容器
if (host == null) {
this.getHost().addChild(ctx);
} else {
host.addChild(ctx);
}
return ctx;
}
添加Enine容器
public Host getHost() {
//先去得到Enine容器,没有就创建
Engine engine = this.getEngine();
if (engine.findChildren().length > 0) {
return (Host)engine.findChildren()[0];
} else {
Host host = new StandardHost();
host.setName(this.hostname);
this.getEngine().addChild(host);
return host;
}
}
public Engine getEngine() {
//Enine容器是在Service容器里面的,所以这里先去添加Service容器(不存在就添加)
Service service = this.getServer().findServices()[0];
if (service.getContainer() != null) {
return service.getContainer();
} else {
Engine engine = new StandardEngine();
engine.setName("Tomcat");
engine.setDefaultHost(this.hostname);
engine.setRealm(this.createDefaultRealm());
service.setContainer(engine);
return engine;
}
}
//这里就是添加最上层的Server容器,然后把Service容器也添加进去
//tomcat这里的容器都是以Stardardxxx的,比如StardardServer、StardardService等等
public Server getServer() {
if (this.server != null) {
return this.server;
} else {
System.setProperty("catalina.useNaming", "false");
this.server = new StandardServer();
this.initBaseDir();
this.server.setPort(-1);
Service service = new StandardService();
service.setName("Tomcat");
this.server.addService(service);
return this.server;
}
}
所以tomcat启动就是一层一层的去添加容器,按照最上面说的容器启动顺序去添加,第一次添加容器的时候可能都需要去创建容器,一层一层的创建,当后面再调用addWebapp的时候就只需要把当前的context容器添加到Enine容器里面就行了,因为第一次都会创建Server 、Service、Engine容器。
Tomcat启动
public void start() throws LifecycleException {
//得到Sever对象
this.getServer();
//得到Connector对象,作为http访问请求连接的对象,做监听接受请求
this.getConnector();
//容器启动
this.server.start();
}
public Connector getConnector() {
Service service = this.getService();
if (service.findConnectors().length > 0) {
return service.findConnectors()[0];
} else if (this.defaultConnectorCreated) {
return null;
} else {
Connector connector = new Connector("HTTP/1.1");
connector.setPort(this.port);
service.addConnector(connector);
this.defaultConnectorCreated = true;
return connector;
}
}
下面这个方法是tomcat启动容器的核心方法,容器一层一层的启动,就是向内引爆的模式
先启动sever容器,再启动Service、Engine容器(Connector、Context),核心方法就是调用
startInternal,这个是一个模板设计模式,star方法是fianl的,每个容器启动都会调用这个star方法
public final synchronized void start() throws LifecycleException {
if (!LifecycleState.STARTING_PREP.equals(this.state) && !LifecycleState.STARTING.equals(this.state) && !LifecycleState.STARTED.equals(this.state)) {
if (this.state.equals(LifecycleState.NEW)) {
this.init();
} else if (this.state.equals(LifecycleState.FAILED)) {
this.stop();
} else if (!this.state.equals(LifecycleState.INITIALIZED) && !this.state.equals(LifecycleState.STOPPED)) {
this.invalidTransition("before_start");
}
try {
this.setStateInternal(LifecycleState.STARTING_PREP, (Object)null, false);
this.startInternal();
if (this.state.equals(LifecycleState.FAILED)) {
this.stop();
} else if (!this.state.equals(LifecycleState.STARTING)) {
this.invalidTransition("after_start");
} else {
this.setStateInternal(LifecycleState.STARTED, (Object)null, false);
}
} catch (Throwable var2) {
this.handleSubClassException(var2, "lifecycleBase.startFail", this.toString());
}
} else {
if (log.isDebugEnabled()) {
Exception e = new LifecycleException();
log.debug(sm.getString("lifecycleBase.alreadyStarted", new Object[]{this.toString()}), e);
} else if (log.isInfoEnabled()) {
log.info(sm.getString("lifecycleBase.alreadyStarted", new Object[]{this.toString()}));
}
}
}
其余的代码就不多说了,就是每个容器依次启动,server启动了,调用Service启动,依次启动。
模拟SpringMvc编写web框架
我们知道了sevlet的一些概念,我们可以通过内嵌的tomcat来编写一个简单的spring mvc的容器框架,首先我们知道spring mvc发展到现在创建要给Controller有很多中模式,比如:
1.实现Controller接口;
2.实现HttpRequestHandler ;
3.通过注解@Controller的方式;
那么这么多方式,它的处理肯定是很复杂的,首先你要清楚的是请求的是url中对应的是哪个类型的Controller,那么你才会去执行相对应的Controller中的方法,现在用的比较多的方式就是通过@Controller注解的方式,说白了注解的方式就是说spring在启动的过程中把所有的加了@Controller的注解的bean拿出来,循环里面的方法,如果加了@RequestMapping的注解的封装一个对象,然后加入一个map,key=请求的URI地址,value=就是封装的mapping方法,然后如果前端请求过来得到URI地址,然后去拿到这个封装的mapping方法,然后根据HttpServletRequest中的请求参数进行参数封装,最后封装完成了执行反射调用,调用完成过后通过HttpServletResponse将结果回写;这其中涉spring有一个非常重要的servlet就是DispatcherServlet,是它来完成的所有请求,只是请求的业务逻辑部分是交给了开发者,也就是实现了@Controller注解的类的处理方法。
但是spring从早期的版本到现在,mvc的实现已经经过了很多版本的迭代,出现了很多中不同的方式,那么它是怎么来兼容的呢?其实这里涉及到一个设计模式,就是适配器模式,就是spring会定义一个适配器接口,让所有的请求来适配,如果你适配到了某种模式,就进入这种模式的处理,所以里面涉及两个比较重要的概念就是HandlerMapping和HandlerAdapter,HandlerMapping就是去找到所有符合条件的Controller,然后放入map,HandlerAdapter就是通过请求的url找到对应的mapping,然后开始适配,适配到了就进入相对应的处理流程,去处理具体的业务,最后返回。简单来书主流程就是这样,我这里简单写了一个spring mvc的web框架,就是学习研究使用,首先项目结构如下:
代码放在gitee上,地址是:https://gitee.com/scjava/dev-project.git
有兴趣的可以去下载看下,这里主要看下几个比较重要的类:
handermapping:
//匹配mapping的接口
public interface HandlerMapping {
Object gethandlerMapping(String requestUrl);
}
我这里实现了两种方式,一种以/beanName的方式,一种是注解的方式
@Component
public class BeanNameHandlerMapping implements InstantiationAwareBeanPostProcessor,HandlerMapping {
private final Map<String, Controller> controllerMap = new ConcurrentHashMap<>(8);
@Override
public Object gethandlerMapping(String requestUrl) {
return controllerMap.get(requestUrl);
}
@Override
public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
if(beanName.startsWith("/")){
controllerMap.put(beanName,(Controller)bean);
}
return true;
}
}
@Component
public class AnnotationHandlerMapping implements InstantiationAwareBeanPostProcessor, HandlerMapping {
private final Map<String, RequestMethodInfo> requestMethodInfoMap = new ConcurrentHashMap<>(8);
@Override
public Object gethandlerMapping(String requestUrl) {
return requestMethodInfoMap.get(requestUrl);
}
@Override
public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
Class<?> beanClass = bean.getClass();
if (beanClass.isAnnotationPresent(Controller.class)) {
Method[] declaredMethods = beanClass.getDeclaredMethods();
for (Method declaredMethod : declaredMethods) {
if (declaredMethod.isAnnotationPresent(RequestMapping.class)) {
RequestMethodInfo info = new RequestMethodInfo();
info.setMethod(declaredMethod);
info.setTarget(bean);
info.setUrl(declaredMethod.getAnnotation(RequestMapping.class).value());
requestMethodInfoMap.put(info.getUrl(), info);
}
}
}
return true;
}
}
利用bean的后置处理器来完成的
Adapter
//适配的接口
public interface HandlerAdapter {
boolean isSuuport(Object object);
Object handler(HttpServletRequest request, HttpServletResponse response, Object handler);
}
@Component
public class BeanNameHandlerAdapter implements HandlerAdapter {
@Override
public boolean isSuuport(Object object) {
return object instanceof Controller;
}
@Override
public Object handler(HttpServletRequest request, HttpServletResponse response, Object handler) {
if(handler != null && handler instanceof Controller){
Controller controller = (Controller) handler;
return controller.handler(request,response);
}
return null;
}
}
@Component
public class AnnotationHandlerAdapter implements HandlerAdapter {
@Override
public boolean isSuuport(Object object) {
return object instanceof RequestMethodInfo;
}
@Override
public Object handler(HttpServletRequest request, HttpServletResponse response, Object handler) {
if (handler == null || !(handler instanceof RequestMethodInfo)) {
return null;
}
RequestMethodInfo methodInfo = (RequestMethodInfo) handler;
Method method = methodInfo.getMethod();
Object target = methodInfo.getTarget();
Parameter[] targetPameters = method.getParameters();
Map<String, String[]> paramMap = request.getParameterMap();//请求携带的参数
Object[] argument = new Object[method.getParameterCount()];
for (int i = 0; i < targetPameters.length; i++) {
Parameter parameter = targetPameters[i];
String parName = parameter.getName();
for (Map.Entry<String, String[]> entry : paramMap.entrySet()) {
if (parameter.isAnnotationPresent(RequestParam.class)) {
if (parameter.getAnnotation(RequestParam.class).value().equals(entry.getKey())) {
argument[i] = entry.getValue()[0];
}
} else if (parName.equals(entry.getKey())) {
argument[i] = entry.getValue()[0];
}
if (ServletRequest.class.isAssignableFrom(parameter.getType())) {
argument[i] = request;
}
if (ServletResponse.class.isAssignableFrom(parameter.getType())) {
argument[i] = response;
}
}
}
try {
Object result = method.invoke(target, argument);
if (result instanceof String) {
if ("forward".equals(((String) result).split(":")[0])) {
request.getRequestDispatcher(((String) result).split(":")[1]).forward(request, response);
} else {
return result;
}
}
if (method.isAnnotationPresent(ResponseBody.class)) {
return JSON.toJSONString(result);
}
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (ServletException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}
DispatcherServlet
public class DispatcherServlet extends HttpServlet {
Collection<HandlerMapping> handlerMappings;
Collection<HandlerAdapter> handlerAdapters;
public DispatcherServlet(){
System.out.println("1111111111111111111111111111");
}
public DispatcherServlet(AnnotationConfigApplicationContext ac) {
handlerMappings = ac.getBeansOfType(HandlerMapping.class).values();
handlerAdapters = ac.getBeansOfType(HandlerAdapter.class).values();
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
Object handlerMapping = getHandlerMapping(req);
if (handlerMapping == null) {
throw new RuntimeException("未找到指定的mapping");
}
HandlerAdapter adapter = getHandlerAdapter(handlerMapping);
if (adapter == null) {
throw new RuntimeException("未找到指定的HandlerAdapter");
}
Object result = adapter.handler(req, resp, handlerMapping);
PrintWriter writer = resp.getWriter();
writer.println(result);
writer.flush();
writer.close();
}
private Object getHandlerMapping(HttpServletRequest request) {
for (HandlerMapping handlerMapping : handlerMappings) {
Object mapping = handlerMapping.gethandlerMapping(request.getRequestURI());
if (mapping != null) {
return mapping;
}
}
return null;
}
private HandlerAdapter getHandlerAdapter(Object handlerMapping) {
for (HandlerAdapter handlerAdapter : handlerAdapters) {
boolean falg = handlerAdapter.isSuuport(handlerMapping);
if (falg) {
return handlerAdapter;
}
}
return null;
}
}
controller来测试:
@Controller
public class HellController {
@RequestMapping("/test.do")
public String test(@RequestParam("id")String id){
System.out.println(id);
return "ok";
}
@RequestMapping("/map.do")
@ResponseBody
public Map<String,String> map(){
Map<String,String> data = new HashMap<>();
data.put("id", "11");
data.put("name", "abc");
data.put("age", "34");
return data;
}
@RequestMapping("/user.do")
@ResponseBody
public User user(){
User user = new User();
user.setAge(23);
user.setId("123");
user.setName("bml");
return user;
}
}
@Component("/index")
public class IndexController implements Controller {
@Override
public Object handler(HttpServletRequest request, HttpServletResponse response) {
System.out.println("controller test...");
return "beanNameControllerTeesst";
}
}
然后test代码:
@ComponentScan("com.springmvc")
public class Test {
public static void main(String[] args) throws LifecycleException {
Tomcat tomcat = new Tomcat();
tomcat.setPort(8080);
Context context = tomcat.addWebapp("/", "D:\\study\\idea\\dev-project\\dev-springmvvc");
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(Test.class);
Tomcat.addServlet(context, "dispatcherServlet", new DispatcherServlet(ac));
context.addServletMapping("/*", "dispatcherServlet");
tomcat.start();
tomcat.getServer().await();
}
}
简单来说就是利用spring的ioc功能,将我的handlermapping注册到ioc中,是在bean的后置处理器中去完成的处理,然后请求过来会到DispatcherServlet中的doGet方法,然后doGet先去找到mapping,然后根据mapping去适配是那种模式的Controller,然后适配好了过后,就调用对应controller中的适配方法handler,
测试结果如下:
SPI机制
这里有个SPI的机制,就是什么呢?我们文章的最开始不是说了tomcat启动加了一个监听器吗,这个监听器在tomcat容器启动完成过后,会触发一个事件,就是告诉spring,它启动完成了,如果它启动完成了,spring就可以启动了,所以这里利用了spi的机制,服务发现机制,我这里简单说下,就是在我们的项目里面写一个这样的类,实现了ServletContainerInitializer,然后tomcat启动完成会找到所有的ServletContainerInitializer实现类,然后回调里面的方法onStartup
public class SpringInit implements ServletContainerInitializer {
@Override
public void onStartup(Set<Class<?>> c, ServletContext servletContext) throws ServletException {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(Test.class);
DispatcherServlet servlet = new DispatcherServlet(ac);
ServletRegistration.Dynamic dy = servletContext.addServlet("app",servlet);
dy.setLoadOnStartup(1);
dy.addMapping("*.do");
}
}
我们把spring容器启动的逻辑放在这里,然后在resources下面配置一个服务的配置:
然后tomcat在启动完成过后就会在conext下部署的容器中去找到所有的sevices,然后回调实现了ServletContainerInitializer 的方法,这个tomcat是在那里调用的呢?
this.ok就表示容器启动完毕了,然后调用spi的接口,这个在tomcat的源码里面,有兴趣的可以根据tomcat启动容器的概念和顺序一步一步的去分析源码,我这个专题不是分析tomcat,是分析了springmvc,所以tomat中的源码只是大概找下有些相关的地方然后去简单分析下。