码农小汪-spring框架学习之9-基于 Java 的配置元数据 @ImportResource

基于 Java 的配置元数据

Spring 新功能 Java-cofiguration 支持@Configuration 类注解和@Bean 方法注解@Bean 注解用于表明一个方法将会实例化、配置、初始化一个新对象,该对象由Spring IoC 容器管理。大家都熟悉 Spring 的< beans/>XML 配置, @Bean 注解方
法和它一样。可以在任何 Spring @Component 中使用@Bean 注解方法,当然了,大多数情况下,@Bean 是配合@Configuration 使用的。@Configuration 注解的类表明该类的主要目的是作为 bean 定义的源。此外,@Configuration 类允许依赖本类中使用@Bean 定义的 bean。

@Configuration
public class AppConfig {

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

}

The AppConfig class above would be equivalent to the following Spring < beans/> XML:
我觉得直接使用XML挺好的,配合注解使用

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

使用 AnnotationConfigApplicationContext 实例化 Spring IoC 容器

Spring 的 AnnotationConfigApplicationContext 部分,是 Spring3.0 中新增的。这是一个强大的(译注原文中是多才多艺的 versatile)ApplicationContext 实现,不仅能解析@Configuration 注解类,也能解析@Componnet 注解的类和使用 JSR-330
注解的类。使用@Configuration 注解的类作为配置元数据的时候, @Configuration 类本身也
会注册为一个 bean 定义,类内所有的@Bean 注解的方法也会注册为 bean 定义。使用@Component 和 JSR-330 注解类作为配置元数据时,他们本身被注册为bean 定义,并假设 DI(依赖注入)元数据,像类内使用的@Autowired 或者@Inject都是

简单结构

Spring 以 XML 作为配置元数据实例化一个 ClassPathXmlApplicationContext,以@Configuration 类作为配置元数据时, Spring 以差不多的方式,实例化一个AnnotationConfigApplicationContext。因此, Spring 容器可以实现零 XML 配
置。

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

AnnotationConfigApplicationContext 不是仅能与@Configuration 注解类配合使用。任何@Component 或者 JSR-330 注解的类都可以作为其构造函数的参数:

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

上述代码中,假设 MyServiceImpl,Dependency1 ,Dependency2 使用了 Spring 依赖注入注解, 比如@Autowired。
我们就得使用编程注入依赖~

使用 register(Class…)编程式构造 Spring 容器

AnnotationConfigApplicationContext 也可以通过无参构造函数实例化,然后调用 registor()方法配置。此法应用于编程式构

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

这些好麻烦的感觉~xml挺好的为什么要基于java配置呢?

开启组件扫描

@Configuration
@ComponentScan(basePackages = "com.acme")
public class AppConfig {
...
}

<beans>
    <context:component-scan base-package="com.acme"/>
</beans>

上面的栗子中,会扫描 com.acme package 包,检索出所有@Component-annotated类, Spring 容器将会注册这些类为 Spring bean 定义。
在上面的示例中,com.acme包将被扫描,寻找任何@Component带注释的类,这些类将注册为Spring bean 定义在容器内AnnotationConfigApplicationContext暴露了scan(String…)方法以允许相同的组件扫描功能:

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

Note:Remember that @Configuration classes are meta-annotated with @Component,(他的元注解是@Component) so they are candidates for component-scanning! In the example above, assuming that AppConfig is declared within the com.acme package (or any package underneath), it will be picked up during the call to scan(), and upon refresh() all its @Bean methods will be processed and registered as bean definitions within the container.

使用 AnnotationConfigWebApplicationContext 支持 WEB 应用

WebApplicationContext 接口一个实现 AnnotationConfigWebApplicationContext,是AnnotationConfigApplicationContext 的一个变体。在配置ContextLoaderListener、 Spring MVCDispatcherServlet 等等时,使用此实现类。下面这段 web.xml 片段,是典型 Spring MVC 的 Web 应用的配置。注意contextClass 类的 context-param 和 init-param。

<web-app>
    <!-- Configure ContextLoaderListener to use AnnotationConfigWebApplicationContext
        instead of the default XmlWebApplicationContext -->
    <context-param>
        <param-name>contextClass</param-name>
        <param-value>
            org.springframework.web.context.support.AnnotationConfigWebApplicationContext
        </param-value>
    </context-param>

    <!-- Configuration locations must consist of one or more comma- or space-delimited
        fully-qualified @Configuration classes. Fully-qualified packages may also be
        specified for component-scanning -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>com.acme.AppConfig</param-value>
    </context-param>

    <!-- Bootstrap the root application context as usual using ContextLoaderListener -->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <!-- Declare a Spring MVC DispatcherServlet as usual -->
    <servlet>
        <servlet-name>dispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!-- Configure DispatcherServlet to use AnnotationConfigWebApplicationContext
            instead of the default XmlWebApplicationContext -->
        <init-param>
            <param-name>contextClass</param-name>
            <param-value>
                org.springframework.web.context.support.AnnotationConfigWebApplicationContext
            </param-value>
        </init-param>
        <!-- Again, config locations must consist of one or more comma- or space-delimited
            and fully-qualified @Configuration classes -->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>com.acme.web.MvcConfig</param-value>
        </init-param>
    </servlet>

    <!-- map all requests for /app/* to the dispatcher servlet -->
    <servlet-mapping>
        <servlet-name>dispatcher</servlet-name>
        <url-pattern>/app/*</url-pattern>
    </servlet-mapping>
</web-app>

这些和我们在学习servlet的时候差不多吧~比如监听器,拦截器~进行处理!

使用@Bean 注解

@Bean 是方法注解,和 XML 中的元素十分相似。该注解支持的一些属性,比如 init-method, destroy-method,autowiring 和 name

声明Bean

要声明 bean 非常简单,只需要在方法上使用@Bean 注解。使用此方法,将会在ApplicationContext 内注册一个 bean, bean 的类型是方法的返回值类型

@Configuration
public class AppConfig {

    @Bean
    public TransferService transferService() {
        return new TransferServiceImpl();
    }

}

上面的配置和下面的 XML 配置等价:

<beans>
    <bean id="transferService" class="com.acme.TransferServiceImpl"/>
</beans>

上面两种配置,都会在 ApplicationContext 内产生一个 bean 定义,名称为transferService,该 Spring bean 绑定到一个类型为 TransferServiceImpl 的实例:
transferService -> com.acme.TransferServiceImpl

Receiving lifecycle callbacks 接受生命期的回调

使用@Bean 注解的 bean 定义,都支持常规生命周期回调,能使用 JSR-250 中的@PostConstruct 和@PreDestroy 注解
The regular Spring lifecycle callbacks are fully supported as well(常规的回调周期,使用编码的方式注入的也是支持的). If a bean implements InitializingBean, DisposableBean, or Lifecycle, their respective methods are called by the container.(这些都会被容器调用的哦)
The standard set of Aware interfaces such a**s BeanFactoryAware, BeanNameAware, MessageSourceAware, ApplicationContextAware*, and so on are also fully supported.

下面这个是我刚刚用MyExclipse查看的她可以使用的属性!相信大家都是知道这些到底是啥子意思吧!

@Bean(initMethod=”dd”,destroyMethod=”XX”,name=”ddd”,autowire=Autowire.BY_NAME)

public class Foo {
    public void init() {
        // initialization logic
    }
}

public class Bar {
    public void cleanup() {
        // destruction logic
    }
}

@Configuration
public class AppConfig {

    @Bean(initMethod = "init")
    public Foo foo() {
        return new Foo();
    }

    @Bean(destroyMethod = "cleanup")
    public Bar bar() {
        return new Bar();
    }

}

Of course, in the case of Foo above, it would be equally as valid to call the init() method directly during construction:
自己主动的去调用也是可以得!当你直接在Java中,你可以做任何你喜欢的,做你的对象 并不总是需要依靠容器生命周期!

@Configuration
public class AppConfig {
    @Bean
    public Foo foo() {
        Foo foo = new Foo();
        foo.init();
    return foo;
    }

    // ...

}

使用@Scope 注解

The default scope is singleton, but you can override this with the @Scope annotation:

@Configuration
public class MyConfiguration {

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

}

@Scope and scoped-proxy 两个作用域不一样的之间的依赖关系

作用域代理完成作用域bean 依赖。若使用 XML 配置,最简单的方式是使用元素创建一个代理。若是在 Java 代码中配置 bean,有一种等价的做法,使用@Scope注解并配置其 proxyMOde 属性.默认配置是没有代理 ScopedProxyMode.NO,但是你可以设置 ScopedProxyMode.TARGET_CLASS 或者 ScopedProxyMode.INTERFACES。 如
果将 XML 格式的作用域代理示例转换成 Java 中使用@Bean

// an HTTP Session-scoped bean exposed as a proxy
@Bean
@Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS)
public UserPreferences userPreferences() {
    return new UserPreferences();
}

@Bean
public Service userService() {
    UserService service = new SimpleUserService();
    // a reference to the proxied userPreferences bean
    service.setUserPreferences(userPreferences());
    return service;
}

默认情况下,配置类中,使用@Bean 的方法名作为返回 bean 的名字。通过配置可以覆盖此设置,使用 name 属性 即可。

@Configuration
public class AppConfig {

    @Bean(name = "myFoo")
    public Foo foo() {
        return new Foo();
    }

}

默认为foo

bean 别名在之前讨论过的 bean 别名“Naming beans”,有时候需要给一个bean 指定多个 name。 @Bean 注解的 name 属性就是干这个用,该属性接收一个字串数组。

Bean的描述,我们这个就可以使用到很多地方,File,Method,Type

挺有用的,查看使用的时候,非常的方便!

Configuration
public class AppConfig {

    @Bean
    @Desciption("Provides a basic example of a bean")
    public Foo foo() {
        return new Foo();
    }

}

Lookup method injection 之前我们提到过吧,使用XML方式处理

两个不同生命周期之间的依赖关系!
我们之前有两种不同的处理方式
每次,我们调用process方法的内部,自己去主动的调用新的createCommand()

// a class that uses a stateful Command-style class to perform some processing
package fiona.apple;

// Spring-API imports
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

public class CommandManager implements ApplicationContextAware {

    private ApplicationContext applicationContext;

    public Object process(Map commandState) {
        // grab a new instance of the appropriate Command
        Command command = createCommand();
        // set the state on the (hopefully brand new) Command instance
        command.setState(commandState);
        return command.execute();
    }

    protected Command createCommand() {
        // notice the Spring API dependency!
        return this.applicationContext.getBean("command", Command.class);
    }

    public void setApplicationContext(
            ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}

第二种方法:使用CGLIB的动态调用(我也不是很了解这个玩意)

和上面的思路是一样的,每次改变的时候我们自己动态的调用新的东西!

package fiona.apple;

// no more Spring imports!

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

<!-- a stateful bean deployed as a prototype (non-singleton) -->
<bean id="command" class="fiona.apple.AsyncCommand" scope="prototype">
    <!-- inject dependencies here as required -->
</bean>

<!-- commandProcessor uses statefulCommandHelper -->
<bean id="commandManager" class="fiona.apple.CommandManager">
    <lookup-method name="createCommand" bean="command"/>
</bean>

Using Java-configuration support , you can create a subclass of CommandManager where the abstract createCommand() method is overridden in such a way that it looks up a new (prototype) command object:

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 command() overridden
    // to return a new prototype Command object
    return new CommandManager() {
        protected Command createCommand() {
            return asyncCommand();
        }
    }
}

这里返回的对象其实是个无名的子类,然后调用子类的方法,去进行操作!

进一步基于java的配置是如何工作的内部信息

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

}

clientDao()被 clientService1()调用了一次,被 clientService2()调用了一次。因为这个方法会创建一个 ClientDaoImpl 类的实例并返回,也许你以为会有 2 个实例(分别返回给各个 service)。这个定义会有问题:在 Spring 中,实例化bean 默认的作用域是单例。这就是它的神奇之处:所有的@Configuration 类在启动时,都是通过 CGLIB 创建一个子类。在调用父类的方法并创建一个新的实例之前,子类中的方法首先检查是否缓存过。 仅仅会有一个实例!这里没有采用New而是调用的方法。被限制为单例

组装 java 配置元数据 Composing Java-based configurations

很多个java的配置文件,我们要把他们何在一起的塞!让后扫描的时候比较的方便,加入到容器中。这个和我们不同的XML文件的时候我们也需要把他们合并在一起一个意思的!
在 Spring XML 配置中使用< import/>元素,意在模块化配置, @Import 注解也允许从其他配置类中加载@Bean 定义。
例子:使用一个当主的,其他的加入/导入进去就行了。就是这么的简单!

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

}

现在,实例化 context 时,不需要同时指定 ConfigA.class 和 ConfigB.class,而是仅需要提供 ConfigB 即可:

public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigB.class);

    // now both beans A and B will be available...
    A a = ctx.getBean(A.class);
    B b = ctx.getBean(B.class);
}

该方式简化了容器实例化,只需要一个类去处理,而不是需要开发者在构造期间记着大量的@Configuration。能让每个开发者自己做自己的事情!

Injecting dependencies on imported @Bean definitions

在大部分实际场景中, bean 都会跨配置依赖。若使用 XML,这不是问题,因为不包含编译器,开发者简单的声明ref=somBean 并相信 Spring 在容器实例化期间会正常运行。但是,使用@Configuration 类,配置模型替换为 java 编译器,为了引用另一个 bean, Java编译器会校验该引用必须是有效的合法 Java 语法。非常幸运,解决这个这个问题非常简单。还记得不, @Configuration 类在容器中本身就是一个 bean,这意味着他们能使用高级@Autowired 注入元数据,就像其他 bean 一样。
构造的时候,依赖别的哦~

@Configuration
public class ServiceConfig {

    @Autowired
    private AccountRepository accountRepository;

    @Bean
    public TransferService transferService() {
        return new TransferServiceImpl(accountRepository);//构造函数的时候注入依赖关系!
    }

}

@Configuration
public class RepositoryConfig {

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

@Autowired 可以很好的工作,使设计更具模块化,但是自动注入的是哪个 bean 依然有些模糊不清。Spring Tool Suite 提供了可视化工具,用来展示 bean 之间是如何装配的,也许这就是你需要的。(这个可视化工具呢?)

混合 java 和 xml 配置

Spring 的@Configuration 类并非是为了完全替换 Spring XML。有些工具,比如XML 命名空间就是一种理想的配置方式。如果 XML 更方便或是必须的,你就得选择:或者选择基于 XML 的配置方式实例化容器,比如使用ClassPathXmlApplicationContext,或者选择基于 Java 配置风格使用AnnotationConfigApplcationContext 加上@ImportResource 注解导入必须的XML。

基于 XML 混合使用@Configuration 类

已存在大量的使用了 SPringXML 的代码,有需求需要使用@Configuration 类,这些配置类需要引入到现存的 XML 文件中,此种做法也许更容易。接下来看看此场景。
@Configuration 类本身在容器内就是一个 bean。下面的样例中,创建了一个@Configuration 类,类名是 AppConfig,引入一个配置文件 system-testconfig.xml。由于< context:annotation-config/>打开,容器会识别@Configuration 注解,并处理 AppConfig 类内声明的@Bean 注解的方法。

@Configuration
public class AppConfig {

    @Autowired
    private DataSource dataSource;

    @Bean
    public AccountRepository accountRepository() {
        return new JdbcAccountRepository(dataSource);
    }

    @Bean
    public TransferService transferService() {
        return new TransferService(accountRepository());
    }

}
system-test-config.xml
<beans>
    <!-- enable processing of annotations such as @Autowired and @Configuration -->
    <context:annotation-config/>
    <context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>

    <bean class="com.acme.AppConfig"/>//本身就是一个bean,识别里面的注解!加入到容器中去~

    <bean class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
</beans>
jdbc.properties
jdbc.url=jdbc:hsqldb:hsql://localhost/xdb
jdbc.username=sa
jdbc.password=
public static void main(String[] args) {
    ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:/com/acme/system-test-config.xml");
    TransferService transferService = ctx.getBean(TransferService.class);
    // ...
}


system.test-config.xml 中, AppConfig< bean/>没有 id 属性,因为没有其他bean 引用,也不会根据 name 从容器获取,所以 id 不是必须指定的,同样,DataSourcebean,它只会根据类型自动装配,所以明确的 id 也不是必须的。因为@Configuration 是@Component 的元数据注解,@Configuration 注解类也会自动作为扫描组件的候选者。

我们能重新定义 system-testconfig.xml,使之能启用高级扫描组件。注意,在此场景中,我们不需要明确的
声明< context:annotation-config/>,因为< context:component-scan/>会开启所有相同的功能。

system-test-config.xml
<beans>
    <!-- picks up and registers AppConfig as a bean definition --> 这种方式扫描组件也是可以的。自己的多注意这些之间的关系和区别!
    <context:component-scan base-package="com.acme"/>
    <context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>

    <bean class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
</beans>

基于@Configuration 混合使用 xml 配置 谁是谁的主导呢

在应用中, @Configuration 类是主要的容器配置机制,但是仍然可能会需要一些 XML。在这些场景中, 使用@ImportResource,即可引用 XML 配置。这样配置可是实现此效果,基于 java 配置,尽可能少的使用 XML。
XML的唯一问题是你要等到运行时的时候来发现Bean里面的错误或者其他愚蠢的问题。

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

}
properties-config.xml
<beans>
    <context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>
</beans>
public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
    TransferService transferService = ctx.getBean(TransferService.class);
    // ...
}

`

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值