通过实现servlet接口函数来实现了简单的MVC逻辑,通过web.xml将servlet引入到tomcat容器,顺便加了暂时用不上的filter和listener;利用java标注装配controller和viewer,controller实现了简单的地址映射处理、ModelView数据回传,viewer实现了文本资源(html)读取及简单数据渲染。
1、pom.xml配置
由于需要利用web.xml引入servlet到容器,因此在war打包配置需要指明web.xml位置:
<plugin>
<artifactId>maven-war-plugin</artifactId>
<version>2.3</version>
<configuration>
<warSourceDirectory>WebContent</warSourceDirectory>
<webXml>F:\java_demo\rumia_spring_web3\res\web.xml</webXml>
<failOnMissingWebXml>true</failOnMissingWebXml>
</configuration>
</plugin>
如果不想用web.xml,可以实现WebApplicationInitializer接口或继承AbstractAnnotationConfigDispatcherServletInitializer可以实现xml零配置。
2、web.xml配置
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<context-param> <!-- 更换上下文,从xml配置到java配置 -->
<param-name>contextClass</param-name>
<param-value>
org.springframework.web.context.support.AnnotationConfigWebApplicationContext
</param-value>
</context-param>
<context-param> <!-- 利用java配置上下文直接指定配置类 -->
<param-name>contextConfigLocation</param-name>
<param-value>
gensoku.config.appConfig
</param-value>
</context-param>
<listener> <!-- java配置上下文需加入内容监听组件 -->
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<listener> <!-- 测试的监听组件 -->
<listener-class>gensoku.myListener</listener-class>
</listener>
<servlet> <!-- 引入自定义的主servlet -->
<servlet-name>firstServlet</servlet-name>
<servlet-class>gensoku.myServlet</servlet-class>
<init-param> <!-- 自定义的初始配置参数,用于指定webConfig配置类 -->
<param-name>webConfigClass</param-name>
<param-value>
gensoku.config.webConfig
</param-value>
</init-param>
<!-- 创建servlet的优先级,无指定则按需创建 -->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping> <!-- 指定上述主servlet的映射地址 -->
<servlet-name>firstServlet</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
<filter> <!-- 测试用过滤器 -->
<filter-name>firstFilter</filter-name>
<filter-class>gensoku.myFilter</filter-class>
<init-param>
<param-name>charset</param-name>
<param-value>UTF-8</param-value>
</init-param>
</filter>
<filter-mapping> <!-- 指定过滤器的映射地址 -->
<filter-name>firstFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
3、ModelView数据结构
用于controller将数据传给viewer,这里利用HashMap储存model数据的键值对。
package gensoku.data;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
public class ModelView
{
private String viewName;
private Map<String, String> modelData;
public ModelView()
{
modelData = new HashMap<>();
}
//读写viewname
public void setViewName(String name)
{
viewName = name;
}
public String getViewName()
{
return viewName;
}
//读写model数据
public void setModelData(String k, String v)
{
modelData.put(k, v);
}
public String getModelData(String k)
{
if(modelData.containsKey(k))
{
return modelData.get(k);
}
else
return null;
}
//返回迭代器供viewer渲染时遍历model数据
public Iterator getMapKeyIterator()
{
return modelData.keySet().iterator();
}
}
4、webConfig配置类
利用spring的java标注技术对controller和viewer进行组件初始化,供后面servlet使用。
package gensoku.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import gensoku.*;
@Configuration
public class webConfig
{
//初始化controller
@Bean(name = "C")
public myController retMyController()
{
return new myController();
}
//初始化viewer及资源路径
@Bean(name = "V")
public myViewer retMyViewer()
{
return new myViewer("F:\\java_demo\\rumia_spring_web3\\target\\TouhouProject-1.0\\WEB-INF\\classes\\pages\\");
}
}
5、servlet前端控制器
在初始化时需要利用java装配上下文进行装配,拉取装配完成的controller、viewer;在实际处理请求时先将请求传给controller处理,然后将返回的ModelView数据传给viewer拉取、渲染页面,最后返回渲染结果。
一个惨痛的教训是springMVC并不将servlet当做bean参与装配初始化,而仅仅构建controller和viewer的bean;而且web.xml中的webConfig仅仅只是指出配置类,容器本身并不会主动进行装配——因此如果是自己实现的servlet就需要利用AnnotationConfigApplicationContext进行装配。
package gensoku;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import gensoku.data.ModelView;
public class myServlet implements Servlet
{
//servlet的controller和viewer
private myController sController;
private myViewer sView;
public myServlet()
{
;
}
@Override //servlet初始化
public void init(ServletConfig config) throws ServletException
{
System.out.println("A new servlet is born");
try
{
//读取web.xml中servlet的配置参数,得到配置类名
String myWebConfig = config.getInitParameter("webConfigClass");
System.out.println("Loading webConfig ... " + myWebConfig);
//利用反射得到配置类并构建java装配上下文进行装配
ApplicationContext context = new AnnotationConfigApplicationContext(Class.forName(myWebConfig));
//拉取装配完成后的controller和viewer
sController = context.getBean("C", myController.class);
sView = context.getBean("V", myViewer.class);
//检查是否拉取成功
if(sController==null || sView==null)
{
System.out.println("Loading fail..");
}
}
catch(Exception e)
{
System.out.println("Loading fail..");
e.printStackTrace();
}
}
@Override
public ServletConfig getServletConfig()
{
// TODO Auto-generated method stub
return null;
}
@Override //请求到来时的实际处理函数
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException
{
HttpServletRequest hr = (HttpServletRequest)req;
System.out.println("GET <- "+hr.getRequestURI());
//检查controller和viewer是否正常
if(sController==null || sView==null)
{
//直接返回error信息
PrintWriter pw = res.getWriter();
pw.println("<h1> Error 1 </h1>");
return;
}
//将请求交由controller处理,得到返回的ModelView数据
ModelView mv = sController.process(req);
if(mv == null)
{
mv = new ModelView();
mv.setViewName("error.html");
}
System.out.println("Control -> view : " + mv.getViewName());
//将返回的ModelView数据交给viewer读取、渲染页面
String content = sView.process(mv);
if(content == null)
{
content = "<h1> Error 2 <h1>";
}
//将最终结果返回
PrintWriter pw = res.getWriter();
pw.println(content);
}
@Override
public String getServletInfo()
{
// TODO Auto-generated method stub
return null;
}
@Override
public void destroy()
{
System.out.println("A servlet is dead");
}
}
6、Controller控制器
将用户请求URL与处理函数进行绑定,在收到请求时用对应的函数进行处理,返回ModelView。
package gensoku;
import java.lang.reflect.Method;
import java.text.DateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Component;
import gensoku.data.ModelView;
@Component
public class myController
{
//(请求URL,处理函数)映射
private Map<String, Method> requestMapping;
//处理函数1
public ModelView retMain(ServletRequest sr)
{
ModelView mv = new ModelView();
mv.setViewName("home.html"); //设置ModelView的视图名
mv.setModelData("nowTime", retTime()); //设置ModelView的模型数据
mv.setModelData("imgsrc", "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1596036560048&di=27f10e87e0df7e82da6800a7636b267d&imgtype=0&src=http%3A%2F%2Fimg1.imgtn.bdimg.com%2Fit%2Fu%3D4021827866%2C61334938%26fm%3D214%26gp%3D0.jpg");
return mv;
}
//处理函数2
public ModelView retRumia(ServletRequest sr)
{
ModelView mv = new ModelView();
mv.setViewName("rumia.html");
mv.setModelData("nowTime", retTime());
mv.setModelData("imgsrc", "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1596036659527&di=be8e5c541b833ad3ee348819796e7a4a&imgtype=0&src=http%3A%2F%2Fi0.hdslb.com%2Fbfs%2Farticle%2Fa626eafc37d29c79f16a32b8f66460280f8e6b57.jpg");
return mv;
}
//初始化
public myController()
{
requestMapping = new HashMap<>();
//为请求url添加相应的处理函数
addMapping("/TouhouProject/", "retMain");
addMapping("/TouhouProject/best", "retRumia");
}
//添加(URL,处理函数)映射
public void addMapping(String url, String funcName)
{
try
{
//利用反射取得类函数指针
Class cl = getClass();
Method m = cl.getMethod(funcName, ServletRequest.class); //注意形参的限定
if(m.getReturnType() == ModelView.class) //检查函数的返回值是否为ModelView
{
System.out.println(url +" -> "+ funcName);
requestMapping.put(url, m);
}
}
catch(Exception e)
{
e.printStackTrace();
}
}
//根据请求url调用相应的处理函数
public ModelView process(ServletRequest sr)
{
try
{
//取得请求URL
String targetUrl = ((HttpServletRequest)sr).getRequestURI();
if(requestMapping.containsKey(targetUrl)) //检查映射是否包含该URL
{
//如果包含则取得函数指针并执行
Method m = requestMapping.get(targetUrl);
return (ModelView) m.invoke(this, sr);
}
else
return null;
}
catch(Exception e)
{
e.printStackTrace();
return null;
}
}
//返回当前日期、时间
private String retTime()
{
DateFormat df = DateFormat.getDateInstance(DateFormat.LONG, Locale.ENGLISH);
DateFormat tf = DateFormat.getTimeInstance(DateFormat.LONG, Locale.ENGLISH);
return df.format(new Date())+" "+tf.format(new Date());
}
}
7、viewer渲染器
根据controller返回的ModelView的视图名读取本地文本文件(html),然后根据ModelView的模型数据对读取结果进行渲染并返回。
package gensoku;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.util.Iterator;
import org.springframework.stereotype.Component;
import gensoku.data.ModelView;
@Component
public class myViewer
{
private String resPath;
public myViewer()
{
resPath = "";
}
public myViewer(String p)
{
resPath = p;
}
public String process(ModelView mv)
{
try
{
//读取视图名所指文件
String content = readTextFile(resPath + mv.getViewName());
//简单渲染并返回
content = renderView(content, mv);
return content;
}
catch(Exception e)
{
e.printStackTrace();
return null;
}
}
//读取文本文件
String readTextFile(String filepath)
{
try
{
FileInputStream fs = new FileInputStream(filepath);
InputStreamReader isr = new InputStreamReader(fs);
BufferedReader br = new BufferedReader(isr);
String content = "", buf;
while((buf = br.readLine())!=null)
{
content = content + buf;
}
br.close();
return content;
}
catch(Exception e)
{
e.printStackTrace();
return null;
}
}
//简单渲染(其实就是字符串匹配替换)
private String renderView(String wait, ModelView mv)
{
try
{
String ans = wait;
//轮询ModelView的模型数据
Iterator it = mv.getMapKeyIterator();
while(it.hasNext())
{
//将文本中所有匹配[@key]的子串替换为value
String k = (String)it.next();
ans = ans.replace(String.format("[@"+ k +"]"), mv.getModelData(k));
}
return ans;
}
catch(Exception e)
{
e.printStackTrace();
return wait;
}
}
}
8、filter过滤器组件
package gensoku;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
public class myFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException
{
System.out.println("A new filter is born");
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException
{
System.out.println("Filter gets a request");
chain.doFilter(request, response); //移交给下一个filter
}
@Override
public void destroy()
{
System.out.println("A filter is dead");
}
}
9、listener监听器组件
package gensoku;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
public class myListener implements ServletContextListener
{
@Override
public void contextInitialized(ServletContextEvent sce)
{
System.out.println("Listener context initialized");
}
@Override
public void contextDestroyed(ServletContextEvent sce)
{
System.out.println("Listener context destroyed");
}
}
10、HTTP页面
其中形如[@xxxx]的字符串为需要在渲染时填充数据的标识。
<h1>Welcome home !</h1>
<h2>[@nowTime]<h2>
<img src="[@imgsrc]">