【微思探索】【实战】灵活控制不同环境下Spring bean的加载

在平时我们使用Spring的过程中,往往会使用不同的配置来区分不同环境下Spring bean的加载。尤其是不同的系统集成在一个工程中时,也就是说代码上是一个大的工程,但是部署时是分开的,这时不同的系统间存在代码复用的可能性就很大,而且很多时候一个接口会有多个实现bean,发生冲突的可能性也不小,那如何来灵活控制Spring bean的加载呢?

一般有以下几种方式来控制Spring bean的加载:

1.使用Spring的profile来控制;

2.自定义标注来控制bean的加载。

很多人都知道怎么通过Spring的profile来控制是否加载某个bean,一般都是以下的方式:

@Profile("admin")
@Service
public class TestServiceImpl implements TestService {
}

然后在application.properties中我们会配置:

spring.profiles.include=admin

这样这个bean就会被加载。

那使用profile就能满足我们的一切场景了吗?no,如果全部通过profile来控制,有以下缺点:

1.当这样的bean很多时,你会苦恼于配哪些profile,会有一种无所适从的感觉,而且这种方式的配置往往跟业务无关,一段时间后你会发现你配置的profile自己都不知道是什么意思;

2.profile只能根据上述配置控制这个bean加载或者不加载,在某些场合我们甚至需要SPEL表达式来灵活控制bean的加载,它做不到。

所以我们需要自定义标注,以实现更加灵活的控制,例如:

@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
@Documented
@Conditional(RegisterIfCondition.class) //控制bean加载的具体逻辑
public @interface RegisterIf {
    String value();
} 

public class RegisterIfCondition implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        AnnotationAttributes attributes = AnnotationAttributes.fromMap(
            metadata.getAnnotationAttributes(RegisterIf.class.getName()));

        BeanFactoryContextExpressionResolver resolver = new BeanFactoryContextExpressionResolver();
        resolver.setBeanFactory(context.getBeanFactory());
        resolver.setEnvironment(context.getEnvironment());

        return resolver.resolveAsBoolean(attributes.getString("value"));
    }
}

这样你就可以以下面的方式来控制这个bean是否加载:

@RegisterIf("${TestServiceImpl.enabled:false}")
@Service
public class TestServiceImpl implements TestService {
}

然后在application.properties中加以下配置就能让它加载:

TestServiceImpl.enabled=true  #这样的配置就更加清晰,一看就知道是跟哪个业务相关的

笔者在实现后端服务化的过程中,在开发环境,我们都希望能跑在单体应用中来调试各种问题,单体应用跟服务化后的环境是完全不同的,服务化后一些接口间的调用往往都是RPC调用,然而单体应用调试时并不需要RPC调用。也就是说,接口有服务端版本和客户端版本两个实现bean,运行在本地开发模式时加载服务端版本,运行在服务化模式时加载客户端版本。代码如下:

@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
@Documented
@Conditional(ApiImportCondition.class)
public @interface ApiImport { //服务端版本
    String value();
}

@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
@Documented
@Conditional(ApiImportCondition.class)
public @interface ApiClientImport { //客户端版本
    String value();
}

public class ApiImportCondition implements Condition {

    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        String service, mode;

        AnnotationAttributes attributes = AnnotationAttributes.fromMap(
            metadata.getAnnotationAttributes(ApiClientImport.class.getName()));

        if (CollectionUtils.isEmpty(attributes)) {
            attributes = AnnotationAttributes.fromMap(metadata.getAnnotationAttributes(ApiImport.class.getName()));
            mode = "server";
        } else {
            mode = "client";
        }

        if (CollectionUtils.isEmpty(attributes)) {
            throw new RuntimeException("Invalid annotation");
        }

        service = attributes.getString("value");
        if (!StringUtils.hasLength(service)) {
            throw new RuntimeException("service name need specified");
        }

        BeanFactoryContextExpressionResolver resolver = new BeanFactoryContextExpressionResolver();
        resolver.setBeanFactory(context.getBeanFactory());
        resolver.setEnvironment(context.getEnvironment());

        String expr = String.format("#{'${%1s.run.mode:server}'.equalsIgnoreCase('%2s')}", service, mode); //默认运行在服务端模式,只有在运行在客户端模式时才需要此配置

        return resolver.resolveAsBoolean(expr);
    }
}

我们可以这么使用:

@ApiImport("test-service")
@Service
public class TestServiceImpl implements TestService {
    //服务端版本
}

@ApiClientImport("test-service")
@Service
public class TestApiServiceImpl implements TestService {
    //客户端版本
    //RPC调用
}

然后我们就可以在application.properties中控制是加载服务端的bean还是客户端的bean:

test-service.run.mode=client  #该test service将运行在服务化的模式

希望这篇文章对你有用,也希望能提出宝贵的意见,谢谢!

文章已收录在此

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值