Spring的前身-BaseServlet

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只专注于处理逻辑和解析数据

============================================================================

三次优化:对于重定向问题的统一处理,这部分也是属于重复代码能否进一步优化?

扫描二维码关注公众号,回复: 2516845 查看本文章

那么,什么是重定向?

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:依赖注入,控制反转,

猜你喜欢

转载自blog.csdn.net/qq_36505948/article/details/80950287