首先,我们在web.xml中定义一个servlet,
<servlet>
<servlet-name>Mvc4meServlet</servlet-name>
<servlet-class>net.csdn.blog.deltatang.mvc4me.Mvc4meServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>Mvc4meServlet</servlet-name>
<url-pattern>/mvc/*</url-pattern>
</servlet-mapping>
然后做一个实现:
package net.csdn.blog.deltatang.mvc4me;
public class Mvc4meServlet extends HttpServlet {
public void init(ServletConfig config) throws ServletException {
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String uri = request.getRequestURI();
//根据uri取得对应的Controller
Controller controller = getControllerByUriMapping(uri);
Map<String, Object> modelAndView = controller.process(request);
String jspPath = controller.getJspPath();
String classPath = controller.getClassPath();
JspHandler handler = JspCompiler.compile(jspPath, classPath);
String content = handler.buildContent(modelAndView);
response.getWriter().println(content);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
请注意,这里面 jspPath, classPath 开始的流程是从上一个章节《再造一个jsp》从直接引用而来。
我们新定义了一个 Controller,希望它能提供一切我们需要的数据,用以让程序正常执行。
当然,因为我的开发方式是,先确定需求,在去做实现,因此,在IDE中,代码现在的表现是这样的:
没有关系,让我们使用IDE的帮助工具,迅速构建:
和
我们得到了接口:
package net.csdn.blog.deltatang.mvc4me;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
interface Controller {
public Map<String, Object> process(HttpServletRequest request);
public String getJspPath();
public String getClassPath();
}
现在,不急着实现这个接口,我们继续从需求入手,梳理我们的想法:
我们需要一个配置文件,类似于spring.xml,在这个配置文件中,我们需要定义一个类,
提供他的 uri(为了简便起见,我们用bean id 代替),类名,对应的jsp模板文件,类路径信息。
有了这些信息,我们就能够提供 我们的主分发器
Mvc4meServlet
足够的数据。
先定义xml格式如下:
<beans >
<bean id=="/mvc/search" view="./search.template.jsp" class="net.csdn.blog.deltatang.mvc4me.SearchCtrol"/>
</beans>
接着,是实现Mvc4meServlet中 getControllerByUriMapping 方法的时候了:)
别忘了,我们已经实现了dom4me 和 spring4me,因此, 剩下的过程是一个 weave的过程。
通缉令/刺客联盟 里面的 weaver 是很吊的说:)
再来看BeanFactory,我们遇到了一个麻烦,因为它的简陋,我们并没有进行传递属性的操作,因此,
<bean id=="/mvc/search" view="./search.template.jsp" class="net.csdn.blog.deltatang.mvc4me.SearchCtrol"/>
中的view属性无法传递给controller实例,
解决方式有两种:第一、丰富我们的spring4me,提供更多的xml构造类的方式,第二、修改我们的接口,在初始化之后做cast。
简便起见,我选择第二种,让我们重新定义controller接口:
package net.csdn.blog.deltatang.mvc4me;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
public abstract class Controller {
protected String jspPath;
protected String classPath;
public abstract Map<String, Object> process(HttpServletRequest request);
public String getJspPath() {
return jspPath;
}
public void setJspPath(String jspPath) {
this.jspPath = jspPath;
}
public String getClassPath() {
return classPath;
}
public void setClassPath(String classPath) {
this.classPath = classPath;
}
}
以上将接口改造为一个抽象类,内置了 setter 方法,供BeanFactory调用设置属性。
然后,我们在BeanFactory 中的
public static Object getBean(String id)
方法返回之前,简单的做一个判断 obj 是否是 controller 的实例, 如果是,设置属性值:)
public static Object getBean(String id) {
Object obj = delegate_getBean(id);
if(obj instanceof Controller) {
Controller ctrl = (Controller) obj;
Node n = domTree.getById(id);
String jspPath = n.getAttributes().get("view");
String classPath = n.getAttributes().get("class");
ctrl.setJspPath(jspPath);
ctrl.setClassPath(classPath);
}
return obj;
}
//原有的方法被改名为:
public static Object delegate_getBean(String id) {
这样,我们的中心转发器Mvc4meServlet就完成了:
package net.csdn.blog.deltatang.mvc4me;
import net.csdn.blog.deltatang.dom4me.DomTree;
import net.csdn.blog.deltatang.dom4me.Node;
import net.csdn.blog.deltatang.dom4me.XmlParser;
import net.csdn.blog.deltatang.jsp4me.JspCompiler;
import net.csdn.blog.deltatang.jsp4me.JspHandler;
import net.csdn.blog.deltatang.spring4me.BeanFactory;
public class Mvc4meServlet extends HttpServlet {
public void init(ServletConfig config) throws ServletException {
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String uri = request.getRequestURI();
//根据uri取得对应的Controller
Controller controller = getControllerByUriMapping(uri);
Map<String, Object> modelAndView = controller.process(request);
String jspPath = controller.getJspPath();
String classPath = controller.getClassPath() + "_JSP";
JspHandler handler = JspCompiler.compile(jspPath, classPath);
String content = handler.buildContent(modelAndView);
response.getWriter().println(content);
}
private static Map<String, Controller> ctrlMapping = null;
private Controller getControllerByUriMapping(String uri) {
return (Controller)BeanFactory.getBean(uri);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
现在,是我们实现Controller的时间了,还记得以前写的SearchServlet么?直接把中间的业务逻辑代码copy过来就好了:)
package net.csdn.blog.deltatang.mvc4me;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import cn.com.sitefromscrath.BeanFactory;
import cn.com.sitefromscrath.service.SearchService;
public class SearchCtrol extends Controller {
@Override
public Map<String, Object> process(HttpServletRequest request) {
String keywords = request.getParameter("keywords");
if(keywords == null) keywords = "";
SearchService searchService = (SearchService)BeanFactory.getBean("searchService");
List results = searchService.search(keywords);
Map<String, Object> mov = new HashMap<String, Object>();
mov.put("keywords", keywords);
mov.put("results", results);
return mov;
}
}
最后一步,view模板,当然,因为我们的JavaSign Pages兼容 JavaServer Pages, 所以,简单的复制就可以了:)
当然,要注意,我们的jsp可是和web 容器分离的,所以 request 、page 是不能用的了,当然,好消息是:out可以用呢!
<%@page pageEncoding="UTF-8"%>
<%@page import="java.util.List"%>
<%@page import="cn.com.sitefromscrath.entity.Result"%>
<%
String keywords = (String)request.getAttribute("keywords");
List results = (List)request.getAttribute("results");
%>
改成最终的结果:
<%@page import="java.util.List"%>
<%@page import="cn.com.sitefromscrath.entity.Result"%>
<%
String keywords = (String)context.get("keywords");
List results = (List)context.get("results");
%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
</head>
<body>
<h1>search it!</h1>
<form action="/mvc/search" method="get">
<input type="text" value="<%=keywords %>" name="keywords" />
<input type="submit" value="search!" />
</form>
<%
if(!results.isEmpty()) {
%>
<table border="1" bordercolor="grey" >
<%
for(int i = 0; i < results.size(); i++) {
Result result = (Result)results.get(i);
%>
<tr>
<td><%=result.title %></td><td><%=result.content %></td>
</tr>
<%
}
%>
</table>
<%
}
%>
</body>
</html>
好了 , run一把试试吧:)
问题大条了。。。
D:\dev\Tomcat 6.0\webapps\ROOT\WEB-INF\classes\net\csdn\blog\deltatang\mvc4me\SearchCtrol_JSP.java:3: 错误: 程序包net.csdn.blog.deltatang.jsp4me不存在
import net.csdn.blog.deltatang.jsp4me.JspHandler;
^
D:\dev\Tomcat 6.0\webapps\ROOT\WEB-INF\classes\net\csdn\blog\deltatang\mvc4me\SearchCtrol_JSP.java:5: 错误: 程序包cn.com.sitefromscrath.entity不存在
import cn.com.sitefromscrath.entity.Result;
^
D:\dev\Tomcat 6.0\webapps\ROOT\WEB-INF\classes\net\csdn\blog\deltatang\mvc4me\SearchCtrol_JSP.java:6: 错误: 找不到符号
public class SearchCtrol_JSP implements JspHandler {
^
符号: 类 JspHandler
D:\dev\Tomcat 6.0\webapps\ROOT\WEB-INF\classes\net\csdn\blog\deltatang\mvc4me\SearchCtrol_JSP.java:29: 错误: 找不到符号
Result result = (Result)results.get(i);
^
符号: 类 Result
位置: 类 SearchCtrol_JSP
D:\dev\Tomcat 6.0\webapps\ROOT\WEB-INF\classes\net\csdn\blog\deltatang\mvc4me\SearchCtrol_JSP.java:29: 错误: 找不到符号
产生这个的原因,就是编译没有通过,因为classpath的设置有问题,没有包含我们部署的class文件,
另一方面,在运行的时候,classLoader 采用的是 system默认的,而不是tomcat 的 WebClassLoader,这样,显然在加载的时候(容器内加载)会出问题。
因此,我们做一个调整:
首先是 修改 Mvc4meServlet
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String uri = request.getRequestURI();
//根据uri取得对应的Controller
Controller controller = getControllerByUriMapping(uri);
Map<String, Object> modelAndView = controller.process(request);
String jspPath = controller.getJspPath();
String classPath = controller.getClassPath() + "_JSP";
// JspHandler handler = JspCompiler.compile(jspPath, classPath);
ClassLoader classLoader = this.getClass().getClassLoader();
JspHandler handler = JspCompiler.compile(jspPath, classPath, classLoader);
String content = handler.buildContent(modelAndView);
response.getWriter().println(content);
}
我们获得web容器的classLoader,作为参数船体给 JspCompiler。当然,
JspCompiler.compile
也需要同步调整:
public static JspHandler compile(String jspPath, String classPath, ClassLoader classLoader) {
String className = classPath.substring(classPath.lastIndexOf('.') + 1);
String source = new JspToJavaLangBuilder().convert(jspPath, className);
System.out.println(source);
StringToObjectBuilder builder = new StringToObjectBuilder();
JspHandler handler = (JspHandler)builder.build(source, classPath, classLoader);
return handler;
}
最后,到达了我们的真正实现类StringToObjectBuilder
public Object build(String source, String classPath, ClassLoader classLoader) {
String javaFilePath = classPath.replaceAll("\\.", "/") + ".java";
try {
File root = new File(workdir);
File sourceFile = new File(root, javaFilePath);
sourceFile.getParentFile().mkdirs();
new FileWriter(sourceFile).append(source).close();
// JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
// compiler.run(null, null, null, sourceFile.getPath());
javaCodeCompile(sourceFile);
if(classLoader == null) {
URL[] urls = new URL[]{root.toURI().toURL()};
classLoader = URLClassLoader.newInstance(urls);
}
Class<?> cls = Class.forName(classPath, true, classLoader);
Object instance = cls.newInstance();
return instance;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
我们将classLoader作为参数读取,null的情况,才调用我们原有的实现:
URL[] urls = new URL[]{root.toURI().toURL()};
classLoader = URLClassLoader.newInstance(urls);
同时,取消了
// JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
// compiler.run(null, null, null, sourceFile.getPath());
代之以:
public void javaCodeCompile(File sourceFile) {
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<JavaFileObject>();
StandardJavaFileManager fileManager = compiler.getStandardFileManager(diagnostics, null, null);
Iterable<? extends JavaFileObject> compilationUnits = fileManager.getJavaFileObjectsFromFiles(Arrays.asList(new File[]{sourceFile}));
List<String> optionList = new ArrayList<String>();
String classpath = System.getProperty("java.class.path");
classpath += ";" + workdir;
optionList.addAll(Arrays.asList("-classpath", classpath));
JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager, diagnostics, optionList, null, compilationUnits);
task.call();
}
这里我们改为使用
compiler.getTask
因为通过它,可以传递classpath等编译参数进去。
现在,万事大吉,
run一把试试:
成功了~~~!
而且,提请注意,虽然作为工具打造者,我们拉拉杂杂写了很多,
但是作为工具使用者,你只需要:
写一个Controller 的实现
写一个 JSignP 模板
配置我们的 xml文件
万事大吉~~是不是跟 spring-mvc 或者 structs 体验一致呢:)