spring框架中的web模块,可以让我们更好的管理servlet来进行交互操作。
8.1 配置文件
利用XML进行配置,xml是可扩展的标记语言。
XML包含3个部分:
1)XML声明,必须在XML的第一行
2)DTD声明
3)XML正文
利用java读入xml内容,利用反射实现类,这样可以使解耦合无法扩大,更加的利于代码维护。
<?xml version="1.0" encoding="UTF-8"?>
<beans>
<bean id="fruit" class="online.fatotaku.controller.FruitController"></bean>
</beans>
8.2 DispatcherServlet
有了xml,我们需要利用java将xml内容读取,在我们没有实现spring-mvc.jar之前,我们手写了一个模仿DispatcherServlet的类。
8.2.1读取XML
我们写到可以提前运行的位置,也就是init方法当中。
//我们让dispatcherServlet读取出xml里面的bean来声明出来我们需要使用的controller
//输入流
InputStream inputStream =getClass().getClassLoader().getResourceAsStream("ApplicationContext.xml");
//文档工厂创建
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
try {
//文档创建者 创建
DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
//文档创建
Document document = documentBuilder.parse(inputStream);
//从文档中获取标签为bean的内容
NodeList beans = document.getElementsByTagName("bean");
//遍历并把它放入MAP当中
for (int i=0;i<beans.getLength();i++){
Node bean = beans.item(i);
if (bean.getNodeType()==Node.ELEMENT_NODE){
Element element=(Element) bean;
String id = element.getAttribute("id");
String className = element.getAttribute("class");
Object controllerClass =Class.forName(className).getDeclaredConstructor().newInstance();
controllers.put(id,controllerClass);
}
8.2.2 反射创建类
我们在service方法中利用网址的内容通过反射实现类的实现与方法的调用。
//从MAP当中获取你需要的类
Object fruitController = controllers.get("fruit");
String action = req.getParameter("action");
try {
//利用这个类反射调用你需要的方法
Method method = fruitController.getClass().getDeclaredMethod(action,HttpServletRequest.class,HttpServletResponse.class);
//实现方法
method.invoke(fruitController,req, resp);
Method setContext = fruitController.getClass().getDeclaredMethod("setContext", ServletContext.class);
//实现方法
setContext.invoke(fruitController,this.getServletContext());
} catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
e.printStackTrace();
}
8.2.3 问题
当我们将本身的controller类取消掉Servlet类后,他失去了实现父类ViewBaseServlet的init方法,那我们的servletContext便无法通过init获取到值,所以需要改变一些内容。将dispatcherServlet里面的servletContext通过反射发给我们的Controller,在通过执行父类方法,传入我们的servletContext来实现重新编译。
Method setContext = fruitController.getClass().getDeclaredMethod("setContext", ServletContext.class);
setContext.invoke(fruitController,this.getServletContext());
private ServletContext context;
public void setContext(ServletContext context) {
this.context = context;
try {
super.init(context);
} catch (ServletException e) {
e.printStackTrace();
}
}
public void init(ServletContext context) throws ServletException {
servletContext=context;
// 2.创建Thymeleaf解析器对象
ServletContextTemplateResolver templateResolver = new ServletContextTemplateResolver(servletContext);
}
8.2.4统一方法参数
可以看到目前所使用的controller类中方法参数一样,并且十分臃肿,如果可以将它整体搬运到dispatcherServlet中进行统一放入,那将完成更好的松耦合。
//我们利用反射获取到的那个方法进行对于这个方法的参数获取。
Method[] methods = controllerClass.getMethods();
Method method = null;
for (Method m : methods) {
if (m.getName().equals(action)) {
method=m;
method.setAccessible(true);
}
}
//所有参数
assert method != null;
Parameter[] parameters = method.getParameters();
Object[] parameterValues=new Object[parameters.length];
for (int i = 0; i < parameterValues.length; i++) {
String paraName = parameters[i].getName();
if (paraName.equals("req")) {
parameterValues[i]=req;
}else if(paraName.equals("session")){
parameterValues[i]=req.getSession();
}else{
parameterValues[i]=req.getParameter(paraName);
}
}
这里可以看到所调用的内容我们统一放在了object数组中,那在参数使用的方法中,他们是不同数据类型的参数,这就会引起报错。
我们该如何解决这个问题呢?
//我们获取参数类型进行判断
String paraTypeName = parameters[i].getType().getName();
if (paraTypeName.equals("java.lang.Integer")) {
parameterValues[i]=Integer.parseInt(req.getParameter(paraName));
}else{
parameterValues[i]=req.getParameter(paraName);
}