Spring @Lookup深入分析实现原理

 更新时间:2023年01月03日 09:38:48   作者:程序员小潘  
这篇文章主要介绍了Spring @Lookup实现原理,我们知道在spring容器中单独的一个抽象类是不能成为一个bean的,那么有没有办法呢?这个时候我们可以使用Lookup注解

1. 前言

在使用Spring的时候,往单例bean注入原型bean时,原型bean可能会失效,如下:

@Component
public class Person {
    @Autowired
    Car car;
    public Car getCar() {
        return car;
    }
}
@Component
@Scope("prototype")
public class Car {
}

调用Person#getCar()方法返回的总是同一个Car对象,这也很好理解,因为Person是单例的,Spring在创建Person时只会注入一次Car对象,以后Car都不会再改变了。

怎么解决这个问题呢?Spring提供了多种方式来获取原型bean。

2. 解决方案

解决方案有很多,本文重点分析@Lookup注解的方式。

1、每次从ApplicationContext重新获取bean。

@Component
public class Person implements ApplicationContextAware {
    private ApplicationContext applicationContext;
    @Lookup
    public Car getCar() {
        return applicationContext.getBean(Car.class);
    }
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}

2、通过CGLIB生成Car子类代理对象,每次都从容器内获取bean执行。

@Component
@Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class Car {
}

3、通过**@Lookup**注解的方式,方法体已经不重要了,代理对象每次都会从容器中重新获取bean。

@Component
public class Person {
    @Lookup
    public Car getCar() {
        return null;
    }
}

3. 源码分析

为什么方法上加了@Lookup注解,调用该方法就能拿到原型bean了呢?其实纵观上述三种方式,要想拿到原型bean,底层原理都是一样的,那就是每次都通过ApplicationContext#getBean()方法从容器中重新获取,只要Car本身是原型的,Spring就会保证每次拿到的都是新创建的Car实例。

**@Lookup**注解也是通过生成代理类的方式,重写被标记的方法,每次都从ApplicationContext获取bean。

1、Spring加载Person的时候,容器内不存在该bean,那首先就是要实例化Person对象。Spring会通过SimpleInstantiationStrategy#instantiate()方法去实例化Person。

public Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner) {
    /**
     * 如果bean没有要重写的方法,直接反射调用构造函数创建对象
     * 反之,需要通过CGLIB创建增强子类代理对象
     */
    if (!bd.hasMethodOverrides()) {
        Constructor<?> constructorToUse;
        synchronized (bd.constructorArgumentLock) {
            constructorToUse = (Constructor<?>) bd.resolvedConstructorOrFactoryMethod;
            if (constructorToUse == null) {
                final Class<?> clazz = bd.getBeanClass();
                if (clazz.isInterface()) {
                    throw new BeanInstantiationException(clazz, "Specified class is an interface");
                }
                try {
                    if (System.getSecurityManager() != null) {
                        constructorToUse = AccessController.doPrivileged(
                                (PrivilegedExceptionAction<Constructor<?>>) clazz::getDeclaredConstructor);
                    } else {
                        constructorToUse = clazz.getDeclaredConstructor();
                    }
                    bd.resolvedConstructorOrFactoryMethod = constructorToUse;
                } catch (Throwable ex) {
                    throw new BeanInstantiationException(clazz, "No default constructor found", ex);
                }
            }
        }
        return BeanUtils.instantiateClass(constructorToUse);
    } else {
        /**
         * 存在 lookup-method 和 replaced-method
         * 通过CGLIB生成代理类
         */
        return instantiateWithMethodInjection(bd, beanName, owner);
    }
}

BeanDefinition有一个属性**methodOverrides**,它里面存放的是当前bean里面是否有需要被重写的方法,这些需要被重写的方法可能是**lookup-method****replaced-method**。如果没有需要重写的方法,则直接通过反射调用构造函数来实例化对象;如果有需要重写的方法,这个时候就不能直接实例化对象了,需要通过CGLIB来创建增强子类,把父类的方法给重写掉。

于是会调用instantiateWithMethodInjection()方法来实例化bean,最终是通过CglibSubclassCreator来实例化。

@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);
}

2、CglibSubclassCreator创建CGLIB子类,重写父类方法。Spring会通过Enhancer来创建增强子类,被@Lookup标记的方法会被LookupOverrideMethodInterceptor拦截。

public Object instantiate(@Nullable Constructor<?> ctor, Object... args) {
    Class<?> subclass = createEnhancedSubclass(this.beanDefinition);
    Object instance;
    if (ctor == null) {
        instance = BeanUtils.instantiateClass(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);
        }
    }
    Factory factory = (Factory) instance;
    factory.setCallbacks(new Callback[] {NoOp.INSTANCE,
            new LookupOverrideMethodInterceptor(this.beanDefinition, this.owner),
            new ReplaceOverrideMethodInterceptor(this.beanDefinition, this.owner)});
    return instance;
}

3、LookupOverrideMethodInterceptor要拦截被@Lookup标记的方法,必然要实现intercept()方法。逻辑很简单,就是每次都调用BeanFactory#getBean()从容器中获取bean。

@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 ? this.owner.getBean(lo.getBeanName(), argsToUse) :
                this.owner.getBean(lo.getBeanName()));
    }
    else {
        return (argsToUse != null ? this.owner.getBean(method.getReturnType(), argsToUse) :
                this.owner.getBean(method.getReturnType()));
    }
}

4. 总结

一个bean一旦拥有被@Lookup注解标记的方法,就意味着该方法需要被重写掉,Spring在实例化bean的时候会自动基于CGLIB生成增强子类对象重写掉父类方法。此时父类被@Lookup注解标记的方法体已经不重要了,不会被执行了,CGLIB子类会通过LookupOverrideMethodInterceptor拦截掉被@Lookup注解标记的方法。方法体重写的逻辑也很简单,就是每次都通过BeanFactory获取bean,只要bean本身是原型的,每次拿到的都将是不同的实例。

到此这篇关于Spring @Lookup深入分析实现原理的文章就介绍到这了,更多相关Spring @Lookup内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • SpringBoot实现分布式任务调度的详细步骤

    SpringBoot实现分布式任务调度的详细步骤

    随着互联网应用的规模和复杂度不断增加,单节点任务调度系统已经难以满足高并发、大数据量的处理需求,分布式任务调度成为了解决这一问题的重要手段,本文将介绍如何在Spring Boot中实现分布式任务调度,需要的朋友可以参考下
    2024-08-08
  • JAVA正则表达式及字符串的替换与分解相关知识总结

    JAVA正则表达式及字符串的替换与分解相关知识总结

    今天给大家带来的是关于Java的相关知识总结,文章围绕着JAVA正则表达式及字符串的替换与分解展开,文中有非常详细的介绍及代码示例,需要的朋友可以参考下
    2021-06-06
  • Java 实现简单静态资源Web服务器的示例

    Java 实现简单静态资源Web服务器的示例

    这篇文章主要介绍了Java 实现简单静态资源Web服务器的示例,帮助大家更好的理解和使用Java,感兴趣的朋友可以了解下
    2020-11-11
  • 基于JSON和java对象的互转方法

    基于JSON和java对象的互转方法

    下面小编就为大家带来一篇基于JSON和java对象的互转方法。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-09-09
  • Spring Boot集成Resilience4J实现限流/重试/隔离

    Spring Boot集成Resilience4J实现限流/重试/隔离

    在Java的微服务生态中,对于服务保护组件,像springcloud的Hystrix,springcloud alibaba的Sentinel,以及当Hystrix停更之后官方推荐使用的Resilience4j,所以本文给大家介绍了Spring Boot集成Resilience4J实现限流/重试/隔离,需要的朋友可以参考下
    2024-03-03
  • 告别无尽等待:Java中的轮询终止技巧

    告别无尽等待:Java中的轮询终止技巧

    在Java中,轮询是一种常见的处理方式,用于检查某个条件是否满足,直到满足条件或达到一定的时间限制,本文将介绍Java中常用的轮询结束方式,包括使用循环、定时器和线程池等方法,需要的朋友可以参考下
    2023-10-10
  • Java Servlet异步请求开启的简单步骤

    Java Servlet异步请求开启的简单步骤

    Java servlet是大家公认的服务器端web技术的标准,包括jsp,jsf,和大量的web框架,soap,RESTful web service api,还有新闻供应,下面这篇文章主要给大家介绍了关于Java Servlet异步请求开启的简单步骤,需要的朋友可以参考下
    2022-02-02
  • 使用Java程序模拟实现新冠病毒传染效果

    使用Java程序模拟实现新冠病毒传染效果

    这篇文章主要介绍了用Java程序模拟实现新冠病毒传染效果,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-08-08
  • SpringBoot集成Spring Security用JWT令牌实现登录和鉴权的方法

    SpringBoot集成Spring Security用JWT令牌实现登录和鉴权的方法

    这篇文章主要介绍了SpringBoot集成Spring Security用JWT令牌实现登录和鉴权的方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-05-05
  • Mybatis之@MapKey的实现

    Mybatis之@MapKey的实现

    本文介绍了Mybatis中@MapKey注解的使用场景与效果,包含使用@MapKey和不使用@MapKey注解的区别,然后通过源码解析产生各种结果的原因,具有一定的参考价值,感兴趣的可以了解一下
    2024-09-09

最新评论