001/*
002 * Copyright 2002-2019 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.aop.scope;
018
019import org.springframework.aop.framework.autoproxy.AutoProxyUtils;
020import org.springframework.beans.factory.config.BeanDefinition;
021import org.springframework.beans.factory.config.BeanDefinitionHolder;
022import org.springframework.beans.factory.support.AbstractBeanDefinition;
023import org.springframework.beans.factory.support.BeanDefinitionRegistry;
024import org.springframework.beans.factory.support.RootBeanDefinition;
025import org.springframework.lang.Nullable;
026import org.springframework.util.Assert;
027
028/**
029 * Utility class for creating a scoped proxy.
030 *
031 * <p>Used by ScopedProxyBeanDefinitionDecorator and ClassPathBeanDefinitionScanner.
032 *
033 * @author Mark Fisher
034 * @author Juergen Hoeller
035 * @author Rob Harrop
036 * @author Sam Brannen
037 * @since 2.5
038 */
039public abstract class ScopedProxyUtils {
040
041        private static final String TARGET_NAME_PREFIX = "scopedTarget.";
042
043        private static final int TARGET_NAME_PREFIX_LENGTH = TARGET_NAME_PREFIX.length();
044
045
046        /**
047         * Generate a scoped proxy for the supplied target bean, registering the target
048         * bean with an internal name and setting 'targetBeanName' on the scoped proxy.
049         * @param definition the original bean definition
050         * @param registry the bean definition registry
051         * @param proxyTargetClass whether to create a target class proxy
052         * @return the scoped proxy definition
053         * @see #getTargetBeanName(String)
054         * @see #getOriginalBeanName(String)
055         */
056        public static BeanDefinitionHolder createScopedProxy(BeanDefinitionHolder definition,
057                        BeanDefinitionRegistry registry, boolean proxyTargetClass) {
058
059                String originalBeanName = definition.getBeanName();
060                BeanDefinition targetDefinition = definition.getBeanDefinition();
061                String targetBeanName = getTargetBeanName(originalBeanName);
062
063                // Create a scoped proxy definition for the original bean name,
064                // "hiding" the target bean in an internal target definition.
065                RootBeanDefinition proxyDefinition = new RootBeanDefinition(ScopedProxyFactoryBean.class);
066                proxyDefinition.setDecoratedDefinition(new BeanDefinitionHolder(targetDefinition, targetBeanName));
067                proxyDefinition.setOriginatingBeanDefinition(targetDefinition);
068                proxyDefinition.setSource(definition.getSource());
069                proxyDefinition.setRole(targetDefinition.getRole());
070
071                proxyDefinition.getPropertyValues().add("targetBeanName", targetBeanName);
072                if (proxyTargetClass) {
073                        targetDefinition.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE);
074                        // ScopedProxyFactoryBean's "proxyTargetClass" default is TRUE, so we don't need to set it explicitly here.
075                }
076                else {
077                        proxyDefinition.getPropertyValues().add("proxyTargetClass", Boolean.FALSE);
078                }
079
080                // Copy autowire settings from original bean definition.
081                proxyDefinition.setAutowireCandidate(targetDefinition.isAutowireCandidate());
082                proxyDefinition.setPrimary(targetDefinition.isPrimary());
083                if (targetDefinition instanceof AbstractBeanDefinition) {
084                        proxyDefinition.copyQualifiersFrom((AbstractBeanDefinition) targetDefinition);
085                }
086
087                // The target bean should be ignored in favor of the scoped proxy.
088                targetDefinition.setAutowireCandidate(false);
089                targetDefinition.setPrimary(false);
090
091                // Register the target bean as separate bean in the factory.
092                registry.registerBeanDefinition(targetBeanName, targetDefinition);
093
094                // Return the scoped proxy definition as primary bean definition
095                // (potentially an inner bean).
096                return new BeanDefinitionHolder(proxyDefinition, originalBeanName, definition.getAliases());
097        }
098
099        /**
100         * Generate the bean name that is used within the scoped proxy to reference the target bean.
101         * @param originalBeanName the original name of bean
102         * @return the generated bean to be used to reference the target bean
103         * @see #getOriginalBeanName(String)
104         */
105        public static String getTargetBeanName(String originalBeanName) {
106                return TARGET_NAME_PREFIX + originalBeanName;
107        }
108
109        /**
110         * Get the original bean name for the provided {@linkplain #getTargetBeanName
111         * target bean name}.
112         * @param targetBeanName the target bean name for the scoped proxy
113         * @return the original bean name
114         * @throws IllegalArgumentException if the supplied bean name does not refer
115         * to the target of a scoped proxy
116         * @since 5.1.10
117         * @see #getTargetBeanName(String)
118         * @see #isScopedTarget(String)
119         */
120        public static String getOriginalBeanName(@Nullable String targetBeanName) {
121                Assert.isTrue(isScopedTarget(targetBeanName), () -> "bean name '" +
122                                targetBeanName + "' does not refer to the target of a scoped proxy");
123                return targetBeanName.substring(TARGET_NAME_PREFIX_LENGTH);
124        }
125
126        /**
127         * Determine if the {@code beanName} is the name of a bean that references
128         * the target bean within a scoped proxy.
129         * @since 4.1.4
130         */
131        public static boolean isScopedTarget(@Nullable String beanName) {
132                return (beanName != null && beanName.startsWith(TARGET_NAME_PREFIX));
133        }
134
135}