博学,切问,近思--詹子知 (https://jameszhan.github.io)
直接利用web.xml去配置和定义我们的对象组件显然是不灵活和不方便扩展的,由于我们系统中将会需要配置很多个不同的对象资源,比如控制器,View对象,HandlerMapping对象等等,如何对它们进行管理,如何能让我们的前端控制器访问和利用到到它们便是我们不得不面对的问题。还好,现在有了Spring,现在很多流行的MVC框架都支持使用Spring对自己容器里的对象资源进行管理。尽管Spring千好万好,我们这里还是决定不使用它,而是自己来写一个对象容器来管理我们的相关资源,这样我们不仅可以了解对象资源配置管理的细节,还可以顺带学习一下Spring等IOC容器的实现原理。当然,我们这里的实现方案将会尽可能的简单。
如下便是我们的WebApplicationContext类的实现,它能够自动查找WEB-INF路径下所有以config结尾的xml文件,并把其中定义的对象抽取出来,放到Application作用域中,由于我们这里只是个Sample,不需要考虑太多的并发的情况,所有对象的类型,我们都是用Singleton,也就是定义的每个对象都为所有的请求和Servlet共享。 WebApplicationContext中还定义了一个很有用的方法,beansOfType(Class<T>),该方法用于查找出系统中定义的所有的属于当前类型的所有对象资源。package com.google.mvc.web.context;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import javax.servlet.ServletContext;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.apache.log4j.Logger;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
public class WebApplicationContext {
private static final Logger LOGGER = Logger.getLogger(WebApplicationContext.class);
private static final String WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".CONTEXT";
private Map<String, Object> cacheMap;
private ServletContext servletContext;
private DocumentBuilder builder;
public WebApplicationContext(ServletContext servletContext) {
this.servletContext = servletContext;
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
try {
builder = factory.newDocumentBuilder();
} catch (ParserConfigurationException e) {
LOGGER.error("Can't load dom builder", e);
}
}
public void init() {
ServletContext context = getServletContext();
Set<?> set = context.getResourcePaths("/WEB-INF");
Object map = servletContext.getAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE);
if (map != null) {
cacheMap = (Map<String, Object>) map;
} else {
cacheMap = new ConcurrentHashMap<String, Object>();
servletContext.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, cacheMap);
for (Object o : set) {
String path = (String) o;
if (path.endsWith("config.xml")) {
try {
loadResource(servletContext.getResourceAsStream(path));
} catch (Exception ex) {
LOGGER.error("Can't load resource " + path);
}
}
}
}
}
private void loadResource(InputStream resource) throws Exception{
Document doc = builder.parse(resource);
Element root = doc.getDocumentElement();
NodeList nodeList = root.getElementsByTagName("bean");
for(int i = 0; i < nodeList.getLength(); i++){
Element el = (Element)nodeList.item(i);
String id = el.getAttribute("id");
String className = el.getAttribute("class");
Class<?> clazz = this.getClass().getClassLoader().loadClass(className);
Object o = createBean(id, clazz);
NodeList propertyList = el.getElementsByTagName("property");
for(int j = 0; j < propertyList.getLength(); j++){
Element prop = (Element)propertyList.item(j);
String methodName = getMethodName(prop.getAttribute("name"));
Method m = clazz.getMethod(methodName, String.class);
String property = prop.getAttribute("value");
Object dependObject = cacheMap.get(property);
if(dependObject != null){
m.invoke(o, dependObject);
} else {
m.invoke(o, property);
}
}
cacheMap.put(id, o);
}
}
protected String getMethodName(String methodName){
StringBuilder sb = new StringBuilder();
sb.append("set");
sb.append(methodName.substring(0, 1).toUpperCase(Locale.US));
sb.append(methodName.substring(1));
return sb.toString();
}
public Object createBean(Class<?> clazz) throws Exception{
return createBean(clazz.getCanonicalName(), clazz);
}
public Object createBean(String name, Class<?> clazz) throws Exception{
Object o = cacheMap.get(name);
if(o == null){
o = clazz.newInstance();
if(o instanceof WebApplicationContextAware){
((WebApplicationContextAware)o).setWebApplicationContext(this);
}
cacheMap.put(name, o);
}
LOGGER.info(name + "=" + clazz.getCanonicalName());
return o;
}
public Object getBean(String beanName){
return servletContext.getAttribute(beanName);
}
public ServletContext getServletContext() {
return servletContext;
}
public void setServletContext(ServletContext servletContext) {
this.servletContext = servletContext;
}
public <T> Map<String, T> beansOfType(Class<T> clazz){
Map<String, T> map = new HashMap<String, T>();
for(String key : cacheMap.keySet()){
Object o = cacheMap.get(key);
if(clazz.isAssignableFrom(o.getClass())){
map.put(key, (T)o);
}
}
return map;
}
}
我们再来看一下*.config.xml文件里对象资源的定义示例:<?xml version="1.0" encoding="UTF-8"?>
<beans>
<bean id="ControllerAdapter" class="com.google.mvc.web.servlet.mvc.ControllerHandlerAdapter" />
<bean id="HttpRequestAdapter" class="com.google.mvc.web.servlet.mvc.HttpRequestHandlerAdapter" />
<bean id="ViewResolver" class="com.google.mvc.web.servlet.mvc.DefaultViewResolver">
<property name="viewClass" value="com.google.mvc.web.servlet.mvc.InternalResourceView"/>
<property name="prefix" value="/WEB-INF/"/>
<property name="suffix" value=".jsp"/>
</bean>
<bean id="login.do" class="com.google.mvc.web.sample.LoginController" />
<bean id="hello.do" class="com.google.mvc.web.sample.HelloController" />
<bean id="404" class="com.google.mvc.web.servlet.mvc.HandlerFor404" />
</beans> 如果哪个对象资源需要在运行过程中使用到WebApplicationContext的资源及方法,只需实现接口WebApplicationContextAware即可,一旦实现了该接口,当前的WebApplicationContext会被自动的注入到此对象资源中。package com.google.mvc.web.context;
public interface WebApplicationContextAware {
void setWebApplicationContext(WebApplicationContext wac);
}
相关文章: