BaseServlet的应用
我们知道Tomcat的web.xml中维护的servlet标签对就相当于一个个的小对象! 尽管一个Servlet只会被初始化化一次,但每次有请求过来的时候都会创建一个实例。每一种不同的功能请求同时也会产生一个ActionServlet,这无疑会造成大量对象的存在,占据内存而不做功! 同时Servlet作为解析数据和处理数据对象的程序,在同一个类中创建多个对象是避免不了的,那么如何避免或者减少Servlet运行过程中,冗余对象的存在呢? BaseServlet应运而生
初次优化:对于Servlet来说,每次实现一个功能都会产生一个doGet/doPost,频繁创建会占据内存资源,导致内存泄漏,可否通过在请求中传入方法名,利用映射来定位每个资源方法;从而实现将同对象的多个方法合并在一起?
在Tomcat的Context转发http请求进入到Servlet之前,Servlet会进行初始化,先执行HttpServlet对其HttpServletRequest和HttpServetResponse进行构造并判断doPost/doGet,同时也会按顺序执行其静态-构造-init-service。我们可以使ActionServlet继承httpServlet并重写其service方法,通过在serive'方法里面判断URL连接传递回来的方法是哪个,判断目标方法!
============================================================================
二次优化:对于Servlet来说,每次都继承HttpServlet并重写其Service方法,会产生很多无用的资源,浪费很多开发时间,能否继续优化!或者把service独立出来,不用每次都重写Service方法!
通过创建一个BaseServlet继承HttpServlet,来重写service接受请求和处理http请求回来的方法,再由目标的ActionServlet继承BaseServlet,而ActionSerlvet只需要写资源方法则可以。当请求到达的时候,从请求中获取方法名称,然后获取当前继承该BaseServlet的ActionServlet的Class类,通过类的方与请求提供的请求方法名进行匹配,如果匹配成功,说明当前继承BaseServlet的ActionServlet里面存在着该方法,可以利用方法的invoke()回调该方法!
此BaseServlet目标是实现将Service脱离出来,使得以后每个ActionServlet只专注于处理逻辑和解析数据
============================================================================
三次优化:对于重定向问题的统一处理,这部分也是属于重复代码能否进一步优化?
那么,什么是重定向?
1.RequestDispatcher.forward() 是在服务器端起作用,当使用forward()时,Servlet engine传递HTTP请求从当前的Servlet or JSP到另外一个Servlet,JSP 或普通HTML文件,也即你的form提交至a.jsp,在a.jsp用到了forward()重定向至b.jsp,此时form提交的所有信息在b.jsp都可以获得,参数自动传递. 但forward()无法重定向至有frame的jsp文件,可以重定向至有frame的html文件,同时forward()无法在后面带参数传递,比如servlet?name=frank,这样不行,可以程序内通过response.setAttribute("name",name)来传至下一个页面. 重定向后浏览器地址栏URL不变. 最初请求的参数会跟随 例:在servlet中进行重定向 public void doPost(HttpServletRequest request,HttpServletResponse response) throws ServletException,IOException { response.setContentType("text/html; charset=gb2312"); ServletContext sc = getServletContext(); RequestDispatcher rd = null; rd = sc.getRequestDispatcher("/index.jsp"); //定向的页面 rd.forward(request, response); } 通常在servlet中使用,不在jsp中使用。 2.response.sendRedirect() 是在用户的浏览器端工作,sendRedirect()可以带参数传递,比如servlet?name=frank传至下个页面,同时它可以重定向至不同的主机上,sendRedirect()可以重定向有frame.的jsp文件. 重定向后在浏览器地址栏上会出现重定向页面的URL ,原路返回给客户端,让客户端重新发起请求,原请求相当于查询效果,客户端两度请求 例:在servlet中重定向 public void doPost(HttpServletRequest request,HttpServletResponse response) throws ServletException,IOException { response.setContentType("text/html; charset=gb2312"); response.sendRedirect("/index.jsp"); } |
String result = (String)method.invoke(this,req,resp);回调方法是具备返回结果的功能。当目标资源方法处理完成后如果需要重定向可以返回携带定向标志(自定义)和定向目标的页面,回到BaseServlet中进行通过公共方法统一处理。以下为再网络上寻找的公共模块:
public void abstract BaseServlet extends HttpServlet{
public void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException,IOException{
String methdoName = req.getParameter("method");
if(methodName == null || methodName.trim().isEmpty()){
throw new RuntimeException("您没有传递method参数,无法确定要调用的方法!");
}
Class c = this.getClass();
Method method=null;
try{
method = c.getMethod(methodName,
HttpServletRequest.class,HttpServletResponse.class);
}catch(Exception e){
throw new RuntimeException("您要调用的"+methodName+"方法,它不存在!");
}
// 调用 method 方法
try{
String result = (String)method.invoke(this,req,resp);
/*
* 获取请求处理方法执行后返回的字符串, 它表示转发或重定向的路径!
* 完成转发或重定向.
*
* 如果用户返回的字符串为 null, 或为 "", 那么我们什么也不做!
*
* 查看返回的字符串中是否包含冒号, 如果没有, 表示转发
* 如果有, 使用冒号分割字符串, 得到前缀和后缀!!
* 其中前缀如果是 f, 表示转发, 如果是 r, 表示重定向, 后缀就是要转发或重定向的路径了!
*/
if(result == null || result.trim().isEmpty()){
return;
}
// 如果不为空
if(result.contains(":")){
// 使用冒号分割字符串, 得到前缀和后缀
int index = result.indexOf(":"); // 获取冒号的位置
String s = result.substring(0,index); // 获取前缀
String path = result.subString(index+1); // 获取后缀, 即路径
if(e.equalsIgnoreCase("r")){ // 如果前缀是 r, 重定向
resp.sendRedirect(req.getContextPath()+path);
}else if(e.equalsIgnoreCase("f")){
req.getRequestDispatcher(path).forward(req,resp);
} else {
throw new RuntimeException("您指定的操作:"+s+",当前版本不支持!");
}
} else { // 没有冒号, 默认为转发
req.getRequestDispatcher(result).forward(req,resp);
}
}catch(Exception e){
throw new RuntimeException(e);
}
}
}
==============================================================================================================================================
对于该处理方式目前已经将重复和繁琐的代码统一移除到BaseServlet这个公共类中,大大减少了人力和重复创建ActionServlet的麻烦!但是问题依旧存在,当一个ActionServlet中 存在着多个对象(Bean或者其他的工具对象),但这些对象并不是每次一次资源方法都是必需的,那么就会产生很多冗余的对象。Servlet除了初始化外,请求都会产生一个线程,每个线程产生一个Servlet实例,同样的每个实例会创建很多对象,其中没用的对象占据了每次请求的大部分内存!这样无疑会让程序产生很多冗余对象!这个问题在BaseServlet优化后棒没有解决,只是变得更为集中了?那么我们可以有什么方式处理吗?
第四次优化: 利用配置文件的方式,初始化一个工厂类对所有的bean对象进行处理。
1.维护一个xxx.propetires文件,里面以key-value的形式,将用用户实现类给的路径给维护进去
noticeDao=com.rose.dao.impl.NoticeDaoImpl shopDao=com.rose.dao.impl.ShopDaoImpl saleDao=com.rose.dao.impl.SaleDaoImpl |
2.建立一个PropertiesBeanFactory
public class PropertiesBeanFactory implements BeanFactory { // 定义一个容器来存放生产出来的bean对像 private Map beans = new HashMap(); public PropertiesBeanFactory() { // 假定默认情况下是去读取beans.properties this("Beans.properties"); } public PropertiesBeanFactory(String configFile) { // 读取配置文件, try { Properties properties = new Properties(); properties.load(Thread.currentThread().getContextClassLoader() .getResourceAsStream(configFile)); Set set = properties.entrySet(); //根据set集合构建遍历对象 Iterator iterator = set.iterator(); while(iterator.hasNext()){ Map.Entry entry=(Map.Entry)iterator.next(); //获取key String key = (String)entry.getKey(); String className = (String) entry.getValue(); //根据className来获取Class对象 com.blog.dao.impl.BlogDaoImpl Class clazz = Class.forName(className); Object bean = clazz.newInstance(); //按照key-value形式放置到 beans.put(key, bean); } } catch (Exception e) { e.printStackTrace(); } } @Override public Object getBean(String name) { return beans.get(name); } public Map getBeans() { return beans; } public void setBeans(Map beans) { this.beans = beans; } } |
该对象利用无参的构造方法加载xxx.prperires文件,获取key-value对并利用Object bean= Class.forName(classNamevalue).newInstance();将对应value的路径文件创建对象后存放到beans集合中
3.创建一个InitBeanFactorySerlvet对象,继承自HttpServlet,用于读取properites文件的对象,形成引用,然后在初始化init()方法中调用PropertesBeanFactory()对象的有参构造创建容纳了各种对象实现类的Beans的Dao实现类对象集合,并将该集合放入公共容器ServletContext中,定义为factory对象!
initBeanFactoryServlet只需要被执行一次,那么最好跟随在Tomcat随着项目的启动而启动,而且需要在请求到达项目之前而初始化完毕,那么可以在web.xml中配置,随着系统的初始化而启动,配置如下:
<servlet> <servlet-name>InitBeanFactoryServlet</servlet-name> <servlet-class>com.rose.servlet.InitBeanFactoryServlet</servlet-class>//路径 <init-param> <param-name>configFile</param-name>//Tomcat加载项目时,会随着加载在ServletContext容器中的内容(对象)名称 <param-value>Beans.properties</param-value> </init-param> <load-on-startup>0</load-on-startup>//在tomcat启动的时候跟随系统而完成初始化,可能导致tomcat启动变慢 </servlet> <servlet> |
伴随系统启动而加载.porperties文件,而创建Beans的实现类对象集合!
4.BaseServlet初始化Factory()对象。
public class BaseServlet extends HttpServlet { @Override public void init(ServletConfig config) throws ServletException { // 获取factory BeanFactory factory = (BeanFactory) config.getServletContext().getAttribute( "factory"); // 考虑去使用反射机制来解析类,匹配setXXX方法进行调用实现注入 Method[] methods = this.getClass().getMethods(); for (Method m : methods) { if (m.getName().startsWith("set")) { String propertyName = m.getName().substring(3); StringBuffer sb = new StringBuffer(propertyName); sb.replace(0, 1, (propertyName.charAt(0) + "").toLowerCase());// 获取到真正的属性名称 propertyName = sb.toString();// StringBuffer转化为String // 从Factory中获取bean对象 Object bean = factory.getBean(propertyName); // 考虑调用setXXX方法来完成注入 try { m.invoke(this, bean); } catch (Exception e) { e.printStackTrace(); } } } super.init(config); } |
在此处说明:Servlet作为三层架构中的service层,需要非常频繁地调用Beans对象的集合类,而当该Beans对象的实现类由系统创建后,那么在Class中会同时将Beans对象相关的实现类中涉及的方法一次性创建在Class中,也即是说Class由于原本每次实例Servlet会产生多个对象,变成有且只有一个对象被使用!从而使得项目在运行ActionServlet的时候不会再创键对象以占据内存,只有再Class内存中找不到才会去创建对象!
============================================================================
我们的每一个ActionServlet都需要继承BaseServlet并且每一个BaseServlet都需要注入想要使用的Beans对象的dao实现了类引用,再通过ActionServlet的构造将引用指向全局使用(全局变量指向该引用):
private ImgDaoImpl imgDao; public ImgDaoImpl getImgDao() { return imgDao; } |
再由各个方法引用,这就是注入!那么spring作为其集合和继承到底有什么作用呢?
spring:依赖注入,控制反转,