SpringMVC作为最常用的web框架,如果有一天用自己写的框架去实现自己的项目,是不是也挺有意思的。之前的博客中已经完成了,SpirngIOC容器的模拟实现,所以就在原来的基础上实现自己的SpringMVC框架。话不多说上源码:
1、实现自定义注解:
package servlet.annotation;
import java.lang.annotation.*;
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
String value() default "";
}
package servlet.annotation;
import java.lang.annotation.*;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Controller {
String value() default "";
}
package servlet.annotation;
import java.lang.annotation.*;
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestMapping {
String value() default "";
}
package servlet.annotation;
import java.lang.annotation.*;
@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestParam {
String value();
}
package servlet.annotation;
import java.lang.annotation.*;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Service {
String value() default "";
}
2、实现Bean的定义类BeanDefinition,扩展类BeanWrapper,事件监听类BeanPostProcessor
package servlet.context.beans;
/**
* 存储配置文件中的信息,相当于保存在内存中的配置
*/
public class BeanDefinition {
private String beanClassName;
private String factoryBeanName;
private boolean lazyInit=false;
public String getBeanClassName() {
return beanClassName;
}
public void setBeanClassName(String beanClassName) {
this.beanClassName = beanClassName;
}
public String getFactoryBeanName() {
return factoryBeanName;
}
public void setFactoryBeanName(String factoryBeanName) {
this.factoryBeanName = factoryBeanName;
}
public boolean isLazyInit() {
return lazyInit;
}
public void setLazyInit(boolean lazyInit) {
this.lazyInit = lazyInit;
}
// public void setBeanClassName(String beanClassName){
//
// }
//
// public String getBeanCLassName(){
// return null;
// }
//
// public void setFactoryBeanName(String factoryBeanName){
//
// }
//
// public String getFactoryBeanName(){
// return null;
// }
//
// public void setLazyInit(boolean lazyInit){
//
// }
//
// public boolean isLazyInit(){
// return false;
// }
}
package servlet.context.beans;
//用于做事件监听
public class BeanPostProcessor {
public Object postProcessBeforeInitialization(Object bean,String beanName){
return null;
}
public Object postProcessAfterInitializatioin(Object bean,String beanName){
return null;
}
}
package servlet.context.beans;
public class BeanWrapper {
//还会用到观察者模式
// 支持事件响应,会有一个监听
private BeanPostProcessor beanPostProcessor;
private Object wrapperInstance;
//通过反射new出来的,要把它包装起来
private Object originalInstance;
public BeanWrapper(Object instance) {
this.wrapperInstance = instance;
this.originalInstance = instance;
}
public Object getWrappedInstance() {
return this.wrapperInstance;
}
//返回代理以后的Class
//可能会是$Proxy0
public Class<?> getWrappedClass() {
return this.wrapperInstance.getClass();
}
public BeanPostProcessor getBeanPostProcessor() {
return beanPostProcessor;
}
public void setBeanPostProcessor(BeanPostProcessor beanPostProcessor) {
this.beanPostProcessor = beanPostProcessor;
}
}
3、实现Bean信息的读取类
package servlet.context.support;
import servlet.context.beans.BeanDefinition;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
//用于对配置文件查找,读取,解析
public class BeanDefinitiionReader {
private Properties config=new Properties();
private List<String> registyBeanClasses=new ArrayList<String>();
//配置文件中用来获取自动扫描的包名的key
private final String SCAN_PACKAGE="scanPackage";
public BeanDefinitiionReader(String ... locations){
//spring中通过reader去查找和定位
InputStream is=this.getClass().getClassLoader().getResourceAsStream(locations[0].replace("classpath:",""));
try {
config.load(is);
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
if(null!=is) {
is.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
doScanner(config.getProperty(SCAN_PACKAGE));
}
public List<String> getRegistyBeanClasses(){
return null;
}
public List<String> loadBeanDefinitions(){
return this.registyBeanClasses;
};
//每注册一个className,就返回一个BeanDefinition,自己包装
//BeanDefinition只是为了对配置信息进行包装
public BeanDefinition registerBean(String className){
if(this.registyBeanClasses.contains(className)){
BeanDefinition beanDefinition=new BeanDefinition();
beanDefinition.setBeanClassName(className);
beanDefinition.setFactoryBeanName(toLowerFirstWord(className.substring(className.lastIndexOf(".")+1)));
return beanDefinition;
}
return null;
}
//递归扫描所有相关联的class,保存在一个List中
private void doScanner(String packageName){
URL url=this.getClass().getClassLoader().getResource("/"+packageName.replaceAll("\\.","/"));
File classDir=new File(url.getFile());
for(File file:classDir.listFiles()){
if(file.isDirectory()){
doScanner(packageName+"."+file.getName());
}else {
registyBeanClasses.add(packageName+"."+file.getName().replace(".class",""));
}
}
}
private String toLowerFirstWord(String str){
char[] charArr=str.toCharArray();
charArr[0]+=32;
return String.valueOf(charArr);
}
public Properties getConfig(){
return this.config;
}
}
4、定义Bean的顶级接口
package servlet.core;
public interface BeanFactory {
Object getBean(String beanName);
}
5、实现IOC容器ApplicationContext
package servlet.context;
import servlet.annotation.Autowired;
import servlet.annotation.Controller;
import servlet.annotation.Service;
import servlet.context.beans.BeanDefinition;
import servlet.context.beans.BeanPostProcessor;
import servlet.context.beans.BeanWrapper;
import servlet.context.support.BeanDefinitiionReader;
import servlet.context.support.DefaultListableBeanFactory;
import servlet.core.BeanFactory;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
public class ApplicationContext implements BeanFactory {
private String[] configLocations;
private BeanDefinitiionReader reader;
//用来保存配置信息的map
private Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<String, BeanDefinition>();
//用来保存注册是单例的容器
private Map<String, Object> beanCacheMap = new HashMap<>();
//用来存储所有的被代理过得对象
private Map<String,BeanWrapper> beanWrapperMap = new ConcurrentHashMap<>();
public ApplicationContext(String... configLocations) {
this.configLocations = configLocations;
refresh();
}
public void refresh() {
//定位
this.reader = new BeanDefinitiionReader(configLocations);
//加载
List<String> beanDefinitions = reader.loadBeanDefinitions();
//注册
doRegistry(beanDefinitions);
//依赖注入,(lazy-init=false,执行依赖注入)
//这里自动调用getBean方法
doAutowired();
System.out.println("IOC注册成功");
}
private void doAutowired(){
for(Map.Entry<String,BeanDefinition> beanDefinitionEntry:beanDefinitionMap.entrySet()){
//如果这个bean不是延迟加载,调用getBean方法进行实例化
if(!beanDefinitionEntry.getValue().isLazyInit()){
String beanName=beanDefinitionEntry.getKey();
getBean(beanName);
}
}
}
//依赖注入
public void populateBean(String bean,Object instance){
Class clazz=instance.getClass();
//对包含Controller.class和包含Service.class的类进行依赖注入
if(clazz.isAnnotationPresent(Controller.class)||clazz.isAnnotationPresent(Service.class)){
Field[] fields=clazz.getDeclaredFields();
for (int i = 0; i <fields.length; i++) {
Field field=fields[i];
//如果字段内不包含autowire注释,跳过该字段继续循环
if(!field.isAnnotationPresent(Autowired.class)){
continue;
}
Autowired autowired=field.getAnnotation(Autowired.class);
//获取autowired注解的值
String beanName=autowired.value().trim();
//如果注解值为空,使用默认注解
if("".equals(beanName)){
beanName=field.getType().getName();
}
Object object=beanWrapperMap.get(beanName).getWrappedInstance();
field.setAccessible(true);
try {
field.set(instance,object);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
// 真正的将beanDefiniton注册到beanDefinitionMap
private void doRegistry(List<String> beanDefinitions) {
//beanName有三种情况
//1、默认首字母小写
//2、自定义名字
//3、接口注入
try {
for (String className : beanDefinitions) {
Class<?> beanClass = Class.forName(className);
if (beanClass.isInterface()) {
continue;
}
BeanDefinition beanDefinition = reader.registerBean(className);
if(beanDefinition!=null){
this.beanDefinitionMap.put(beanDefinition.getFactoryBeanName(),beanDefinition);
}
Class<?>[] interfaces=beanClass.getInterfaces();
for (Class clazz:interfaces
) {
this.beanDefinitionMap.put(clazz.getName(),beanDefinition);
}
//到这里为止,容器初始化完毕
}
} catch (Exception e) {
e.printStackTrace();
}
}
//依赖注入从这里开始,通过读取BeanDefinition中的信息,
//然后,通过反射机制创建一个实例,并返回
//Spring的做法是,不会把最原始的对象放出去,会用一个BeanWrapper来进行一次包装
//装饰器模式,
//1、保留原来的OOP关系
//2、我需要对他进行扩展和增强
@Override
public Object getBean(String beanName) {
BeanDefinition beanDefinition=this.beanDefinitionMap.get(beanName);
String className=beanDefinition.getBeanClassName();
try{
//生成通知事件
BeanPostProcessor beanPostProcessor=new BeanPostProcessor();
Object instance=instantionBean(beanDefinition);
if(null==instance){
return null;
}
//实例初始化以前调用一次
beanPostProcessor.postProcessBeforeInitialization(instance,beanName);
BeanWrapper beanWrapper=new BeanWrapper(instance);
beanWrapper.setBeanPostProcessor(beanPostProcessor);
this.beanWrapperMap.put(beanName,beanWrapper);
//实例初始化以后调用一次
beanPostProcessor.postProcessAfterInitializatioin(instance,beanName);
populateBean(beanName,instance);
//通过这样一调用,相当于给自己留下了可操作空间
return this.beanWrapperMap.get(beanName).getWrappedInstance();
}catch (Exception e){
e.printStackTrace();
}
return null;
}
//传一个beanDefinition,返回一个instance
private Object instantionBean(BeanDefinition beanDefinition){
Object instance=null;
String className=beanDefinition.getBeanClassName();
try {
//根据class才能确定一个类是否有实例
if(this.beanCacheMap.containsKey(className)){
instance=this.beanCacheMap.get(className);
}else{
Class clazz=Class.forName(className);
instance=clazz.newInstance();
beanCacheMap.put(className,instance);
}
}catch (Exception e){
e.printStackTrace();
}
return instance;
}
public String[] getBeanDefinitionNames(){
return this.beanDefinitionMap.keySet().toArray(new String[this.beanDefinitionMap.size()]);
}
public int getBeanDefinitionCount(){
return this.beanDefinitionMap.size();
}
public Properties getConfig(){
return reader.getConfig();
}
// @Override
// protected void onRefresh() {
// super.onRefresh();
// }
//
// @Override
// protected void refreshBeanFactory() {
// super.refreshBeanFactory();
// }
}
6、实现SpringMVC入口启动类DispatcherServlet
package servlet;
import servlet.annotation.Controller;
import servlet.annotation.RequestMapping;
import servlet.annotation.RequestParam;
import servlet.context.ApplicationContext;
import servlet.webmvc.HandlerAdapter;
import servlet.webmvc.HandlerMapping;
import servlet.webmvc.ModelAndView;
import servlet.webmvc.ViewResolver;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
//Servlet只是作为mvc的启动入口
public class DispatcherServlet extends HttpServlet {
private final String LOCATION="contextConfigLocation";
//SpringMVC最核心的一个设计
private List<HandlerMapping> handlerMappings=new ArrayList<HandlerMapping>();
private List<ViewResolver> viewResolvers=new ArrayList<ViewResolver>();
private Map<HandlerMapping,HandlerAdapter> handlerAdapters=new HashMap<HandlerMapping,HandlerAdapter>();
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
try {
this.doDispatch(req, resp);
}catch (Exception e){
resp.getWriter().write("500 Exception"+Arrays.toString(e.getStackTrace()).replaceAll("\\[|\\]","")
.replaceAll("\\s+","\r\n"));
}
}
@Override
public void init(ServletConfig config) throws ServletException {
ApplicationContext applicationContext=new ApplicationContext(config.getInitParameter(LOCATION));
initStrategy(applicationContext);
}
private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception{
HandlerMapping handler=getHandler(req);
if(handler==null){
resp.getWriter().write("404 NOT FOUND");
return;
}
HandlerAdapter ha=getHandlerAdapter(handler);
ModelAndView mv=ha.handle(req,resp,handler);
processDispatchServlet(resp,mv);
}
private HandlerMapping getHandler(HttpServletRequest req){
if(this.handlerMappings.isEmpty()){
return null;
}
String uri=req.getRequestURI();
String contextPath=req.getContextPath();
String url=uri.replace(contextPath,"").replaceAll("/+","/");
for(HandlerMapping handlerMapping:this.handlerMappings){
Matcher matcher=handlerMapping.getPattern().matcher(url);
if(matcher.matches()){
return handlerMapping;
}
}
return null;
}
private HandlerAdapter getHandlerAdapter(HandlerMapping handler){
return handlerAdapters.get(handler);
}
private void processDispatchServlet(HttpServletResponse resp,ModelAndView mv)throws Exception{
if(mv==null){
return;
}
if(viewResolvers.isEmpty()){
return;
}
for(ViewResolver viewResolver:viewResolvers){
if(viewResolver.getViewName().equals(mv.getViewname())){
String result=viewResolver.doViewResolver(mv);
resp.getWriter().write(result);
return;
}
}
}
private void initStrategy(ApplicationContext context){
initHandlerMapping(context);
initHandlerAdapters(context);
initViewResolvers(context);
}
private void initHandlerMapping(ApplicationContext context){
String[] beanNames=context.getBeanDefinitionNames();
for (String beanName:beanNames) {
Object instance=context.getBean(beanName);
Class<?> clazz=instance.getClass();
if(!clazz.isAnnotationPresent(Controller.class)){
continue;
}
RequestMapping requestMapping=clazz.getAnnotation(RequestMapping.class);
String baseUrl="";
if(requestMapping!=null){
baseUrl=requestMapping.value();
}
Method[] methods=clazz.getMethods();
for (Method me :
methods) {
if(!me.isAnnotationPresent(RequestMapping.class)){
continue;
}
RequestMapping rm=me.getAnnotation(RequestMapping.class);
String url=baseUrl+rm.value().replaceAll("/+","/");
Pattern pattern=Pattern.compile(url);
HandlerMapping handlerMapping=new HandlerMapping(pattern,instance,me);
this.handlerMappings.add(handlerMapping);
}
}
}
private void initHandlerAdapters(ApplicationContext context){
for (HandlerMapping handlerMapping:this.handlerMappings
) {
Map<String,Integer> paramMapping=new HashMap<String,Integer>();
Method me=handlerMapping.getMethod();
Annotation[][] annotations=me.getParameterAnnotations();
for(int i=0;i<annotations.length;i++){
Annotation[] annotationsArr=annotations[i];
for(Annotation annotation:annotationsArr){
if(annotation instanceof RequestParam){
String paramName=((RequestParam) annotation).value();
if(!"".equals(paramName)){
paramMapping.put(paramName,i);
}
}
}
}
//接下来处理非命名参数,只处理Request Response
Class<?>[] parameterTypes=me.getParameterTypes();
for(int i=0;i<parameterTypes.length;i++){
Class<?> clazz=parameterTypes[i];
if(clazz==HttpServletRequest.class||clazz==HttpServletResponse.class){
paramMapping.put(clazz.getName(),i);
}
}
handlerAdapters.put(handlerMapping,new HandlerAdapter(paramMapping));
}
}
private void initViewResolvers(ApplicationContext context){
Properties config=context.getConfig();
String path=config.getProperty("templateRoot");
String templateRootPath=this.getClass().getClassLoader().getResource(path).getFile();
File templateRootFile=new File(templateRootPath);
for(File file:templateRootFile.listFiles()){
ViewResolver viewResolver=new ViewResolver(file.getName(),file);
this.viewResolvers.add(viewResolver);
}
}
}
7、实现SpringMVC三大核心组件HandlerMapping , HandlerAdapter ,ViewResolver 和ModelAndView
package servlet.webmvc;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Arrays;
import java.util.Map;
public class HandlerAdapter {
private Map<String,Integer> paramMapping;
public HandlerAdapter(Map<String, Integer> paramMapping) {
this.paramMapping = paramMapping;
}
public ModelAndView handle(HttpServletRequest req, HttpServletResponse resp, HandlerMapping handler)throws Exception{
//req 根据用户的请求信息,跟method中的参数进行动态匹配
//resp主要是为了给方法中的参数赋值
//获取方法的形参列表 parameterTypes
Class<?>[] parameterTypes=handler.getMethod().getParameterTypes();
//获取请求参数列表
Map<String,String[]> parameterMap=req.getParameterMap();
//构建参数数组
Object[] paramValues=new Object[parameterTypes.length];
for(Map.Entry<String,String[]> entry:parameterMap.entrySet()){
String value= Arrays.toString(entry.getValue()).replaceAll("\\[|\\]","").replaceAll("\\s","");
if(paramMapping.containsKey(entry.getKey())){
int index=paramMapping.get(entry.getKey());
Object obj=convertToOtherType(value,parameterTypes[index]);
paramValues[index]=obj;
}
}
if(this.paramMapping.containsKey(HttpServletRequest.class.getName())) {
int reqIndex = paramMapping.get(HttpServletRequest.class.getName());
paramValues[reqIndex] = req;
}
if(this.paramMapping.containsKey(HttpServletResponse.class.getName())) {
int respIndex = paramMapping.get(HttpServletResponse.class.getName());
paramValues[respIndex] = resp;
}
Object result= handler.getMethod().invoke(handler.getController(),paramValues);
if(result==null){
return null;
}
if(handler.getMethod().getReturnType()==ModelAndView.class){
return (ModelAndView)result;
}else {
return null;
}
}
private Object convertToOtherType(String value,Class<?> clazz){
if(clazz==String.class){
return value;
}else if(clazz==Integer.class){
return Integer.valueOf(value);
}else if(clazz==int.class){
return Integer.valueOf(value).intValue();
}else{
return null;
}
}
}
package servlet.webmvc;
import java.lang.reflect.Method;
import java.util.regex.Pattern;
public class HandlerMapping {
private Pattern pattern;
private Object controller;
private Method method;
public HandlerMapping(Pattern pattern, Object controller, Method method) {
this.pattern = pattern;
this.controller = controller;
this.method = method;
}
public Pattern getPattern() {
return pattern;
}
public Object getController() {
return controller;
}
public Method getMethod() {
return method;
}
public void setPattern(Pattern pattern) {
this.pattern = pattern;
}
public void setController(Object controller) {
this.controller = controller;
}
public void setMethod(Method method) {
this.method = method;
}
}
package servlet.webmvc;
import java.util.HashMap;
import java.util.Map;
public class ModelAndView {
private String viewname;
private Map<String,Object> model=new HashMap<String,Object>();
public ModelAndView(String viewname, Map<String, Object> model) {
this.viewname = viewname;
this.model = model;
}
public String getViewname() {
return viewname;
}
public Map<String, Object> getModel() {
return model;
}
public void setViewname(String viewname) {
this.viewname = viewname;
}
public void setModel(Map<String, Object> model) {
this.model = model;
}
}
package servlet.webmvc;
import java.io.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
//设计这个类的主要目的
//1、将一个静态文件转化为一个动态文件
//2、根据用户传的参数不同,产生不同的结果
//最终输出字符串,交给resp输出
public class ViewResolver {
private String viewName;
private File templateFile;
public ViewResolver(String viewName,File templateFile){
this.viewName=viewName;
this.templateFile=templateFile;
}
public String getViewName() {
return viewName;
}
public File getTemplateFile() {
return templateFile;
}
public void setViewName(String viewName) {
this.viewName = viewName;
}
public void setTemplateFile(File templateFile) {
this.templateFile = templateFile;
}
public String doViewResolver(ModelAndView mv) throws Exception{
InputStream is=new FileInputStream(this.templateFile);
BufferedReader ir=new BufferedReader(new InputStreamReader(is,"utf-8"));
String line=null;
StringBuffer sb=new StringBuffer();
while((line=ir.readLine())!=null){
Matcher matcher=matcher(line);
while (matcher.find()){
for(int i=1;i<=matcher.groupCount();i++){
//把¥{}中间的字符创取出来
String paramName=matcher.group(i);
Object paramValue=mv.getModel().get(paramName);
if(paramValue==null){
continue;
}
line=line.replaceAll("¥\\{"+paramName+"\\}",paramValue.toString());
}
}
sb.append(line);
}
return sb.toString();
}
private static Matcher matcher(String line){
Pattern pattern=Pattern.compile("¥\\{(.+?)\\}",Pattern.CASE_INSENSITIVE);
Matcher matcher=pattern.matcher(line);
return matcher;
}
public static void main(String[] args) {
String line="<h1>Hello ¥{teacher}! ¥{teacher}</h1>";
Matcher matcher=matcher(line);
while (matcher.find()){
for(int i=1;i<=matcher.groupCount();i++){
//把¥{}中间的字符创取出来
String paramName=matcher.group(i);
// Object paramValue=mv.getModel().get(paramName);
// if(paramValue==null){
// continue;
// }
line=line.replaceAll("¥\\{"+paramName+"\\}","tom");
System.out.println(line);
}
}
}
}
8、业务demo:
package demo.controller;
import demo.service.IService;
import servlet.annotation.Autowired;
import servlet.annotation.Controller;
import servlet.annotation.RequestMapping;
import servlet.annotation.RequestParam;
import servlet.webmvc.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
@Controller
public class DemoController {
@Autowired
IService iService;
@RequestMapping("/getInfo")
public void getInfo(@RequestParam("param") String param){
iService.add();
}
@RequestMapping("/doTest")
public ModelAndView test1(HttpServletRequest request, HttpServletResponse response,
@RequestParam("param") String param){
Map<String,Object> map=new HashMap<String,Object>();
map.put("teacher",param);
ModelAndView mv=new ModelAndView("first.html",map);
return mv;
}
@RequestMapping("/doTest2")
public void test2(HttpServletRequest request, HttpServletResponse response){
try {
response.getWriter().println("doTest2 method success!");
} catch (IOException e) {
e.printStackTrace();
}
}
}
package demo.service;
import servlet.annotation.Service;
public interface IService {
public void add();
}
package demo.service.impl;
import demo.service.IService;
import servlet.annotation.Service;
@Service
public class IServiceImpl implements IService {
@Override
public void add() {
System.out.println("新增一条数据");
}
}
9、web.xml 和配置文件
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
<display-name>Archetype Created Web Application</display-name>
<servlet>
<servlet-name>mvc</servlet-name>
<servlet-class>servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:application.properties</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>mvc</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
</web-app>
application.properties
scanPackage=demo
templateRoot=layouts
first.html
<!DOCTYPE html/>
<html lang="zh-cn">
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8"/>
</head>
<body>
<h1>¥{teacher}</h1>
</body>
</html>
10.运行后在浏览器中访问: