大数据技术之_31_Java 面试题_01_JavaSE 面试题 + SSM 面试题 + Java 高级面试题 + Java 项目面试题

2019-06-19 10:39:32 浏览数 (1)

第1章 JavaSE 面试题

1、自增变量

代码语言:javascript复制
    public static void main(String[] args) {
        int i = 1;
        i = i  ; // i=1
        int j = i  ; // j=1  i=2
        int k = i     i * i  ; // 2   3 * 3 = 11
        System.out.println("i="   i); // i=4
        System.out.println("j="   j); // j=1
        System.out.println("k="   k); // 11
    }

(1)

(2)

(3)

小总结

2、编程题:写一个 Singleton 示例

  • Singleton:在 Java 中即指单例设计模式,它是软件开发中最常用的设计模式之一。
  • 单:唯一
  • 例:实例
  • 单例设计模式,即某个类在整个系统中只能有一个实例对象可被获取和使用的代码模式。
  • 例如:代表 JVM 运行环境的 Runtime 类。

要点: 1、某个类只能有一个实例;   构造器私有化 2、它必须自行创建这个实例;   含有一个该类的静态变量来保存这个唯一的实例 3、它必须自行向整个系统提供这个实例;   对外提供获取该实例对象的方式:   (1)直接暴露   (2)用静态变量的get方法获取

几种常见形式: 饿汉式:在类初始化时直接创建对象,不存在线程安全问题   直接实例化饿汉式(简洁直观)   枚举式(最简洁)   静态代码块饿汉式(适合复杂实例化) 示例代码如下:

代码语言:javascript复制
package com.atguigu.singleton;

/*
 * 饿汉式:
 *     在类初始化时直接创建实例对象,不管你是否需要这个对象都会创建
 * 
 * (1)构造器私有化
 * (2)自行创建,并且用静态变量保存
 * (3)向外提供这个实例
 * (4)强调这是一个单例,我们可以用 final 修饰
 */
public class Singleton1 {
    public static final Singleton1 INSTANCE = new Singleton1();

    private Singleton1() {
    }
}

示例代码如下:

代码语言:javascript复制
package com.atguigu.singleton;

/*
 * 枚举类型:表示该类型的对象是有限的几个
 * 我们可以限定为一个,就成了单例
 */
public enum Singleton2 {
    INSTANCE
}

示例代码如下:

代码语言:javascript复制
package com.atguigu.singleton;

import java.io.IOException;
import java.util.Properties;

public class Singleton3 {
    public static final Singleton3 INSTANCE;

    private String info;

    static {
        try {
            Properties properties = new Properties();
            properties.load(Singleton3.class.getClassLoader().getResourceAsStream("single.properties"));

            INSTANCE = new Singleton3(properties.getProperty("info"));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    private Singleton3(String info) {
        this.info = info;
    }

    public String getInfo() {
        return info;
    }

    public void setInfo(String info) {
        this.info = info;
    }

    @Override
    public String toString() {
        return "Singleton3 [info="   info   "]";
    }
}

懒汉式:延迟创建对象   线程不安全(适用于单线程)   线程安全(适用于多线程)   静态内部类形式(适用于多线程) 示例代码如下:

代码语言:javascript复制
package com.atguigu.singleton;

/*
 * 懒汉式:
 *     延迟创建这个实例对象
 *  线程不安全(适用于单线程)
 * 
 * (1)构造器私有化
 * (2)用一个静态变量保存这个唯一的实例
 * (3)提供一个静态方法,获取这个实例对象
 */
public class Singleton4 {
    private static Singleton4 instance;

    private Singleton4() {
    }

    public static Singleton4 getInstance() {
        if (instance == null) {
            instance = new Singleton4();
        }
        return instance;
    }
}

示例代码如下:

代码语言:javascript复制
package com.atguigu.singleton;

/*
 * 懒汉式:
 *     延迟创建这个实例对象
 *  线程安全(适用于多线程)
 * 
 * (1)构造器私有化
 * (2)用一个静态变量保存这个唯一的实例
 * (3)提供一个静态方法,获取这个实例对象
 */
public class Singleton5 {
    private static Singleton5 instance;

    private Singleton5() {
    }

    public static Singleton5 getInstance() {
        if (instance == null) { // 优化性能
            synchronized (Singleton5.class) { // 线程安全
                if (instance == null) {
                    instance = new Singleton5();
                }
            }
        }
        return instance;
    }
}

示例代码如下:

代码语言:javascript复制
package com.atguigu.singleton;

/* 懒汉式:
 *     延迟创建这个实例对象
 *     静态内部类形式(适用于多线程)
 * 
 *     在内部类被加载和初始化时,才创建 INSTANCE 实例对象
 *     静态内部类不会自动随着外部类的加载和初始化而初始化,它是要单独去加载和初始化的。(是最懒的)
 *     因为是在内部类加载和初始化时,创建的,因此是线程安全的
 */
public class Singleton6 {
    private Singleton6() {
    }

    private static class Inner {
        private static final Singleton6 INSTANCE = new Singleton6();
    }

    public static Singleton6 getInstance() {
        return Inner.INSTANCE;
    }
}

3、类初始化和实例初始化

考点   类初始化过程   实例初始化过程   方法的重写

类初始化过程

代码语言:javascript复制
一个类要创建实例需要先加载并初始化该类
    main 方法所在的类需要先加载和初始化

一个子类要初始化需要先初始化父类

一个类初始化就是执行 <clinit>() 方法
    <clinit>() 方法由静态类变量显示赋值代码和静态代码块组成
    类变量显示赋值代码 和 静态代码块代码 从上到下顺序执行
    <clinit>() 方法只执行一次

实例初始化过程

代码语言:javascript复制
实例初始化就是执行 <init>() 方法
    <init>() 方法可能重载有多个,有几个构造器就有几个 <init> 方法
    <init>() 方法由非静态实例变量显示赋值代码和非静态代码块、对应构造器代码组成

    非静态实例变量显示赋值代码和非静态代码块代码从上到下顺序执行,而对应构造器的代码最后执行

    每次创建实例对象,调用对应构造器,执行的就是对应的 <init> 方法

    <init> 方法的首行是 super() 或 super(实参列表),即对应父类的 <init> 方法

方法的重写

代码语言:javascript复制
哪些方法不可以被重写
    final 方法
    静态方法
    private 等子类中不可见方法

对象的多态性
    子类如果重写了父类的方法,通过子类对象调用的一定是子类重写过的代码
    非静态方法默认的调用对象是 this
    this 对象在构造器或者说 <init> 方法中就是正在创建的对象

Override 和 Overload 的区别?

代码语言:javascript复制
Override 重写的要求?
    方法名
    形参列表
    返回值类型
    抛出的异常列表
    修饰符

了解《JVM 虚拟机规范》中关于 <clinit> 和 <init> 方法的说明、invokespecial 指令

示例代码如下:

代码语言:javascript复制
package com.atguigu.init;

/*
 * 父类的初始化 <clinit>:
 * (1)父类的静态方法 j = method();
 * (2)父类的静态代码块
 * 
 * 
 * 
 * -----------------------------------------
 *  父类的实例化 <init>:
 * (1)super()(最前)
 * (2)i = test();
 * (3)父类的非静态代码块
 * (4)父类的无参构造(最后)
 * 
 * 非静态方法前面其实有一个默认的对象 this
 * this 在构造器(或 <init>)它表示的是正在创建的对象,
 * 因为这里是在创建 Son 对象,所以 test() 执行的是子类重写的代码(面向对象 多态)
 * 
 * 这里 i=test() 执行的是子类重写的 test() 方法
 */
public class Father {
    private int i = test();
    private static int j = method();

    static {
        System.out.print("(1)");
    }

    Father() {
        System.out.print("(2)");
    }

    {
        System.out.print("(3)");
    }

    public int test() {
        System.out.print("(4)");
        return 1;
    }

    public static int method() {
        System.out.print("(5)");
        return 1;
    }
}

示例代码如下:

代码语言:javascript复制
package com.atguigu.init;

/*
 * 子类的初始化 <clinit>:
 * (1)子类的静态方法 j = method();
 * (2)子类的静态代码块
 * 
 * 先初始化父类的静态方法和静态代码块:(5)(1)
 * 再初始化子类的静态方法和静态代码块:(10)(6)
 * -----------------------------------------
 * 子类的实例化  <init>:
 * (1)super()(最前)            (9)(3)(2)
 * (2)i = test();                (9)
 * (3)子类的非静态代码块        (8)
 * (4)子类的无参构造(最后)    (7)
 * 
 * 因为创建了两个 Son 对象,因此实例化方法 <init> 执行了两次
 * 即又出现了一次 (9)(3)(2)(9)(8)(7)
 */
public class Son extends Father {
    private int i = test();
    private static int j = method();

    static {
        System.out.print("(6)");
    }

    Son() {
        // super(); // 写或不写都在,在子类构造器中一定会调用父类的构造器
        System.out.print("(7)");
    }

    {
        System.out.print("(8)");
    }

    public int test() {
        System.out.print("(9)");
        return 1;
    }

    public static int method() {
        System.out.print("(10)");
        return 1;
    }

    public static void main(String[] args) {
        Son s1 = new Son();
        System.out.println();
        Son s2 = new Son();
    }
}

4、方法的参数传递机制

考点   方法的参数传递机制   String、包装类等对象的不可变性

方法的参数传递机制

代码语言:javascript复制
形参是基本数据类型时
    传递数据值

形参是引用数据类型时
    传递地址值,本质也是值
    特殊的类型:String、包装类等对象不可变性

示例代码如下:

代码语言:javascript复制
package com.atguigu.passvalue;

import java.util.Arrays;

public class Exam4 {
    public static void main(String[] args) {
        int i = 1;
        String str = "hello"; // 在常量池中(jdk1.8 中,常量池在元空间中;jdk1.7 中,常量池在堆中)
        Integer num = 200; // 值大于 127,对象在堆中
        int[] arr = { 1, 2, 3, 4, 5 }; // 堆中
        MyData my = new MyData(); // 堆中

        change(i, str, num, arr, my);

        System.out.println("i = "   i); // i = 1
        System.out.println("str = "   str); // str = hello
        System.out.println("num = "   num); // num = 200
        System.out.println("arr = "   Arrays.toString(arr)); // arr = [2, 2, 3, 4, 5]
        System.out.println("my.a = "   my.a); // my.a = 11
    }

    public static void change(int j, String s, Integer n, int[] a, MyData m) { // 方法在栈中,执行完毕后销毁
        j  = 1; // j = 1
        s  = "world"; // 产生新的对象
        n  = 1; // 产生新的对象
        a[0]  = 1; // a[0] = 2
        m.a  = 1; // 11
    }
}

class MyData {
    int a = 10;
}

5、递归与迭代

编程题:有 n 步台阶,一次只能上 1 步或 2 步,共有多少种走法?

递归(理解上直观,效率上可能会差些(针对某些情况下))

示例代码如下:

代码语言:javascript复制
package com.atguigu.step;

import org.junit.Test;

public class TestStep {
    @Test
    public void test() {
        long start = System.currentTimeMillis();
        System.out.println(f(40)); // 165580141
        long end = System.currentTimeMillis();
        System.out.println(end - start); // 308ms
    }

    // 实现 f(n):求 n 步台阶,一共有几种走法
    public int f(int n) {
        if (n < 1) {
            throw new IllegalArgumentException(n   "不能小于1");
        }
        if (n == 1 || n == 2) {
            return n;
        }
        return f(n - 2)   f(n - 1);
    }
}

循环迭代

示例代码如下:

代码语言:javascript复制
package com.atguigu.step;

import org.junit.Test;

public class TestStep2 {
    @Test
    public void test() {
        long start = System.currentTimeMillis();
        System.out.println(loop(40)); // 165580141
        long end = System.currentTimeMillis();
        System.out.println(end - start); // <1ms
    }

    public int loop(int n) {
        if (n < 1) {
            throw new IllegalArgumentException(n   "不能小于1");
        }
        if (n == 1 || n == 2) {
            return n;
        }

        int one = 2; // 初始化 走到第二级台阶的走法
        int two = 1; // 初始化 走到第一级台阶的走法
        int sum = 0;

        for (int i = 3; i <= n; i  ) {
            // 最后跨2步   最后跨1步的走法
            sum = two   one;
            two = one;
            one = sum;
        }
        return sum;
    }
}

小总结

6、成员变量与局部变量

考点   就近原则   变量的分类     成员变量:类变量、实例变量     局部变量   非静态代码块的执行:每次创建实例对象都会执行   方法的调用规则:调用一次执行一次

局部变量与成员变量的区别:

代码语言:javascript复制
声明的位置
    局部变量:方法体 {} 中,形参,代码块 {} 中
    成员变量:类中方法外
        类变量:有 static 修饰
        实例变量:没有 static 修饰

修饰符
    局部变量:final
    成员变量:public、protected、private、final、static、volatile、transient

值存储的位置
    局部变量:栈
    实例变量:堆
    类变量:方法区

作用域
    局部变量:从声明处开始,到所属的 } 结束
    实例变量:在当前类中 “this.” (有时 this. 可以缺省),在其他类中 “对象名.” 访问
    类变量:在当前类中 “类名.”(有时类名.可以省略),在其他类中 “类名.” 或 “对象名.” 访问

生命周期
    局部变量:每一个线程,每一次调用执行都是新的生命周期
    实例变量:随着对象的创建而初始化,随着对象的被回收而消亡,每一个对象的实例变量是独立的
    类变量:随着类的初始化而初始化,随着类的卸载而消亡,该类的所有对象的类变量是共享的

当局部变量与 xx 变量重名时,如何区分:
    局部变量与实例变量重名
        在实例变量前面加 “this.”
    局部变量与类变量重名
        在类变量前面加 “类名.”

Java 运行时数据区

示例代码如下:

代码语言:javascript复制
package com.atguigu.variable;

public class Exam5 {
    static int s; // 成员变量,类变量
    int i; // 成员变量,实例变量
    int j; // 成员变量,实例变量

    {
        int i = 1; // 非静态代码块中的局部变量 i
        i  ; // i = 2
        j  ; // j = 1
        s  ; // 
    }

    public void test(int j) { // 形参,局部变量 j
        j  ; // 
        i  ; // i = 1
        s  ; // 
    }

    public static void main(String[] args) { // 形参,局部变量 args
        Exam5 obj1 = new Exam5(); // 局部变量 obj1
        Exam5 obj2 = new Exam5(); // 局部变量 obj2
        obj1.test(10);
        obj1.test(20);
        obj2.test(30);

        System.out.println(obj1.i   ","   obj1.j   ","   obj1.s); // 2,1,5
        System.out.println(obj2.i   ","   obj2.j   ","   obj2.s); // 1,1,5
    }
}

代码内存图解如下:

第2章 SSM 面试题

1、Spring Bean 的作用域之间有什么区别?

  在 Spring 中,可以在 <bean> 元素的 scope 属性里设置bean的作用域,以决定这个 bean 是单实例的还是多实例的。

  默认情况下,Spring 只为每个在 IOC 容器里声明的 bean 创建唯一一个实例(单实例),整个 IOC 容器范围内都能共享该实例:所有后续的 getBean() 调用和 bean 引用都将返回这个唯一的 bean 实例。该作用域被称为 singleton,它是所有 bean 的默认作用域。

  当 bean 的作用域为单例时,Spring 会在 IOC 容器对象创建时就创建 bean 的对象实例。而当 bean 的作用域为 prototype 时,IOC 容器在获取 bean 的实例时创建 bean 的实例对象。

2、Spring 支持的常用数据库事务传播属性和事务隔离级别

  当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。

  事务的传播行为可以由传播属性指定。Spring 定义了 7 种类传播行为。

代码语言:javascript复制
事务的属性:
    1.★ propagation:用来设置事务的传播行为
    事务的传播行为:一个方法运行在了一个开启了事务的方法中时,当前方法是使用原来的事务还是开启一个新的事务
        -Propagation.REQUIRED:默认值,使用原来的事务
        -Propagation.REQUIRES_NEW:将原来的事务挂起,开启一个新的事务
    2.★ isolation:用来设置事务的隔离级别
        -Isolation.REPEATABLE_READ:可重复读,MySQL默认的隔离级别
        -Isolation.READ_COMMITTED:读已提交,Oracle默认的隔离级别,开发时通常使用的隔离级别

数据库事务并发问题 假设现在有两个事务:Transaction01 和 Transaction02 并发执行。 1) 脏读(读取到了未提交的数据)   ① Transaction01 将某条记录的 AGE 值从 20 修改为 30。   ② Transaction02 读取了 Transaction01 更新后的值:30。   ③ Transaction01 回滚,AGE 值恢复到了20。   ④ Transaction02 读取到的 30 就是一个无效的值。 2) 不可重复读(读取到了已提交的数据)   ① Transaction01 读取了 AGE 值为 20。   ② Transaction02 将 AGE 值修改为 30。   ③ Transaction01 再次读取 AGE 值为 30,和第一次读取不一致。 3) 幻读   ① Transaction01 读取了 STUDENT 表中的一部分数据。   ② Transaction02 向 STUDENT 表中插入了新的行。   ③ Transaction01 读取了 STUDENT 表时,多出了一些行。

事务的隔离级别   数据库系统必须具有隔离并发运行各个事务的能力,使它们不会相互影响,避免各种并发问题。一个事务与其他事务隔离的程度称为隔离级别。SQL 标准中规定了多种事务隔离级别,不同隔离级别对应不同的干扰程度,隔离级别越高,数据一致性就越好,但并发性越弱。 1) 读未提交:READ UNCOMMITTED   允许 Transaction01 读取 Transaction02 未提交的修改。 2) 读已提交:READ COMMITTED   要求 Transaction01 只能读取 Transaction02 已提交的修改。 3) 可重复读:REPEATABLE READ   确保 Transaction01 可以多次从一个字段中读取到相同的值,即 Transaction01 执行期间禁止其它事务对这个字段进行更新。 4) 串行化:SERIALIZABLE   确保 Transaction01 可以多次从一个表中读取到相同的行,在 Transaction01 执行期间,禁止其它事务对这个表进行添加、更新、删除操作。可以避免任何并发问题,但性能十分低下。 5) 各个隔离级别解决并发问题的能力见下表

6) 各种数据库产品对事务隔离级别的支持程度

3、SpringMVC 中如何解决 POST 请求中文乱码问题

在 web.xml 中配置一个过滤器

代码语言:javascript复制
  <filter>
    <!-- SpringMVC 中如何解决 POST 请求中文乱码问题 -->
    <filter-name>CharacterEncodingFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
      <!-- POST 请求中文乱码问题 -->
      <param-name>encoding</param-name>
      <param-value>UTF-8</param-value>
    </init-param>
    <init-param>
      <!-- POST 响应中文乱码问题 -->
      <param-name>forceEncoding</param-name>
      <param-value>true</param-value>
    </init-param>
  </filter>
  <filter-mapping>
    <filter-name>CharacterEncodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>

4、SpringMVC 中如何解决 GET 请求中文乱码问题

方法一:稍微修改 tomcat 的配置文件 server.xml,修改第 65 行代码

方法二:参考百度

5、简单的谈一下 SpringMVC 的工作流程

6、MyBatis 中当实体类中的属性名和数据库表中的字段名不一样怎么办?

第一种解决方案:写 sql 语句时起别名。别名和数据库表中的字段名一样即可。

第二种解决方案:在 MyBatis 的全局配置文件中开启驼峰命名规则。可以将数据库中下划线映射为驼峰命名。(有局限性)

第三种解决方案:在 Mapper 的映射文件中使用 resultMap 来自定义映射规则。

第3章 Java 高级面试题

1、Linux 常用服务类相关命令

CentOS 6

运行级别

CentOS 7

2、git 分支相关命令和实际应用

Git 工作流

3、redis 持久化

Redis 提供了 2 种不同形式的持久化方式:

RDB(Redis Database):全量替换

AOF(Append Of File):增量追加

4、Mysql 什么时候建索引

5、JVM 垃圾回收机制

GC 发生在 JVM 哪一个部分,有几种 GC,它们的算法是什么 答:堆中。2 种:Minor GC(新生代)、 Full GC(老年代)。引用计数算法、复制算法、标记清除算法、标记压缩算法、标记清除压缩算法。

引用计数算法(已经淘汰了,因为没有办法处理循环引用)

复制算法(新生代中使用的是 Minor GC)

标记清除算法(老年代一般是由标记清除或者标记清除与标记整理的混合实现)

标记压缩算法(老年代一般是由标记清除或者标记清除与标记整理的混合实现)

标记清除压缩算法

第5章 Java 项目面试题

1、redis 在项目中的使用场景

2、Elasticsearch 与 solr 的区别

背景:它们都是基于 Lucene 搜索服务器基础之上开发,一款优秀的,高性能的企业级搜索服务器。【因为它们都是基于分词技术构建的倒排索引的方式进行查询开发语言:java 语言开发 诞生时间:   Solr:2004年诞生。   Elasticsearch:2010年诞生。更新、功能更强大。

区别:   1、当实时建立索引的时候,solr 会产生 io 阻塞,而 es 则不会,es 查询性能要高于 solr。   2、在不断动态添加数据的时候,solr 的检索效率会变的低下,而 es 则没有什么变化。   3、Solr 利用 zookeeper 进行分布式管理,而 es 自身带有分布式系统管理功能。Solr 一般都要部署到 web 服务器上,比如 tomcat。启动 tomcat 的时候需要配 tomcat 与 solr 的关联。【因为 Solr 的本质 是一个动态 web 项目】   4、Solr 支持更多的格式数据 [xml,json,csv等],而 es 仅支持 json 文件格式。   5、Solr 是传统搜索应用的有力解决方案,但是 es 更适用于新兴的实时搜索应用。   a) 如果单纯的对已有数据进行检索的时候,solr 效率更好,高于 es。   6、Solr 官网提供的功能更多,而 es 本身更注重于核心功能(即检索),高级功能多有第三方插件。

Solr 利用 zookeeper 进行分布式管理,部署在 tomcat 上

SolrCloud:集群图

Elasticsearch:集群图

3、单点登录实现过程

单点登录:一处登录多处使用! 前提:单点登录多使用在分布式系统中。

Demo: 参观动物园流程: 检票员=认证中心模块 1、我直接带着大家进动物园,则会被检票员拦住【看我们是否有门票】,没有[售票处买票]   登录=买票 2、我去买票【带着票,带着大家一起准备进入动物园】检票员 check【有票】   Token=piao 3.我们手中有票就可以任意观赏动物的每处景点。   京东:单点登录,是将 token 放入到 cookie 中的。   案例:如果将浏览器的 cookie 禁用,则在登录京东后就失败!即无论如何登录不了!

4、购物车实现过程

代码语言:javascript复制
购物车:
1. 购物车跟用户的关系?
    a) 一个用户必须对应一个购物车【一个用户不管买多少商品,都会存在属于自己的购物车中。】
    b) 单点登录的开发一定在开发购物车之前。
2. 跟购物车有关的操作有哪些?
    a) 添加购物车
        i.用户未登录状态
            1. 添加到什么地方?未登录将数据保存到什么地方?
                a) Redis ? --> 京东使用
                b) Cookie ? --> 自己开发项目的时候【如果浏览器禁用 cookie,则京东登录不上】
        ii.用户登录状态
            1. Redis 缓存中【读写速度快】
                a) Hash:hset(key,field,value)
                    i. key=user:userId:cart
                    ii. Hset(key,skuId,value);
            2. 存在数据库中【oracle、mysql,保证数据安全性】
    b) 展示购物车
        i.未登录状态展示
            1.直接从 cookie 中取得数据进行展示即可
        ii.登录状态
            1.用户一旦登录:必须显示数据库 【redis】 【cookie】 中的购物车的数据
                a) Cookie 中有三条记录
                b) Redis 中有五条记录
                c) 合并后,真正展示的时候应该是小于或等于八条记录

5、消息队列在项目中的使用

背景:在分布式系统中是如何处理高并发的。   由于在高并发的环境下,来不及同步处理用户发送的请求,则会导致请求发生阻塞。比如说,大量的 insert,update 之类的请求同时到达数据库 MySQL,直接导致无数的行锁表锁,甚至会导致请求堆积很多。从而触发 too many connections 错误。使用消息队列可以解决【异步通信】

1、异步

2、并行

3、排队

消息队列电商使用场景:

消息队列的弊端:   解决消息的不确定性:延迟队列 和 轮询技术来解决,比如:订单模块 定时向 支付宝 查询当前订单是否支付成功。   推荐大家使用 activemq!因为环境都是 java。

0 人点赞