2024Java面试

1.你使用了什么技术栈?

我使用的是Spring Cloud Alibaba技术栈
用Nginx进行服务端(服务器之间)的负载均衡以及反向代理
使用Spring Cloud Gateway 网关进行鉴权以及结合Sentinel进行限流
使用Nacos对服务自动化注册与发现
使用Nacos Config对配置文件进行管理
使用Ribbon对客户端(服务之间的调用)进行负载均衡
利用Sentinel进行服务容错,(隔离、超时、限流、熔断、降级)
使用Open Fegin以及Kafka进行服务之间的调用
使用Seata保证分布式事务一致
使用Canal加Kafka以及Redis对数据进行读写分离
使用Mqtt和Netty进行数据之间的传输
使用Mybatis Plus持久层框架与数据库之间进行交互
使用Mysql进行数据的存储
使用Docker部署服务

1.1 Nginx如何配置负载均衡以及反向代理?

  • 反向代理:Nginx作为一个反向代理服务器,它接收客户端的请求,并把这些请求转发给内部网络中的后端服务器处理,然后将后端服务器返回的结果再返回给客户端。在这个过程中,客户端只知道Nginx的存在,而不知道真正提供服务的是哪台后端服务器。
    • 在server块中,通过location指令指定哪些请求应该被代理到上面定义的后端服务器池,并且使用proxy_pass指令指明这个后端服务器池的名称。
server {
    listen 80; # 监听HTTP默认端口
    server_name yourdomain.com; # 替换为你的域名

    location / { # 针对所有根路径的请求
        proxy_pass http://backend_servers; # 将请求转发到上游服务器池
        # 下面是几个常用的proxy_set_header指令,用于传递真实客户端信息给后端服务器
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        
        # 可以根据需要设置超时时间
        proxy_connect_timeout 60s;
        proxy_read_timeout 60s;
    }
}

完成以上配置后,当用户访问 yourdomain.com 时,Nginx会按照负载均衡策略(此处为默认的轮询方式)将请求分发到 backend1.example.com、backend2.example.com 或 backend3.example.com 等后端服务器上进行处理。

  • 负载均衡:是一种计算机网络技术,它通过将工作负载或网络流量智能地分散到多个服务器上,从而达到优化资源使用、提高系统响应速度、确保服务高可用性和避免单点故障的目的。在实际应用中,负载均衡通常位于客户端与服务器之间的网络层级,它负责接收请求并将这些请求按预设策略分配给后端服务器。
    • 如果您有三组不同的服务器集群,并希望为每组设置独立的负载均衡,您可以按照以下步骤在Nginx中配置多个upstream模块来分别管理这些集群:
http {
    # 第一组服务器集群(例如web应用)
    upstream backend_group1 {
        server server1_web1.example.com;
        server server2_web1.example.com;
        server server3_web1.example.com;

        # 可选地,可以根据需要添加负载均衡策略或权重
        # 默认是轮询(round-robin)
    }

    # 第二组服务器集群(例如API服务)
    upstream backend_group2 {
        server api1.example.com:8080;
        server api2.example.com:8080;
        server api3.example.com:8080;

        # 可以根据实际需求调整负载均衡算法或其他参数
    }

    # 第三组服务器集群(例如数据库读写分离)
    upstream backend_group3_read {
        server db1_read.example.com;
        server db2_read.example.com;
        server db3_read.example.com;
    }
    upstream backend_group3_write {
        server db1_write.example.com;
        server db2_write.example.com;
        server db3_write.example.com;
    }

    # 针对每个集群配置相应的server和location块进行反向代理
    server {
        listen 80;
        server_name yourdomain1.com; # 对应第一组服务的域名

        location / {
            proxy_pass http://backend_group1;
            # 设置其他必要的proxy指令,如header传递等
        }
    }

    server {
        listen 8080;
        server_name yourdomain2.com; # 对应第二组服务的域名

        location / {
            proxy_pass http://backend_group2;
            # 设置其他必要的proxy指令
        }
    }

    # 假设第三组用于数据库读写操作,可以通过不同路径代理到不同的上游
    server {
        listen 9000;
        server_name db.yourdomain.com;

        # 读请求负载均衡
        location /read {
            proxy_pass http://backend_group3_read;
        }

        # 写请求负载均衡
        location /write {
            proxy_pass http://backend_group3_write;
        }
    }

    # 确保配置文件正确后,执行如下命令检查并应用配置更改
    # sudo nginx -t && sudo systemctl reload nginx
}

在这个配置中:

  • 每个upstream定义了一个服务器池,对应一个服务组。
  • server 在upstream中表示各个具体的后端服务器。
  • 分别为每一组服务器创建了对应的server块,并在其中的location部分设置了代理转发规则。
  • 这样,当用户通过不同的域名或端口访问时,Nginx会根据配置将请求分发到对应的后端服务器群组中。

1.2Gateway 网关如何进行鉴权的?原理是什么?

在Spring Cloud Gateway中,进行鉴权主要是通过实现自定义的过滤器(GatewayFilter)来完成的。其原理是利用Spring Cloud Gateway提供的强大路由和过滤功能,在请求经过网关时对请求进行拦截并验证其权限。

原理:

  1. 过滤器链:
    • Spring Cloud Gateway使用了一种基于责任链模式的设计,每个路由配置都可以关联一系列的过滤器。这些过滤器按照顺序执行,形成一个过滤链。
  2. 全局过滤器与路由特定过滤器:
    • 全局过滤器(Global Filter):适用于所有路由,通常用于处理全局性的请求预处理或后处理逻辑,如通用的鉴权、日志记录等。
    • 路由特定过滤器(Route-specific Filter):只针对特定路由生效,可以在特定的服务或者路径上设置鉴权规则。
  3. 鉴权流程:
    • 当客户端发起请求到达网关时,请求会先经过预先配置好的过滤器。
    • 自定义的鉴权过滤器会在链中的相应位置检查请求信息,例如检查HTTP头中的JWT令牌,或者查询参数中的API密钥等。
    • 过滤器根据预设的鉴权策略验证请求的有效性,比如解码JWT并校验其中的签名、有效期和权限声明。
    • 如果请求通过了鉴权,则允许请求继续向下传递到下一个过滤器或者直接转发到目标服务;否则,过滤器可以返回未授权的响应或其他适当的错误信息。
  4. 集成安全框架:
    • 有时还会结合使用Spring Security或其他安全框架,以提供更复杂、全面的安全控制能力,如OAuth2、角色权限验证等。
      示例代码简述(基于JWT鉴权):
// 定义自定义鉴权过滤器
public class AuthenticationGatewayFilterFactory extends AbstractGatewayFilterFactory<AuthenticationGatewayFilterFactory.Config> {

    @Override
    public GatewayFilter apply(Config config) {
        return (exchange, chain) -> {
            // 从请求中提取JWT令牌
            ServerHttpRequest request = exchange.getRequest();
            String jwtToken = extractToken(request);
            
            // 验证JWT令牌
            if (!isValidToken(jwtToken)) {
                // 鉴权失败,返回401 Unauthorized
                ServerHttpResponse response = exchange.getResponse();
                response.setStatusCode(HttpStatus.UNAUTHORIZED);
                // ...
                return Mono.empty();
            }
            
            // 鉴权成功,继续处理请求
            return chain.filter(exchange);
        };
    }

    // 实现具体的令牌提取和验证逻辑...
}

// 在网关配置中应用过滤器
spring:
  cloud:
    gateway:
      routes:
      - id: secured_route
        uri: lb://backend-service
        predicates:
        - Path=/secured/**
        filters:
        - name: AuthenticationGatewayFilter # 自定义过滤器名称

这样,每当有请求访问/secured/**这样的路径时,都会触发自定义的鉴权过滤器进行验证。

1.3 Sentinel是如何进行限流的?原理是什么?怎么实现?

Sentinel是阿里巴巴开源的一款流量控制组件,用于实现服务的高可用防护。其限流功能主要基于滑动窗口算法进行设计和实现,以保证系统的稳定性和可靠性。以下是 Sentinel 进行限流的基本原理和实现步骤:

  1. 滑动窗口算法:
    • Sentinel采用滑动窗口算法统计单位时间内的请求量。滑动窗口将时间分为多个小的时间段(桶),每个时间段内记录通过的请求次数。
    • 在判断是否触发限流时,它会考虑最近一段时间窗口的请求总数,而不是仅仅关注瞬时的请求数。
  2. 实时调用信息收集:
    • Sentinel通过一系列的Slot(槽位)机制对请求进行拦截处理,FlowSlot负责流量控制部分,它会在链路中收集每一次资源调用的信息,包括调用信息、调用结果等。
  3. 规则配置与匹配:
    • 用户可以为不同的资源定义限流规则,包括QPS(每秒查询数)、线程数、系统并发线程数等多种维度的限流阈值。
    • 当新的请求到来时,Sentinel 根据预先配置好的限流规则,检查对应资源在当前滑动窗口内已通过的请求数量是否超过了预设的阈值。
  4. 拒绝策略执行:
    • 如果请求的数量超过阈值,则会触发限流逻辑,并根据预设的拒绝策略来处理超出的请求,常见的拒绝策略有:快速失败(直接返回错误)、Warm Up(预热模式,缓慢增加流量)、匀速排队(按比例均匀地让请求通过)、降级等。
  5. 资源管理与统计:
    • Sentinel 内部有一个资源管理器,负责存储和管理不同资源及其对应的限流规则。同时,Sentinel 实现了统计模块,用于实时统计和更新资源访问情况。

总之,Sentinel 的限流功能是通过对资源调用情况进行实时监控,并结合灵活可配置的限流规则以及多种拒绝策略,在系统级别上实现了对请求流量的精准控制和保护。

1.3 Nacos的配置中心如何实现

2.你有遇到什么线上问题,是怎么解决的?

  • 解决CPU经常占用100%问题
    有一段时间发现接口响应很慢,通过top命令和开启数据库慢日志发现,有两个sql导致查询慢(平均耗时75s,解决后是1秒左右)一值占用CPU,解决方案是优化sql,对字段增加索引,以及实现读写分离

  • Lock wait timeout exceeded; try restarting transaction
    出现死锁问题,通过查看MySQL的运行错误日志定位到是上传心率数据逻辑问题,导致长时间占用锁(场景是:每分钟一条心率数据,一天有1440条数据,同事实现逻辑是先查询然后对比,由于该用户这一次上传数据是把前七天的数据一起上传,所以导致大量占用锁),解决方案是,优化上传逻辑,验重以天为单位,不再对比数据。

  • 传输数据出现半包问题
    上传用户心电数据,发现半包问题,通过自定义解码器(分隔符)解决

3.你是如何实现读写分离的

  • 我使用的是Canal结合Kafka和Redis技术实现的,具体实现的逻辑是:通过Canal监听解析Mysql的BinLog,然后把对应的数据发送到消息队列里面,最后通过消息队列把数据同步到redis里。

3.1为什么不用MySQL的主从库实现?

  • 从使用场景分析,redis这个方案更适用,我们的业务场景是多个不同类型的手表,他们数据类型,数据格式,协议都不一样。而我们则需要把他统一起来,比如心率统一为整形,血氧统一为浮点型。而使用redis这个方案则可以在插入数据库的时候就实现了,其次是查询的时候统一从redis上获取就可以,不用根据不同的手表去调用不同的数据库。

    1.通过kafka对数据进行清洗
    2.性能高
    3.统一的数据获取入口

4.说一下Redis的缓存击穿、穿透和雪崩

  • 缓存穿透: 是指redis和数据库中都没有数据,而用户不断地发起请求(主要产生是恶意攻击)。
    解决: 接口加鉴权、缓存空对象、使用布隆过滤器
  • 缓存击穿: 是指某一段时间内redis中没有数据,导致大量请求查询数据库。
    解决: 使用分布式锁,第一次查询到就放在缓存里面、设置热点数据永不过期
  • 缓存雪崩: 是指同一时间段内大量数据过期,导致大量请求到数据库。
    解决: 数据的过期时间不要设置为同一时间、缓存预热、降级限流、建集群

5.JVM Eden区进行 Minor GC 时,存活下来的对象会被复制到Survivor,一个对象要进行多少次GC才可以晋升到老年代?

  • 年龄阈值(Tenuring Threshold):默认情况下,HotSpot JVM使用分代垃圾回收机制,其中每个新创建的对象首先会被分配到Eden区。经过一次Minor GC后,存活下来的对象会被复制到Survivor区(通常是Survivor0区)。如果一个对象在经历了一定次数的Minor GC之后仍然存活,它就会被移动到老年代。这个次数就是年龄阈值,JVM会根据程序运行时的实际情况动态调整
  • 空间担保(Promotion Failure):即使对象年龄未达到预设的阈值,当Survivor空间不足时(例如连续多次GC后,Survivor区无法容纳所有应该存活的对象),JVM也会直接将这部分对象提前晋升至老年代,以防止因为年轻代内存不足而导致频繁的Minor GC。
  • 大对象直接进入老年代(TLAB或直接分配):对于特别大的对象(大于 survivor 空间的一半或者配置了 − X X : P r e t e n u r e S i z e T h r e s h o l d \color{#FF0000}{-XX:PretenureSizeThreshold} XX:PretenureSizeThreshold 参数指定的大小),JVM可以决定直接将其分配到老年代,无需经历Minor GC。

综上所述,一个对象至少需要经历一次Minor GC,并在Survivor区之间辗转复制若干次后才能晋升到老年代,具体次数取决于JVM的实际运行情况和相关参数配置。

6. 如何设置?

要配置JVM中对象晋升到老年代的年龄阈值(Tenuring Threshold)以及相关参数,可以使用以下Java虚拟机选项:

  • 设置年龄阈值: 通过 − X X : M a x T e n u r i n g T h r e s h o l d \color{#FF0000}{-XX:MaxTenuringThreshold} XX:MaxTenuringThreshold 参数来设置。这个参数定义了年轻代对象经历多少次Minor GC后能够晋升到老年代的最大次数。例如: -XX:MaxTenuringThreshold=15
    这表示一个对象在Eden区和Survivor区之间最多复制15次后仍未被回收,则会被晋升到老年代。

  • 调整Survivor区大小: Survivor区大小可以通过 − X X : N e w R a t i o \color{#FF0000}{-XX:NewRatio} XX:NewRatio − X X : S u r v i v o r R a t i o \color{#FF0000}{-XX:SurvivorRatio} XX:SurvivorRatio 来间接影响。例如: -XX:SurvivorRatio=8
    表示年轻代中 Eden 区与一个 Survivor 区的比例是 8:1,两个 Survivor 区则相等

  • 直接进入老年代的大对象设置: − X X : P r e t e n u r e S i z e T h r e s h o l d \color{#FF0000}{-XX:PretenureSizeThreshold } XX:PretenureSizeThreshold可以指定大于该值的对象直接在老年代分配。例如: -XX:PretenureSizeThreshold=3145728

  • 表示大于3MB的对象会直接分配到老年代。

7.说一下JVM的内存模型

JVM内存模型是java程序在运行时的数据存储区域的划分,它是JVM规划的一部分,定义了如何管理、分配和回收内存空间。JVM主要分配为以下几个逻辑区域:

  1. 程序计数器:

    • 程序计数器是线程私有的,是一块较小的内存空间
    • 它用于指示当前线程所执行的字节码行号的地址,也可以看做是当前线程执行的下一条指令的位置。
    • 每个线程都有一个独立的程序计数器,因此线程之间的执行互不影响。
  2. 虚拟机栈:

    • 虚拟机栈也是线程私有的,它的生命周期与线程相同。
    • 栈中的每个元素都称为栈帧,对应方法的调用,每次方法的调用都会创建一个新的栈帧,并随着方法的调用结束而销毁。
    • 栈帧中存放着局部变量表、操作数栈、动态链接和方法出口等数据,用于支持方法的调用和返回。
  3. 本地方法栈:

    • 本地方法栈和虚拟机栈类似,但是它是服务于Native 方法,即是使用C++/C等非java语言编写的本地方法
    • 同样是线程私有,生命周期与对应的线程相同。
  4. 堆:

    • 堆是所有线程共享的一片内存区域,它在JVM启动时就被创建。
    • 堆主要用于存储对象实例和数组,也就是所有的java对象都在堆上分配内存。
    • 堆空间是垃圾回收的主要场所,可以根据需要进行扩展和收缩,并且进一步细分为新生代(Eden区、两个Survivor区)和老年代(Old区)
    • 新生代通常采用复制算法进行垃圾回收,而老年代则是采用标记-清除或标记-整理算法进行回收。
  5. 方法区:

    • 方法区(也被称为Non-Heap内存或永久代/元空间,在不同的版本中JVM的实现方式都不同)同样是线程共享的内存区域。
    • 它主要用于存储已经被加载的类的信息、常量池、静态变量、即时编译器编译后的代码等数据。
    • 在java7及版本之前,这部分区域被称为永久代,而在8之后改为元空间,并将原本部分的永久代的内容移到堆里面,如常量池和类的静态变量。

8.JVM的进行垃圾回收原理

JVM会通过以下方式来判断是不是需要进行垃圾回收:

  1. 通过对象可达性分析算法:

    • JVM通过对象可达性分析算法来确定哪些对象是“垃圾”;这个算法是从一系列的被称为“ GC Roots”的对象为起点,这些对象包括全局性的引用、栈中局部变量的引用和方法区内的静态属性的引用等。如果一个对象从“GC Roots”的任何一个路径出发都到达不了,就认为这个对象可以被回收
    • 在一些简单特定的情况下会使用引用计数法来判断是否回收,但它不是主要的算法,原因是因为它无法解决循坏依赖的问题(即两个或多个对象相互引用,并没有其他对象使用它,容易造成内存泄漏)。
  2. 触发了可以进行垃圾回收的条件:

    • 当新生代中的Eden区的空间不足时,会触发Minor GC
    • 老年代的空间不足时,会触发Major GC或Full GC。在一些特定的情况下,即使年轻代不满也会触发垃圾回收(如,在CMS或G1收集器中,为了满足担保策略:意思是为了保证老年代有足够的空间存放下一次GC晋升的对象,会对老年代的一些对象进行清理,如果说进行了Full GC后还是存放不下,那就会报“OutOfMemoryError: Java heap space”异常,表示堆内存资源耗尽)
    • 手动执行System.gc() 方法,但是这个方法不是说你调用了就执行垃圾回收,而是向JVM建议进行垃圾回收,真正是否执行垃圾回收还是由JVM说了算。

9. JVM进行垃圾回收的方法

  1. 分代收集
    • 新生代通常采用的是复制算法 。(因为新生代分为Eden区、From Survivor和To Survivor区。当发生Minor GC的时候,Eden和From Survivor中存活的对象会被复制到To Survivor中,然后再把Eden和From Survivor中的所有对象清除掉。同时会把To Survivor中满足晋升条件的对象晋升到老年代(Survivor空间不足会直接晋升)里面。)
    • 老年代则采用的是标记-清除、标记-整理或者标记-压缩等算法。例如,CMS收集器使用的是并发标记-清理-压缩过程,而G1则采用了标记-整理的方式,在Region之间进行跨区域复制。

10. 垃圾回收各个算法的基本原理

  1. 标记-清除(Mark-Sweep)

    • 原理:该算法分为两个阶段,首先进行“标记”,即遍历堆内存,识别出活动对象;然后进行“清除”,回收未被标记的、不可达的对象占用的空间。然而,此算法执行后会导致内存碎片,因为清理后的空闲空间可能不连续
  2. 复制(Copying)

    • 原理:将堆内存划分为两个或多个大小相等的区域(如新生代中的Eden区与Survivor区),只用其中一部分分配新对象。当这部分空间满了之后,GC会暂停程序执行,将存活的对象复制到另一块空白区域中,并清空当前已使用的区域。这样可以避免内存碎片问题,但缺点是牺牲了一半的有效内存空间用于复制
  3. 标记-压缩(Mark-Compact)

    • 原理: 类似于标记-清除算法,先进行标记阶段确定哪些对象是存活的。接着不是简单地清除垃圾对象,而是将所有存活对象向一端移动,从而形成一个连续的可用空间区域,有效地解决了内存碎片的问题。但是,由于涉及对象迁移,这一过程可能会比较耗时
  4. 分代收集(Generational Collection)

    • 原理: 基于对象生命周期的不同特性,将内存划分为新生代(Young Generation)和老年代(Old Generation)。新生代通常采用复制算法(如ParNew、Parallel Scavenge),老年代则更倾向于采用标记-压缩或标记-清除后整理(如CMS、G1的混合模式)的算法。
  5. 增量更新(Incremental Compacting Collector, ICS)

    • 原理: 对整个堆内存进行多次小步长的标记和压缩操作,以减少每次GC造成的停顿时间 (少吃多餐的意思)。
  6. 并发标记-压缩(Concurrent Mark-Sweep, CMS)

    • 原理: 大部分工作在应用线程运行时并发进行,包括并发标记并发预清理阶段。为了降低STW(Stop-The-World)停顿时间,它会在多个阶段进行并发处理,但在某些特定步骤仍需要短暂的STW。
  7. Garbage-First Garbage Collector (G1)

    • 原理: G1是一个并行、并发的垃圾收集器,它将堆内存划分为许多大小固定的区域(Region)。每个区域都可以作为年轻代或者老年代的一部分,垃圾回收过程中会优先回收收益最大的区域,通过动态调整堆分区和并发处理来降低停顿时间和提高整体性能。

现代JVM,如HotSpot JVM中的ZGC和Shenandoah GC,还引入了其他创新技术来进一步降低延迟和减少STW时间,例如着色指针、读屏障以及并发转移等。

11.什么是Spring?它有什么用?怎么使用?有什么优点?

Spring是一个开源的Java应用程序框架,主要是用于简化企业级开发。它的核心优势是控制反转(IOC)和依赖注入(DI)以及面向切面编程(AOP)。

  1. Spring的主要用途和功能:

    • 控制反转:是一个软件设计原则,它指的是创建对象的控制权从应用程序代码中转移到一个外部的容器或框架中。在传统的编程方式下,通常由程序初始化和管理依赖关系,而在IOC模式下,对象不再自行创建所依赖的对象,而是由容器创建并注入这些依赖对象。

      • 如:在Spring应用中,你不会直接实例化某个服务类(例如UserService),而是将其定义为Spring容器中的Bean。当程序运行时,Spring容器会根据配置自动创建并管理这些Bean,而不是由你的业务代码直接控制他们的生命周期
    • 依赖注入:是实现控制反转的一种具体的设计模式和技术手段。DI的基本思想是组件不直接创建所依赖的对象,而是通过构造函数、setter()方法或者接口注入等方式,将需要的依赖对象传递给组件。这样可以减少组件中的耦合度,并使得组件更加灵活、可复用、可测试。

      • 如:假设UserService依赖于UserRepository,在Spring中,可以通过XML配置文件或注解的方式声明这种依赖关系。然后,Spring容器会在创建UserService Bean时,自动将已创建好的UserRepository Bean注入到UserService中,可能是通过构造器参数注入或setter方法注入。这样就实现了UserServiceUserRepository的依赖注入,从而达到了解耦的目的。
    • 面向切面编程:是一种编程范式或设计模式,它允许开发人员将横切关注点从主要的业务逻辑中分离出来,并已模块化的方式进行管理。这些横切关注点是指在多个类或模块中都会出现的重复的功能和行为,如:日志记录、事务管理、权限验证、性能监控等。

    • 一站式服务 :Spring包含对众多第三方库和框架的支持,如数据访问(JDBC、Hibernate、MyBatis等)、Web框架(Spring MVC)、消息传递(RabbitMQ、Kafka)、安全性(Spring Security)等。

    • 方便的测试:Spring提供了Mock对象以及对JUnit和其他测试框架的良好集成,简化了单元测试和集成测试。

  2. 如何使用Spring?

    • 环境搭建:
      • 添加Spring框架依赖: 如果你使用的是Maven或Gradle构建工具,需要在对应的配置文件(pom.xml或build.gradle)中添加Spring框架和相关模块的依赖。
      • 配置类路径: 确保所需的Spring JAR包被正确地包含在项目中。
    • 创建Spring配置:
      • XML配置方式: 创建一个或多个Spring配置文件(如applicationContext.xml),在这个文件中定义Bean以及它们之间的依赖关系、数据源、事务管理器等组件。
      • Java配置方式: 编写@Configuration注解标记的Java类,在其中通过@Bean方法来声明和配置Bean。
    • 定义Bean:
      • 注解驱动开发: 在你的业务类上使用@Component(或者@Service、@Repository、@Controller等更具体的 stereotype)注解将其声明为Spring Bean,然后通过@Autowired注解自动注入其依赖项。
      • XML配置: 在XML配置文件中用 bean标签定义类及属性,并指定构造函数参数或setter方法来注入依赖。
    • 启动Spring容器:
      • 使用ClassPathXmlApplicationContextAnnotationConfigApplicationContext等类加载并初始化Spring配置,从而创建并管理所有的Bean。
    • 使用Bean:
      • 一旦容器启动后,可以从容器中获取所需的Bean开始执行业务逻辑。例如:
     ApplicationContext context = new AnnotationConfigApplicationContext(MyConfiguration.class);
     MyService myService = context.getBean(MyService.class);
     // 然后调用myService的方法执行业务操作
  1. Spring框架的优点:
    • 非侵入式设计:Spring允许开发者编写纯粹的Java POJO(Plain Old Java Object),不强制引入特定框架的API。
    • 轻量级:Spring是轻量级的,相比EJB等重量级容器,它的资源消耗较少。
    • 控制反转(IoC)和依赖注入:降低组件间的耦合度,提高代码的可重用性和可测试性。
    • 声明式事务管理:简化了数据库事务的处理,只需通过注解即可实现事务的管理和控制。
    • 良好的模块化和扩展性:Spring包含了众多模块,可以根据项目需求灵活选用,同时提供了丰富的SPI扩展机制,便于定制和扩展。
    • 对各种优秀框架的集成支持:Spring可以与其他流行的开源框架无缝集成,简化了各种复杂技术的使用难度。

12.什么是Spring Boot?它有什么用?怎么使用?有什么优点?

Spring Boot 是基于Spring 框架的开源项目,它提供了快速构建生产级、独立运行java程序的能力。Spring Boot 简化了Spring 应用的初始搭建以及开发过程,通过自动配置和starter POMs来让开发者能够快速启动服务

  1. Spring Boot的主要用途:

    • 简化Spring 应用的开发: 提供了一系列的starter依赖,只需要添加少量配置就能快速创建出一个功能齐全的应用。
    • 内置Web服务器: 可以内嵌Tomcat、Jetty或Undertow等Servlet容器,无需额外部署WAR文件。
    • 自动化配置: 根据类路径中的jar包自动配置Bean,减少了大量的XML配置工作。
    • 微服务支持: 与Spring Cloud配合,可以轻松实现服务发现、熔断器、负载均衡等功能,适用于构建微服务架构。
    • 快速开发和测试: 包含一系列便捷工具,如Actuator用于监控和管理应用,DevTools提升开发效率。
  2. 如何使用?

    • 创建一个新的Spring Boot项目可以通过官方提供的Spring Initializr(https://start.spring.io/),在线生成基础项目结构并下载到本地。
    • 使用IDE(如IntelliJ IDEA或Eclipse)新建项目时,选择Spring Boot项目模板。
    • 编写带有@SpringBootApplication注解的主类,该注解包含了@ComponentScan、@Configuration和@EnableAutoConfiguration注解,用来启动Spring Boot应用及进行组件扫描和自动配置。
    • 根据需求引入对应的starter依赖,并编写业务代码。
    • 运行主类中的main方法启动应用程序。
  3. Spring Boot的优点:

    • 开箱即用:提供默认配置选项,减少繁杂的配置工作。
    • 快速开发:以约定优于配置的原则,极大地提升了开发速度。
    • 独立运行:可直接打包为单个可执行的jar或war文件,方便部署。
    • 强大的监控能力:集成Actuator模块,提供了丰富的健康检查、审计和监控信息。
    • 良好的生态系统:与Spring Cloud、Spring Data、Spring Security等众多Spring生态项目深度整合,便于构建复杂的分布式系统。
    • 强大的文档支持:官方提供了详细的文档和指南,社区活跃,问题解决及时有效。

13.Spring 和Spring Boot的对比

Spring Boot基于Spring框架构建,它更侧重于简化开发流程、提高生产力和减少出错概率,使得开发者能够更加专注于业务逻辑开发

  1. 区别

    • 初始化和配置:
      Spring 是一个基础的Java企业级应用开发框架,它通过控制反转(IoC)和依赖注入(DI)等机制来解耦组件。在传统的Spring项目中,开发者需要手动编写大量的XML或注解配置文件来定义Bean、数据源、事务管理器等。
      Spring Boot 在Spring的基础上进一步发展,提供了一种快速构建独立运行的应用程序的方式,通过自动配置和“约定优于配置”的原则,极大地简化了项目的配置过程,许多常见的功能模块无需手动配置即可工作。
    • 启动速度与便利性:
      Spring 开发时环境搭建较为复杂,需要较多的配置和集成工作。
      Spring Boot 提供了一个可以“开箱即用”的体验,只需简单的几个步骤就能创建出可执行的JAR或WAR包,并且内嵌了Web容器,使得应用程序能够快速启动和部署。
    • 自动化配置:
      Spring 框架本身并不包含自动化配置的功能,所有的组件都需要手动进行装配和配置。
      Spring Boot 自带了大量的starter模块,能够根据类路径中的jar包自动检测并配置相关组件,减少了大量重复的工作。
    • 依赖管理:
      Spring 的依赖管理相对分散,每个模块的依赖版本可能需要单独维护。
      Spring Boot 引入了starter POMs的概念,提供了统一的依赖管理方式,能更好地解决版本冲突问题,确保组件之间兼容。
    • 微服务支持:
      Spring 可以用于构建复杂的系统,但要实现微服务架构,通常需要更多的手工配置和集成。
      Spring Boot 更加倾向于微服务架构的设计,自带了很多微服务相关的特性,比如Actuator用于监控和管理应用状态,结合Spring Cloud可以方便地构建和管理微服务集群。
  2. 联系

    • Spring Boot 建立在 Spring 框架之上:Spring Boot 是对 Spring 生态系统的扩展和封装,它仍然基于Spring的核心技术,包括IoC、AOP等,只是将这些技术更高效、便捷地整合在一起。
    • 共享相同的基础组件:Spring Boot 应用依然可以使用Spring的所有功能模块,如Spring MVC、Spring Data、Spring Security等,并在此基础上做了优化和增强。
    • 目标一致:两者都是为了简化企业级Java应用的开发,提高开发效率和代码质量,只不过Spring Boot在这个目标上做得更为彻底,为现代软件工程实践提供了更为简洁高效的解决方案。

14.什么是Spring MVC?它有什么用?怎么使用?有什么优点?

Spring MVC是一个基于java的web框架,它是Spring Framework的一部分,用于构建模型-视图-控制器(MVC)架构的web应用程序,在Spring MVC 中,请求从用户通过DispatcherServlet分发到各个组件,然后进行处理、数据验证、业务逻辑调用,并将最终结果渲染到客户端。

  1. 作用
    • 分离关注点: Spring MVC 将应用程序的不同部分(如输入验证、业务逻辑和视图呈现)分离开来,使得代码更加模块化和易于维护。
    • 灵活配置: 支持通过 XML 或注解的方式进行灵活配置,可以根据项目需求定制化 Controller、HandlerMapping、ViewResolver 等核心组件。
    • 统一资源管理: 通过 HandlerMapping 和 HandlerAdapter 提供了一种处理 HTTP 请求并返回响应的标准方式,包括 GET、POST 等多种请求类型。
  2. 使用方法:
    • 创建 DispatcherServlet:在 web.xml 文件中配置 Spring MVC 的入口 Servlet,即 DispatcherServlet。
    • 配置 HandlerMappings:可以自定义或使用默认的 HandlerMapping 来映射 URL 到具体的处理器方法(通常位于 Controller 类中)。
    • 编写 Controller 类:使用 @Controller 注解标记类,并用 @RequestMapping 注解映射请求路径到方法,该方法处理请求并返回逻辑视图名或 ModelAndView 对象。
    • 视图解析:配置 ViewResolver 以根据返回的逻辑视图名找到实际的视图技术(如 JSP、Thymeleaf、FreeMarker 等),并将数据填充到视图中展示给用户。
    • 数据绑定和验证:利用 Spring 提供的数据绑定机制自动将请求参数绑定到方法参数上,并可配合 JSR-303/JSR-349 规范实现数据验证。
  3. 优点:
    • 高性能:SpringMVC 使用了基于注解的控制器,减少了大量的XML配置,同时结合SpringIoC容器和面向切面编程(AOP)等技术,提高了Web应用的性能表现。
    • 灵活性:提供了高度灵活的配置选项,能够轻松地集成其他框架和技术,比如安全控制、国际化支持等。
    • 易于测试:由于Controller层是POJO对象,可以直接进行单元测试,无需启动整个Web容器环境。
    • 安全性:内置了多种安全特性,例如防止跨站请求伪造(CSRF)、XSS攻击,并且可以方便地集成Spring Security来进行更复杂的基于角色的访问控制。
    • 易扩展性:Spring MVC框架的设计允许开发人员根据需要扩展和自定义各种组件,从而适应不同规模和复杂度的应用场景。

15.Spring MVC 的执行流程

  • 客户端发起请求:用户通过浏览器或其他客户端向服务器发送HTTP请求。
  • DispatcherServlet接收请求:作为Spring MVC的核心前端控制器,DispatcherServlet首先接收到这个请求。
  • HandlerMapping进行映射:DispatcherServlet将请求委托给HandlerMapping组件,该组件根据请求的URL、HTTP方法以及自定义的注解(如**@RequestMapping**)找到对应的处理请求的处理器(即Controller中的方法)。
  • HandlerAdapter执行处理器方法:找到处理器后,DispatcherServlet 会使用 HandlerAdapter 来调用相应的 Controller 类中的处理器方法。在这个过程中,Spring 会进行数据绑定(将请求参数自动绑定到方法参数上)和可能的数据验证。
  • Controller 处理请求并返回模型视图:Controller 方法执行业务逻辑,然后可能会更新模型对象,并返回一个逻辑视图名或者 ModelAndView 对象。这个对象包含了要显示的数据和视图名称。
  • ViewResolver 解析视图:DispatcherServlet 根据 Controller 返回的结果,利用 ViewResolver 查找实际的视图技术实现类(如 JSP、Thymeleaf 或 Freemarker 等)。ViewResolver 根据逻辑视图名解析成具体的视图对象。
  • 渲染视图并响应客户端:最后,DispatcherServlet 将模型数据传递给视图对象,视图负责渲染结果并将最终的 HTML 响应内容发送回客户端。
  • 异常处理:在整个流程中,如果发生任何未捕获的异常,Spring MVC 提供了全局异常处理器机制,可以根据不同的异常类型来生成对应的错误页面或错误信息响应。

通过这种职责明确的分层设计,Spring MVC 极大地简化了 Web 开发过程,并提供了高度的灵活性和可扩展性。

16.什么是Spring cloud?它有什么用?怎么使用?

Spring Cloud是一套基于Spring Boot实现的微服务解决方案,它为开发者提供了在分布式系统(微服务架构)中快速构建和部署的一系列工具集。通过整合多个开源项目,Spring Cloud提供了一种标准化的方式来处理分布式常见的问题,如服务发现、配置管理、熔断器、路由、服务之间的调用、消息总线、负载均衡、全局锁、领导选举等。
下面详细介绍 Spring Cloud 中的一些关键组件及其功能:

  1. 服务注册与发现:
    • Eureka:Spring Cloud Netflix Eureka 提供了一个服务注册中心,使得各个微服务实例能够自动注册到注册中心,并通过注册中心实现服务间的相互发现。
    • Consul:Spring Cloud Consul 也是一个服务注册与发现的解决方案,使用 HashiCorp 的 Consul 工具作为后端存储。
  2. 服务间调用:
    • Ribbon:客户端负载均衡器,可以实现对服务提供者的软负载均衡,通过 HTTP 或者 TCP 在微服务之间进行透明、智能的负载均衡调用。
    • Feign:基于 Ribbon 和 Hystrix 的声明式 REST 客户端,简化了服务间 RPC 调用的代码编写。
  3. 熔断器:
    • Hystrix:通过命令模式封装依赖服务的调用,当依赖服务出现故障时,能够快速失败并返回 fallback 方法,防止故障扩散,实现系统的高可用性。
    • Resilience4j:另一个轻量级的容错库,提供熔断器、限流器等功能,与 Spring Cloud 结合良好。
  4. API 网关:
    • Zuul:Netflix 开发的一个边缘服务工具,用作 API Gateway,支持路由转发、过滤、安全控制、限流等功能。
    • Spring Cloud Gateway:Spring 官方推出的下一代网关,基于 Reactive 模型,性能更优,功能更强。
  5. 配置中心:
    • Spring Cloud Config:允许将应用的外部化配置信息统一存放到配置服务器上,实现配置的集中管理和动态刷新。
  6. 消息驱动:
    • Spring Cloud Stream:为开发者提供了创建消息驱动微服务的能力,可以方便地与 RabbitMQ、Kafka 等消息中间件集成。
  7. 服务跟踪:
    • Sleuth:提供分布式追踪解决方案,可以生成详细的请求链路日志,便于问题排查和性能分析。
    • Zipkin:一个开源的分布式追踪系统,结合 Sleuth 可以收集和展示服务间的调用链路信息。
  8. 一致性服务
    • Spring Cloud Zookeeper / Etcd:利用这些分布式协调服务来实现服务实例状态的同步和数据一致性保证。
  9. 安全性:
    Spring Cloud Security:集成了 OAuth2 认证授权机制,提供了一套安全认证和权限控制方案,用于保护微服务资源的安全。
    综上所述,Spring Cloud 构建了一个完整的微服务生态系统,帮助开发团队轻松构建和管理大规模的分布式系统,确保系统的稳定性和可扩展性。

17.Spring 框架中使用了什么设计模式,请举例说明。

  1. 工厂模式(Factory Pattern):
    • BeanFactory 和ApplicationContext是两个主要的Bean工厂接口,它们实现了工厂模式,负责创建和管理所有的Spring Bean。
    • 例如,通过配置文件或注解定义Bbean的类型、属性等信息,Spring容器会根据这些配置自动实例化对象。
  2. 单例模式:
    • Spring容器默认将所有由其管理的Bean实例化为单例。这意味着每个Bean在容器中只有一个实例,并且则会个实例会在整个应用程序生命周期内共享。
  3. 代理模式:
    • Spring AOP(面向切面编程)模块利用代理模式来创建代理对象,以便在不修改目标类代码的基础上添加额外功能,例如事务管理、日志记录、权限控制等。
    • 使用JDK代理或者CGLIB库生成代理对象。
  4. 模板方法模式:
    • Spring 中的JdbcTemplate、JmsTemplate等类就是模板方法模式的应用,它们提供了一套预定义的执行流程骨架,并允许用户自定义其中部分逻辑。
  5. 策略模式:
    • 在处理事务时,不同的事务传播行为可以看作是策略模式的应用。用户可以根据需求选择不同的事务策略。Spring将根据这些策略进行相应的事务管理。
  6. 观察者模式:
    • Spring事件驱动模型基于观察者模式,当某个感兴趣的事件发生的时候,应用程序可以通过监听器(ApplicationListener)接受到通知并执行相应的操作。
  7. 中介者模式:
    • Java web开发中MVC模式(Model-View-Controller)就使用了中介者模式,Controller就是Model和View的中介。

18.什么是循环依赖,Spring是如何解决的?

循环依赖是指在Spring框架中两个或者多个Bean之间相互依赖形成闭环的情况,具体来说就是:

  1. 循环依赖的类型:
    • 构造器注入循环依赖:类A的构造函数需要类B的实例,而类B的构造函数有需要类A的实例,导致互相依赖无法正常初始化。
    • 属性注入循环依赖:类A通过setter方法或者直接字段注入的方式引用了类B的实例,同时类B也以相同的方式引用了类A的实例。
  2. Spring如何解决属性注入的循环依赖:
    • Spring容器在创建Bean的时候采用了三级缓存机制来解决非构造器注入引起的循环依赖问题。这三级缓存内容分别是:
      • singletonObjects(一级缓存):存放已经完全初始化好的单例Bean。
      • earlysingletonObjects(二级缓存):存放已实例化但尚未完全初始化的单例Bean。
      • singletonFactories(三级缓存):存放可以生成Bean实例的工厂对象。
        当Spring检测到循环依赖时,它会按照以下步骤处理:
    • 实例化Bean A,并将其放入二级缓存中。
    • 然后尝试实例化 Bean B,在解析依赖的时候发现对Bean A 的依赖,此时从二级缓存中取出已经实例化但未完全初始化的Bean A 对象,注入给Bean B。
    • 完成Bean B的初始化并添加到一级缓存中。
    • 最后返回去完成Bean A的剩余初始化过程。
  3. 对于构造器注入的循环依赖:
    • Spring无法解决由于构造器引起的循环依赖问题。因为构造器注入要去所有依赖在构造阶段就必须得到满足,而在初始化的过程中一旦遇到循环依赖就无法决定哪个Bean应该先被创建。因此,当Spring检测到因构造器注入引起的循环依赖时,它将抛出一个异常,阻止容器继续运行。

总结,Spring能够解决的是由于通过setter或者是fieid注入引起的循环依赖,而对于构造器引起的循环依赖则需要手动解决。

19.Seata的实现原理?

Seata是一个开源的分布式事务解决方案,旨在提供高性能和简单易用的分布式事务服务。其设计目标是简化分布式事务的实现,提高系统的可扩展性和可用性。

在Seata的架构中,一共有三个角色:
TC-事务协调者: 维护全局和分支事务的状态,驱动全局事务提交或者回滚。
TM-事务管理器: 定义全局事务的范围:开始全局事务、提交或回滚全局事务
RM-资源管理器: 管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚
其中,TC为单独部署的server服务端,TM和RM为嵌入到应用中的client客户端

Seata的执行流程

Seata的整体执行流程设计为两阶段提交,其执行流程如下:
第一阶段:
所有的RM-资源管理器 执行自己的本地事务。在执行本地事务时,seata使用数据源代理,在执行SQL之前,对SQL进行解析,生成前置镜像SQL和后置镜像SQL,同时向undo-log中插入一条数据,方便后期出现异常做回滚,然后向TC-事务协调器 注册分支事务,提交本地事务,最后向TC提交他的事务状态。
第二阶段:
所有的RM本地事务都执行成功,此时TC-事务协调器 会向TC发起全局事务 提交,TC会立马释放全局锁然后异步驱动所有的RM做分支事务的提交。
存在一个RM本地事务不成功,此时TM会向TC发起全局事务回滚,TC会驱动所有的RM进行回滚操作,等待所有的RM回滚成功后再释放全局锁。

AT模型
阶段一RM的工作:
注册分支事务
记录undo-log(数据快照)
执行业务SQL并提交
报告事务状态
阶段二提交时RM的工作:
删除undo-log即可
阶段二回滚时RM的工作:
根据undo-log恢复数据到更新前

20.内存泄漏可能由哪些原因导致的?

内存泄漏是指对象在使用完之后没有进行回收,久而久之导致的内存溢出

  1. 静态集合类引起的内存泄漏 : 静态集合的生命周期和JVM一样,所以静态集合引用的对象不能被释放。
public class OOM {
	static List list = new ArrayList();

	public void oomTest(){
		Object obj = new Object();

		list.add(obj);
	}
}
  1. 单例模式: 和上面的例子原理类似,单例对象在初始化后会以静态变量的方式在JVM的整个生命周期中存在。如果单例对象持有外部的引用,那么这个外部的对象将不能被GC回收,导致内存泄漏。
  2. 数据链接、IO、Socket等链接: 创建的链接不再使用时没有调用close方法进行关闭链接,导致一直占用内存。
  3. 不合理的变量作用域: 一个变量的定义作用域大于其使用的范围,很可能导致内存泄漏;或不再使用对象时没有及时将对象设置为null,很可能导致内存泄漏的发生。
  4. hash值发生变化: 对象的hash值发生改变,使用HashMap、HashSet等容器的时候,由于对象修改之后的Hash值和存储进容器时的Hash值不同,所以无法找到存入的对象,自然就无法删除了,这个也会造成内存泄漏。这个也是为什么String类型设置成不可变类型的原因。
  5. ThreadLocal使用不当: ThreadLocal的弱引用导致内存泄漏也是一个经常发生的事情了,使用完ThreadLocal一定要调用remove方法进行清除。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

默语玄

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值