继承GenericServlet类
Generic:泛型
在通过实现Servlet接口来定义一个Servlet类时存在一个很不方便的问题:有太多不需要的方法必须要实现。通常我们只在service()方法中完成业务逻辑,但由于Servlet 接口中还存在另外四个方法,所以也要必须实现。为了解决这个问题JavaEE的API中提供了一个javax.servet.GenericServlet类,开发者在定义一个servlet时继承该GenericServlet类,此时只需要重写service方法即可。
我们新建一个java文件 继承GenericServlet类
package s3;
import java.io.IOException;
import javax.servlet.GenericServlet;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
public class s3 extends GenericServlet {
@Override
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
// 继承GenericServlet类 必须要重写service方法 因为 service方法在GenericServlet中是抽象的!
}
public static void main(String[] args) {
// TODO Auto-generated method stub
}
}
我们点进去他的源码去查看 会发现很多知识:
适配器模式
什么是适配器模式:
适配器模式把一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工作。可以将这里的适配器看做是一个万能充电接口,该充电接口一端可以连接安卓手机的充电器,另一端连接苹果手机,这样就实现了使用安卓手机的充电器给苹果手机充电了。
GenericServlet类源码分析
GenericServlet类中就使用了适配器模式。如上面编写的 s3,使用了GenericServlet后,可以将s3类和Servlet接口适配在一起,只重写service方法就能够创建一个servlet类了。
通过查看GenericServlet的源码可以看到,servlet 类是一个抽象类,实现了servlet接口和ServletConfig接口并重写了除了service方法以外的全部方法,这样子类在继承GenericServlet类时,只需重写service方法。如果想要使用destroy方法时,直接重写GenericServlet类中的destroy方法就行,而在GenericServlet类中的destroy方法本身就是一个空实现,里面没有代码。
意思是什么呢: 意思就是说 继承这个类 你可以不用去重写那些烦人的接口方法 , 你用的到什么就重写什么 Generic 中都有给到你重写。
但它里面的 init 方法 是个很特别的 "适配器模式" 方法 :
以下代码 是 GenericServlet 中的 init 方法 :
@Override
public void init(ServletConfig config) throws ServletException {
this.config = config;
this.init();
}
/**
* A convenience method which can be overridden so that there's no need to
* call <code>super.init(config)</code>.
* <p>
* Instead of overriding {@link #init(ServletConfig)}, simply override this
* method and it will be called by
* <code>GenericServlet.init(ServletConfig config)</code>. The
* <code>ServletConfig</code> object can still be retrieved via
* {@link #getServletConfig}.
*
* @exception ServletException
* if an exception occurs that interrupts the servlet's
* normal operation
*/
public void init() throws ServletException {
// NOOP by default
}
我们可以看到有两个 一个是空参、空方法体的 一个自动调好了:
其中无参的init方法是GenericServlet中自己定义的,那为什么要这么做呢?
如果在其子类中想要调用init方法的话,需要重写其init方法,但这个重写的 init(ServletConfig) 方法必须要调用父类的 init(ServletConfig) 方法,即在第一句必须写上super.init(config);否则将无法获取到 ServletConfig 对象。若 ServletConfig 对象未获取,程序在运行时就有可能会出现空指针异常,但这个 init(ServletConfig)语句,有时候会忘写,例如下面程序就会出现问题:
<servlet>
<servlet-name>s3</servlet-name>
<servlet-class>s3.s3</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>s3</servlet-name>
<url-pattern>/s3</url-pattern>
</servlet-mapping>
记得先配好xml配置文件哦!!
然后我们尝试 在 service中重写init方法。
package s3;
import java.io.IOException;
import javax.servlet.GenericServlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
public class s3 extends GenericServlet {
@Override
public void init(ServletConfig config) throws ServletException {
// 注意!重写的这个init 方法 是有参数的!!
// super.init(config); //如果忘記這行代碼
}
@Override
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
// 继承GenericServlet类 必须要重写service方法 因为 service方法在GenericServlet中是抽象的!
System.out.println(super.getServletName()); //小栗子 父类尝试获取配置的Servlet名
//如果报空指针异常那么就是 重写有参的init时忘记了 “super.init(config);” 这行代码!
//因为没有获取父类中的config对象 即: 父类中的config对象为空的 因为你重写了它有参的那个方法。
}
public static void main(String[] args) {
// TODO Auto-generated method stub
}
}
配合看Generic的源代码 和 看了以上注释 得出以下结论:
它的内部是用的是适配器模式 其次为什么 调用父类的 getServletName 会报错,因为你重写了它有参数的那个方法 ,那么 父类init方法中的 config 参数就会没东西传给它 就会为为空,你得在重写的方法中
调用它,并且将重写的那个 config 传入 ,那样父类 init 方法的 config 就会有东西,即不会空指针异常~~
其次为什么说 他 要这样设计 两个 init 方法呢? 而且 在 有参数的init 中调用init? 因为:
父类中的init已经配置好(有参数的init方法)了的 你自己用即可 但是呢 他得给你重写啊 你重写了之后那些参数传达就会缺省,那样的话岂不是要调用父类的 init 且 传送参数过去,那样很不方便,那么我们得设置一个空的给你重写,并且呢 在有参的(配置好的)init 方法中 调用空的方法(你重写的) 那么就能一举两得了 既不用自己配置 也能自己重写init方法 自定义的代码写入空参的 init方法中。
所以呢:
所以为了避免这个问题的出现,在GenericServlet类中自己定义了一个没有参数的init方法,该方法就是让子类去重写的,子类重写该方法时,无需编写super.init(config);为了保证该无参方法在初始化时执行,在init(ServletConfig config)方法中对其进行了调用。
还是那句话 除了service方法 其他的有用得到才去重写!!!!!!! 重写的时候注意一下init这个方法即可!!!!! 其他没什么。
早上起来有点想不通这个模式 然后自己又去测试了下 其实涉及到了最基础的继承优先级那块 就是说想重写的方法比父方法永远优先(覆盖)!
下面来个代码你就懂了 我100% 还原(Junit测试!):
package ll;
import org.junit.Test;
public class junit extends ini{
@Test
public void s1() { //冲这里开始Junit!! 记住了!!
init(1);
}
@Override
public void init() {
System.out.println("我是无参的init");
}
}
class ini{
public void init(int a) {
System.out.println("我是有参的init");
init();
}
public void init() {
System.out.println("我是傻逼");
}
}