org.springframework.cloud.context.scope.GenericScope Maven / Gradle / Ivy
/*
* Copyright 2012-2019 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
*
* https://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.lang.reflect.Method;
import java.lang.reflect.UndeclaredThrowableException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.aop.framework.Advised;
import org.springframework.aop.scope.ScopedObject;
import org.springframework.aop.scope.ScopedProxyFactoryBean;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
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.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.Scope;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.RootBeanDefinition;
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.util.ReflectionUtils;
import org.springframework.util.StringUtils;
/**
*
* A generic Scope implementation.
*
*
* @author Dave Syer
* @since 3.1
*
*/
public class GenericScope implements Scope, BeanFactoryPostProcessor,
BeanDefinitionRegistryPostProcessor, DisposableBean {
/**
* Prefix for the scoped target.
*/
public static final String SCOPED_TARGET_PREFIX = "scopedTarget.";
private static final Log logger = LogFactory.getLog(GenericScope.class);
private BeanLifecycleWrapperCache cache = new BeanLifecycleWrapperCache(
new StandardScopeCache());
private String name = "generic";
private ConfigurableListableBeanFactory beanFactory;
private StandardEvaluationContext evaluationContext;
private String id;
private Map errors = new ConcurrentHashMap<>();
private ConcurrentMap locks = new ConcurrentHashMap<>();
static RuntimeException wrapIfNecessary(Throwable throwable) {
if (throwable instanceof RuntimeException) {
return (RuntimeException) throwable;
}
if (throwable instanceof Error) {
throw (Error) throwable;
}
return new IllegalStateException(throwable);
}
/**
* 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 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);
}
/**
* A map of bean name to errors when instantiating the bean.
* @return The errors accumulated since the latest destroy.
*/
public Map getErrors() {
return this.errors;
}
@Override
public void destroy() {
List errors = new ArrayList();
Collection wrappers = this.cache.clear();
for (BeanLifecycleWrapper wrapper : wrappers) {
try {
Lock lock = this.locks.get(wrapper.getName()).writeLock();
lock.lock();
try {
wrapper.destroy();
}
finally {
lock.unlock();
}
}
catch (RuntimeException e) {
errors.add(e);
}
}
if (!errors.isEmpty()) {
throw wrapIfNecessary(errors.get(0));
}
this.errors.clear();
}
/**
* Destroys the named bean (i.e. flushes it from the cache by default).
* @param name The bean name to flush.
* @return True if the bean was already cached; false otherwise.
*/
protected boolean destroy(String name) {
BeanLifecycleWrapper wrapper = this.cache.remove(name);
if (wrapper != null) {
Lock lock = this.locks.get(wrapper.getName()).writeLock();
lock.lock();
try {
wrapper.destroy();
}
finally {
lock.unlock();
}
this.errors.remove(name);
return true;
}
return false;
}
@Override
public Object get(String name, ObjectFactory objectFactory) {
BeanLifecycleWrapper value = this.cache.put(name,
new BeanLifecycleWrapper(name, objectFactory));
this.locks.putIfAbsent(name, new ReentrantReadWriteLock());
try {
return value.getBean();
}
catch (RuntimeException e) {
this.errors.put(name, e);
throw e;
}
}
@Override
public String getConversationId() {
return this.name;
}
@Override
public void registerDestructionCallback(String name, Runnable callback) {
BeanLifecycleWrapper value = this.cache.get(name);
if (value == null) {
return;
}
value.setDestroyCallback(callback);
}
@Override
public Object remove(String name) {
BeanLifecycleWrapper value = this.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();
}
@Override
public Object resolveContextualObject(String key) {
Expression expression = parseExpression(key);
return expression.getValue(this.evaluationContext, this.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;
}
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
throws BeansException {
this.beanFactory = beanFactory;
beanFactory.registerScope(this.name, this);
setSerializationId(beanFactory);
}
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry)
throws BeansException {
for (String name : registry.getBeanDefinitionNames()) {
BeanDefinition definition = registry.getBeanDefinition(name);
if (definition instanceof RootBeanDefinition) {
RootBeanDefinition root = (RootBeanDefinition) definition;
if (root.getDecoratedDefinition() != null && root.hasBeanClass()
&& root.getBeanClass() == ScopedProxyFactoryBean.class) {
if (getName().equals(root.getDecoratedDefinition().getBeanDefinition()
.getScope())) {
root.setBeanClass(LockedScopedProxyFactoryBean.class);
root.getConstructorArgumentValues().addGenericArgumentValue(this);
// surprising that a scoped proxy bean definition is not already
// marked as synthetic?
root.setSynthetic(true);
}
}
}
}
}
/**
* 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) {
List list = new ArrayList<>(
Arrays.asList(beanFactory.getBeanDefinitionNames()));
Collections.sort(list);
String names = list.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, scoped proxy beans "
+ "cannot be serialized.");
}
}
protected String getName() {
return this.name;
}
/**
* The name of this scope. Default "generic".
* @param name The name value to set.
*/
public void setName(String name) {
this.name = name;
}
protected ReadWriteLock getLock(String beanName) {
return this.locks.get(beanName);
}
private static class BeanLifecycleWrapperCache {
private final ScopeCache cache;
BeanLifecycleWrapperCache(ScopeCache cache) {
this.cache = cache;
}
public BeanLifecycleWrapper remove(String name) {
return (BeanLifecycleWrapper) this.cache.remove(name);
}
public Collection clear() {
Collection