All Downloads are FREE. Search and download functionalities are using the official Maven repository.

infra.beans.factory.support.CglibSubclassingInstantiationStrategy Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2017 - 2024 the original author or authors.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see [https://www.gnu.org/licenses/]
 */

package infra.beans.factory.support;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

import infra.beans.BeanInstantiationException;
import infra.beans.BeanUtils;
import infra.beans.factory.BeanFactory;
import infra.beans.factory.config.ConfigurableBeanFactory;
import infra.bytecode.core.ClassLoaderAwareGeneratorStrategy;
import infra.bytecode.core.NamingPolicy;
import infra.bytecode.proxy.Callback;
import infra.bytecode.proxy.CallbackFilter;
import infra.bytecode.proxy.Enhancer;
import infra.bytecode.proxy.Factory;
import infra.bytecode.proxy.MethodInterceptor;
import infra.bytecode.proxy.MethodProxy;
import infra.bytecode.proxy.NoOp;
import infra.core.ResolvableType;
import infra.lang.Assert;
import infra.lang.Nullable;
import infra.logging.Logger;
import infra.logging.LoggerFactory;
import infra.util.StringUtils;

/**
 * Default object instantiation strategy for use in BeanFactories.
 *
 * 

Uses CGLIB to generate subclasses dynamically if methods need to be * overridden by the container to implement Method Injection. * * @author Rod Johnson * @author Juergen Hoeller * @author Sam Brannen * @author Harry Yang * @since 4.0 2022/3/8 12:21 */ public class CglibSubclassingInstantiationStrategy extends InstantiationStrategy { /** * Index in the CGLIB callback array for passthrough behavior, * in which case the subclass won't override the original class. */ private static final int PASSTHROUGH = 0; /** * Index in the CGLIB callback array for a method that should * be overridden to provide method lookup. */ private static final int LOOKUP_OVERRIDE = 1; /** * Index in the CGLIB callback array for a method that should * be overridden using generic method replacer functionality. */ private static final int METHOD_REPLACER = 2; @Override protected Object instantiateWithMethodInjection(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner) { return instantiateWithMethodInjection(bd, beanName, owner, null); } @Override protected Object instantiateWithMethodInjection(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner, @Nullable Constructor ctor, Object... args) { // Must generate CGLIB subclass... return new CglibSubclassCreator(bd, owner).instantiate(ctor, args); } @Override public Class getActualBeanClass(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner) { if (!bd.hasMethodOverrides()) { return super.getActualBeanClass(bd, beanName, owner); } return new CglibSubclassCreator(bd, owner).createEnhancedSubclass(bd); } /** * An inner class created for historical reasons to avoid external CGLIB dependency. */ private record CglibSubclassCreator(RootBeanDefinition beanDefinition, BeanFactory owner) { private static final Class[] CALLBACK_TYPES = new Class[] { NoOp.class, LookupOverrideMethodInterceptor.class, ReplaceOverrideMethodInterceptor.class }; /** * Create a new instance of a dynamically generated subclass implementing the * required lookups. * * @param ctor constructor to use. If this is {@code null}, use the * no-arg constructor (no parameterization, or Setter Injection) * @param args arguments to use for the constructor. * Ignored if the {@code ctor} parameter is {@code null}. * @return new instance of the dynamically generated subclass */ public Object instantiate(@Nullable Constructor ctor, Object... args) { Class subclass = createEnhancedSubclass(this.beanDefinition); Object instance; if (ctor == null) { instance = BeanUtils.newInstance(subclass); } else { try { Constructor enhancedSubclassConstructor = subclass.getConstructor(ctor.getParameterTypes()); instance = enhancedSubclassConstructor.newInstance(args); } catch (Exception ex) { throw new BeanInstantiationException(this.beanDefinition.getBeanClass(), "Failed to invoke constructor for CGLIB enhanced subclass [" + subclass.getName() + "]", ex); } } // SPR-10785: set callbacks directly on the instance instead of in the // enhanced class (via the Enhancer) in order to avoid memory leaks. Factory factory = (Factory) instance; factory.setCallbacks(new Callback[] { NoOp.INSTANCE, new LookupOverrideMethodInterceptor(this.beanDefinition, this.owner), new ReplaceOverrideMethodInterceptor(this.beanDefinition, this.owner) }); return instance; } /** * Create an enhanced subclass of the bean class for the provided bean * definition, using CGLIB. */ private Class createEnhancedSubclass(RootBeanDefinition beanDefinition) { Enhancer enhancer = new Enhancer(); enhancer.setAttemptLoad(true); enhancer.setSuperclass(beanDefinition.getBeanClass()); enhancer.setNamingPolicy(NamingPolicy.forInfrastructure()); if (this.owner instanceof ConfigurableBeanFactory cbf) { ClassLoader cl = cbf.getBeanClassLoader(); enhancer.setStrategy(new ClassLoaderAwareGeneratorStrategy(cl)); } enhancer.setCallbackFilter(new MethodOverrideCallbackFilter(beanDefinition)); enhancer.setCallbackTypes(CALLBACK_TYPES); return enhancer.createClass(); } } /** * Class providing hashCode and equals methods required by CGLIB to * ensure that CGLIB doesn't generate a distinct class per bean. * Identity is based on class and bean definition. */ private static class CglibIdentitySupport { private final RootBeanDefinition beanDefinition; public CglibIdentitySupport(RootBeanDefinition beanDefinition) { this.beanDefinition = beanDefinition; } public RootBeanDefinition getBeanDefinition() { return this.beanDefinition; } @Override public boolean equals(@Nullable Object other) { return (other != null && getClass() == other.getClass() && this.beanDefinition.equals(((CglibIdentitySupport) other).beanDefinition)); } @Override public int hashCode() { return this.beanDefinition.hashCode(); } } /** * CGLIB callback for filtering method interception behavior. */ private static class MethodOverrideCallbackFilter extends CglibIdentitySupport implements CallbackFilter { private static final Logger logger = LoggerFactory.getLogger(MethodOverrideCallbackFilter.class); public MethodOverrideCallbackFilter(RootBeanDefinition beanDefinition) { super(beanDefinition); } @Override public int accept(Method method) { MethodOverride methodOverride = getBeanDefinition().getMethodOverrides().getOverride(method); if (logger.isTraceEnabled()) { logger.trace("MethodOverride for {}: {}", method, methodOverride); } if (methodOverride == null) { return PASSTHROUGH; } else if (methodOverride instanceof LookupOverride) { return LOOKUP_OVERRIDE; } else if (methodOverride instanceof ReplaceOverride) { return METHOD_REPLACER; } throw new UnsupportedOperationException("Unexpected MethodOverride subclass: " + methodOverride.getClass().getName()); } } /** * CGLIB MethodInterceptor to override methods, replacing them with an * implementation that returns a bean looked up in the container. */ private static class LookupOverrideMethodInterceptor extends CglibIdentitySupport implements MethodInterceptor { private final BeanFactory owner; public LookupOverrideMethodInterceptor(RootBeanDefinition beanDefinition, BeanFactory owner) { super(beanDefinition); this.owner = owner; } @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy mp) throws Throwable { // Cast is safe, as CallbackFilter filters are used selectively. LookupOverride lo = (LookupOverride) getBeanDefinition().getMethodOverrides().getOverride(method); Assert.state(lo != null, "LookupOverride not found"); Object[] argsToUse = args.length > 0 ? args : null; // if no-arg, don't insist on args at all if (StringUtils.hasText(lo.getBeanName())) { return argsToUse != null ? owner.getBean(lo.getBeanName(), argsToUse) : owner.getBean(lo.getBeanName()); } else { // Find target bean matching the (potentially generic) method return type ResolvableType genericReturnType = ResolvableType.forReturnType(method); return argsToUse != null ? owner.getBeanProvider(genericReturnType).get(argsToUse) : owner.getBeanProvider(genericReturnType).get(); } } } /** * CGLIB MethodInterceptor to override methods, replacing them with a call * to a generic MethodReplacer. */ private static class ReplaceOverrideMethodInterceptor extends CglibIdentitySupport implements MethodInterceptor { private final BeanFactory owner; public ReplaceOverrideMethodInterceptor(RootBeanDefinition beanDefinition, BeanFactory owner) { super(beanDefinition); this.owner = owner; } @Nullable @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy mp) throws Throwable { ReplaceOverride ro = (ReplaceOverride) getBeanDefinition().getMethodOverrides().getOverride(method); Assert.state(ro != null, "ReplaceOverride not found"); // TODO could cache if a singleton for minor performance optimization MethodReplacer mr = this.owner.getBean(ro.getMethodReplacerBeanName(), MethodReplacer.class); return processReturnType(method, mr.reimplement(obj, method, args)); } @Nullable private T processReturnType(Method method, @Nullable T returnValue) { Class returnType = method.getReturnType(); if (returnValue == null && returnType != void.class && returnType.isPrimitive()) { throw new IllegalStateException( "Null return value from MethodReplacer does not match primitive return type for: " + method); } return returnValue; } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy