Java tutorial
/* * Copyright 2002-2009 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * specific language governing permissions and limitations under the License. */ package org.springframework.cloud.context.scope; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.LinkedHashSet; import java.util.List; import java.util.UUID; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.aop.scope.ScopedProxyUtils; import org.springframework.beans.BeansException; import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.ObjectFactory; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinitionHolder; import org.springframework.beans.factory.config.BeanDefinitionVisitor; import org.springframework.beans.factory.config.BeanFactoryPostProcessor; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.config.Scope; import org.springframework.beans.factory.support.BeanDefinitionReaderUtils; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.context.expression.BeanFactoryAccessor; import org.springframework.expression.Expression; import org.springframework.expression.ExpressionParser; import org.springframework.expression.ParseException; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.expression.spel.support.StandardEvaluationContext; import org.springframework.cloud.context.config.BeanLifecycleDecorator; import org.springframework.cloud.context.config.BeanLifecycleDecorator.Context; import org.springframework.cloud.context.config.StandardBeanLifecycleDecorator; import org.springframework.util.Assert; import org.springframework.util.StringUtils; import org.springframework.util.StringValueResolver; /** * <p> * A generic Scope implementation. * </p> * * @author Dave Syer * * @since 3.1 * */ public class GenericScope implements Scope, BeanFactoryPostProcessor, DisposableBean { private static final Log logger = LogFactory.getLog(GenericScope.class); private BeanLifecycleWrapperCache cache = new BeanLifecycleWrapperCache(new StandardScopeCache()); private String name = "generic"; private boolean proxyTargetClass = true; private boolean autoProxy = true; private ConfigurableListableBeanFactory beanFactory; private StandardEvaluationContext evaluationContext; private String id; private BeanLifecycleDecorator<?> lifecycle; /** * Manual override for the serialization id that will be used to identify * the bean factory. The default is a unique key based on the bean names in * the bean factory. * * @param id * the id to set */ public void setId(String id) { this.id = id; } /** * The name of this scope. Default "refresh". * * @param name * the name value to set */ public void setName(String name) { this.name = name; } /** * Flag to indicate that proxies should be created for the concrete type, * not just the interfaces, of the scoped beans. * * @param proxyTargetClass * the flag value to set */ public void setProxyTargetClass(boolean proxyTargetClass) { this.proxyTargetClass = proxyTargetClass; } /** * Flag to indicate that all scoped beans should automatically be proxied. * If true then scoped beans can be injected as dependencies of another * component and the concrete target will only be instantiated when it is * used. Proxying is a huge advantage if the context storage for the scope * cache is not available at configuration time (e.g. for thread-based, or * other transient scopes). If this flag is false you can expect maybe to * have to add extra meta-data to the bean definitions individually (e.g. * <aop:scoped-proxy/> for an XML configuration). * * @param autoProxy * the flag value to set, default is true */ public void setAutoProxy(boolean autoProxy) { this.autoProxy = autoProxy; } /** * The cache implementation to use for bean instances in this scope. * * @param cache * the cache to use */ public void setScopeCache(ScopeCache cache) { this.cache = new BeanLifecycleWrapperCache(cache); } /** * Helper to manage the creation and destruction of beans. * * @param lifecycle * the bean lifecycle to set */ public void setBeanLifecycleManager(BeanLifecycleDecorator<?> lifecycle) { this.lifecycle = lifecycle; } public void destroy() { List<Throwable> errors = new ArrayList<Throwable>(); Collection<BeanLifecycleWrapper> wrappers = cache.clear(); for (BeanLifecycleWrapper wrapper : wrappers) { try { wrapper.destroy(); } catch (RuntimeException e) { errors.add(e); } } if (!errors.isEmpty()) { throw wrapIfNecessary(errors.get(0)); } } protected void destroy(String name) { BeanLifecycleWrapper wrapper = cache.remove(name); if (wrapper != null) { wrapper.destroy(); } } public Object get(String name, ObjectFactory<?> objectFactory) { if (lifecycle == null) { lifecycle = new StandardBeanLifecycleDecorator(proxyTargetClass); } BeanLifecycleWrapper value = cache.put(name, new BeanLifecycleWrapper(name, objectFactory, lifecycle)); return value.getBean(); } public String getConversationId() { return name; } public void registerDestructionCallback(String name, Runnable callback) { BeanLifecycleWrapper value = cache.get(name); if (value == null) { return; } value.setDestroyCallback(callback); } public Object remove(String name) { BeanLifecycleWrapper value = cache.remove(name); if (value == null) { return null; } // Someone might have added another object with the same key, but we // keep the method contract by removing the // value we found anyway return value.getBean(); } public Object resolveContextualObject(String key) { Expression expression = parseExpression(key); return expression.getValue(evaluationContext, beanFactory); } private Expression parseExpression(String input) { if (StringUtils.hasText(input)) { ExpressionParser parser = new SpelExpressionParser(); try { return parser.parseExpression(input); } catch (ParseException e) { throw new IllegalArgumentException("Cannot parse expression: " + input, e); } } else { return null; } } public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { beanFactory.registerScope(name, this); setSerializationId(beanFactory); this.beanFactory = beanFactory; evaluationContext = new StandardEvaluationContext(); evaluationContext.addPropertyAccessor(new BeanFactoryAccessor()); if (!autoProxy) { // No need to try and create proxies return; } Assert.state(beanFactory instanceof BeanDefinitionRegistry, "BeanFactory was not a BeanDefinitionRegistry, so RefreshScope cannot be used."); BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory; for (String beanName : beanFactory.getBeanDefinitionNames()) { BeanDefinition definition = beanFactory.getBeanDefinition(beanName); // Replace this or any of its inner beans with scoped proxy if it // has this scope boolean scoped = name.equals(definition.getScope()); Scopifier scopifier = new Scopifier(registry, name, proxyTargetClass, scoped); scopifier.visitBeanDefinition(definition); if (scoped) { createScopedProxy(beanName, definition, registry, proxyTargetClass); } } } /** * If the bean factory is a DefaultListableBeanFactory then it can serialize * scoped beans and deserialize them in another context (even in another * JVM), as long as the ids of the bean factories match. This method sets up * the serialization id to be either the id provided to the scope instance, * or if that is null, a hash of all the bean names. * * @param beanFactory * the bean factory to configure */ private void setSerializationId(ConfigurableListableBeanFactory beanFactory) { if (beanFactory instanceof DefaultListableBeanFactory) { String id = this.id; if (id == null) { String names = Arrays.asList(beanFactory.getBeanDefinitionNames()).toString(); logger.debug("Generating bean factory id from names: " + names); id = UUID.nameUUIDFromBytes(names.getBytes()).toString(); } logger.info("BeanFactory id=" + id); ((DefaultListableBeanFactory) beanFactory).setSerializationId(id); } else { logger.warn("BeanFactory was not a DefaultListableBeanFactory, so RefreshScope beans " + "cannot be serialized reliably and passed to a remote JVM."); } } static RuntimeException wrapIfNecessary(Throwable throwable) { if (throwable instanceof RuntimeException) { return (RuntimeException) throwable; } if (throwable instanceof Error) { throw (Error) throwable; } return new IllegalStateException(throwable); } private static BeanDefinitionHolder createScopedProxy(String beanName, BeanDefinition definition, BeanDefinitionRegistry registry, boolean proxyTargetClass) { BeanDefinitionHolder proxyHolder = ScopedProxyUtils .createScopedProxy(new BeanDefinitionHolder(definition, beanName), registry, proxyTargetClass); registry.registerBeanDefinition(beanName, proxyHolder.getBeanDefinition()); return proxyHolder; } /** * Helper class to scan a bean definition hierarchy and force the use of * auto-proxy for scoped beans. * * @author Dave Syer * */ private static class Scopifier extends BeanDefinitionVisitor { private final boolean proxyTargetClass; private final BeanDefinitionRegistry registry; private final String scope; private final boolean scoped; public Scopifier(BeanDefinitionRegistry registry, String scope, boolean proxyTargetClass, boolean scoped) { super(new StringValueResolver() { public String resolveStringValue(String value) { return value; } }); this.registry = registry; this.proxyTargetClass = proxyTargetClass; this.scope = scope; this.scoped = scoped; } @Override protected Object resolveValue(Object value) { BeanDefinition definition = null; String beanName = null; if (value instanceof BeanDefinition) { definition = (BeanDefinition) value; beanName = BeanDefinitionReaderUtils.generateBeanName(definition, registry); } else if (value instanceof BeanDefinitionHolder) { BeanDefinitionHolder holder = (BeanDefinitionHolder) value; definition = holder.getBeanDefinition(); beanName = holder.getBeanName(); } if (definition != null) { boolean nestedScoped = scope.equals(definition.getScope()); boolean scopeChangeRequiresProxy = !scoped && nestedScoped; if (scopeChangeRequiresProxy) { // Exit here so that nested inner bean definitions are not // analysed return createScopedProxy(beanName, definition, registry, proxyTargetClass); } } // Nested inner bean definitions are recursively analysed here value = super.resolveValue(value); return value; } } private static class BeanLifecycleWrapperCache { private final ScopeCache cache; public BeanLifecycleWrapperCache(ScopeCache cache) { this.cache = cache; } public BeanLifecycleWrapper remove(String name) { return (BeanLifecycleWrapper) cache.remove(name); } public Collection<BeanLifecycleWrapper> clear() { Collection<Object> values = cache.clear(); Collection<BeanLifecycleWrapper> wrappers = new LinkedHashSet<BeanLifecycleWrapper>(); for (Object object : values) { wrappers.add((BeanLifecycleWrapper) object); } return wrappers; } public BeanLifecycleWrapper get(String name) { return (BeanLifecycleWrapper) cache.get(name); } public BeanLifecycleWrapper put(String name, BeanLifecycleWrapper value) { return (BeanLifecycleWrapper) cache.put(name, (Object) value); } } /** * Wrapper for a bean instance and any destruction callback (DisposableBean * etc.) that is registered for it. Also decorates the bean to optionally * guard it from concurrent access (for instance). * * @author Dave Syer * */ private static class BeanLifecycleWrapper { private Object bean; private Context<?> context; private final String name; @SuppressWarnings("rawtypes") private final BeanLifecycleDecorator lifecycle; private final ObjectFactory<?> objectFactory; @SuppressWarnings("rawtypes") public BeanLifecycleWrapper(String name, ObjectFactory<?> objectFactory, BeanLifecycleDecorator lifecycle) { this.name = name; this.objectFactory = objectFactory; this.lifecycle = lifecycle; } public void setDestroyCallback(Runnable callback) { this.context = lifecycle.decorateDestructionCallback(callback); } @SuppressWarnings("unchecked") public Object getBean() { if (bean == null) { bean = lifecycle.decorateBean(objectFactory.getObject(), context); } return bean; } public void destroy() { if (context == null) { return; } Runnable callback = context.getCallback(); if (callback != null) { callback.run(); } } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((name == null) ? 0 : name.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; BeanLifecycleWrapper other = (BeanLifecycleWrapper) obj; if (name == null) { if (other.name != null) return false; } else if (!name.equals(other.name)) return false; return true; } } }