配置型Servlet
Context类中的改动
-
我们前面已经使用了Servlet的简单测试,但是Tomcat中的Servlet是通过配置文件解析进行配置的。设计的逻辑是这样的:
- 在Conf文件夹下使用一个context.xml文件来来保存servlet的配置文件的位置。
<?xml version='1.0' encoding='utf-8'?> <Context> <WatchedResource>WEB-INF/web.xml</WatchedResource> </Context>
这个文件指定了context文件下的servlet配置所在的路径,都是在应用目录/WEB-INF/web.xml文件中。
-
在Constant类中设置一个变量用于保存这个文件对象。
public final static File contextFile = new File(confFolder, "context.xml");
-
建立一个工具类用于解析出context.xml文件中
watchedResource
标签指定的文件package jerrymice.util; import cn.hutool.core.io.FileUtil; import cn.hutool.log.LogFactory; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; public class ContextXmlUtil { public static String getWatchedResource(){ try{ String xml = FileUtil.readUtf8String(Constant.contextFile); Document d = Jsoup.parse(xml); Element element = d.selectFirst("WatchedResource"); return element.text(); }catch(Exception e){ LogFactory.get().info(e.toString()); return "WEB-INF/web.xml"; } } }
这个类只有一个工具方法就是用于解析context.xml文件指定的位置文件的位置。
-
在Context类中添加一个属性用于标记这个文件。建立四个哈希表用于储存这个文件中列出的映射信息。
private File contextWebXmlFile; this.url_servletName = new HashMap<>(); this.url_servletClassName = new HashMap<>(); this.servletName_className = new HashMap<>(); this.className_servletName = new HashMap<>();
获取这个文件,判断是否存在,如果存在的话,先判断其中是否存在这重复的映射,如果存在的话抛出异常。
WEB-INF\web.xml
文件的大概样子是下面这种形式的:<?xml version="1.0" encoding="UTF-8"?> <web-app> <servlet> <servlet-name>HelloServlet</servlet-name> <servlet-class>jerrymice.webappservlet.HelloServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>HelloServlet</servlet-name> <url-pattern>/hello</url-pattern> </servlet-mapping> </web-app>
-
检查文件中是否存在重复映射信息的代码如下:
/** * 通过检查xml文件中的映射是否有重复 * @param document: 从web.xml文件中解析获得的doc对象 * @param mapping: 想要检查的元素映射,例如 servlet servlet-name * @param msg: 反馈的重复信息提示 * @throws WebConfigDuplicatedException */ private void checkDuplicated(Document document, String mapping, String msg) throws WebConfigDuplicatedException { Elements elements = document.select(mapping); // 判断的逻辑是先将元素提取出来,放到Set中,从而判断是否重复 Set<String> elementText = new HashSet<>(); for(Element element: elements){ String text = element.text(); boolean b = elementText.add(text); if (!b){ // 说明其中已经有这个元素了,即重复,抛出异常 throw new WebConfigDuplicatedException(StrUtil.format(msg, text)); } } } /** * 检查uri, servletName和className是否重复 * @throws WebConfigDuplicatedException */ private void checkDuplicated(Document document) throws WebConfigDuplicatedException{ checkDuplicated(document, "servlet-mapping url-pattern","servlet url重复,请保持其唯一性:{}"); checkDuplicated(document, "servlet servlet-name","servlet名称重复,请保持其唯一性:{}"); checkDuplicated(document, "servlet servlet-class", "servlet类名重复,请保持其唯一性:{}"); }
-
上面的映射对应了servlet的几个属性值,一个是它的名字,另外一个是它对应的Java类的名称,还有一个是访问这个servlet时使用的uri路径,使用四个哈希表来保存信息之间的映射关系,二这些映射关系都是在上面的文件中定义的。
/** * 从Document对象中解析出映射信息 * @param document: config文件中context.xml中指定的watchedSourceFile */ private void parseServletMapping(Document document){ // 获取url和ServletName之间的映射 Elements mappingElements = document.select("servlet-mapping url-pattern"); for (Element element: mappingElements){ // 获取这个Servlet对应的url String urlPattern = element.text(); // 获取这个Servlet对应的name String servletName = element.parent().select("servlet-name").first().text(); url_servletName.put(urlPattern, servletName); } // 获取servletName和className之间的映射 Elements servletNameElements = document.select("servlet servlet-name"); for(Element element : servletNameElements){ String servletName = element.text(); String className = element.parent().select("servlet-class").first().text(); className_servletName.put(className, servletName); servletName_className.put(servletName, className); } // 获取url和className之间的映射信息 // 先将所有的url拿出来 Set<String> urls = url_servletName.keySet(); for (String url: urls){ // 获取url对应的servletName String servletName = url_servletName.get(url); // 根据servletName获取className String className = servletName_className.get(servletName); url_servletClassName.put(url, className); } }
HttpServlet中的改动
-
现在当一个请求传入时,首先获取它的
uri
,然后获取context
,在根据context
和uri
来获取这个servlet对应的类,根据反射来创建这个类的对象然后调用这个对象的doGet()
方法。 -
String uri = request.getUri(); System.out.println(uri); if (uri == null) { // 说明此时没有请求过来 return; } // 获取request的context,context的path是访问的文件夹路径,docBase是实际在系统中的绝对路径 Context context = request.getContext(); // 根据context和uri来获取className String servletClassName = context.getServletClassName(uri); if (null != servletClassName) { // 如果请求的类存在,根据反射获取这个类的一个实例 Object servletObj = ReflectUtil.newInstance(servletClassName); // 调用这个对象的方法 ReflectUtil.invoke(servletObj, "doGet", request, response); }
测试
- 启动服务器,然后访问指定的uri来访问指定的servlet