Servlet的使用标准.docx
- 文档编号:27228860
- 上传时间:2023-06-28
- 格式:DOCX
- 页数:17
- 大小:58.42KB
Servlet的使用标准.docx
《Servlet的使用标准.docx》由会员分享,可在线阅读,更多相关《Servlet的使用标准.docx(17页珍藏版)》请在冰豆网上搜索。
Servlet的使用标准
从Servlet容器说起
要介绍Servlet必须要先把Servlet容器说清楚,Servlet与Servlet容器的关系有点像枪和子弹的关系,枪是为子弹而生,而子弹又让枪有了杀伤力。
虽然它们是彼此依存的,但是又相互独立发展,这一切都是为了适应工业化生产的结果。
从技术角度来说是为了解耦,通过标准化接口来相互协作。
既然接口是连接Servlet与Servlet容器的关键,那我们就从它们的接口说起。
前面说了Servlet容器作为一个独立发展的标准化产品,目前它的种类很多,但是它们都有自己的市场定位,很难说谁优谁劣,各有特点。
例如现在比较流行的Jetty,在定制化和移动领域有不错的发展,我们这里还是以大家最为熟悉Tomcat为例来介绍Servlet容器如何管理Servlet。
Tomcat本身也很复杂,我们只从Servlet与Servlet容器的接口部分开始介绍,关于Tomcat的详细介绍可以参考我的另外一篇文章《Tomcat系统架构与模式设计分析》。
Tomcat的容器等级中,Context容器是直接管理Servlet在容器中的包装类Wrapper,所以Context容器如何运行将直接影响Servlet的工作方式。
图1.Tomcat容器模型
从上图可以看出Tomcat的容器分为四个等级,真正管理Servlet的容器是Context容器,一个Context对应一个Web工程,在Tomcat的配置文件中可以很容易发现这一点,如下:
清单1Context配置参数
\projects\projectOne" reloadable="true"/> 下面详细介绍一下Tomcat解析Context容器的过程,包括如何构建Servlet的过程。 Servlet容器的启动过程 Tomcat7也开始支持嵌入式功能,增加了一个启动类org.apache.catalina.startup.Tomcat。 创建一个实例对象并调用start方法就可以很容易启动Tomcat,我们还可以通过这个对象来增加和修改Tomcat的配置参数,如可以动态增加Context、Servlet等。 下面我们就利用这个Tomcat类来管理新增的一个Context容器,我们就选择Tomcat7自带的examplesWeb工程,并看看它是如何加到这个Context容器中的。 清单2.给Tomcat增加一个Web工程 Tomcattomcat=getTomcatInstance(); FileappDir=newFile(getBuildDirectory(),"webapps/examples"); tomcat.addWebapp(null,"/examples",appDir.getAbsolutePath()); tomcat.start(); ByteChunkres=getUrl("http: //localhost: "+getPort()+ "/examples/servlets/servlet/HelloWorldExample"); assertTrue(res.toString().indexOf("HelloWorld!
")>0);
清单1的代码是创建一个Tomcat实例并新增一个Web应用,然后启动Tomcat并调用其中的一个HelloWorldExampleServlet,看有没有正确返回预期的数据。
Tomcat的addWebapp方法的代码如下:
清单3.Tomcat.addWebapp
publicContextaddWebapp(Hosthost,Stringurl,Stringpath){
silence(url);
Contextctx=newStandardContext();
ctx.setPath(url);
ctx.setDocBase(path);
if(defaultRealm==null){
initSimpleAuth();
}
ctx.setRealm(defaultRealm);
ctx.addLifecycleListener(newDefaultWebXmlListener());
ContextConfigctxCfg=newContextConfig();
ctx.addLifecycleListener(ctxCfg);
ctxCfg.setDefaultWebXml("org/apache/catalin/startup/NO_DEFAULT_XML");
if(host==null){
getHost().addChild(ctx);
}else{
host.addChild(ctx);
}
returnctx;
}
前面已经介绍了一个Web应用对应一个Context容器,也就是Servlet运行时的Servlet容器,添加一个Web应用时将会创建一个StandardContext容器,并且给这个Context容器设置必要的参数,url和path分别代表这个应用在Tomcat中的访问路径和这个应用实际的物理路径,这个两个参数与清单1中的两个参数是一致的。
其中最重要的一个配置是ContextConfig,这个类将会负责整个Web应用配置的解析工作,后面将会详细介绍。
最后将这个Context容器加到父容器Host中。
接下去将会调用Tomcat的start方法启动Tomcat,如果你清楚Tomcat的系统架构,你会容易理解Tomcat的启动逻辑,Tomcat的启动逻辑是基于观察者模式设计的,所有的容器都会继承Lifecycle接口,它管理者容器的整个生命周期,所有容器的的修改和状态的改变都会由它去通知已经注册的观察者(Listener),关于这个设计模式可以参考《Tomcat的系统架构与设计模式,第二部分:
设计模式》。
Tomcat启动的时序图可以用图2表示。
图2.Tomcat主要类的启动时序图(查看大图)
上图描述了Tomcat启动过程中,主要类之间的时序关系,下面我们将会重点关注添加examples应用所对应的StandardContext容器的启动过程。
当Context容器初始化状态设为init时,添加在Contex容器的Listener将会被调用。
ContextConfig继承了LifecycleListener接口,它是在调用清单3时被加入到StandardContext容器中。
ContextConfig类会负责整个Web应用的配置文件的解析工作。
ContextConfig的init方法将会主要完成以下工作:
1.创建用于解析xml配置文件的contextDigester对象
2.读取默认context.xml配置文件,如果存在解析它
3.读取默认Host配置文件,如果存在解析它
4.读取默认Context自身的配置文件,如果存在解析它
5.设置Context的DocBase
ContextConfig的init方法完成后,Context容器的会执行startInternal方法,这个方法启动逻辑比较复杂,主要包括如下几个部分:
1.创建读取资源文件的对象
2.创建ClassLoader对象
3.设置应用的工作目录
4.启动相关的辅助类如:
logger、realm、resources等
5.修改启动状态,通知感兴趣的观察者(Web应用的配置)
6.子容器的初始化
7.获取ServletContext并设置必要的参数
8.初始化“loadonstartup”的Servlet
Java代码
1.sun.misc.Launcher$AppClassLoader@19821f
2.parent class loader:
sun.misc.Launcher$ExtClassLoader@addbf1
3.sun.misc.Launcher$ExtClassLoader@addbf1
4.parent class loader:
null
我们看到,当前系统类装载器为AppClassLoader,AppClassLoader的父类装载器是ExtClassLoader,ExtClassLoader的父装载器为null,表示为BootstrapClassLoader。
BootstrapClassLoader由JVM采用本地代码实现,因此没有对应的Java类,所以ExtClassLoader的getParent()返回null。
ClassLoader的职责之一是保护系统名字空间。
以下为ClassLoader类部分代码:
Java代码
1.private ProtectionDomain preDefineClass(String name,
2. ProtectionDomain protectionDomain)
3. {
4. if (!
checkName(name))
5. throw new NoClassDefFoundError("IllegalName:
" + name);
6.
7. if ((name !
= null) && name.startsWith("java.")) {
8. throw new SecurityException("Prohibited package name:
" +
9. name.substring(0, name.lastIndexOf('.')));
10. }
11. if (protectionDomain == null) {
12. protectionDomain = getDefaultDomain();
13. }
14.
15. if (name !
= null)
16. checkCerts(name, protectionDomain.getCodeSource());
17.
18. return protectionDomain;
19. }
那么,当我们定义如下类Foo,虽然能够通过编译,但是会报java.lang.SecurityException:
Prohibitedpackagename:
java.lang异常,因为我们试图将Foo类写入到java.lang包下。
Java代码
1.package java.lang;
2.
3.public class Foo {
4.
5. public static void main(String args[]) throws Exception {
6. Foo f = new Foo();
7. System.out.println(f.toString());
8. }
9.}
3.定制ClassLoader
Java自带的ClassLoader类的定义为:
Java代码
1.public abstract class ClassLoader{
2.}
启动类加载器是JVM通过调用ClassLoader.loadClass()方法。
Java代码
1.public Class
> loadClass(String name) throws ClassNotFoundException {
2. return loadClass(name, false);
3. }
4.
5.protected synchronized Class
> loadClass(String name, boolean resolve)
6. throws ClassNotFoundException
7. {
8. // First, check if the class has already been loaded
9. Class c = findLoadedClass(name);
10. if (c == null) {
11. try {
12. if (parent !
= null) {
13. c = parent.loadClass(name, false);
14. } else {
15. c = findBootstrapClass0(name);
16. }
17. } catch (ClassNotFoundException e) {
18. // If still not found, then invoke findClass in order
19. // to find the class.
20. c = findClass(name);
21. }
22. }
23. if (resolve) {
24. resolveClass(c);
25. }
26. return c;
27. }
28.
29.protected Class
> findClass(String name) throws ClassNotFoundException {
30. throw new ClassNotFoundException(name);
31. }
loadClass(Stringname,booleanresolve)方法中的resolve如果为true,表示分析这个Class对象,包括检查ClassLoader是否已经初始化等。
loadClass(Stringname)在加载类之后不会对该类进行初始化,直到第一次使用该类时,才会对该类进行初始化。
那么,我们在定制ClassLoader的时候,通常只需要覆写findClass(Stringname)方法。
在findClass(Stringname)方法内,我们可以通过文件、网络(URL)等形式获取字节码。
以下为获取字节码的方法:
Java代码
1.public InputStream getResourceAsStream(String name);
2.public URL getResource(String name);
3.public InputStream getResourceAsStream(String name);
4.public Enumeration
在取得字节码后,需要调用defineClass()方法将字节数组转换成Class对象,该方法签名如下:
Java代码
1.protected final Class
> defineClass(String name, byte[] b, int off, int len,
2. ProtectionDomain protectionDomain)
3. throws ClassFormatError
对于相同的类,JVM最多会载入一次。
如果同一个class文件被不同的ClassLoader载入(定义),那么载入后的两个类是完全不同的。
Java代码
1.public class Foo{
2. //
3. private static final AtomicInteger COUNTER = new AtomicInteger(0);
4.
5. public Foo() {
6. System.out.println("counter:
" + COUNTER.incrementAndGet());
7. }
8.
9. public static void main(String args[]) throws Exception {
10. URL urls[] = new URL[]{new URL("file:
/c:
/")};
11. URLClassLoader ucl1 = new URLClassLoader(urls);
12. URLClassLoader ucl2 = new URLClassLoader(urls);
13. Class
> c1 = ucl1.loadClass("Foo");
14. Class
> c2 = ucl2.loadClass("Foo");
15. System.out.println(c1 == c2);
16. c1.newInstance();
17. c2.newInstance();
18. }
19.}
以上程序需要保证Foo.class文件不在classpath路径下。
从而使AppClassLoader无法加载Foo.class。
输出结果:
Java代码
1.false
2.counter:
1
3.counter:
1
4.Web应用的ClassLoader
绝大多数的EJB容器,Servlet容器等都会提供定制的ClassLoader,来实现特定的功能。
但是通常情况下,所有的servlet和filter使用一个ClassLoader。
每个jsp都使用一个独立的ClassLoader。
5.隐式(implicit)和显示(explicit)的加载
隐式加载:
我们使用new关键字实例化一个类,就是隐身的加载了类。
显示加载分为两种:
java.lang.Class的forName()方法;
java.lang.ClassLoader的loadClass()方法。
Class.forName()方法有两个重载的版本:
Java代码
1. public static Class
> forName(String className)
2. throws ClassNotFoundException {
3. return forName0(className, true, ClassLoader.getCallerClassLoader());
4. }
5.
6.public static Class
> forName(String name, boolean initialize,
7. ClassLoader loader)
8. throws ClassNotFoundException
可以看出,forName(StringclassName)默认以true和ClassLoader.getCallerClassLoader()调用了三参数的重载方法。
ClassLoader.getCallerClassLoader()表示以callerclassloader加载类,并会初始化类(即静态变量会被初始化,静态初始化块中的代码也会被执行)。
如果以false和ClassLoader.getCallerClassLoader()调用三参数的重载方法,表示加载后的类不会被初始化。
ClassLoader.loadClass()方法在类加载后,也同样不会初始化类。
6.两个异常(exception)
NoClassDefFoundError:
当java源文件已编译成.class文件,但是ClassLoader在运行期间搜寻路径load某个类时,没有找到.class文件则抛出这个异常。
ClassNotFoundException:
试图通过一个String变量来创建一个Class类时不成功则抛出这个异常
8.Activation
通过调用UnicastRemoteObject.exportObject()方法发布的远程对象的生命周期是从发布起,一直到所在应用停止为止。
RMI的activation机制允许在rmid中发布可激活的激活描述符,只有当客户端发起远程调用时才真正构造远程对象。
8.1MyRemote类
Java代码
1.public interface MyRemote extends Remote{
2.
3. public Product getProduct(int productId) throws RemoteException;
4.}
8.2Product实现了Serializable
Java代码
1. public class Product implements Serializable{
2.
3. private static final long serialVersionUID = 5206082551028038485L;
4.
5. private int id;
6.
7. private String name;
8.
9. public int getId()
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- Servlet 使用 标准
![提示](https://static.bdocx.com/images/bang_tan.gif)