【问题标题】:How Spring knows bean to inject with @Bean and method callSpring如何知道bean注入@Bean和方法调用
【发布时间】:2019-09-10 20:45:21
【问题描述】:

我正在使用 Spring Framework 尝试一些东西,我想知道 Spring 在使用 java 配置时如何通过方法调用注入单例依赖项?

例子:

@Configuration
public class AppConfiguration {

    @Bean
    public BlogRepository blogRepository() {
        return new BlogRepositoryImpl();
    }

    @Bean
    @Scope("prototype")
    public BlogService blogService() {
        return new BlogServiceImpl(blogRepository());
    }

    @Bean
    public AuthorService authorService() {
        return new AuthorServiceImpl(blogRepository());
    }
}

我知道这个类也是一个 bean,它由 Spring 代理,但是,由于我从类中调用 blogRepository(),所以 Spring 如何总是获取现有的 BlogRepository 单例,因此代理无法处理调用?

【问题讨论】:

    标签: java spring dependency-injection


    【解决方案1】:

    当您使用@Configuration 注释类时,使用@Bean 注释的方法被CGLIB 代理包装。

    如果是第一次调用这个方法,那么原始方法的主体将被执行,结果对象将存储在Spring上下文中。所有后续调用都只返回从上下文中检索到的 bean。

    【讨论】:

    • 是的,但是当Spring第一次调用blogService()时,调用传递给super.blogService()来实例化bean,然后,blogRepository()从超类调用,所以代理无法进行检查。
    • 如果您调试应用程序,您会注意到您的类AppConfiguration 是Spring 在运行时使用CGLIB 动态生成的。它很可能有一个类似于AppConfiguration$$EnhancerBySpringCGLIB$$... 的名称。一旦调用了blogRepository() 方法,真正调用的是ConfigurationClassEnhancer 的intercept(..) 方法。它使用AbstractBeanFactory 来实例化bean。如果存在这样的 bean,则从上下文中返回它。否则,将创建新 bean 并将其存储在上下文中。
    • 我会测试一下。我不介意对此进行调试。谢谢
    【解决方案2】:

    是什么让您认为代理无法处理呼叫?

    Spring可以生成类似于这个子类的代理:

    class AppConfigurationProxy extends AppConfiguration {
        private BlogRepository blogRepository;
    
        @Override
        public BlogRepository blogRepository() {
            if (blogRepository == null)
                blogRepository = super.blogRepository();
            return blogRepository;
        }
    
        // same for the other two @Bean methods
    }
    

    现在,无论AppConfiguration 中的方法调用它自己的blogRepository() 方法多少次,它总是会得到相同的对象。


    更新:证明上述是可行的。

    简单的 Bean 接口

    public interface BlogRepository {
    }
    
    public interface BlogService {
    }
    
    public interface AuthorService {
    }
    

    简单的 Bean 类

    它们没有任何实际逻辑,只是一个显示对象“身份”的toString() 实现,类似于Object 类中默认的toString() 实现。

    public class BlogRepositoryImpl implements BlogRepository {
        @Override
        public String toString() {
            return "BlogRepositoryImpl@" + Integer.toHexString(hashCode());
        }
    }
    
    public class BlogServiceImpl implements BlogService {
        private BlogRepository blogRepository;
        public BlogServiceImpl(BlogRepository blogRepository) {
            this.blogRepository = blogRepository;
        }
        @Override
        public String toString() {
            return "BlogServiceImpl@" + Integer.toHexString(hashCode()) + "[blogRepository=" + this.blogRepository + "]";
        }
    }
    
    public class AuthorServiceImpl implements AuthorService {
        private BlogRepository blogRepository;
        public AuthorServiceImpl(BlogRepository blogRepository) {
            this.blogRepository = blogRepository;
        }
        @Override
        public String toString() {
            return "AuthorServiceImpl@" + Integer.toHexString(hashCode()) + "[blogRepository=" + this.blogRepository + "]";
        }
    }
    

    配置类

    如问题中所定义。

    public class AppConfiguration {
        public BlogRepository blogRepository() {
            return new BlogRepositoryImpl();
        }
        public BlogService blogService() {
            return new BlogServiceImpl(blogRepository());
        }
        public AuthorService authorService() {
            return new AuthorServiceImpl(blogRepository());
        }
    }
    

    作为 String 的代理类可以实现它

    与上面的答案相同,只是完成了所有方法。

    public class AppConfigurationProxy extends AppConfiguration {
        private BlogRepository blogRepository;
        private BlogService blogService;
        private AuthorService authorService;
        @Override
        public BlogRepository blogRepository() {
            if (this.blogRepository == null)
                this.blogRepository = super.blogRepository();
            return this.blogRepository;
        }
        @Override
        public BlogService blogService() {
            if (this.blogService == null)
                this.blogService = super.blogService();
            return this.blogService;
        }
        @Override
        public AuthorService authorService() {
            if (this.authorService == null)
                this.authorService = super.authorService();
            return this.authorService;
        }
    }
    

    测试

    public class Test {
        public static void main(String[] args) {
            // Show result without proxy
            AppConfiguration config = new AppConfiguration();
            System.out.println(config.blogRepository());
            System.out.println(config.blogService());
            System.out.println(config.authorService());
    
            // Show how only one BlogRepository is craeted when proxy is used
            config = new AppConfigurationProxy();
            System.out.println(config.blogRepository());
            System.out.println(config.blogService());
            System.out.println(config.authorService());
        }
    }
    

    输出

    BlogRepositoryImpl@1e81f4dc
    BlogServiceImpl@7960847b[blogRepository=BlogRepositoryImpl@6a6824be]
    AuthorServiceImpl@2c13da15[blogRepository=BlogRepositoryImpl@77556fd]
    
    BlogRepositoryImpl@9e89d68
    BlogServiceImpl@3b192d32[blogRepository=BlogRepositoryImpl@9e89d68]
    AuthorServiceImpl@16f65612[blogRepository=BlogRepositoryImpl@9e89d68]
    

    可以看出,第一部分没有使用代理,最终得到了 3 个不同的 BlogRepositoryImpl 实例。

    使用代理,即使blogService()“直接”调用blogRepository(),也只会创建BlogRepositoryImpl 的一个实例并共享。

    【讨论】:

    • 是的,但是当Spring第一次调用blogService()时,调用传递给super.blogService()来实例化bean,然后从超类调用blogRepository(),所以代理无法进行检查。
    • @FaridM 当AppConfiguration.blogService() 调用blogRepository() 时,调用的是AppConfigurationProxy.blogRepository() 方法,因为它是方法覆盖的工作方式!!!是传承!!!是动态调度!!!
    • AppConfiguration 如何知道有代理? AppConfiguration 内没有代理的引用。那么blogRepository()里面的AppConfiguration.blogService()怎么能调用代理呢?
    • Class AppConfiguration 没有。 this 引用变量引用AppConfiguration 的代理子类。就像当你有类Animal 和Dog,并且创建的对象实例是Dog,那么Animal 中的一个方法调用另一个恰好被类Dog 覆盖的方法将调用@ 987654355@ 版本的方法。它是标准 Java,如果您不了解继承和方法覆盖,请退后一步,回到您的 Java 初学者指南并(重新)阅读它。
    • @FaridM "this 从何而来?" this always 指的是创建的对象的实际实例。 --- 使用super.method(...) 意味着编译解析该方法仅通过查看超类中声明的方法。该方法中的 this 值将其称为与当前方法相同的 this 值,即它仍然引用代理子类的实例。
    猜你喜欢
    • 2013-06-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-01-08
    • 1970-01-01
    • 2011-09-09
    • 1970-01-01
    相关资源
    最近更新 更多