`
Tyrion
  • 浏览: 257612 次
  • 性别: Icon_minigender_1
  • 来自: 南京
社区版块
存档分类
最新评论

Tomcat7中web应用加载原理(二)web.xml解析

阅读更多

前一篇文章讲了org.apache.catalina.startup.HostConfig的lifecycleEvent方法中所做的事情。最后看到在Tomcat启动时或启动后(后台线程定时扫描)会调用HostConfig类的deployApps方法:

    /**
     * Deploy applications for any directories or WAR files that are found
     * in our "application root" directory.
     */
    protected void deployApps() {

        File appBase = appBase();
        File configBase = configBase();
        String[] filteredAppPaths = filterAppPaths(appBase.list());
        // Deploy XML descriptors from configBase
        deployDescriptors(configBase, configBase.list());
        // Deploy WARs
        deployWARs(appBase, filteredAppPaths);
        // Deploy expanded folders
        deployDirectories(appBase, filteredAppPaths);

    }

可以看到这里部署应用有三种方式:XML文件描述符、WAR包、文件目录。三种方式部署的总体流程很相似,都是一个web应用分配一个线程来处理,这里统一放到与Host内部的线程池对象中(startStopExecutor),所以有时会看到在默认配置下Tomcat启动后可能有一个叫“-startStop-”的线程还会运行一段时间才结束。但浏览这三种部署方式的实现代码,里面都是构建一个Context对象,并将构建好的Context对象与Host组件关联起来(即调用host.addChild(context)这句,具体代码在HostConfig类的deployDescriptor(ContextName cn, File contextXml)、deployDirectory(ContextName cn, File dir)、deployWAR(ContextName cn, File war)三个方法中,这里不再贴出代码来详细分析)。

前一篇文章只分析到这步,可以看出与一个web应用相对应的一个Context对象已经构建出来了,但如果容器只执行到这里根本无法响应一个浏览器的一次请求。就web服务器的实现来看一次请求过来除了需要根据内部Context构建找到这次请求访问的web应用具体所对应的Context对象,还需要包含web应用中具体的哪个Servlet来处理这次请求,中间是否还需要执行相应的过滤器(filter)、监听器(listener)等,做过java的web开发的都知道,这些信息是配置在一个web应用的WEB-INF\web.xml文件的(servlet3中已经支持将这些配置信息放到Java文件的注解中,但万变不离其宗,总归要在web应用的某个地方说明,并在容器启动时加载,这样才能真正提供web服务,响应请求)。

 

看到这里可以猜到Tomcat容器加载web应用时必定会有对于每个应用的web.xml文件的解析过程,本文就来看看这个解析过程。

 

在本文开头提到的三种部署应用的实现代码中有一些共通的代码,这里摘出来说明一下:

            Class<?> clazz = Class.forName(host.getConfigClass());
            LifecycleListener listener =
                (LifecycleListener) clazz.newInstance();
            context.addLifecycleListener(listener);
            host.addChild(context);

第一段是在所有Context对象构建时会添加一个监听器,这里监听器的类名是StandardHost类的实例变量configClass,其默认值就是org.apache.catalina.startup.ContextConfig。第二段是将当前构建的Context对象添加到父容器Host对象中。

先看下StandardHost的addChild方法的实现:

    public void addChild(Container child) {

        child.addLifecycleListener(new MemoryLeakTrackingListener());

        if (!(child instanceof Context))
            throw new IllegalArgumentException
                (sm.getString("standardHost.notContext"));
        super.addChild(child);

    }

可以看到这段代码最后调用了父类的addChild方法:

    public void addChild(Container child) {
        if (Globals.IS_SECURITY_ENABLED) {
            PrivilegedAction<Void> dp =
                new PrivilegedAddChild(child);
            AccessController.doPrivileged(dp);
        } else {
            addChildInternal(child);
        }
    }

这里看下addChildInternal方法的实现:

    private void addChildInternal(Container child) {

        if( log.isDebugEnabled() )
            log.debug("Add child " + child + " " + this);
        synchronized(children) {
            if (children.get(child.getName()) != null)
                throw new IllegalArgumentException("addChild:  Child name '" +
                                                   child.getName() +
                                                   "' is not unique");
            child.setParent(this);  // May throw IAE
            children.put(child.getName(), child);
        }

        // Start child
        // Don't do this inside sync block - start can be a slow process and
        // locking the children object can cause problems elsewhere
        if ((getState().isAvailable() ||
                LifecycleState.STARTING_PREP.equals(getState())) &&
                startChildren) {
            try {
                child.start();
            } catch (LifecycleException e) {
                log.error("ContainerBase.addChild: start: ", e);
                throw new IllegalStateException
                    ("ContainerBase.addChild: start: " + e);
            }
        }

        fireContainerEvent(ADD_CHILD_EVENT, child);
    }

可以看到会调用子容器的start方法,就是指调用StandardContext的start方法。

即给host对象添加子容器时将会调用子容器的start方法,按照前面文章的分析,调用StandardContext的start方法最终会调用org.apache.catalina.core.StandardContext类的startInternal方法(该方法代码较长,建议自己阅读,不再贴出),这里将会发布一系列事件,按调用前后顺序这些事件包括:BEFORE_INIT_EVENT、AFTER_INIT_EVENT、BEFORE_START_EVENT、CONFIGURE_START_EVENT、START_EVENT、AFTER_START_EVENT。

 

前面提到在构建Context对象时都会注册一个监听器org.apache.catalina.startup.ContextConfig,看下这个类的lifecycleEvent方法中(为什么会执行这个方法可以看本博文章的分析)监听了哪些事件:

    /**
     * Process events for an associated Context.
     *
     * @param event The lifecycle event that has occurred
     */
    @Override
    public void lifecycleEvent(LifecycleEvent event) {

        // Identify the context we are associated with
        try {
            context = (Context) event.getLifecycle();
        } catch (ClassCastException e) {
            log.error(sm.getString("contextConfig.cce", event.getLifecycle()), e);
            return;
        }

        // Process the event that has occurred
        if (event.getType().equals(Lifecycle.CONFIGURE_START_EVENT)) {
            configureStart();
        } else if (event.getType().equals(Lifecycle.BEFORE_START_EVENT)) {
            beforeStart();
        } else if (event.getType().equals(Lifecycle.AFTER_START_EVENT)) {
            // Restore docBase for management tools
            if (originalDocBase != null) {
                context.setDocBase(originalDocBase);
            }
        } else if (event.getType().equals(Lifecycle.CONFIGURE_STOP_EVENT)) {
            configureStop();
        } else if (event.getType().equals(Lifecycle.AFTER_INIT_EVENT)) {
            init();
        } else if (event.getType().equals(Lifecycle.AFTER_DESTROY_EVENT)) {
            destroy();
        }

    }

与Context的start方法调用相关的事件监听前后顺序为:AFTER_INIT_EVENT(执行init方法)、BEFORE_START_EVENT(执行beforeStart方法)、CONFIGURE_START_EVENT(执行configureStart方法)。

在configureStart方法将直接调用webConfig方法,正是在这个方法中将会解析web.xml文件:

    /**
     * Scan the web.xml files that apply to the web application and merge them
     * using the rules defined in the spec. For the global web.xml files,
     * where there is duplicate configuration, the most specific level wins. ie
     * an application's web.xml takes precedence over the host level or global
     * web.xml file.
     */
    protected void webConfig() {
        /*
         * Anything and everything can override the global and host defaults.
         * This is implemented in two parts
         * - Handle as a web fragment that gets added after everything else so
         *   everything else takes priority
         * - Mark Servlets as overridable so SCI configuration can replace
         *   configuration from the defaults
         */

        /*
         * The rules for annotation scanning are not as clear-cut as one might
         * think. Tomcat implements the following process:
         * - As per SRV.1.6.2, Tomcat will scan for annotations regardless of
         *   which Servlet spec version is declared in web.xml. The EG has
         *   confirmed this is the expected behaviour.
         * - As per http://java.net/jira/browse/SERVLET_SPEC-36, if the main
         *   web.xml is marked as metadata-complete, JARs are still processed
         *   for SCIs.
         * - If metadata-complete=true and an absolute ordering is specified,
         *   JARs excluded from the ordering are also excluded from the SCI
         *   processing.
         * - If an SCI has a @HandlesType annotation then all classes (except
         *   those in JARs excluded from an absolute ordering) need to be
         *   scanned to check if they match.
         */
        Set<WebXml> defaults = new HashSet<WebXml>();
        defaults.add(getDefaultWebXmlFragment());

        WebXml webXml = createWebXml();

        // Parse context level web.xml
        InputSource contextWebXml = getContextWebXmlSource();
        parseWebXml(contextWebXml, webXml, false);

        ServletContext sContext = context.getServletContext();

        // Ordering is important here

        // Step 1. Identify all the JARs packaged with the application
        // If the JARs have a web-fragment.xml it will be parsed at this
        // point.
        Map<String,WebXml> fragments = processJarsForWebFragments();

        // Step 2. Order the fragments.
        Set<WebXml> orderedFragments = null;
        orderedFragments =
                WebXml.orderWebFragments(webXml, fragments, sContext);

        // Step 3. Look for ServletContainerInitializer implementations
        if (ok) {
            processServletContainerInitializers(orderedFragments);
        }

        if  (!webXml.isMetadataComplete() || typeInitializerMap.size() > 0) {
            // Step 4. Process /WEB-INF/classes for annotations
            if (ok) {
                // Hack required by Eclipse's "serve modules without
                // publishing" feature since this backs WEB-INF/classes by
                // multiple locations rather than one.
                NamingEnumeration<Binding> listBindings = null;
                try {
                    try {
                        listBindings = context.getResources().listBindings(
                                "/WEB-INF/classes");
                    } catch (NameNotFoundException ignore) {
                        // Safe to ignore
                    }
                    while (listBindings != null &&
                            listBindings.hasMoreElements()) {
                        Binding binding = listBindings.nextElement();
                        if (binding.getObject() instanceof FileDirContext) {
                            File webInfClassDir = new File(
                                    ((FileDirContext) binding.getObject()).getDocBase());
                            processAnnotationsFile(webInfClassDir, webXml,
                                    webXml.isMetadataComplete());
                        } else {
                            String resource =
                                    "/WEB-INF/classes/" + binding.getName();
                            try {
                                URL url = sContext.getResource(resource);
                                processAnnotationsUrl(url, webXml,
                                        webXml.isMetadataComplete());
                            } catch (MalformedURLException e) {
                                log.error(sm.getString(
                                        "contextConfig.webinfClassesUrl",
                                        resource), e);
                            }
                        }
                    }
                } catch (NamingException e) {
                    log.error(sm.getString(
                            "contextConfig.webinfClassesUrl",
                            "/WEB-INF/classes"), e);
                }
            }

            // Step 5. Process JARs for annotations - only need to process
            // those fragments we are going to use
            if (ok) {
                processAnnotations(
                        orderedFragments, webXml.isMetadataComplete());
            }

            // Cache, if used, is no longer required so clear it
            javaClassCache.clear();
        }

        if (!webXml.isMetadataComplete()) {
            // Step 6. Merge web-fragment.xml files into the main web.xml
            // file.
            if (ok) {
                ok = webXml.merge(orderedFragments);
            }

            // Step 7. Apply global defaults
            // Have to merge defaults before JSP conversion since defaults
            // provide JSP servlet definition.
            webXml.merge(defaults);

            // Step 8. Convert explicitly mentioned jsps to servlets
            if (ok) {
                convertJsps(webXml);
            }

            // Step 9. Apply merged web.xml to Context
            if (ok) {
                webXml.configureContext(context);
            }
        } else {
            webXml.merge(defaults);
            convertJsps(webXml);
            webXml.configureContext(context);
        }

        // Step 9a. Make the merged web.xml available to other
        // components, specifically Jasper, to save those components
        // from having to re-generate it.
        // TODO Use a ServletContainerInitializer for Jasper
        String mergedWebXml = webXml.toXml();
        sContext.setAttribute(
               org.apache.tomcat.util.scan.Constants.MERGED_WEB_XML,
               mergedWebXml);
        if (context.getLogEffectiveWebXml()) {
            log.info("web.xml:\n" + mergedWebXml);
        }

        // Always need to look for static resources
        // Step 10. Look for static resources packaged in JARs
        if (ok) {
            // Spec does not define an order.
            // Use ordered JARs followed by remaining JARs
            Set<WebXml> resourceJars = new LinkedHashSet<WebXml>();
            if (orderedFragments != null) {
                for (WebXml fragment : orderedFragments) {
                    resourceJars.add(fragment);
                }
            }
            for (WebXml fragment : fragments.values()) {
                if (!resourceJars.contains(fragment)) {
                    resourceJars.add(fragment);
                }
            }
            processResourceJARs(resourceJars);
            // See also StandardContext.resourcesStart() for
            // WEB-INF/classes/META-INF/resources configuration
        }

        // Step 11. Apply the ServletContainerInitializer config to the
        // context
        if (ok) {
            for (Map.Entry<ServletContainerInitializer,
                    Set<Class<?>>> entry :
                        initializerClassMap.entrySet()) {
                if (entry.getValue().isEmpty()) {
                    context.addServletContainerInitializer(
                            entry.getKey(), null);
                } else {
                    context.addServletContainerInitializer(
                            entry.getKey(), entry.getValue());
                }
            }
        }
    }

这个方法里面做的事情,在英文注释中说的很清楚了,概括起来包括合并Tomcat全局web.xml、当前应用中的web.xml、web-fragment.xml和web应用的注解中的配置信息,并将解析出的各种配置信息(如servlet配置、filter配置等)关联到Context对象中(在上面的代码第140行:webXml.configureContext(context))。

看下configureContext方法:

    /**
     * Configure a {@link Context} using the stored web.xml representation.
     *
     * @param context   The context to be configured
     */
    public void configureContext(Context context) {
        // As far as possible, process in alphabetical order so it is easy to
        // check everything is present
        // Some validation depends on correct public ID
        context.setPublicId(publicId);

        // Everything else in order
        context.setEffectiveMajorVersion(getMajorVersion());
        context.setEffectiveMinorVersion(getMinorVersion());

        for (Entry<String, String> entry : contextParams.entrySet()) {
            context.addParameter(entry.getKey(), entry.getValue());
        }
        context.setDisplayName(displayName);
        context.setDistributable(distributable);
        for (ContextLocalEjb ejbLocalRef : ejbLocalRefs.values()) {
            context.getNamingResources().addLocalEjb(ejbLocalRef);
        }
        for (ContextEjb ejbRef : ejbRefs.values()) {
            context.getNamingResources().addEjb(ejbRef);
        }
        for (ContextEnvironment environment : envEntries.values()) {
            context.getNamingResources().addEnvironment(environment);
        }
        for (ErrorPage errorPage : errorPages.values()) {
            context.addErrorPage(errorPage);
        }
        for (FilterDef filter : filters.values()) {
            if (filter.getAsyncSupported() == null) {
                filter.setAsyncSupported("false");
            }
            context.addFilterDef(filter);
        }
        for (FilterMap filterMap : filterMaps) {
            context.addFilterMap(filterMap);
        }
        for (JspPropertyGroup jspPropertyGroup : jspPropertyGroups) {
            JspPropertyGroupDescriptor descriptor =
                new ApplicationJspPropertyGroupDescriptor(jspPropertyGroup);
            context.getJspConfigDescriptor().getJspPropertyGroups().add(
                    descriptor);
        }
        for (String listener : listeners) {
            context.addApplicationListener(
                    new ApplicationListener(listener, false));
        }
        for (Entry<String, String> entry : localeEncodingMappings.entrySet()) {
            context.addLocaleEncodingMappingParameter(entry.getKey(),
                    entry.getValue());
        }
        // Prevents IAE
        if (loginConfig != null) {
            context.setLoginConfig(loginConfig);
        }
        for (MessageDestinationRef mdr : messageDestinationRefs.values()) {
            context.getNamingResources().addMessageDestinationRef(mdr);
        }

        // messageDestinations were ignored in Tomcat 6, so ignore here

        context.setIgnoreAnnotations(metadataComplete);
        for (Entry<String, String> entry : mimeMappings.entrySet()) {
            context.addMimeMapping(entry.getKey(), entry.getValue());
        }
        // Name is just used for ordering
        for (ContextResourceEnvRef resource : resourceEnvRefs.values()) {
            context.getNamingResources().addResourceEnvRef(resource);
        }
        for (ContextResource resource : resourceRefs.values()) {
            context.getNamingResources().addResource(resource);
        }
        for (SecurityConstraint constraint : securityConstraints) {
            context.addConstraint(constraint);
        }
        for (String role : securityRoles) {
            context.addSecurityRole(role);
        }
        for (ContextService service : serviceRefs.values()) {
            context.getNamingResources().addService(service);
        }
        for (ServletDef servlet : servlets.values()) {
            Wrapper wrapper = context.createWrapper();
            // Description is ignored
            // Display name is ignored
            // Icons are ignored

            // jsp-file gets passed to the JSP Servlet as an init-param

            if (servlet.getLoadOnStartup() != null) {
                wrapper.setLoadOnStartup(servlet.getLoadOnStartup().intValue());
            }
            if (servlet.getEnabled() != null) {
                wrapper.setEnabled(servlet.getEnabled().booleanValue());
            }
            wrapper.setName(servlet.getServletName());
            Map<String,String> params = servlet.getParameterMap();
            for (Entry<String, String> entry : params.entrySet()) {
                wrapper.addInitParameter(entry.getKey(), entry.getValue());
            }
            wrapper.setRunAs(servlet.getRunAs());
            Set<SecurityRoleRef> roleRefs = servlet.getSecurityRoleRefs();
            for (SecurityRoleRef roleRef : roleRefs) {
                wrapper.addSecurityReference(
                        roleRef.getName(), roleRef.getLink());
            }
            wrapper.setServletClass(servlet.getServletClass());
            MultipartDef multipartdef = servlet.getMultipartDef();
            if (multipartdef != null) {
                if (multipartdef.getMaxFileSize() != null &&
                        multipartdef.getMaxRequestSize()!= null &&
                        multipartdef.getFileSizeThreshold() != null) {
                    wrapper.setMultipartConfigElement(new MultipartConfigElement(
                            multipartdef.getLocation(),
                            Long.parseLong(multipartdef.getMaxFileSize()),
                            Long.parseLong(multipartdef.getMaxRequestSize()),
                            Integer.parseInt(
                                    multipartdef.getFileSizeThreshold())));
                } else {
                    wrapper.setMultipartConfigElement(new MultipartConfigElement(
                            multipartdef.getLocation()));
                }
            }
            if (servlet.getAsyncSupported() != null) {
                wrapper.setAsyncSupported(
                        servlet.getAsyncSupported().booleanValue());
            }
            wrapper.setOverridable(servlet.isOverridable());
            context.addChild(wrapper);
        }
        for (Entry<String, String> entry : servletMappings.entrySet()) {
            context.addServletMapping(entry.getKey(), entry.getValue());
        }
        if (sessionConfig != null) {
            if (sessionConfig.getSessionTimeout() != null) {
                context.setSessionTimeout(
                        sessionConfig.getSessionTimeout().intValue());
            }
            SessionCookieConfig scc =
                context.getServletContext().getSessionCookieConfig();
            scc.setName(sessionConfig.getCookieName());
            scc.setDomain(sessionConfig.getCookieDomain());
            scc.setPath(sessionConfig.getCookiePath());
            scc.setComment(sessionConfig.getCookieComment());
            if (sessionConfig.getCookieHttpOnly() != null) {
                scc.setHttpOnly(sessionConfig.getCookieHttpOnly().booleanValue());
            }
            if (sessionConfig.getCookieSecure() != null) {
                scc.setSecure(sessionConfig.getCookieSecure().booleanValue());
            }
            if (sessionConfig.getCookieMaxAge() != null) {
                scc.setMaxAge(sessionConfig.getCookieMaxAge().intValue());
            }
            if (sessionConfig.getSessionTrackingModes().size() > 0) {
                context.getServletContext().setSessionTrackingModes(
                        sessionConfig.getSessionTrackingModes());
            }
        }
        for (Entry<String, String> entry : taglibs.entrySet()) {
            TaglibDescriptor descriptor = new ApplicationTaglibDescriptor(
                    entry.getValue(), entry.getKey());
            context.getJspConfigDescriptor().getTaglibs().add(descriptor);
        }

        // Context doesn't use version directly

        for (String welcomeFile : welcomeFiles) {
            /*
             * The following will result in a welcome file of "" so don't add
             * that to the context
             * <welcome-file-list>
             *   <welcome-file/>
             * </welcome-file-list>
             */
            if (welcomeFile != null && welcomeFile.length() > 0) {
                context.addWelcomeFile(welcomeFile);
            }
        }

        // Do this last as it depends on servlets
        for (JspPropertyGroup jspPropertyGroup : jspPropertyGroups) {
            String jspServletName = context.findServletMapping("*.jsp");
            if (jspServletName == null) {
                jspServletName = "jsp";
            }
            if (context.findChild(jspServletName) != null) {
                for (String urlPattern : jspPropertyGroup.getUrlPatterns()) {
                    context.addServletMapping(urlPattern, jspServletName, true);
                }
            } else {
                if(log.isDebugEnabled()) {
                    for (String urlPattern : jspPropertyGroup.getUrlPatterns()) {
                        log.debug("Skiping " + urlPattern + " , no servlet " +
                                jspServletName);
                    }
                }
            }
        }

        for (Entry<String, String> entry : postConstructMethods.entrySet()) {
            context.addPostConstructMethod(entry.getKey(), entry.getValue());
        }

        for (Entry<String, String> entry : preDestroyMethods.entrySet()) {
            context.addPreDestroyMethod(entry.getKey(), entry.getValue());
        }
    }

可以看到里面对context调用了各种set、add方法,从而将web.xml中的各种配置信息与表示一个web应用的context对象关联起来。

3
2
分享到:
评论
5 楼 Tyrion 2017-03-24  
PS:这里的博客已经不更新了,新博客在简书:http://www.jianshu.com/u/6c5d73c5b4cb
Tomcat的源码分析是几年前搞的了,现在精力主要focus在微服务的相关技术实践上。
4 楼 Tyrion 2017-03-24  
小帅1127 写道
确实难得得好文 就是不知道作者现在还从不从事软件开发了

啊好久不登javaeye了,刚看到,我从毕业到现在一直在coding。。。
3 楼 小帅1127 2017-03-02  
确实难得得好文 就是不知道作者现在还从不从事软件开发了
2 楼 akka_li 2016-04-03  
非常感谢楼主!终于对容器解析web.xml的过程有了一点点了解了!!
1 楼 steafler 2013-09-19  
wonderful, come on baby.

相关推荐

    JAVA WEB 开发详解:XML+XSLT+SERVLET+JSP 深入剖析与实例应用.part3

    本书共分4部分,从xml、servlet、jsp和应用的角度向读者展示了java web开发中各种技术的应用,循序渐进地引导读者快速掌握java web开发。.  本书内容全面,涵盖了从事java web开发所应掌握的所有知识。在知识的讲解...

    JAVA WEB 开发详解:XML+XSLT+SERVLET+JSP 深入剖析与实例应用.part2

    本书共分4部分,从xml、servlet、jsp和应用的角度向读者展示了java web开发中各种技术的应用,循序渐进地引导读者快速掌握java web开发。.  本书内容全面,涵盖了从事java web开发所应掌握的所有知识。在知识的讲解...

    JAVA WEB 开发详解:XML+XSLT+SERVLET+JSP 深入剖析与实例应用.part4

    本书共分4部分,从xml、servlet、jsp和应用的角度向读者展示了java web开发中各种技术的应用,循序渐进地引导读者快速掌握java web开发。.  本书内容全面,涵盖了从事java web开发所应掌握的所有知识。在知识的讲解...

    JAVA WEB 开发详解:XML+XSLT+SERVLET+JSP 深入剖析与实例应用.part5

    本书共分4部分,从xml、servlet、jsp和应用的角度向读者展示了java web开发中各种技术的应用,循序渐进地引导读者快速掌握java web开发。.  本书内容全面,涵盖了从事java web开发所应掌握的所有知识。在知识的讲解...

    12-Servlet执行原理.pdf

    1. 当服务器接受到客户端浏览器的请求后,会解析请求URL路径,获取访问的Servlet的资源路径。在上图 的URL中,获取的资源...4. tomcat会将字节码文件加载进内存,并且创建其对象 5. 调用其方法(主要调用service方法)

    tomcat加载jar异常问题的分析与解决

    项目使用springboot启动一个web项目,在启动阶段看到console中出现了异常“1.10.3-1.4.3\hdf5.jar 系统找不到指定的文件”,虽然这些异常不影响项目的正常运行,但作为一个严谨的技术人员,看到这些异常就像见到...

    axis1.4 部署解析webservie

    1.TOMCAT+AXIS的安装配置 首先机子上应该安装JDK1.5版本以上(带有XML解析包)。我这里是1.5.0.6 从APACHE的官方网站(www.apache.org)下载TOMCAT安装软件。下载版本要在4.0以上。这里用的是5.0版本。安装完后,...

    Java 面试宝典

    一. Java 基础部分............................................................................................................43、Java 中的异常处理机制的简单原理和应用。 .....................................

    JAVA核心知识点整理(有效)

    1. 目录 1. 2. 目录 .........................................................................................................................................................1 JVM ........................

    java面试题

    84.2. 我们在web应用开发过程中经常遇到输出某种编码的字符,如iso8859-1等,如何输出一个某种编码的字符串? 106 84.3. 设计4个线程,其中两个线程每次对j增加1,另外两个线程对j每次减少1。写出程序。以下程序使用...

    Spring.3.x企业应用开发实战(完整版).part2

    14.1 认识XML解析技术 14.1.1 什么是XML 14.1.2 XML的处理技术 14.2 XML处理利器:XStream 14.2.1 XStream概述 14.2.2 快速入门 14.2.3 使用XStream别名 14.2.4 XStream转换器 14.2.5 XStream注解 14.2.6 流化对象 ...

    android web图片加载

    用GridView来呈现,从自建的tomcat上得到的图片.android端得到服务器下发的xml文件数据,解析后得到图片路径,然后根据图片路径下载保存在tomcat服务器上的图片.点击图片后,可以保存到SD卡的指定位置.

    java开源包7

    Blister是一个用于操作苹果二进制PList文件格式的Java开源类库(可用于发送数据给iOS应用程序)。 重复文件检查工具 FindDup.tar FindDup 是一个简单易用的工具,用来检查计算机上重复的文件。 OpenID的Java客户端...

    Spring3.x企业应用开发实战(完整版) part1

    14.1 认识XML解析技术 14.1.1 什么是XML 14.1.2 XML的处理技术 14.2 XML处理利器:XStream 14.2.1 XStream概述 14.2.2 快速入门 14.2.3 使用XStream别名 14.2.4 XStream转换器 14.2.5 XStream注解 14.2.6 流化对象 ...

    java开源包10

    Blister是一个用于操作苹果二进制PList文件格式的Java开源类库(可用于发送数据给iOS应用程序)。 重复文件检查工具 FindDup.tar FindDup 是一个简单易用的工具,用来检查计算机上重复的文件。 OpenID的Java客户端...

    java开源包101

    Blister是一个用于操作苹果二进制PList文件格式的Java开源类库(可用于发送数据给iOS应用程序)。 重复文件检查工具 FindDup.tar FindDup 是一个简单易用的工具,用来检查计算机上重复的文件。 OpenID的Java客户端...

    java开源包1

    Blister是一个用于操作苹果二进制PList文件格式的Java开源类库(可用于发送数据给iOS应用程序)。 重复文件检查工具 FindDup.tar FindDup 是一个简单易用的工具,用来检查计算机上重复的文件。 OpenID的Java客户端...

    java开源包11

    Blister是一个用于操作苹果二进制PList文件格式的Java开源类库(可用于发送数据给iOS应用程序)。 重复文件检查工具 FindDup.tar FindDup 是一个简单易用的工具,用来检查计算机上重复的文件。 OpenID的Java客户端...

Global site tag (gtag.js) - Google Analytics