Spring条件初始化资源

概述

在项目中会有依据一定条件来初始化Bean的需求,特别是在分Module的代码中,底层Module实现了对各种资源的封装,比如DB,Thrift,MQ,Http,调度任务等,但是上层的需求多种多样,比如有的Module只需要使用Thrift,但由于其依赖的底层Module封装了所有的资源,导致在上层Module运行时,除了Thrift,实际还初始化了DB,MQ,Http等资源,这不仅是种不必要的浪费,有时还可能带来问题。因此,本文将主要探讨如何能依据一定的条件,按需初始化Bean,以避免不必要的浪费。

说明

范例项目通过 Main
函数启动项目,项目结构参考,其中 Db.java,Http.java,Kafka.java
代表三种不同的资源,它们在 init
时会打印日志:

.
├── pom.xml
└── src
    ├── main
    │   ├── java
    │   │   └── com
    │   │       └── example
    │   │           └── foo
    │   │               ├── Boot.java
    │   │               └── beans
    │   │                   ├── Db.java
    │   │                   ├── Http.java
    │   │                   └── Kafka.java
    │   └── resources
    │       ├── applicationContext.xml

applicationContext.xml
文件内容为:

<?xml version="1.0" encoding="UTF-8"?>
<!-- Spring基础配置 -->
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans.xsd


http://www.springframework.org/schema/context


http://www.springframework.org/schema/context/spring-context.xsd


http://www.springframework.org/schema/aop


http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- 类增强代理(而不是基于java的接口代理) -->
    <aop:aspectj-autoproxy proxy-target-class="true"/>

</beans>

启动类 Boot.java
内容为:

package com.example.foo;

import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * 启动类
 *
 * @author huanhuang
 */
public class Boot {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext context =
                new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
        context.start();
    }
}

资源代码以 Db.java
为例:

public class Db {

    public void init() {
        System.out.println("Db has bean initialized");
    }
}

方法一:Spring Profile

Profile的概念其实很常见,比如Maven也有,大家一般用Maven的Profile来管理不同环境的配置文件。但我自己之前有一个误区,以为Spring的Profile只能选者一种特定的Profile,但实际上它是支持同时指定多种Profile的,这就带来了很大的灵活性,下面介绍如何基于Spring Profile来条件初始化资源,先列下项目结构,主要多了一个配置文件 enableProfiles.properties

├── pom.xml
└── src
    ├── main
    │   ├── java
    │   │   └── com
    │   │       └── example
    │   │           └── foo
    │   │               ├── Boot.java
    │   │               └── beans
    │   │                   ├── Db.java
    │   │                   ├── Http.java
    │   │                   └── Kafka.java
    │   └── resources
    │       ├── applicationContext.xml
    │       └── enableProfiles.properties

Profile配置

applicationContext.xml
文件中为不同的资源指定了Profile:

<?xml version="1.0" encoding="UTF-8"?>
<!-- Spring基础配置 -->
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans.xsd


http://www.springframework.org/schema/aop


http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- 类增强代理(而不是基于java的接口代理) -->
    <aop:aspectj-autoproxy proxy-target-class="true"/>

    <beans profile="enableDb">
        <bean class="com.example.foo.beans.Db" init-method="init"/>
    </beans>

    <beans profile="enableHttp">
        <bean class="com.example.foo.beans.Http" init-method="init"/>
    </beans>

    <beans profile="enableKafka">
        <bean class="com.example.foo.beans.Kafka" init-method="init"/>
    </beans>

</beans>

启用特定的Profile

这里我们把启用的profile放在了一个 enableProfiles.properties
文件里,内容为:

enableProfiles=enableDb,enableKafka

表明我们只启用 Db
Kafka
资源,这里要注意的一点是,从结果来看,需要在Spring初始化之前,设置选用好Profile, Boot.java
代码如下:

/**
 * 启动类
 *
 * @author huanhuang
 */
public class Boot {
    public static void main(String[] args) {
        try {
            Properties properties = PropertiesLoaderUtils
            .loadAllProperties("enableProfiles.properties");
            String enableProfiles = properties.getProperty("enableProfiles");
            System.out.println(enableProfiles);
            System.setProperty(
            AbstractEnvironment.ACTIVE_PROFILES_PROPERTY_NAME, enableProfiles);
        } catch (IOException e) {
            e.printStackTrace();
        }
        ClassPathXmlApplicationContext context =
                new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
        context.start();
    }
}

这段代码里我们首先手动读取了 enableProfiles.properties
文件,然后最关键的代码在于下面一行:

System.setProperty(AbstractEnvironment.ACTIVE_PROFILES_PROPERTY_NAME, enableProfiles);

这个设定了一个环境变量,实际对应是的是 spring.profiles.active
,最后运行结果如下:

Db has bean initialized
Kafka has bean initialized

符合预期,只有 Db
Kafka
被启用了,这种方式简单明了。

方法二:Spring Conditional

Spring的 @Conditional
注解也就是依据一定的条件去初始化Bean,实际上Spring 4之后, @Profile
注解就是通过 @Conditional
来实现的,基于 @Conditional
注解,实际上想怎么初始化资源都可以,Profile实际知识它的一个Feature。这个工具非常强大,唯一的问题是貌似不支持XML。下面是使用这种方式的代码结构:

├── pom.xml
├── src
│   ├── main
│   │   ├── java
│   │   │   └── com
│   │   │       └── example
│   │   │           └── foo
│   │   │               ├── Boot.java
│   │   │               ├── beans
│   │   │               │   ├── Db.java
│   │   │               │   ├── Http.java
│   │   │               │   └── Kafka.java
│   │   │               └── config
│   │   │                   ├── Config.java
│   │   │                   ├── DbCondition.java
│   │   │                   ├── HttpCondition.java
│   │   │                   └── KafkaCondition.java
│   │   └── resources
│   │       └── applicationContext.xml

因为 @Conditional
是注解,所以Bean的初始化也就通过 @Configuration
来实现了,需要配置 applicationContext.xml
如下:

<?xml version="1.0" encoding="UTF-8"?>
<!-- Spring基础配置 -->
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans.xsd


http://www.springframework.org/schema/aop


http://www.springframework.org/schema/aop/spring-aop.xsd


http://www.springframework.org/schema/context


http://www.springframework.org/schema/context/spring-context.xsd">

    <!-- 类增强代理(而不是基于java的接口代理) -->
    <aop:aspectj-autoproxy proxy-target-class="true"/>

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

Bean的初始化在 Config.java
中:

@Configuration
public class Config {

    @Bean(initMethod = "init")
    @Conditional(DbCondition.class)
    public Db db() {
        return new Db();
    }

    @Bean(initMethod = "init")
    @Conditional(HttpCondition.class)
    public Http http() {
        return new Http();
    }

    @Bean(initMethod = "init")
    @Conditional(KafkaCondition.class)
    public Kafka kafka() {
        return new Kafka();
    }
}

可以看到,我们可以给不同的资源使用不同的Condition,非常强大,当然自己得多写点代码,以 DbCondition.class
为例:

/**
 * 启用Db的条件
 *
 * @author huanhuang
 */
public class DbCondition implements Condition {

    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        return false;
    }
}

需要继承 Condition
接口,然后这个方法里怎么匹配,自己想怎么写都可以,这里我只有 HttpCondition.class
的方法返回 true
,其它都是 false
,运行结果如下:

Http has bean initialized

总结

Profile实际是Conditional的一个Feature,如果没有更复杂的需求,Profile就够用了,对于业务代码来讲Profile更容易理解,Conditional更适合于框架的场景。

原文 

http://sadwxqezc.github.io/HuangHuanBlog/framework/2018/08/05/Spring条件初始化Bean.html

本站部分文章源于互联网,本着传播知识、有益学习和研究的目的进行的转载,为网友免费提供。如有著作权人或出版方提出异议,本站将立即删除。如果您对文章转载有任何疑问请告之我们,以便我们及时纠正。

PS:推荐一个微信公众号: askHarries 或者qq群:474807195,里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多

转载请注明原文出处:Harries Blog™ » Spring条件初始化资源

赞 (0)
分享到:更多 ()

评论 0

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址