Spring--基于Java配置的容器配置

Spring的非侵入性–基于Java的容器配置介绍

一、Spring框架的非侵入性

1.前言

在上一篇博文中讲到,Spring允许以非侵入方式使用注解,无需接触目标组件的源代码。 要想真正理解这句话,我们就有必要先弄清楚非侵入式设计的概念了。

2.侵入式与非侵入式

  • 侵入式:代码结构要与所使用的技术产生依赖。
  • 非侵入式:使用一个新的技术不会或者基本不改变原有代码结构,原有代码不作任何修改即可。

3.侵入式框架与非侵入式框架

  1. 侵入式框架:引入了框架,对现有的类的结构有影响,需要实现框架某些接口或者基础某些特定的类。

    • 优点:侵入式可以使得用户的代码与框架更好的结合,充分利用框架提供的功能。
    • 缺点:侵入式让用户的代码对框架产生了依赖,不利于代码的复用,当去除框架的时候,程序就无法运行。
    • 例子:Struts1框架, Struts1代码严重依赖于Struts1 API,属于侵入性框架。
  2. 非侵入式框架:引入了框架,对现有的类结构没有影响,不需要实现框架某些接口或者特定的类。

    • 优点:代码对框架没有过多的依赖,允许所开发出来的应用系统能够在不用的环境中自由移植,不需要修改应用系统中的核心功能实现的代码
    • 缺点:无法复用框架提供的代码和功能
    • 例子:Spring框架,通过配置完成依赖注入就可以使用,当我们想换个框架,只需要修改相应的配置,程序仍然可以运行。

二、在Java代码中使用注解来配置容器

1.基础概念:@Bean@Configuration

@Configuration修饰的类和由@Bean修饰的方法组成了Spring Java配置的主要构件。

@Bean注解用于方法实例化,配置和初始化要由Spring IoC容器管理的对象。 使用的时候,@Bean注解的方法,所在的类需要用@Configuration来修饰,表示该类是作为Bean定义的来源,容器初始化时,需要扫描该类,进行自动装配。

@Configuration注解的类,表示该类的主要目的是作为Bean定义的来源。 此外,@Configuration类允许通过调用同一类中的其他@Bean方法来定义Bean间的依赖关系。 最简单的@Configuration类的内容如下:

@Configuration
public class AppConfig {

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

2. @Component@Bean的区别

@Component和@Bean都可以定义bean,但实际上@Component@Bean是做两个完全不同的事情,不应该混为一谈,具体比较如下:

  • @Component(和@Service@Repository)用于自动检测和使用类路径扫描自动配置bean。 注释类和bean之间存在隐式的一对一映射(即每个类一个bean)。这种方法对需要进行逻辑处理的控制非常有限,因为它纯粹是声明性的。
  • @Bean用于显式声明单个bean,而不是让Spring像上面那样自动执行它。它将bean的声明与类定义分离,并允许您精确地创建和配置bean。

那我们在什么时候会使用@Bean?假设下,某天你想创建一个第三方的组件,但是你没有源代码,也就没办法使用@Component进行自动配置,这种时候使用@Bean就比较合适了。 又比如,具体采用哪个实现是依赖于某些参数的,在这种情况下@Bean无疑是更合适的选择。 示例如下:

@Bean
@Scope("prototype")
public SomeService someService(String state) {
    switch (state) {
    case 1:
        return new Impl1();
    case 2:
        return new Impl2();
    case 3:
        return new Impl3();
    default:
        return new Impl();
    }
}

3.使用AnnotationConfigApplicationContext实例化Spring容器

以下各节介绍了Spring 3.0中引入的AnnotationConfigApplicationContext。 这种通用的ApplicationContext实现,不仅能够接受@Configuration类作为输入,而且还可以接受普通的@Component类和带有JSR-330元数据注释的类。

  • 当提供@Configuration类作为输入时,@Configuration类本身将注册为Bean定义,并且该类中所有已声明的@Bean方法也将注册为Bean定义。
  • 提供@Component和JSR-330类时,它们将注册为bean定义,并且假定在必要时在这些类中使用了诸如@Autowired@Inject之类的DI元数据。

(1)作为AnnotationConfigApplicationContext构造函数的参数

@Configuration类用作输入,实例化AnnotationConfigApplicationContext,使得Spring容器的使用,完全不依赖XML。 如下面的示例所示:

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

前面还提到,同样可以将任何@Component或JSR-330带注释的类作为输入,提供给AnnotationConfigApplicationContext的构造函数进行实例化,如以下示例所示:

扫描二维码关注公众号,回复: 9395629 查看本文章
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

(2)使用register(Class<?>…)的编程方式构建容器

可以使用无参构造函数来实例化AnnotationConfigApplicationContext,然后使用register()方法对其进行配置。 以编程方式构建AnnotationConfigApplicationContext时,此方法特别有用。 以下示例展示了如何执行此操作:

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

(3)使用scan(String ...)启动组件扫描(常见)

要启用组件扫描,可以按如下方式使用@Configuration注解类:

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

在上面的示例中,com.acme包进行了扫描,以查找任何@Component注解的类,并将这些类注册为容器中的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);
}

另外需要注意,@Configuration类使用了@Component进行元注解( meta-annotated with @Component ),因此它们也是组件扫描的候选对象。 在前面的示例中,假定AppConfig在com.acme包(或下面的任何包)中声明,则在调用scan()时也会将其扫描。 根据refresh(),AppConfig的所有@Bean方法都将在容器中进行处理并注册为Bean。

4.使用@Bean注解

@Bean是方法级别(method-level )的的注解,是对XML配置的<bean/> 节点的直接模拟。@Bean注解同样支持<bean/> 提供的一些属性,如:init-methoddestroy-methodautowiringname可以在@Configuration注解的类或@Component注解的类中使用@Bean注释。

(1)声明一个bean

要声明bean,可以使用@Bean注解,对类方法进行注释,这样子Spring就会在以方法返回值为指定类型的ApplicationContext中定义一个bean。**默认情况下,bean名称与方法名称相同。**如以下示例:

@Configuration
public class AppConfig {

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

上面示例的配置等价于下面的XML配置

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

以上两种方式的声明都使一个名为transferService的bean在ApplicationContext中可用,并绑定到类型为TransferServiceImpl的对象实例,即transferService -> com.acme.TransferServiceImpl

还可以使用接口(或基类)作为返回类型来声明@Bean方法,如以下示例所示:

@Configuration
public class AppConfig {

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

mark

如果您通过声明的服务接口来一致地引用类型,则@Bean返回类型可以安全地加入该设计决策。 但是,对于实现多个接口的组件或存在其实现类型潜在引用的组件,声明尽可能具体的返回类型(至少与引用您的bean的注入点所要求的具体类型一样)更为安全。

(2)bean依赖

@Bean注解的方法可以具有任意数量的参数,这些参数描述构建该bean所需的依赖关系。 例如,如果我们的TransferService需要一个AccountRepository,则可以使用方法参数来实现该依赖关系,如以下示例所示:

@Configuration
public class AppConfig {

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

(3)接收生命周期回调

任何使用@Bean 注解定义的类都支持常规的生命周期回调,并且可以使用JSR-250中的@PostConstruct@PreDestroy注解。

常规Spring生命周期回调也得到完全支持。 如果bean实现了InitializingBeanDisposableBeanLifecycle 等接口,则容器将调用它们各自的方法。

还完全支持标准的* Aware接口集(例如BeanFactoryAwareBeanNameAwareMessageSourceAwareApplicationContextAware等)。

@Bean注解还支持指定任意的初始化和销毁的回调方法,类似于XML配置中bean元素的init-methoddestroy-method属性,如以下示例所示:

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

在bean的构造期间直接调用初始化方法也是可行的,不一定需要依赖容器的生命周期回调。 如以下示例所示:

@Configuration
public class AppConfig {

    @Bean
    public BeanOne beanOne() {
        BeanOne beanOne = new BeanOne();
        beanOne.init();
        return beanOne;
    }

    // ...
}

(4)指定Bean的作用域(Scope)

在用@Bean定义bean时,可以指定bean的作用域,标准的作用域有:singleton、prototype、request、session、application、websocket,其中request、session、application、websocket,这四个作用域只有在web应用中才能够被使用到。

Scope Description
singleton (默认) 将每个Spring IoC容器的单个bean定义的作用域限定为单个对象实例(单例模式)。
prototype 将单个bean定义的作用域限定为任意数量的对象实例。
request 将单个bean定义的作用域限定为单个HTTP请求(request)的生命周期内。 也就是说,每个HTTP请求都会有一个单独的bean。仅在web应用中能使用到。
session 将单个bean定义的作用域限定为HTTP会话(Session)的生命周期内。仅在web应用中能使用到。
application 将单个bean定义的作用域限定为ServletContext的生命周期内。仅在web应用中能使用到。
websocket 将单个bean定义的作用域限定为WebSocket的生命周期内。. 仅在web应用中能使用到。
  • 使用@Sopce注解

    bean默认的作用域是singleton,但是可以使用@Scope 注解进行自定义,以下示例,将bean的作用域指定为prototype:

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

(5)自定义bean名称

默认情况下,配置类使用@Bean方法的名称作为bean的名称。 但是可以使用name属性进行自定义,以下示例将bean名称指定为myThing,默认为thing:

@Configuration
public class AppConfig {

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

(6)bean别名

有时希望为单个Bean设置多个名称,为此,@Bean注解的name属性接受一个String数组。 以下示例显示了如何为bean设置多个别名:

@Configuration
public class AppConfig {

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

5.使用@Configuration注解

@Configuration是一个类级别(class-level )的注解,表示其对象是Bean定义的源。

(1)注入bean之间的依赖关系

当bean彼此依赖时,表示依赖关系就像让一个bean方法调用另一个一样简单,如以下示例所示:

@Configuration
public class AppConfig {

    @Bean
    public BeanOne beanOne() {
        return new BeanOne(beanTwo());
    }

    @Bean
    public BeanTwo beanTwo() {
        return new BeanTwo();
    }
}

在上面的示例中,beanOne通过构造函数注入接收到beanTwo的引用。

再次强调一遍:仅当在@Configuration类中声明@Bean方法时,此声明bean间依赖性的方法才有效。 您能使用普通的@Component类声明Bean间的依赖关系。

(2)查找 方法注入

不常用到,占个位置留着以后用到再补充。

(3)有关基于Java的配置内部中是如何工作的更多信息

不常用到,占个位置留着以后用到再补充。

6.组合基于java的配置

不常用到,占个位置留着以后用到再补充。

参考资料

[1]:Spring学习(1):侵入式与非侵入式,轻量级与重量级

[2]: Spring:@Component 对比 @Bean

[3]:SpringFrameworkReference

发布了15 篇原创文章 · 获赞 0 · 访问量 220

猜你喜欢

转载自blog.csdn.net/qq_40151840/article/details/104406143
今日推荐