执行流程
示意图
就像JavaWeb的通常开发会在web.xml配置DispatcherServlet一样,手写框架也是在内嵌的Tomcat里面绑定我自己手写的DispatcherServlet。此时这个Tomcat容器里面的仅有我手写的DispatcherServlet。当请求来的时候,DispatcherServlet父类FrameworkServlet重写了doGet和doPost方法,这两个方法会调用抽象方法doService,DispatcherServlet重写了这个方法。因此和SpringMVC一样,所有的请求最终会进入DispatcherServlet的doService方法。
doService方法会调用doDispatch方法,doDispatch就是找寻对应controller方法并执行方法的核心部分。首先会根据请求路径获取HandlerExecutionChain,里面封装了处理请求的handler;接着获取对应请求类型的处理器HandlerAdapter去处理HandlerExecutionChain封装的HandlerMapping,并获得controller执行结果。根据有无@ResponseBody决定是否要继续生成视图。在有视图的情况下,会找到对应的视图解析器(本项目中就是返回了一个html静态资源,所以用的View是InternalResourceView)。因此后续会得到一个物理视图(也就是html页面),最终并返回响应。
关键类
Tomcat
因为是手写SpringMVC,模拟DispatcherServlet的运行流程,那就不能直接用本地的Tomcat跑了,所以这里用了内嵌的Tomcat。主要就是标识了静态资源的位置和加载了自定义的Servlet。也就是我们后面手写的DispatcherServlet
public class Tomcat {
public void start(String port, String hostName){
org.apache.catalina.startup.Tomcat tomcat = new org.apache.catalina.startup.Tomcat();
Server server = tomcat.getServer();
Service service = server.findService("Tomcat");
Connector connector = new Connector();
connector.setPort(Integer.parseInt(port));
Engine engine = new StandardEngine();
engine.setDefaultHost(hostName);
Host host = new StandardHost();
host.setName(hostName);
String contextPath="";
String htmlPath = this.getClass().getResource("/").getFile();
htmlPath = new File(htmlPath+"static").getAbsolutePath();
Context context = tomcat.addWebapp(contextPath, htmlPath);
context.addLifecycleListener(new org.apache.catalina.startup.Tomcat.FixContextListener());
//维护起层级关系
host.addChild(context);
engine.addChild(host);
service.setContainer(engine);
service.addConnector(connector);
tomcat.addServlet(contextPath,"dispatcher",new DispatcherServlet());
context.addServletMappingDecoded("/test","dispatcher");
try {
tomcat.start();
tomcat.getServer().await();
} catch (Exception e) {
//因为没有设置jsp的servlet,这里会报java.lang.ClassNotFoundException: org.apache.jasper.servlet.JspServlet
//我直接忽略了。。
}
}
}
FrameworkServlet
因为符合Servlet的规范,所以顶层的自定义Servlet应该继承HttpServlet,该类也是SpringMVC有的类,主要作用是重写doGet和doPost方法,将所有的请求都传给抽象方法doService,而子类DispatcherServlet正好实现该方法,这样就把所有请求都转到了DispatcherServlet里面去了。
public abstract class FrameworkServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.processRequest(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.processRequest(req, resp);
}
private void processRequest(HttpServletRequest req, HttpServletResponse resp) {
doService(req,resp);
}
protected abstract void doService(HttpServletRequest req, HttpServletResponse resp);
}
DispatcherServlet
该类继承FrameworkServlet,所有请求都会执行到该类的doService方法,进而执行到doDispatch方法。doDispatch方法也是分发请求的核心方法,也就是说根据请求路径将请求交给其中一个handler处理(controller)。并且处理完还有交由对应的视图解析器去封装响应。具体大家看源码。
public class DispatcherServlet extends FrameworkServlet {
//请求路径和Controller的Method映射
private List<HandlerMapping> handlerMappings;
//执行对应请求方法的执行器
private List<HandlerAdapter> handlerAdapters;
//请求中是否带文件
private boolean multipartRequestParsed=false;
//因为没有Spring的Context,这里直接把Method的Mapping设置静态属性,好传参
public static AbstractHandlerMethodMapping abstractHandlerMethodMapping = new AbstractHandlerMethodMapping();
//从请求路径中获取视图名
private RequestToViewNameTranslator viewNameTranslator;
//视图解析器,用来处理返回哪个html等视图
private List<ViewResolver> viewResolvers;
@Override
public void init() throws ServletException {
super.init();
initServletBean();
}
private void initServletBean() {
initStrategies();
}
public void doService(HttpServletRequest req, HttpServletResponse resp) {
doDispatch(req, resp);
}
/**
* 分发请求至对应的controller
*
* @param request
* @param response
*/
private void doDispatch(HttpServletRequest request, HttpServletResponse response) {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
ModelAndView mv = null;
//判断是否携带文件
multipartRequestParsed = this.checkMultipart(request);
//获取对应请求的handler,也就是controller
mappedHandler = this.getHandler(processedRequest);
if (mappedHandler == null) {
this.noHandlerFound(processedRequest, response);
return;
}
//获取HandlerAdapter,用来处理请求返回视图
HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
String method = request.getMethod();
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
this.processDispatchResult(processedRequest, response, mappedHandler, mv);
}
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, HandlerExecutionChain mappedHandler, ModelAndView mv) {
this.render(mv, request, response);
}
private void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) {
String viewName = mv.getViewName();
View view=null;
if (viewName != null) {
view = this.resolveViewName(viewName, mv.getModelInternal(), request);
if (view == null) {
throw new RuntimeException("Could not resolve view with name '" + mv.getViewName() + "' in servlet with name '" + this.getServletName() + "'");
}
}
view.render(mv.getModelInternal(), request, response);
}
private View resolveViewName(String viewName, Object modelInternal, HttpServletRequest request) {
if (this.viewResolvers != null) {
Iterator var5 = this.viewResolvers.iterator();
while(var5.hasNext()) {
ViewResolver viewResolver = (ViewResolver)var5.next();
View view = viewResolver.resolveViewName(viewName);
//前后缀补全
if (view instanceof InternalResourceView){
String url = ((InternalResourceView) view).getUrl();
if (viewNameTranslator instanceof DefaultRequestToViewNameTranslator){
String prefix = ((DefaultRequestToViewNameTranslator) viewNameTranslator).getPrefix();
String suffix = ((DefaultRequestToViewNameTranslator) viewNameTranslator).getSuffix();
((InternalResourceView) view).setUrl(prefix+url+suffix);
}
}
if (view != null) {
return view;
}
}
}
return null;
}
private HandlerAdapter getHandlerAdapter(Object handler) {
if (this.handlerAdapters.get(0).support(handler)) {
return this.handlerAdapters.get(0);
}
throw new RuntimeException("can not find handlerAdapters");
}
/**
* 找不到处理链,也就是说找不到对应的Controller的方法,就404
*
* @param processedRequest
* @param response
*/
private void noHandlerFound(HttpServletRequest processedRequest, HttpServletResponse response) {
try {
response.sendError(404);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 获取执行该请求的处理链
*
* @param processedRequest
* @return
*/
private HandlerExecutionChain getHandler(HttpServletRequest processedRequest) {
HandlerExecutionChain handlerExecutionChain = null;
try {
handlerExecutionChain = handlerMappings.get(0).getHandler(processedRequest);
} catch (Exception e) {
e.printStackTrace();
}
return handlerExecutionChain;
}
/**
* 判断请求是否带文件
*
* @param request
* @return
*/
private boolean checkMultipart(HttpServletRequest request) {
String contentType = request.getContentType();
if (contentType == null) {
return false;
}
return request.getContentType().startsWith("multipart/");
}
/**
* 初始化给映射,解析器等等
*/
protected void initStrategies() {
this.initHandlerMappings();
this.initHandlerAdapters();
this.initRequestToViewNameTranslator();
this.initViewResolvers();
}
private void initViewResolvers() {
viewResolvers=Collections.singletonList(new ContentNegotiatingViewResolver());
}
private void initRequestToViewNameTranslator() {
viewNameTranslator=new DefaultRequestToViewNameTranslator();
}
/**
* 源码中是搜索整个项目支持的controller类型(声明一个controller不仅仅可以用@Controller)
* 不同的controller声明方式,会导致HandlerMapping的对应实现类不同
* RequestMappingHandlerAdapter是@Controller的对应形式(简化书写,就放了一个)
*/
private void initHandlerAdapters() {
handlerAdapters = Collections.singletonList(new RequestMappingHandlerAdapter());
}
/**
* 源码中是搜索整个项目支持的controller类型(声明一个controller不仅仅可以用@Controller)
* 不同的controller声明方式,会导致HandlerMapping的对应实现类不同
* RequestMappingHandlerMapping是@RequestMapping的对应形式(简化书写,就放了一个,并且扫描的任务也写在这里)
*/
private void initHandlerMappings() {
handlerMappings = Collections.singletonList(new RequestMappingHandlerMapping());
AbstractHandlerMethodMapping.MappingRegistry mappingRegistry = abstractHandlerMethodMapping.getMappingRegistry();
findController(mappingRegistry);
}
private void findController(AbstractHandlerMethodMapping.MappingRegistry mappingRegistry) {
String path = this.getClass().getResource("/").getFile();
File file = new File(path);
treeFile(mappingRegistry, path, file);
}
private void treeFile(AbstractHandlerMethodMapping.MappingRegistry mappingRegistry, String path, File file) {
for (File listFile : file.listFiles()) {
if (listFile.isDirectory()) {
if (listFile.listFiles().length > 0) {
treeFile(mappingRegistry, path, listFile);
}
} else {
String filePath = listFile.toString();
filePath = filePath.substring(path.length() - 1).replace("\\", ".");
if (filePath.endsWith(".class")) {
filePath = filePath.substring(0, filePath.length() - 6);
try {
Class<?> aClass = this.getClass().getClassLoader().loadClass(filePath);
if (aClass.isAnnotationPresent(Controller.class)) {
for (Method method : aClass.getDeclaredMethods()) {
if (method.isAnnotationPresent(RequestMapping.class)) {
MethodParameter[] methodParameters = new MethodParameter[method.getParameterTypes().length];
for (int i = 0; i < method.getParameterTypes().length; i++) {
Class<?> parameterType = method.getParameterTypes()[i];
//因为反射是获取不到方法参数的参数名的,只能获取参数类型
//获取参数名需要进行反编译,这里简略了,参数名都设置为""
MethodParameter methodParameter = new MethodParameter(i, parameterType, "");
methodParameters[i] = methodParameter;
}
HandlerMethod handlerMethod = new HandlerMethod(aClass, method, methodParameters);
String value = ((RequestMapping) method.getAnnotation(RequestMapping.class)).value();
mappingRegistry.getMappings().put(value, handlerMethod);
}
}
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
}
}
}
HandlerExecutionChain、AbstractHandlerMethodMapping和HandlerMethod
HandlerExecutionChain是处理链类,如果一个请求需要被多个handler处理,就像过滤器那样,这个类就会封装多个handler。而里面的成员属性handler本质上是HandlerMethod类,下文会讲,是一个封装Method封装类,这个Method也就是Controller类里面定义的类。
public class HandlerExecutionChain {
private Object handler;
public HandlerExecutionChain(Object handler) {
if (handler instanceof HandlerMethod){
this.handler = handler;
}else {
throw new RuntimeException("handler not instanceof HandlerMethod");
}
}
public Object getHandler() {
return this.handler;
}
}
而AbstractHandlerMethodMapping类就是存放HandlerMethod的地方,上述的HandlerExecutionChain就是在这个类里面获取对应请求的handler对象。AbstractHandlerMethodMapping中有个内部类MappingRegistry,这个内部类维护了一个Map,存放的就是HandlerMethod
public class AbstractHandlerMethodMapping {
//路径和方法的映射存放位置
private AbstractHandlerMethodMapping.MappingRegistry mappingRegistry = new AbstractHandlerMethodMapping.MappingRegistry();
public MappingRegistry getMappingRegistry() {
return mappingRegistry;
}
public HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception{
String lookupPath = request.getRequestURI();
HandlerMethod handlerMethod = this.lookupHandlerMethod(lookupPath, request);
return handlerMethod;
}
private HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) {
List<Match> matches = new ArrayList();
this.addMatchingMappings(lookupPath, matches, request);
return matches.get(0).handlerMethod;
}
private void addMatchingMappings(String lookupPath, List<Match> matches, HttpServletRequest request) {
matches.add(new Match(lookupPath,(HandlerMethod)this.mappingRegistry.getMappings().get(lookupPath)));
}
private class Match {
private final String mapping;
private final HandlerMethod handlerMethod;
public Match(String mapping, HandlerMethod handlerMethod) {
this.mapping = mapping;
this.handlerMethod = handlerMethod;
}
public String getMapping() {
return mapping;
}
public HandlerMethod getHandlerMethod() {
return handlerMethod;
}
public String toString() {
return this.mapping.toString();
}
}
public class MappingRegistry {
private final Map<String, HandlerMethod> mappingLookup = new LinkedHashMap();
MappingRegistry() {
}
public Map<String, HandlerMethod> getMappings() {
return this.mappingLookup;
}
}
}
最后咱们看看HandlerMethod,这个类成员属性比较简单,beanType是Method所在的controller类的类对象;method是执行请求的对应Method对象;parameters是MethodParameter数组,这个MethodParameter就不做赘述了,用来封装Method对象的参数类型和参数名,但由于参数名只能用反编译获取(SpringMVC也是这么做的),我也不会就没写了
public class HandlerMethod {
private Class<?> beanType;
private Method method;
private MethodParameter[] parameters;
public HandlerMethod(Class<?> beanType, Method method, MethodParameter[] parameters) {
this.beanType = beanType;
this.method = method;
this.parameters = parameters;
}
public Class<?> getBeanType() {
return beanType;
}
public void setBeanType(Class<?> beanType) {
this.beanType = beanType;
}
public Method getMethod() {
return method;
}
public void setMethod(Method method) {
this.method = method;
}
public MethodParameter[] getParameters() {
return parameters;
}
public void setParameters(MethodParameter[] parameters) {
this.parameters = parameters;
}
public Object invoke(Object[] args) {
Object result = null;
try {
Object bean = beanType.newInstance();
if (method.getParameterTypes().length>0){
//不支持传参哦,我不会在这里用反编译获取参数名
throw new RuntimeException("不支持传参哦,我不会在这里用反编译获取参数名");
}else {
result = method.invoke(bean);
}
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
return result;
}
}
HandlerMapping及其实现类
HandlerMapping是个接口,和SpringMVC定义的一样,它的实现类用来获取映射请求路径的handler的,实现类的关键方法就是用来获取HandlerExecutionChain
public class RequestMappingHandlerMapping implements HandlerMapping {
public HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
Object handler = this.getHandlerInternal(request);
HandlerExecutionChain executionChain = this.getHandlerExecutionChain(handler, request);
return executionChain;
}
private HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
return new HandlerExecutionChain(handler);
}
private Object getHandlerInternal(HttpServletRequest request) {
AbstractHandlerMethodMapping abstractHandlerMethodMapping = DispatcherServlet.abstractHandlerMethodMapping;
HandlerMethod handlerMethod=null;
try {
handlerMethod = abstractHandlerMethodMapping.getHandlerInternal(request);
} catch (Exception e) {
e.printStackTrace();
}
return handlerMethod;
}
}
HandlerAdapter及其实现类
HandlerAdapter也是个接口,和SpringMVC定义的一样,它的实现类是用来执行获取到的handler,本质上是取出Method对象,反射执行。最终会获得ModlerAndView。
/**
* @Controller的对应映射的执行器
*/
public class RequestMappingHandlerAdapter implements HandlerAdapter {
@Override
public boolean support(Object handler) {
return handler instanceof HandlerMethod && this.supportsInternal((HandlerMethod) handler);
}
@Override
public ModelAndView handle(HttpServletRequest processedRequest, HttpServletResponse response, Object handler) {
return this.handleInternal(processedRequest, response, (HandlerMethod) handler);
}
private ModelAndView handleInternal(HttpServletRequest processedRequest, HttpServletResponse response, HandlerMethod handler) {
ModelAndView mav = this.invokeHandlerMethod(processedRequest, response, handler);
return mav;
}
private ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) {
ServletInvocableHandlerMethod invocableMethod = this.createInvocableHandlerMethod(handlerMethod);
ModelAndViewContainer mavContainer = new ModelAndViewContainer();
invocableMethod.invokeAndHandle(request,response, mavContainer, new Object[0]);
ModelAndView modelAndView = this.getModelAndView(mavContainer, request,response);
return modelAndView;
}
private ModelAndView getModelAndView(ModelAndViewContainer mavContainer, HttpServletRequest request, HttpServletResponse response) {
ModelMap model = mavContainer.getModel();
ModelAndView mav=null;
if (mavContainer.getViewName()!=null){
mav = new ModelAndView(mavContainer.getViewName(), model);
}
return mav;
}
private ServletInvocableHandlerMethod createInvocableHandlerMethod(HandlerMethod handlerMethod) {
return new ServletInvocableHandlerMethod(handlerMethod);
}
/**
* RequestMappingHandlerAdapter就是处理@Controller类型的请求的
*
* @param handler
* @return
*/
private boolean supportsInternal(HandlerMethod handler) {
return true;
}
}
除此之外还有两个和RequestMappingHandlerAdapter相关的关键类:ServletInvocableHandlerMethod、HandlerMethodReturnValueHandlerComposite。RequestMappingHandlerAdapter在执行方法是是让ServletInvocableHandlerMethod通过反射去执行的,执行回来的结果会在传给HandlerMethodReturnValueHandlerComposite去判断是否要设置视图的相关信息(因为存在@ResponseBody就不需要接下来进入视图解析的流程了)
public class ServletInvocableHandlerMethod {
private HandlerMethod handlerMethod;
private HandlerMethodReturnValueHandlerComposite returnValueHandlers = new HandlerMethodReturnValueHandlerComposite();
public ServletInvocableHandlerMethod(HandlerMethod handlerMethod) {
this.handlerMethod = handlerMethod;
}
public void invokeAndHandle(HttpServletRequest request, HttpServletResponse response, ModelAndViewContainer mavContainer, Object... objects) {
Object returnValue = this.invokeForRequest(request,response, mavContainer, objects);
if (returnValue == null) {
throw new RuntimeException("method erro");
}
//将返回的结果封装在视图容器中
this.returnValueHandlers.handleReturnValue(returnValue, mavContainer, request,response);
}
private Object invokeForRequest(HttpServletRequest request, HttpServletResponse response, ModelAndViewContainer mavContainer, Object... providedArgs) {
Object[] args = this.getMethodArgumentValues(request, mavContainer, providedArgs);
return this.doInvoke(request,args);
}
private Object doInvoke(HttpServletRequest request, Object[] args) {
HandlerMethod handlerMethod = DispatcherServlet.abstractHandlerMethodMapping.getMappingRegistry().getMappings().get(request.getRequestURI());
return handlerMethod.invoke(args);
}
/**
* 传参,因为绑定参数要获取方法的参数名,需要用到反编译技术,所以这里先不写
* @param request
* @param mavContainer
* @param providedArgs
* @return
*/
private Object[] getMethodArgumentValues(HttpServletRequest request, ModelAndViewContainer mavContainer, Object[] providedArgs) {
return providedArgs;
}
}
public class HandlerMethodReturnValueHandlerComposite {
public void handleReturnValue(Object returnValue, ModelAndViewContainer mavContainer, HttpServletRequest request, HttpServletResponse response) {
if (returnValue instanceof CharSequence) {
String viewName = returnValue.toString();
HandlerMethod handlerMethod = DispatcherServlet.abstractHandlerMethodMapping.getMappingRegistry().getMappings().get(request.getRequestURI());
//这里就没写那么深了,@RequestMapping的请求直接在这里传回去了
if (handlerMethod.getMethod().isAnnotationPresent(ResponseBody.class)) {
response.addHeader("Content-Type","text/html;charset=UTF-8");
ServletOutputStream outputStream=null;
try {
outputStream = response.getOutputStream();
outputStream.write(viewName.getBytes());
} catch (IOException e) {
e.printStackTrace();
}finally {
if (outputStream!=null){
try {
outputStream.flush();
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}else {
mavContainer.setViewName(viewName);
}
}
}
}
视图类
ModelAndView是个逻辑视图,里面我就写了两个属性,view是视图名,model是模型。ModelAndViewContainer是一个视图容器,handler执行返回视图名后会设置在容器中,而ModelAndView就是从容器中获取的。View是一个视图的接口,在SpringMVC中有很多实现类,其中物理视图的实现类是InternalResourceView,主要封装了静态文件的url以及转发请求至具体资源的方法。
public class ModelAndView {
private Object view;
private ModelMap model;
public ModelAndView(Object view, ModelMap model) {
this.view = view;
this.model = model;
}
public String getViewName() {
return view.toString();
}
public Object getModelInternal() {
return model;
}
}
public class ModelAndViewContainer {
private Object view;
//model封装类
private ModelMap defaultModel;
//重定向的model封装类
private ModelMap redirectModel;
public ModelAndViewContainer() {
}
public void setViewName(String viewName) {
this.view = viewName;
}
public ModelMap getModel() {
return this.defaultModel;
}
public Object getViewName() {
return this.view;
}
}
public class InternalResourceView implements View {
private String url;
private String contentType = "text/html;charset=ISO-8859-1";
public InternalResourceView(String viewName) {
url=viewName;
}
@Override
public void render(Object modelInternal, HttpServletRequest request, HttpServletResponse response) {
this.renderMergedOutputModel(request,response);
}
private void renderMergedOutputModel(HttpServletRequest request, HttpServletResponse response) {
String dispatcherPath = this.prepareForRendering(request, response);
RequestDispatcher rd = this.getRequestDispatcher(request, dispatcherPath);
try {
rd.forward(request, response);
} catch (ServletException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
private RequestDispatcher getRequestDispatcher(HttpServletRequest request, String dispatcherPath) {
return request.getRequestDispatcher(dispatcherPath);
}
private String prepareForRendering(HttpServletRequest request, HttpServletResponse response) {
if (url.equals(request.getRequestURI())){
throw new RuntimeException("erro loop!");
}
return url;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
}
视图解析类
ViewResolver是个接口,其实现类用来解析视图,返回具体的物理视图,也就是InternalResourceView。而RequestToViewNameTranslator是一个视图名转换类接口,实现类封装了视图的前后缀,ViewResolver接受的视图名都是经过RequestToViewNameTranslator处理后的视图名。
public class DefaultRequestToViewNameTranslator implements RequestToViewNameTranslator {
private String prefix = "";
private String suffix = "";
private String separator = "/";
@Override
public String getViewName(HttpServletRequest var1) {
return null;
}
public String getPrefix() {
return prefix;
}
public String getSuffix() {
return suffix;
}
public String getSeparator() {
return separator;
}
}
public class ContentNegotiatingViewResolver implements ViewResolver {
@Override
public View resolveViewName(String viewName) {
View bestView = this.getBestView(viewName);
return bestView;
}
private View getBestView(String viewName) {
return new InternalResourceView(viewName);
}
}
Main方法及其测试结果
Main方法
就一行代码,开启内嵌的Tomcat
public class Main {
public static void main(String[] args) {
new Tomcat().start("8080","localhost");
}
}
测试结果(不加@ResponseBody)
应该返回物理视图,也就是html页面。
@Controller
public class MyController{
//手写框架目前不支持传参,因为需要用到反编译技术
//支持@ResponseBody或者返回页面
@RequestMapping("/test")
public String test(){
return "test.html";
}
}
测试结果(加@ResponseBody)
应该返回模型数据,当然也会被浏览器解析出来。
@Controller
public class MyController{
//手写框架目前不支持传参,因为需要用到反编译技术
//支持@ResponseBody或者返回页面
@ResponseBody
@RequestMapping("/test")
public String test(){
return "test.html";
}
}
评论区