The IoC Container 1.12

作者: 小鲍比大爷 | 来源:发表于2019-02-25 10:47 被阅读0次

    1.12. Java-based Container Configuration

    1.12.1. Basic Concepts: @Bean and @Configuration

    @Configuration
    public class AppConfig {
    
        @Bean
        public MyService myService() {
            return new MyServiceImpl();
        }
    }
    

    通常使用以上方式实现基于注解的bean配置。这种配置方式与下面的xml配置语义上等价:

    <beans>
        <bean id="myService" class="com.acme.services.MyServiceImpl"/>
    </beans>
    

    Full @Configuration vs “lite” @Bean mode?
    所有的@Bean注解都使用在@Configuration注解的类中时称为Full @Configuration mode;否则,称为“lite” @Bean mode,比如在@Component注解的类中使用@Bean。
    lite @Bean mode:

    @Component
    public class AppConfig {
    
        @Bean
        public MyService myService() {
            return new MyServiceImpl();
        }
    }
    

    在正常的场景中,Spring建议使用Full @Configuration。后面的章节会进行深入讨论。

    1.12.2. Instantiating the Spring Container by Using AnnotationConfigApplicationContext

    本节讲述基于注解方式SpringContainer的几种初始化方式。使用AnnotationConfigApplicationContext作为容器实现进行初始化。

    • 如果使用的是@Configuration,@Configuration注解的类本身与所有@Bean所注解的方法全部作为bean定义注册到容器中
    • 如果使用的是@Component或者JSR-330注解,同样作为bean定义注册到容器,而且在这些被注解的类中可以使用@Autowired 和 @Inject

    Simple Construction
    使用配置类初始化:

    public static void main(String[] args) {
        ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
        MyService myService = ctx.getBean(MyService.class);
        myService.doStuff();
    }
    

    使用@Componet注解或者 JSR-330 注解的类初始化(甚至至POJO都可以):

    public static void main(String[] args) {
        ApplicationContext ctx = new AnnotationConfigApplicationContext(MyServiceImpl.class, Dependency1.class, Dependency2.class);
        MyService myService = ctx.getBean(MyService.class);
        myService.doStuff();
    }
    

    Building the Container Programmatically by Using register(Class<?>…​)

    public static void main(String[] args) {
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
        ctx.register(AppConfig.class, OtherConfig.class);
        ctx.register(AdditionalConfig.class);
        ctx.refresh();
        MyService myService = ctx.getBean(MyService.class);
        myService.doStuff();
    }
    

    Enabling Component Scanning with scan(String…​)
    以下两种自动扫描的方式等价:

    @Configuration
    @ComponentScan(basePackages = "com.acme") 
    public class AppConfig  {
        ...
    }
    
    public static void main(String[] args) {
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
        ctx.scan("com.acme");
        ctx.refresh();
        MyService myService = ctx.getBean(MyService.class);
    }
    

    Support for Web Applications with AnnotationConfigWebApplicationContext
    待补充

    1.12.3. Using the @Bean Annotation

    Declaring a Bean and Bean Dependencies

    @Configuration
    public class AppConfig {
    
        @Bean
        public TransferService transferService(AccountRepository accountRepository) {
            return new TransferServiceImpl(accountRepository);
        }
    }
    

    该定义方式类似于构造函数实例化的方式。
    Receiving Lifecycle Callbacks

    public class BeanOne {
    
        public void init() {
            // initialization logic
        }
    }
    
    public class BeanTwo {
    
        public void cleanup() {
            // destruction logic
        }
    }
    
    @Configuration
    public class AppConfig {
    
        @Bean(initMethod = "init")
        public BeanOne beanOne() {
            return new BeanOne();
        }
    
        @Bean(destroyMethod = "cleanup")
        public BeanTwo beanTwo() {
            return new BeanTwo();
        }
    }
    

    Spring自动推断close或shutdown为这两种方法为销毁方法。所以如果实现了名为这两个的方法,Spring会识别该方法并在合适的时间执行销毁方法。如果想要禁止Spring的这种自动推断行为,自己管理该声明周期,可以使用如下方式,将destroyMethod设置为空:

    @Bean(destroyMethod="")
    public DataSource dataSource() throws NamingException {
        return (DataSource) jndiTemplate.lookup("MyDS");
    }
    

    Specifying Bean Scope

    @Configuration
    public class MyConfiguration {
    
        @Bean
        @Scope("prototype")
        public Encryptor encryptor() {
            // ...
        }
    }
    

    @Scope and scoped-proxy
    @Scope支持指定bean的代理类型:

        @Bean
        @Scope(proxyMode = ScopedProxyMode.NO)
        public AccountRepository accountRepository() {
            return new AccountRepositoryImpl();
        }
    

    Customizing Bean Naming

    @Configuration
    public class AppConfig {
    
        @Bean(name = "myThing")
        public Thing thing() {
            return new Thing();
        }
    }
    

    Bean Aliasing
    bean的别名的基于注解的设置方法:

    @Configuration
    public class AppConfig {
    
        @Bean({"dataSource", "subsystemA-dataSource", "subsystemB-dataSource"})
        public DataSource dataSource() {
            // instantiate, configure and return DataSource bean...
        }
    }
    

    Bean Description

    This can be particularly useful when beans are exposed (perhaps through JMX) for monitoring purposes.

    @Configuration
    public class AppConfig {
    
        @Bean
        @Description("Provides a basic example of a bean")
        public Thing thing() {
            return new Thing();
        }
    }
    

    1.12.4. Using the @Configuration annotation

    @Configuration用来配置bean定义,与@Bean共用。

    Injecting Inter-bean Dependencies
    如果bean之间有依赖,@Configuration中可以直接使用方法指定依赖的bean,语义跟java的工厂方法语义是不同的,@Configuration注解的对象通过代理实现了依赖语义,这样保证beanTwo()方法在这里返回的是单例。

    @Configuration
    public class AppConfig {
    
        @Bean
        public BeanOne beanOne() {
            return new BeanOne(beanTwo());
        }
    
        @Bean
        public BeanTwo beanTwo() {
            return new BeanTwo();
        }
    }
    

    如果将上面的@Configuration替换成@Componet,调用beanTwo() 只会得到一个新的对象:简单的工厂方法调用。如果要实现跟上面代码相同的语义效果,需要像这样实现:

    @Component
    public class AppConfig {
    
        @Autowired
        private BeanTwo beanTwo;
    
        @Bean
        public BeanOne beanOne() {
            return new BeanOne(beanTwo);
        }
    
        @Bean
        public BeanTwo beanTwo() {
            return new BeanTwo();
        }
    }
    

    Lookup Method Injection
    之前已经学习过xml版的,现在给出java注解版的示例:

    public abstract class CommandManager {
        public Object process(Object commandState) {
            // grab a new instance of the appropriate Command interface
            Command command = createCommand();
            // set the state on the (hopefully brand new) Command instance
            command.setState(commandState);
            return command.execute();
        }
    
        // okay... but where is the implementation of this method?
        protected abstract Command createCommand();
    }
    
    @Bean
    @Scope("prototype")
    public AsyncCommand asyncCommand() {
        AsyncCommand command = new AsyncCommand();
        // inject dependencies here as required
        return command;
    }
    
    @Bean
    public CommandManager commandManager() {
        // return new anonymous implementation of CommandManager with createCommand()
        // overridden to return a new prototype Command object
        return new CommandManager() {
            protected Command createCommand() {
                return asyncCommand();
            }
        }
    }
    

    Further Information About How Java-based Configuration Works Internally

    All @Configuration classes are subclassed at startup-time with CGLIB. In the subclass, the child method checks the container first for any cached (scoped) beans before it calls the parent method and creates a new instance.

    1.12.5. Composing Java-based Configurations

    Using the @Import Annotation
    跟xml中的import用法相同,用于导入其他配置类。

    @Configuration
    public class ConfigA {
    
        @Bean
        public A a() {
            return new A();
        }
    }
    
    @Configuration
    @Import(ConfigA.class)
    public class ConfigB {
    
        @Bean
        public B b() {
            return new B();
        }
    }
    

    这种方式让我们可以将bean的配置分散到不同的配置类中,使用时用import导入配置类。
    Injecting Dependencies on Imported @Bean Definitions
    不同的配置类中bean存在依赖关系,如下配置即可:

    @Configuration
    public class ServiceConfig {
    
        @Bean
        public TransferService transferService(AccountRepository accountRepository) {
            return new TransferServiceImpl(accountRepository);
        }
    }
    
    @Configuration
    public class RepositoryConfig {
    
        @Bean
        public AccountRepository accountRepository(DataSource dataSource) {
            return new JdbcAccountRepository(dataSource);
        }
    }
    
    @Configuration
    @Import({ServiceConfig.class, RepositoryConfig.class})
    public class SystemTestConfig {
    
        @Bean
        public DataSource dataSource() {
            // return new DataSource
        }
    }
    
    public static void main(String[] args) {
        ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class);
        // everything wires up across configuration classes...
        TransferService transferService = ctx.getBean(TransferService.class);
        transferService.transfer(100.00, "A123", "C456");
    }
    

    下面使用@Autowired方式注入,与上面代码的效果相同:

    @Configuration
    public class ServiceConfig {
    
        @Autowired
        private AccountRepository accountRepository;
    
        @Bean
        public TransferService transferService() {
            return new TransferServiceImpl(accountRepository);
        }
    }
    
    @Configuration
    public class RepositoryConfig {
    
        private final DataSource dataSource;
    
        @Autowired
        public RepositoryConfig(DataSource dataSource) {
            this.dataSource = dataSource;
        }
    
        @Bean
        public AccountRepository accountRepository() {
            return new JdbcAccountRepository(dataSource);
        }
    }
    
    @Configuration
    @Import({ServiceConfig.class, RepositoryConfig.class})
    public class SystemTestConfig {
    
        @Bean
        public DataSource dataSource() {
            // return new DataSource
        }
    }
    
    public static void main(String[] args) {
        ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class);
        // everything wires up across configuration classes...
        TransferService transferService = ctx.getBean(TransferService.class);
        transferService.transfer(100.00, "A123", "C456");
    }
    

    Fully-qualifying imported beans for ease of navigation
    之前的定义方式是隐式注入的,比如:

    @Configuration
    public class ServiceConfig {
    
        @Autowired
        private AccountRepository accountRepository;
    
        @Bean
        public TransferService transferService() {
            return new TransferServiceImpl(accountRepository);
        }
    }
    

    用户并不能从上面的ServiceConfig类中明确知道AccountRepository到底是怎么注入的,AccountRepository 的实现具体是哪个类。如果有这样的需求,可以通过如下方式实现:

    @Configuration
    public class ServiceConfig {
    
        @Autowired
        private RepositoryConfig repositoryConfig;
    
        @Bean
        public TransferService transferService() {
            return new TransferServiceImpl(repositoryConfig.accountRepository());
        }
    }
    
    @Configuration
    public interface RepositoryConfig {
    
        @Bean
        AccountRepository accountRepository();
    }
    
    @Configuration
    public class DefaultRepositoryConfig implements RepositoryConfig {
    
        @Bean
        public AccountRepository accountRepository() {
            return new JdbcAccountRepository(...);
        }
    }
    
    @Configuration
    @Import({ServiceConfig.class, DefaultRepositoryConfig.class})  // import the concrete config!
    public class SystemTestConfig {
    
        @Bean
        public DataSource dataSource() {
            // return DataSource
        }
    
    }
    
    public static void main(String[] args) {
        ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class);
        TransferService transferService = ctx.getBean(TransferService.class);
        transferService.transfer(100.00, "A123", "C456");
    }
    

    Conditionally Include @Configuration Classes or @Bean Methods
    可以通过实现org.springframework.context.annotation.Condition接口,过滤出符合条件的@Configuration注解的类或者@Bean注解的方法,以供Spring Container使用,符合条件返回true,不符合条件返回false:

    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        if (context.getEnvironment() != null) {
            // Read the @Profile annotation attributes
            MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());
            if (attrs != null) {
                for (Object value : attrs.get("value")) {
                    if (context.getEnvironment().acceptsProfiles(((String[]) value))) {
                        return true;
                    }
                }
                return false;
            }
        }
        return true;
    }
    

    注解@Conditional:

    @Target({ElementType.TYPE, ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface Conditional {
        Class<? extends Condition>[] value();
    }
    

    通过将该注解注释到配置类,并指定Condition的实现类,就可以实现过滤功能。
    Combining Java and XML Configuration
    有两种方式来混合使用java和xml配置方式。
    XML-centric Use of @Configuration Classes
    以xml为中心的使用方式。即初始化的容器类型为:ClassPathXmlApplicationContext。在xml中指定自动扫描的包即可。
    @Configuration Class-centric Use of XML with @ImportResource
    以@Configuration类为中心的配置方式。即初始化的容器类型为:AnnotationConfigApplicationContext。可以使用@ImportResource导入xml的配置:

    @Configuration
    @ImportResource("classpath:/com/acme/properties-config.xml")
    public class AppConfig {
    
        @Value("${jdbc.url}")
        private String url;
    
        @Value("${jdbc.username}")
        private String username;
    
        @Value("${jdbc.password}")
        private String password;
    
        @Bean
        public DataSource dataSource() {
            return new DriverManagerDataSource(url, username, password);
        }
    }
    

    以上混合使用方式主要是针对某些场景使用某种方式更擅长的情况时,可以混合搭配使用。个人倾向于要么使用注解方式,要么使用xml方式,减少混用。

    总结,本章详细介绍了基于java注解的配置方式。

    相关文章

      网友评论

        本文标题:The IoC Container 1.12

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