【Java Web】源码分析Java Web的Filter与Struts的Interceptor是不是单例

来源:互联网 发布:网站关键词优化推广 编辑:程序博客网 时间:2024/06/06 07:16

Java Web中的Filter与Struts中的Interceptor到底是不是单例的?

答案首先是,它们都是单例的。下面我就从源码角度去解释为什么是单例的。

首先我们先对Interceptor进行分析,在分析之前我们先假设Filter是单例的,然后再开始。对Filter是不是单例的的源码分析会稍后补上,其实Filter是不是单例我们从它的方法也可以看到出来,因为它有init的方法,init方法只执行一次,所以我们猜测它是单例的,否则每次加载Filter都应该执行init进行初始化。

好,我们既然假设了Filter是单例的,我们又知道Struts的入口就是一个Filter,它是在init中进行初始化,所以可以看出,那我们可以知道Struts本身应该只初始化一次,也是单例的。但是Struts本身是单例和Struts框架中包含的各种对象是不是单例的并没有直接关系,所以这时候Interceptor是不是单例的我们依旧无法得知。好,那现在我们去看Interceptor源码。

拦截器在Struts中可以说是功能强大,但是我没用到,呵呵。实现拦截器有两种方法,一个是继承一个抽象类AbstractInterceptor,实现抽象方法;而另一个是实现接口Interceptor的所有方法。其实差不多,相差的地方还是在抽象类与接口本身的区别;而且该抽象类其实也是实现了Interceptor接口。我们假设采用第一种方法,可以看到抽象基类中已经给我们实现了两个方法,虽然什么都没做,但是确实是实现了。

public abstract class AbstractInterceptor implements Interceptor {    /**     * Does nothing     */    public void init() { }    /**     * Does nothing     */    public void destroy() {}    /**     * Override to handle interception     */    public abstract String intercept(ActionInvocation invocation) throws Exception;}
好,看到上述源码也有init的方法,那这个时候我们是否可以猜测拦截器是单例的那。下面我们就开始分析,拦截器到底是不是单例的那。那我们要从哪里开始分析那,要去看源码应该看那一部分的源码那。我们知道拦截器必须在xml中声明配置,所以我们应该看和读取配置有关的源码;那Struts读取配置的源码在哪里啊;这个时候我们又知道Struts的入口是个Filter,对于Filter都有一个init方法,而Struts又都是基于xml或者注解进行配置的,那我们猜测读取配置进行初始化工作应该是在init的方法中。那我们就开始对init方法进行分析:
 public void init(FilterConfig filterConfig) throws ServletException {        InitOperations init = new InitOperations();        try {            FilterHostConfig config = new FilterHostConfig(filterConfig);            init.initLogging(config);            Dispatcher dispatcher = init.initDispatcher(config);            init.initStaticContentLoader(config, dispatcher);            prepare = new PrepareOperations(filterConfig.getServletContext(), dispatcher);            execute = new ExecuteOperations(filterConfig.getServletContext(), dispatcher);this.excludedPatterns = init.buildExcludedPatternsList(dispatcher);            postInit(dispatcher, filterConfig);        } finally {            init.cleanup();        }    }
可以看到代码如上,根据代码我们可以看到主要初始化什么log啊,还有神马的。那到底在那个地方是读取配置文件的啊,从命名上看应该是Dispature。为何啊,是因为我找到之后才知道。Dispature的初始化主要是在其init()方法中,可以看到各种init神马的:
 public void init() {    if (configurationManager == null) {    configurationManager = new ConfigurationManager(BeanSelectionProvider.DEFAULT_BEAN_NAME);    }        try {            init_DefaultProperties(); // [1]            init_TraditionalXmlConfigurations(); // [2]            init_LegacyStrutsProperties(); // [3]            init_CustomConfigurationProviders(); // [5]            init_FilterInitParameters() ; // [6]            init_AliasStandardObjects() ; // [7]            Container container = init_PreloadConfiguration();            container.inject(this);            init_CheckConfigurationReloading(container);            init_CheckWebLogicWorkaround(container);            if (!dispatcherListeners.isEmpty()) {                for (DispatcherListener l : dispatcherListeners) {                    l.dispatcherInitialized(this);                }            }        } catch (Exception ex) {            if (LOG.isErrorEnabled())                LOG.error("Dispatcher initialization failed", ex);            throw new StrutsException(ex);        }    }
这么多ini_xx那个才是读取配置文件,猜测是标有注释2的那条语句,因为只有它才含有XML,对不起,猜错了。这个方法是初始化用来初始化xml的类,在Struts中每种初始化工作都会被声明为一个独立的类而且都会实现ConfigurationProvider接口,该接口继承了其他两个接口,这里不展开。那到底是哪里啊。我们看到有个

Container container = init_PreloadConfiguration();通过方法名感觉很像的样子,去深入看一下,感觉还真是。但是它的命名不知道为何会如此,而且如果你深入其中看到其调用的方法命名也不知道为何要那样设计。先不管了,进去看一下。可以看到其最后调用的ConfigurationManager的方法:

   public synchronized Configuration getConfiguration() {        if (configuration == null) {            setConfiguration(new DefaultConfiguration(defaultFrameworkBeanName));            try {                configuration.reloadContainer(getContainerProviders());            } catch (ConfigurationException e) {                setConfiguration(null);                throw new ConfigurationException("Unable to load configuration.", e);            }        } else {            conditionalReload();        }        return configuration;    }

在这里我们也看到,它首先判断是否曾经初始化configuration,如果初始化过,则再检查各种Provider是否需要重新加载初始化。这里指的是struts支持的热加载,当修改配置文件之后,Struts会主动加载、编译最新配置;然后如果没初始化过则调用DefaultConfiguration中的relaodContainer方法,而且把各种初始化工作的providers传递过去。这些providers就是在dispature中init的所有providers。在这里也可以看到它使用的方法名叫relaodContainer,不知道为何。另外注意方法是同步的,因为在很多地方都会调用该方法,获取整个Struts的配置,如果不设置为同步的,就可能导致初始化两次。这也可以认为是单例初始化的第一步。现在我们就去DefaultConfiguration类中看这个奇怪的方法名中完成了什么工作。

public synchronized List<PackageProvider> reloadContainer(List<ContainerProvider> providers) throws ConfigurationException{        packageContexts.clear();        loadedFileNames.clear();        List<PackageProvider> packageProviders = new ArrayList<PackageProvider>();       //1 initialize the config providers;        ContainerProperties props = new ContainerProperties();        ContainerBuilder builder = new ContainerBuilder();        for (final ContainerProvider containerProvider : providers)        {            containerProvider.init(this);            containerProvider.register(builder, props);        }        props.setConstants(builder);        builder.factory(Configuration.class, new Factory<Configuration>() {            public Configuration create(Context context) throws Exception {                return DefaultConfiguration.this;            }        });        ActionContext oldContext = ActionContext.getContext();        try {            //2 Set the bootstrap container for the purposes of factory creation            Container bootstrap = createBootstrapContainer();            setContext(bootstrap);            container = builder.create(false);            setContext(container);            objectFactory = container.getInstance(ObjectFactory.class);            //3 Process the configuration providers first            for (final ContainerProvider containerProvider : providers)            {                if (containerProvider instanceof PackageProvider) {                    container.inject(containerProvider);                    ((PackageProvider)containerProvider).loadPackages();                    packageProviders.add((PackageProvider)containerProvider);                }            }            //4 Then process any package providers from the plugins            Set<String> packageProviderNames = container.getInstanceNames(PackageProvider.class);            if (packageProviderNames != null) {                for (String name : packageProviderNames) {                    //调用PackageProvider的loadPackages()方法,这里主要是针对XmlConfigurationProvider和StrutsXmlConfigurationProvider                    PackageProvider provider = container.getInstance(PackageProvider.class, name);                    provider.init(this);                    provider.loadPackages();                    packageProviders.add(provider);                }            }            //5 invoke the method            rebuildRuntimeConfiguration();        } finally {            if (oldContext == null) {                ActionContext.setContext(null);            }        }        return packageProviders;    }

方法好长,但逻辑很简单,可以看注释说明,主要分为了5步操作。

我们只分析第一步与最后一步,其它略过,因为只有第一步与最后一步才可我们本文的论点有关。在第一步是初始化各种providers,它的来源与作用我们前文已经说明。从文中可以看到,其代码只有两行,主要调用init与register方法。我们在这里只看其中的一种provider-XmlConfigurationProvider,它负责xml配置的读取与初始化,还有一个子类StrutsXmlConfigurationProvider。我们针对init于register方法,进行具体展开

public void init(Configuration configuration) {        this.configuration = configuration;        this.includedFileNames = configuration.getLoadedFileNames();        loadDocuments(configFileName);    }
inti方法主要操作是通过configFIleName读取配置文件进行初始化,configFileName是在Dispature类中初始化xml的providers的时候初始化出来的。loadDocments方法最后调用的是一个私有方法loadConfigurationFiles(String fileName, Element includeElement),很长就不贴源码了。在这里它解析xml使用的是DOM方法,还有一种是SAX方法,前几天IBM让我做的一份题目就是解析xml,哥使用的是SAX。具体两种方法的差异请Google。

这两个方法好长,看的哥恶心了。在此,你就知道这两个是读取配置,还有一些其他的辅助性工作,比如告诉容器,那些读取了;注入容器,在其他地方可以通过注解获取就好了。好恶心的长代码。上面是初始化了相应的配置与具体的实现类,但是还没有对类进行实例化操作。在初始化与注册结束之后,就会调用loadPackages方法。该方法就是针对上述的初始化操作具体对各个对象进行加载然后类的初始化。

最后的初始化方法可以看到是位于ObjectFactory中方法:

public Interceptor buildInterceptor(InterceptorConfig interceptorConfig, Map<String, String> interceptorRefParams) throws ConfigurationException {        String interceptorClassName = interceptorConfig.getClassName();        Map<String, String> thisInterceptorClassParams = interceptorConfig.getParams();        Map<String, String> params = (thisInterceptorClassParams == null) ? new HashMap<String, String>() : new HashMap<String, String>(thisInterceptorClassParams);        params.putAll(interceptorRefParams);        String message;        Throwable cause;        try {            // interceptor instances are long-lived and used across user sessions, so don't try to pass in any extra context            Interceptor interceptor = (Interceptor) buildBean(interceptorClassName, null);            reflectionProvider.setProperties(params, interceptor);            interceptor.init();            return interceptor;        } catch (InstantiationException e) {            cause = e;            message = "Unable to instantiate an instance of Interceptor class [" + interceptorClassName + "].";        } catch (IllegalAccessException e) {            cause = e;            message = "IllegalAccessException while attempting to instantiate an instance of Interceptor class [" + interceptorClassName + "].";        } catch (ClassCastException e) {            cause = e;            message = "Class [" + interceptorClassName + "] does not implement com.opensymphony.xwork2.interceptor.Interceptor";        } catch (Exception e) {            cause = e;            message = "Caught Exception while registering Interceptor class " + interceptorClassName;        } catch (NoClassDefFoundError e) {            cause = e;            message = "Could not load class " + interceptorClassName + ". Perhaps it exists but certain dependencies are not available?";        }        throw new ConfigurationException(message, cause, interceptorConfig);    }

加载每个拦截器并执行其中的init方法。在此整个拦截器初始化结束,这个时候其实不仅是拦截器的配置,各种xml中的配置都会在整个init的完成。只不过完成的程度不一样,比如像拦截器这种,它会一直初始化到对象的初始化;但是有些配置可能只初始化到具体的位置所在,而每次使用到的时候再重新请求。这就是说有些对象可能是单例的有些需要每次使用到的时候重新加载。



然后我们再看具体调用过程,每次DefaultActionInvoction初始化会调用proxy.getConfig().getInterceptors(),获取,而其具体实现是获取ActionConfig并获取其中的成员变量interceptors。而ActionConfig是在Struts整个初始化的时候初始化的,以后不存在初始化工作。





原创粉丝点击