2011-02-23

Spring IoC Container 3.0.5 之四 - JavaConfig

  • 兩大要角:@Configuration 和 @Bean,主要用來產生、設定、初始化 Spring bean。
  • 簡單範例:
    • JavaConfig 設定:
      @Configuration
      public class AppConfig {
        @Bean
        public MyService myService() {
            return new MyServiceImpl();
        }
      }
    • 等同於下面的 XML 設定:
      <beans>
        <bean id="myService" class="com.acme.services.MyServiceImpl"/>
      </beans>
  • 為了使用 JavaConfig,必須使用 Spring 3.0 新生的 AnnotationConfigApplicationContext,除了支援 JavaConfig,也支援 JSR-330 的 annotation。
  •  不像 annotation 還會用到一些 XML,JavaConfig 完全免除 XML
    ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
    // 也可以慢慢註冊
    AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
    ctx.register(AppConfig.class, OtherConfig.class);
    ctx.register(AdditionalConfig.class);
    ctx.refresh();
    // 也可以用掃描的
    AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
    ctx.scan("idv.neil.java119");
    ctx.refresh();
  • 當然一定會有 web 版本:
    <web-app>
      <context-param>
          <param-name>contextClass</param-name>
          <param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext
          </param-value>
      </context-param>
      <!-- 可用逗號或空白指定多個 package -->
      <context-param>
          <param-name>contextConfigLocation</param-name>
          <param-value>com.acme.AppConfig</param-value>
      </context-param>
      <listener>
          <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
      </listener>
      <servlet>
          <servlet-name>dispatcher</servlet-name>
          <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
          <init-param>
              <param-name>contextClass</param-name>
              <param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext
              </param-value>
          </init-param>
          <!-- 可用逗號或空白指定多個 package -->
          <init-param>
              <param-name>contextConfigLocation</param-name>
              <param-value>com.acme.web.MvcConfig</param-value>
          </init-param>
      </servlet>
      <servlet-mapping>
          <servlet-name>dispatcher</servlet-name>
          <url-pattern>/main/*</url-pattern>
      </servlet-mapping>
    </web-app>
  • @Import
    • 使用 @Import 之後,就只要註冊 ConfigB 就可以了。
      @Configuration
      @Import(ConfigA.class)
      public class ConfigB {
        public @Bean B b() { return new B(); }
      } 
  • 同一個 Java class 的 inject:
    @Configuration
    public class AppConfig {
      @Bean
      public Foo foo() {
          return new Foo(bar()); // 直接呼叫 method 就可以了
      }
      @Bean
      public Bar bar() {
          return new Bar();
      }
    }
  • 不同 Java class 的 inject,使用@Autowired:
    @Configuration
    public class ServiceConfig {
      private @Autowired AccountRepository accountRepository;
      public @Bean TransferService transferService() {
          return new TransferServiceImpl(accountRepository);
      }
    }
    @Configuration
    public class RepositoryConfig {
      private @Autowired DataSource dataSource;
      public @Bean AccountRepository accountRepository() {
          return new JdbcAccountRepository(dataSource);
      }
    }
    @Configuration
    @Import({ServiceConfig.class, RepositoryConfig.class})
    public class SystemTestConfig {
      public @Bean DataSource dataSource() { /* return new DataSource */ }
    }
    public static void main(String[] args) {
      ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class);
      // ...
    }
  • JavaConfig 還是沒辦法完全取代 XML,這時可以用 @ImportResource 載入 XML 檔。
  • Lifecycle callbacks
    • Foo 裡要有 init 這個 method,Bar 裡要有 cleanup 這個 method。
      @Configuration
      public class AppConfig {
        @Bean(initMethod = "init")
        public Foo foo() {
            return new Foo();
        }
        @Bean(destroyMethod = "cleanup")
        public Bar bar() {
            return new Bar();
        }
      }
    • 也可以直接呼叫,使用 JavaConfig 的好處是可以使用 java 的方式做任何事情,而不用依靠 Spring container。
      @Configuration
      public class AppConfig {
        @Bean
        public Foo foo() {
            Foo foo = new Foo();
            foo.init();
            return foo;
        }
      }
  • @Scope
    • 預設是 singleton。
      @Configuration
      public class MyConfiguration {
        @Bean
        @Scope("prototype")
        public Encryptor encryptor() {
            // ...
        }
      }
    • Scope proxy
      @Bean
      @Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS)
      public UserPreferences userPreferences() {
       return new UserPreferences();
      }
  • Lookup method inject
    @Bean
    public CommandManager commandManager() {
      // return new anonymous implementation of CommandManager with command() overridden
      // to return a new prototype Command object
      return new CommandManager() {
          protected Command createCommand() {
              return asyncCommand();
          }
      }
    }
  • 自訂 bean id
    • 使用 @Bean 預設的 bean id 為 method name。
    • 可以使用 @Bean 的 name 屬性自訂。
    • @Bean 的 name 屬性也可以輸入字串陣列,用來設定多個 bean name。
      @Bean(name = { "dataSource", "subsystemA-dataSource", "subsystemB-dataSource" })
      public DataSource dataSource() {
         // instantiate, configure and return DataSource bean...
      }
  • 神奇的 singleton 效果
    • 以下的 code 會產生幾個 clientDao instance?
      @Configuration
      public class AppConfig {
        @Bean
        public ClientService clientService1() {
          ClientServiceImpl clientService = new ClientServiceImpl();
          clientService.setClientDao(clientDao());
          return clientService;
        }
        @Bean
        public ClientService clientService2() {
          ClientServiceImpl clientService = new ClientServiceImpl();
          clientService.setClientDao(clientDao());
          return clientService;
        }
        @Bean
        public ClientDao clientDao() {
          return new ClientDaoImpl();
        }
      }
    • 依照 java 的觀點,會有兩個 instance,但是從 Spring 的角度,因為是用 singleton,只能有一個。
    • 所以 Spring 用 CGLIB 去 subclass 這個 AppConfig,然後在每次呼叫 @Bean method 時都會去 container 裡檢查是否已經有 instance 存在。
      • 必須有 CGLIB lib。
      • AppConfig 不可以是 final。
      • AppConfig 要有 no-args 的 constructor。

沒有留言:

張貼留言