你的位置:首页 > 信息动态 > 新闻中心
信息动态
联系我们

2021SC@SDUSC【软件工程应用与实践】Cocoon代码分析(十四)

2021/12/19 7:03:18

2021SC@SDUSC

本次代码分析的主要内容是sitemap-impl文件夹下的core文件夹里的内容。这个core文件夹里可能是实现站点地图的核心代码。由于内容较多,我将会分成两篇博客来一次进行分析。这篇博客主要是基于core文件夹里的container文件夹里的spring文件夹里的avalon文件夹的部分代码进行分析。

文章目录

  • AvalonBeanPostProcessor.java
  • AvalonContextFactoryBean.java
  • AvalonUtils.java
  • ComponentContext.java
  • ComponentInfo.java
  • ConfigurationReader.java
  • 其他类文件

AvalonBeanPostProcessor.java

这是一个添加了对Avalon生命周期接口支持的Spring BeanPostProcessor。

里面有一些简单的小方法,例如setSettings()setResourceLoader()setLocation()setBeanFactory()setConfigurationInfo()setContext(),用于设置一些值。

下面介绍一些比较重要的方法:

1.postProcessAfterInitialization():

在任何bean初始化回调之后,将此BeanPostProcessor应用于给定的新bean实例。bean将已经被属性值填充。返回的bean实例可能是原始bean实例的包装器。

对于FactoryBean,这个回调函数将同时对FactoryBean实例和由FactoryBean创建的对象调用。后处理器可以通过对应的FactoryBean实例检查来决定是应用于FactoryBean还是已创建的对象,或者两者都应用。

与所有其他的BeanPostProcessor回调相比,这个回调也将在一个由方法触发的短路之后被调用。

public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
    try {
        ContainerUtil.start(bean);
    } catch (Exception e) {
        throw new BeanInitializationException("Unable to start bean " + beanName, e);
    }
    return bean;
}

2.postProcessBeforeInitialization():

在任何bean初始化回调之前,将此BeanPostProcessor应用于给定的新bean实例。bean将已经被属性值填充。返回的bean实例可能是原始bean实例的包装器.

public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
    final ComponentInfo info = (ComponentInfo) this.configurationInfo.getComponents().get(beanName);
    try {
        if (info == null) {
            // no info so we just return the bean and don't apply any
            // lifecycle interfaces
            return bean;
        }
        if (bean instanceof LogEnabled) {
            ContainerUtil.enableLogging(bean,
                                        new CLLoggerWrapper(LoggerUtils.getChildLogger(beanFactory, info.getLoggerCategory())));
        } else if (bean instanceof AbstractLogEnabled && info.getLoggerCategory() != null) {
            ((AbstractLogEnabled) bean).setLogger(LoggerUtils.getChildLogger(beanFactory, info.getLoggerCategory()));
        }
        ContainerUtil.contextualize(bean, this.context);
        ContainerUtil.service(bean, (ServiceManager) this.beanFactory.getBean(ServiceManager.class.getName()));
        Configuration config = info.getProcessedConfiguration();
        if (config == null) {
            config = info.getConfiguration();
            if (config == null) {
                config = EMPTY_CONFIG;
            } else {
                config = AvalonUtils.replaceProperties(config, this.settings);
            }
            info.setProcessedConfiguration(config);
        }
        if (bean instanceof Configurable) {
            ContainerUtil.configure(bean, config);
        } else if (bean instanceof Parameterizable) {
            Parameters p = info.getParameters();
            if (p == null) {
                p = Parameters.fromConfiguration(config);
                info.setParameters(p);
            }
            ContainerUtil.parameterize(bean, p);
        }
        ContainerUtil.initialize(bean);
    } catch (Exception e) {
        throw new BeanCreationException("Unable to initialize Avalon component with role " + beanName, e);
    }
    return bean;
}

3.postProcessBeforeDestruction():

在给定的bean实例销毁之前将此BeanPostProcessor应用于其。可以调用自定义销毁回调。这个回调只适用于工厂中的单例bean(包括内部bean)。

public void postProcessBeforeDestruction(Object bean, String beanName) throws BeansException {
    try {
        ContainerUtil.stop(bean);
    } catch (Exception e) {
        throw new BeanInitializationException("Unable to stop bean " + beanName, e);
    }
    ContainerUtil.dispose(bean);
}

AvalonContextFactoryBean.java

这个工厂bean设置Avalon Context对象。它是用于Avalon集成的Spring桥的一部分。

有三个比较重要的属性:

//Avalon上下文
protected ServletContext servletContext;
//servlet上下文
protected Settings settings;
//设置
protected Context context;

有一个需要关注的方法——init():

//创建Avalon上下文对象。
protected void init()throws Exception {
    if ( this.settings == null ) {
        throw new IllegalArgumentException("Settings object is missing.");
    }
    //创建新的Avalon上下文
    final DefaultContext appContext = new ComponentContext();
    //添加环境上下文和配置
    appContext.put(Constants.CONTEXT_ENVIRONMENT_CONTEXT, new HttpContext(this.servletContext));
    //添加设置中包含的Avalon上下文属性
    appContext.put(Constants.CONTEXT_WORK_DIR, new File(this.settings.getWorkDirectory()));
    appContext.put(Constants.CONTEXT_CACHE_DIR, new File(this.settings.getCacheDirectory()));
    appContext.put(Constants.CONTEXT_DEFAULT_ENCODING, this.settings.getFormEncoding());
    this.context = appContext;
}

AvalonUtils.java

这个类里包含一些处理Avalon的实用方法。

1.replaceProperties():

替换配置对象中的所有属性。

public static Configuration replaceProperties(Configuration tree, Settings settings)
throws ConfigurationException {
    if (tree == null || settings == null) {
        return tree;
    }
    //首先复制tree
    final DefaultConfiguration root = new DefaultConfiguration(tree, true);
    //然后调用_replaceProperties方法,替换属性
    _replaceProperties(root, settings);
    return root;
}

2._replaceProperties():

递归替换配置对象的属性。

protected static void _replaceProperties(DefaultConfiguration config, Settings settings)
throws ConfigurationException {
    final String[] names = config.getAttributeNames();
    for (int i = 0; i < names.length; i++) {
        final String value = config.getAttribute(names[i]);
        config.setAttribute(names[i], PropertyHelper.replace(value, settings));
    }
    final String value = config.getValue(null);
    if (value != null) {
        config.setValue(PropertyHelper.replace(value, settings));
    }
    final Configuration[] children = config.getChildren();
    for (int m = 0; m < children.length; m++) {
        _replaceProperties((DefaultConfiguration) children[m], settings);
    }
}

ComponentContext.java

这是Cocoon组件的上下文实现。它通过从对象模型和其他应用程序信息中获取对象的特殊处理来扩展DefaultContext。这个类继承自DefaultContext

重载了构造函数,一个有final Context parent参数,一个没有,作用分别是:创建一个具有指定父级的上下文,和创建一个没有父结点的上下文。

有一个唯一的方法get()——从上下文中检索项:

public Object get( final Object key )
throws ContextException {
    if ( ContextHelper.CONTEXT_OBJECT_MODEL.equals(key)) {
        final Environment env = EnvironmentHelper.getCurrentEnvironment();
        if ( env == null ) {
            throw new ContextException("Unable to locate " + key + " (No environment available)");
        }
        return env.getObjectModel();
    } else if ( ContextHelper.CONTEXT_SITEMAP_SERVICE_MANAGER.equals(key)) {
        final ServiceManager manager = EnvironmentHelper.getSitemapServiceManager();
        if ( manager == null ) {
            throw new ContextException("Unable to locate " + key + " (No environment available)");
        }
        return manager;
    }
    if ( key instanceof String ) {
        String stringKey = (String)key;
        if ( stringKey.startsWith(OBJECT_MODEL_KEY_PREFIX) ) {
            final Environment env = EnvironmentHelper.getCurrentEnvironment();
            if ( env == null ) {
                throw new ContextException("Unable to locate " + key + " (No environment available)");
            }
            final Map objectModel = env.getObjectModel();
            String objectKey = stringKey.substring(OBJECT_MODEL_KEY_PREFIX.length());

            Object o = objectModel.get( objectKey );
            if ( o == null ) {
                final String message = "Unable to locate " + key;
                throw new ContextException( message );
            }
            return o;
        }
    }
    return super.get( key );
}

ComponentInfo.java

关于基于Avalon组件的元信息。这个简单的bean保存了在基于Avalon的配置文件中定义的组件的大部分信息,比如组件的配置、它的记录器等。

这个类里有一些需要关注的属性:

属性名称属性说明
MODEL_POOLED每次请求此类型的组件时,都会创建一个新的实例。
MODEL_PRIMITIVE每个容器只存在这种类型的组件。
MODEL_SINGLETON容器为该类型创建一个组件池,并为该池中的请求提供服务。
MODEL_UNKNOWN组件的模型未知。反射稍后用于确定模型。

有一个名为copy的方法需要关注,顾名思义这个方法用于创建一个新的组件信息与当前相同的配置。

public ComponentInfo copy() {
    final ComponentInfo info = new ComponentInfo();
    //该组件的模型。
    info.model = this.model;
    //一个可选方法,在初始化时由容器调用。
    info.initMethodName = this.initMethodName;
    //一个可选方法,在销毁时由容器调用。
    info.destroyMethodName = this.destroyMethodName;
    //一个可选方法,当组件被放回池中时由容器调用。
    info.poolInMethodName = this.poolInMethodName;
    //一个可选方法,当组件从池中获取时由容器调用。
    info.poolOutMethodName = this.poolOutMethodName;
    //组件的类名。
    info.componentClassName = this.componentClassName;
    //组件配置。
    info.configuration = this.configuration;
    //组件的配置作为参数
    info.parameters = this.parameters;
    //可选的日志记录器类别(相对于容器的类别)。
    info.loggerCategory = this.loggerCategory;
    //组件的角色。
    info.role = this.role;
    //组件角色的别名。
    info.alias = this.alias;
    //选择器的默认组件
    info.defaultValue = this.defaultValue;
    info.lazyInit = this.lazyInit;
    return info;
}

ConfigurationReader.java

该组件读取Avalon样式的配置文件,并返回所有包含的组件及其配置。

一些比较重要的属性:

  • logger——日志记录器
  • resolver——解析器读取配置文件
  • configInfo——配置信息
  • componentConfigs——所有组件配置
  • isRootContext——用于判断是否为根的上下文

一些比较重要的方法:

1.configureRoles()——读取配置对象并创建角色、简写和类名映射。

protected final void configureRoles( final Configuration configuration )
throws ConfigurationException {
    final Configuration[] roles = configuration.getChildren();
    for (int i = 0; i < roles.length; i++) {
        final Configuration role = roles[i];

        if ("alias".equals(role.getName())) {
            final String roleName = role.getAttribute("role");
            final String shorthand = role.getAttribute("shorthand");
            this.configInfo.getShorthands().put(shorthand, roleName);
            continue;
        }
        if (!"role".equals(role.getName())) {
            throw new ConfigurationException("Unexpected '" + role.getName() + "' element at " + role.getLocation());
        }

        final String roleName = role.getAttribute("name");
        final String shorthand = role.getAttribute("shorthand", null);
        final String defaultClassName = role.getAttribute("default-class", null);

        if (shorthand != null) {
            // Store the shorthand and check that its consistent with any previous one
            Object previous = this.configInfo.getShorthands().put(shorthand, roleName);
            if (previous != null && !previous.equals(roleName)) {
                throw new ConfigurationException("Shorthand '" + shorthand + "' already used for role " +
                                                 previous + ": inconsistent declaration at " + role.getLocation());
            }
        }

        if (defaultClassName != null) {
            ComponentInfo info = this.configInfo.getRole(roleName);
            if (info == null) {
                // Create a new info and store it
                info = new ComponentInfo();
                info.setComponentClassName(defaultClassName);
                info.fill(role);
                info.setRole(roleName);
                info.setConfiguration(role);
                info.setAlias(shorthand);
                this.configInfo.addRole(roleName, info);
            } else {
                // Check that it's consistent with the existing info
                if (!defaultClassName.equals(info.getComponentClassName())) {
                    throw new ConfigurationException("Invalid redeclaration: default class already set to " + info.getComponentClassName() +
                                                     " for role " + roleName + " at " + role.getLocation());
                }
                //FIXME: should check also other ServiceInfo members
            }
        }

        final Configuration[] keys = role.getChildren("hint");
        if (keys.length > 0) {
            Map keyMap = (Map) this.configInfo.getKeyClassNames().get(roleName);
            if (keyMap == null) {
                keyMap = new HashMap();
                this.configInfo.getKeyClassNames().put(roleName, keyMap);
            }

            for (int j = 0; j < keys.length; j++) {
                Configuration key = keys[j];

                final String shortHand = key.getAttribute("shorthand").trim();
                final String className = key.getAttribute("class").trim();

                ComponentInfo info = (ComponentInfo) keyMap.get(shortHand);
                if (info == null) {
                    info = new ComponentInfo();
                    info.setComponentClassName(className);
                    info.fill(key);
                    info.setConfiguration(key);
                    info.setAlias(shortHand);
                    keyMap.put(shortHand, info);
                } else {
                    // Check that it's consistent with the existing info
                    if (!className.equals(info.getComponentClassName())) {
                        throw new ConfigurationException("Invalid redeclaration: class already set to " + info.getComponentClassName() +
                                                         " for hint " + shortHand + " at " + key.getLocation());
                    }
                    //FIXME: should check also other ServiceInfo members
                }
            }
        }
    }
}

2.convertUrl()——将一个avalon url(使用可能的cocoon协议)转换为一个spring url。

protected String convertUrl(String url) {
    if (url == null) {
        return null;
    }
    if (url.startsWith("context:")) {
        return url.substring(10);
    }
    if (url.startsWith("resource:")) {
        return "classpath:" + url.substring(10);
    }
    return url;
}

3.getInputSource()——从给定的资源构造输入源,初始化系统Id

protected InputSource getInputSource(Resource rsrc)
throws IOException {
    final InputSource is = new InputSource(rsrc.getInputStream());
    is.setSystemId(getUrl(rsrc));
    return is;
}

4.handleBeanInclude()——处理包含spring bean配置

protected void handleBeanInclude(final String contextURI,
                                 final Configuration includeStatement)
throws ConfigurationException {
    final String includeURI = includeStatement.getAttribute("src", null);
    String directoryURI = null;
    if (includeURI == null) {
        // check for directories
        directoryURI = includeStatement.getAttribute("dir", null);
    }
    if (includeURI == null && directoryURI == null) {
        throw new ConfigurationException(
                "Include statement must either have a 'src' or 'dir' attribute, at "
                        + includeStatement.getLocation());
    }

    if (includeURI != null) {
        try {
            Resource src = this.resolver.getResource(getUrl(includeURI, contextURI));
            this.configInfo.addImport(getUrl(src));
        } catch (Exception e) {
            throw new ConfigurationException("Cannot load '" + includeURI + "' at " +
                                             includeStatement.getLocation(), e);
        }

    } else {
        // test if directory exists
        Resource dirResource = this.resolver.getResource(this.getUrl(directoryURI, contextURI));
        if ( dirResource.exists() ) {
            final String pattern = includeStatement.getAttribute("pattern", null);
            try {
                Resource[] resources = this.resolver.getResources(this.getUrl(directoryURI + '/' + pattern, contextURI));
                if ( resources != null ) {
                    Arrays.sort(resources, ResourceUtils.getResourceComparator());
                    for(int i=0; i < resources.length; i++) {
                       this.configInfo.addImport(getUrl(resources[i]));
                    }
                }
            } catch (IOException ioe) {
                throw new ConfigurationException("Unable to read configurations from "
                        + directoryURI);
            }
        } else {
            if (!includeStatement.getAttributeAsBoolean("optional", false)) {
                throw new ConfigurationException("Directory '" + directoryURI + "' does not exist (" +
                                                 includeStatement.getLocation() + ").");
            }
        }
    }
}

5.handleInclude()——处理包括avalon配置

protected void handleInclude(final String        contextURI,
                             final Set           loadedURIs,
                             final Configuration includeStatement)
throws ConfigurationException {
    final String includeURI = includeStatement.getAttribute("src", null);
    String directoryURI = null;
    if (includeURI == null) {
        // check for directories
        directoryURI = includeStatement.getAttribute("dir", null);
    }
    if (includeURI == null && directoryURI == null) {
        throw new ConfigurationException("Include statement must either have a 'src' or 'dir' attribute, at " +
                                         includeStatement.getLocation());
    }

    if (includeURI != null) {
        try {
            Resource src = this.resolver.getResource(getUrl(includeURI, contextURI));
            loadURI(src, loadedURIs, includeStatement);
        } catch (Exception e) {
            throw new ConfigurationException("Cannot load '" + includeURI + "' at " +
                                             includeStatement.getLocation(), e);
        }

    } else {
        boolean load = true;
        // test if directory exists (only if not classpath protocol is used)
        if (!ResourceUtils.isClasspathUri(directoryURI)) {
            Resource dirResource = this.resolver.getResource(this.getUrl(directoryURI, contextURI));
            if (!dirResource.exists()) {
                if (!includeStatement.getAttributeAsBoolean("optional", false)) {
                    throw new ConfigurationException("Directory '" + directoryURI + "' does not exist (" + includeStatement.getLocation() + ").");
                }
                load = false;
            }
        }
        if (load) {
            final String pattern = includeStatement.getAttribute("pattern", null);
            try {
                Resource[] resources = this.resolver.getResources(this.getUrl(directoryURI + '/' + pattern, contextURI));
                if (resources != null) {
                    Arrays.sort(resources, ResourceUtils.getResourceComparator());
                    for (int i = 0; i < resources.length; i++) {
                        loadURI(resources[i], loadedURIs, includeStatement);
                    }
                }
            } catch (Exception e) {
                throw new ConfigurationException("Cannot load from directory '" + directoryURI + "' at " + includeStatement.getLocation(), e);
            }
        }
    }
}

其他类文件

类的名称类的说明
AvalonNamespaceHandler用于cocoon avalon名称空间的Spring名称空间处理程序
AvalonServiceManager该bean的作用类似于Avalon ServiceManager
AvalonServiceSelector该bean的行为类似于Avalon servicesselector。
AvalonSitemapContextFactoryBean这个工厂bean为站点地图创建一个上下文。
BridgeElementParser这是Avalon-Spring-bridge的主要实现。
ConfigurationInfo该bean存储关于完整Avalon样式配置的信息。
ConfigurationInfoFactoryBean这个spring工厂bean将配置信息添加到bean工厂。

这次的博客我也只分析了这个文件夹下的部分内容,剩下的内容会在下一篇博客中给出。