美文网首页spring
如何玩转Spring Java Config

如何玩转Spring Java Config

作者: 淡淡的橙子 | 来源:发表于2019-02-01 15:38 被阅读0次

    随着SpringBoot的兴起,Spring所鼓励的配置方式也逐渐由传统的xml的方式在向Java Config的方式来倾斜。
    我们今天就来讲讲Java Config配置的一些内容。
    文章的内容参考了Defining Bean Dependencies With Java Config in Spring Framework

    1. 内部Bean引用

    这种方式也是Spring reference doc中所介绍的方式。
    我们定义如下几个类:

    public class MyRepository {
        public String findString() {
            return "some-string";
        }
    }
    
    public class MyService {
        private final MyRepository myRepository;
        public MyService(MyRepository myRepository) {
            this.myRepository = myRepository;
        }
        public String generateSomeString() {
            return myRepository.findString() + "-from-MyService";
        }
    }
    

    此时我们如果想将MyRepository注入到MyService中的话,只需要按照如下的内部引用即可:

    @Configuration
    class MyConfiguration {
        @Bean
        public MyService myService() {
            return new MyService(myRepository());
        }
        @Bean
        public MyRepository myRepository() {
            return new MyRepository();
        }
    }
    

    是不是很简单?除了这种方式,我们还可以引用.property文件中的属性。比如如果我们希望在MyRepository中加入前缀和后缀的话,则需要如下的MyRepository类:

    public class MyRepository {
        private final String prefix;
        private final String suffix;
        public MyRepository (String prefix, String suffix) {
            this.prefix = prefix;
            this.suffix = suffix;
        } 
        public String findString() {
            return prefix + "-some-string-" + suffix;
        }
    }
    

    此时我们的Java Config文件可以按照如下方式进行配置:

    @Configuration
    class MyConfiguration {
        @Bean
        public MyService myService() {
            return new MyService(myRepository(null, null));
        }
        @Bean
        public MyRepository myRepository(@Value("${repo.prefix}") String prefix,
                                         @Value("${repo.suffix}") String suffix) {
            return new MyRepository(prefix, suffix);
        }
    }
    

    等等?是不是感觉对这样的用法感觉有些奇怪呢。我们明明在构建myService Bean的时候通过构造器注入(constructor injection)传入的参数都是null,那这个配置到底是如何工作的呢?
    实际上,被@Configuration注解的类都会被CGLIB代理,从而使得我们被@Bean注解的方法能够实现:

    1. 该方法返回的对象会被注入到Spring的容器中。
    2. 调用该方法返回的对象是个单例,每次都返回同样的Bean。

    因此,每次我们调用该方法(比如上面例子中参数均为null),无论参数为如何,均能够返回正确的Bean。
    当然,需要注意的是,由于使用了CGLIB代理,所以config类和Bean方法不能够是private或者final方法。

    还有一点需要说明,@Bean方法不光能用于被@Configuration注解的类中,还能够用于被类似@Component注解的类中。但是当被@Componet注解的时候,该配置类并不会被CGLIB代理,因此@Bean方法每次返回的都是新的实例(不再是单例)。这种模式也被称为轻量模式(Lite mode)。

    以下的例子证明了这点:
    当我们使用@Configuration时:

    @Configuration
    public class MyConfiguration {
        @Bean
        public MyService myService() {
            return new MyService(myRepository());
        }
        @Bean
        public MyRepository myRepository() {
            return new MyRepository();
        }
    
        public static void main(String[] args) {
          AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(MyConfiguration.class);
            MyConfiguration myConfiguration = ac.getBean("myConfiguration", MyConfiguration.class);
            MyRepository myRepository1 = myConfiguration.myRepository();
            MyRepository myRepository2 = myConfiguration.myRepository();
            System.out.println("is singleton : " + (myRepository1 == myRepository2));
            System.out.println(ac.getBean("myConfiguration").getClass());
        }
    }
    

    输出:

    is singleton : true
    class com.spring.javaconf.MyConfiguration$$EnhancerBySpringCGLIB$$eb2da6d3
    

    可见,通过@Configuration注解的类司机都是通过CGLIB进行了增强,使得我们从容器中获得的bean都是单例的形式。
    当我们使用@Component的时候:

    @Component
    public class MyConfiguration {
        @Bean
        public MyService myService() {
            return new MyService(myRepository());
        }
        @Bean
        public MyRepository myRepository() {
            return new MyRepository();
        }
        public static void main(String[] args) {
            AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(MyConfiguration.class);
            MyConfiguration myConfiguration = ac.getBean("myConfiguration", MyConfiguration.class);
            MyRepository myRepository1 = myConfiguration.myRepository();
            MyRepository myRepository2 = myConfiguration.myRepository();
            System.out.println("is singleton : " + (myRepository1 == myRepository2));
            System.out.println(ac.getBean("myConfiguration").getClass());
        }
    }
    

    输出

    is singleton : false
    class com.spring.javaconf.MyConfiguration
    

    可见,此时的实例并没有被代理。而配置类返回的Bean方法也每次返回的不同对象。

    这种通过内部Bean(inter-bean reference)的配置方式对于所有bean的配置均处于一个@Configuration中时是非常有效的。但是如果我们希望跨类来进行配置呢?

    2. 多个类之间的bean引用

    2.1 通过Autowired

    在Spring官方文档Referencing beans across @Configuration classes就介绍了这种方式。
    比如我们修改我们的测试类如下:
    MyConfiguration.class

    @Configuration
    public class MyConfiguration {
    
        @Autowired
        private MyRepository myRepository;
    
        @Bean
        public MyService myService() {
            return new MyService(myRepository);
        }
    }
    

    MyConfiguration2.class

    @Configuration
    public class MyConfiguration2 {
        @Bean
        public MyRepository myRepository() {
            return new MyRepository();
        }
    }
    

    这种方式也是可以的。
    甚至我们可以通过全限定(full-qualified bean reference)的方式来进行引用。
    比如:

    @Configuration
    public class MyConfiguration {
    
        @Autowired
        private MyConfiguration2 myConfiguration2;
    
        @Bean
        public MyService myService() {
            return new MyService(myConfiguration2.myRepository());
        }
    }
    

    2.2 通过Bean方法参数

    如果我们不喜欢将方法调用作为参数,或者我们需要注入的bean是在另一个配置文件中定义的。那这个时候采用内部bean引用可能就不再适用了,此时我们就需要采用方法参数引用。

    @Configuration
    class MyConfiguration {
        @Bean
        public MyService myService(MyRepository myRepository) {
            return new MyService(myRepository);
        }
    }
    

    等等,如果我们的容器中包含多个MyRepository的bean呢,那么注入的规则是如何的呢。实际这时候注入的规则和@Autowired是一样的,也就是先按照类型进行注入,在按照名称(也就是此时参数的名称必须与bean的名称相符合)。

    @Configuration
    class MyConfiguration {
        @Bean
        public MyRepository myFirstRepository() {
            return new MyRepository("first", "repository");
        }
        //a bean that will be injected by name into myService
        @Bean
        public MyRepository mySecondRepository() {
            return new MyRepository("second", "repository");
        }
        @Bean
        public MyService myService(MyRepository mySecondRepository) {
            return new MyService(myRepository);
        }
    }
    

    如果不希望通过方法参数名称这种形式来进行匹配,那么可以采用@Qualifier注解的方式进行匹配,这种方式将会优先于参数名称匹配。

    
    @Configuration
    class MyConfiguration {
        //a bean that will be injected by name into myService
        @Bean
        public MyRepository myFirstRepository() {
            return new MyRepository("first", "repository");
        }
        @Bean
        public MyRepository mySecondRepository() {
            return new MyRepository("second", "repository");
        }
        @Bean
        public MyService myService(@Qualifier("myFirstRepository") MyRepository someRepository) {
            return new MyService(someRepository);
        }
    }
    

    3. 组合Configuration

    如果由多个Configuration文件,我们通过@Import命令可以很容易的将几个配置文件组合在一起。
    比如:

    @Configuration
    @Import({MyConfiguration2.class})
    public class MyConfiguration {
    
        @Bean
        public MyService myService(MyRepository mySecondRepository) {
            return new MyService(mySecondRepository);
        }
    
        public static void main(String[] args) {
            AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(MyConfiguration.class);
            MyService myService = ac.getBean("myService", MyService.class);
            System.out.println("result : " + myService.generateSomeString());
        }
    
    }
    
    public class MyConfiguration2 {
        @Bean
        public MyRepository myFirstRepository() {
            return new MyRepository("first", "repository");
        }
        //a bean that will be injected by name into myService
        @Bean
        public MyRepository mySecondRepository() {
            return new MyRepository("second", "repository");
        }
    }
    

    可以看到,第二个配置文件即使我们没有使用@Configuration注解,也能够通过@Import注解将其配置注入。

    4. 总结

    在上面,我们介绍了各种通过Java配置的方式,那么我们在实际的应用中应该如何使用呢,通常来讲,啊这个需要依赖与情景与个人或团队的喜好。
    比较常用的选择有:

    • 当我们在一个配置文件中互相引用的时候,适用于使用内部bean的引用。否则我们采用传递方法参数的方式。
    • 当我们有两个配置类在一个上下文中的时候,我们可以采用方法参数或者autowire的形式。
    • 当我们有两个或更多的配置类在不同的上下文的时候,我们可以使用Import将其加入到同一个上下文中,并通过方法参数或者autowired的形式。

    相关文章

      网友评论

        本文标题:如何玩转Spring Java Config

        本文链接:https://www.haomeiwen.com/subject/tfnrsqtx.html