分类 Java 下的文章

使用注解@Profile来标识加载的对象,配合@Component注解一起使用。
@Component类似的注解也可以一起使用,比如@Service@Controller@Repository@Configuration

如下,我们有两个HelloService的实现,一个是用于开发环境的,这个实现可以简单一点;另外一个是用于真正的生产环境的,要进行真正的处理,比如调用RPC,数据库处理等。

@Service
@Profile({"dev"})
public class HelloServiceDevImpl implements HelloService {
    // ...
}
@Service
@Profile({"pro"})
public class HelloServiceImpl implements HelloService {
    // ...
}

application.yml的配置,如果要使用HelloServiceDevImpl,要激活对应的profile,可以使用:

spring:
  profiles:
    active: dev

类似的正式环境的配置,可以使用:

spring:
  profiles:
    active: pro

如果对象依赖于多个profile,也可以传入多个:

@Service
@Profile({"pro", "A", "B"})
public class HelloServiceImpl implements HelloService {
    // ...
}

这样只有当pro、A、B者三个profile都激活的时候才会启用:

spring:
  profiles:
    active: pro,A,B

扩展开来,控制对象的加载的注解父类为@Conditional,使用该注解的话要自己编写一个类实现org.springframework.context.annotation.Condition来进行处理。
spring-boot提供了多个已经实现好的注解可以用,@Profile只是其中一个,其他类似的还有@ConditionalOnBean@ConditionalOnClass@ConditionalOnExpression@ConditionalOnJava@ConditionalOnProperty等。

在使用RPC过程中,经常会调用多个RPC服务来获取数据。

比如获取用户个人主页的时候,就要去用户服务获取用户的头像、昵称等基本信息,还要去访问服务获取用户最近访问的信息,还有去关系服务获取用户新增的粉丝或关注的主播的消息。

值得注意的是,这些服务之间并没有依赖,即并不需要先获取用户的头像才能获取用户的访问信息。

如果一个一个地调用这些RPC服务,那么消耗的总时间就是这些RPC调用耗时之和。

因为这些服务之间并没有依赖,所以可以并行地调用,那么消耗的总时间就是耗时最长的那个RPC调用的时间。特别是当调用的RPC服务众多时,并且调用可以大幅度减少时间,提高效率。

如何并行调用呢?

采用线程池和Future就可以,样例如下。
进行两个计算任务,每个需要耗时3秒,如果按照往常调用,那么总共需要6秒时间。但如果并行调用,只需3秒。

import java.time.LocalDateTime;
import java.util.concurrent.*;

public class FutureTest {

    public static void main(String[] args) throws ExecutionException, InterruptedException, TimeoutException {


        ExecutorService executorService =
                Executors.newFixedThreadPool(4);

        Callable<Integer> calculateA = new SumCalculateTask(3, 4);
        Callable<Integer> calculateB = new SumCalculateTask(4, 6);

        System.out.println("START AT " + LocalDateTime.now());
        Future<Integer> futureA = executorService.submit(calculateA);
        Future<Integer> futureB = executorService.submit(calculateB);

        Integer resultA = futureA.get(4, TimeUnit.SECONDS);
        System.out.println(resultA);
        Integer resultB = futureB.get(4, TimeUnit.SECONDS);
        System.out.println(resultB);

        System.out.println("END AT " + LocalDateTime.now());
        executorService.shutdown();
    }


    static class SumCalculateTask implements Callable<Integer> {

        private Integer a;
        private Integer b;

        public SumCalculateTask(Integer a, Integer b) {
            this.a = a;
            this.b = b;
        }

        @Override
        public Integer call() throws Exception {
            Thread.sleep(3000);
            return a + b;
        }
    }
}

Github源码示例:https://github.com/chengjf/java-snippet/blob/master/src/main/java/com/chengjf/future/ConcurrentCalculatorTest.java

Java8中推出了Optional这个class,用来做null check,我在实际工作中也经常使用,确实方便很多,不过也遇到了不少坑,现在写出来供大家参考。

0x01 这些情况不要使用Optional

  1. 集合类不要包装为Optional。集合类永远必须返回集合,不允许返回null值,即使如果没有数据,也要返回空集合。返回空集合避免了NPE,特别是使用stream接口处理起来非常方便。
  2. POJO的属性不要使用Optional。Optional不要作为class的属性,如果要对空属性进行处理,总是应该在对应属性的getter方法上做。同时由于Optional没有实现Serializable接口,所以这些类在序列化的时候也非常麻烦。
  3. RPC服务的方法返回值和参数不要使用Optional。正如第二点所说,Optional不能正常序列化,所以使用RPC服务时,如果Optional作为参数或者返回值进行调用的时候,序列化失败就会报错。(但是如果方法返回值不能返回Optional对象,而返回null的话又失去了Optional存在的意义。SO上也有Optional为啥不实现Serializable接口的讨论,地址在此stackoverflow

- 阅读剩余部分 -

序列化和反序列化是RPC中非常重要的部分了,也是RPC中必不可少的一部分。

  • 序列化-即将调用的对象转换成可传输的字节序列
  • 反序列化-即将接收到的字节序列转换成对象

当我们使用RPC调用一个方法时,先将我们调用的方法和参数进行序列化,将序列化后的字节序列传输到RPC服务提供者。
当RPC服务提供者收到请求后,进行反序列化将收到的字节序列转换成具体的方法和参数,然后再去执行对应的方法。接着将执行后的结果进行序列化,再传输给调用方。
调用方收到响应的字节序列后,将数据进行反序列化,就得到了调用结果对象。

一次普通的RPC调用,至少要经过上面的两次序列后和两次反序列化。

序列化和反序列化需要一中序列化规范,即序列化前的对象和序列化后的字节之间的一种对应关系。常见的序列化,如json,hessian2,protobuf等。Java中,最基本的就是java自带的Serializable接口了。

由于json是一种可读的文本协议,所以常用于HTTP中。而hessian2和protobuf都是一个二进制协议,序列化后字节序列更小,所以传输速度快,因此这两种协议常用于RPC中序列化和反序列化。



- 阅读剩余部分 -