JAVA面试高级技术栈-06-Spring

JAVA面试高级技术栈-06-Spring

想要了解更多?:
JAVA面试高级技术栈-01-多线程编程
JAVA面试高级技术栈-02Linux基本指令
JAVA面试高级技术栈-03JVM(Java虚拟机)
JAVA面试高级技术栈-04-MySql优化
JAVA面试高级技术栈-05-Redis持久化
JAVA面试高级技术栈-06-Spring

sping简介

Spring是个轻量级的框架,通过IOC达到松耦合的目的,通过AOP可以分离应用业务逻辑和系统服务进行内聚性的开发,不过配置各种组件时比较繁琐,所以后面才出选了SpringBoot的框架。

优点

1、Spring是一个开源免费的框架 , 容器

2、Spring是一个轻量级的框架 , 非侵入式的

3、控制反转 IoC , 面向切面 Aop

4、对事物的支持 , 对框架的支持

一句话概括:

Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器(框架)。


IOC(控制反转)

传统开发中,需要调用对象的时候,需要调用者手动来创建被调用者的实例,即对象是由调用者new出来的
但是在Spring框架中,创建对象的工作不再由调用者来完成,而是交给IOC容器来创建,再推送给调用者,整个流程完成反转,所以控制反转降低对象之间的依赖关系。

创建一个bean(文章后面会介绍)的方式有xml方式、@Bean注解方式、@Component方式

我们在对一个bean进行实例化后,要对他的属性进行填充,大多数我们都是使用 @Autowire直接的填充依赖注入的,他是有限按照类型进行匹配。

IOC的具体化形式就是依赖注入(DI), 所谓的注入就是给属性赋初值


AOP(面向切面(方法)编程)

AOP是面向切面编程,可以将那些与业务不相关但是很多业务都要调用的代码抽取出来,思想就是不侵入原有代码的情况下对

功能进行增强。利用AOP可以对项目的业务逻辑的各个模块进行分离, 降低业务之间的耦合度

动态代理: 动态代理是程序运行期间, 创建目标对象的代理对象, 对目标对象的方法进行功能性增强(一个复制体, 但是可以进行功能增强, 将一些代码嵌入到对象代理中)

SpringAOP是基于动态代理实现的,动态代理是有两种,一种是jdk动态代理,一种是cglib动态代理;

  • jdk动态代理: 原理是利用反射来实现的,需要调用反射包下的Proxy类的newProxyInstance方法来返回代理对象,这个方法中有三个参数,分别是用于加载代理类的类加载器,被代理类实现的接口的class数组和一个用于增强方法的InvocaHandler实现类。(默认模式, 如果对象实现若干接口, 使用该形式去做代理)

  • cglib动态代理: 原理是利用asm开源包来实现的,是把被代理类的class文件加载进来,通过修改它的字节码生成子类来处理. (如果对象没有实现接口, 使用cglib库生成目标对象的代理, 注意:被final修饰过的方法无法获取动态代理)

jdk动态代理要求被代理类必须有实现的接口,生成的动态代理类会和代理类实现同样的接口,cglib则,生成的动态代理类会继承被代理类。Spring默认使用jdk动态代理,当被代理的类没有接口时就使用cglib动态代理

具体实现方案:

  • 已经对需要操作的目标对象进行代理
  • 指定其处理方式(通知类型,增强)

PS:

通知类型,增强: 前置, 后置, 环绕, 异常, 后置返回, 返回

切点: 指定增强的位置(整个类中方法, 指定的一个方法)

切面: 实现对通知和连接点(可以是切点)

常用的切面:

  • 一般切面(只有通知, 没有切点,会对目标对象的所有方法执行增强 )
  • 切点切面(指定方法进行增强)
  • 引介切面(特殊切面, 定义Filter设置)

原始的代理方式通过xml配置ProxyFactoryBean创建对象, 该方法不适合同时代理多个bean对象, spring中提供了自动地理的方案来解决多对象的同时代理


全局异常处理类

想要定义一个全局异常处理类的话,我们需要在这个类上添加@ContaollerAdvice注解,然后定义一些用于捕捉不同异常类型的方法,在这些方法上添加@ExceptionHandler(value = 异常类型.class)和@ResponseBody注解,方法参数是HttpServletRequest和异常类型,然后将异常消息进行处理。

如果我们需要自定义异常的话,就写一个自定义异常类,该类需要继承一个异常接口,类属性包括final类型的连续id、错误码、错误信息,再根据需求写构造方法;

使用 @ControllerAdvicearch + @ExceptionHandler 注解

这种方式的好处是简单统一,但是需要在每一层都要抛出异常,保证异常能够被controller层接收

// 指定要拦截的位置
@ControllerAdvice(annotations = {RestController.class, Controller.class}) 
// 设置以json的形式返回数据
@ResponseBody  
// 日志记录
@Slf4j  
public class GlobalExceptionHandler {  
  
    /**  
     * 帐号存在异常  
     * @param ex  
     * @return  
     */  
    @ExceptionHandler(SQLIntegrityConstraintViolationException.class) 
    //SQLIntegrityConstraintViolationException 违反了数据库的唯一约束条件,插入数据时
    public R<String> exceptionHandler(SQLIntegrityConstraintViolationException ex){  
        // 获取异常信息  
        String exMessage = ex.getMessage();  
        log.error("捕获到异常:{}",exMessage);  
  
        // 处理异常信息  
        if(exMessage.contains("Duplicate entry")){  
            String[] msg = exMessage.split(" ");  
            return R.error("帐号 "+msg[2]+" 已存在!");  
        }  
  
        return R.error("未知错误!");  
    }  
}

循环依赖

循环依赖就是在创建 A 实例的时候里面包含着 B 属性实例,所以这个时候就需要去创建 B 实例,而创 建 B 实例过程中也包含着 A 实例。 这样 A 实例还在创建的过程当中,所以就导致 A 和 B 实例都创建不出来。

spring通过三级缓存来解决循环依赖:

  • 一级缓存:单例池,缓存经过了已经初始化完毕的Bean

  • 二级缓存 :半成品池,缓存还未初始化完毕的Bean

  • 三级缓存:缓存的是获取Bean的代理对象的表达式

我们在创建 A 的过程中,先将 A 放入三级缓存 ,这时要创建B,B要创建A就直接去三级缓存中查找,并且判断需不需要进行 AOP 处理,如果需要就在三级缓存中获取A的代理对象,不需要就取A原始对象。然后将取出的对象放入二级缓存中,这个时候其他需要依赖 A 对象的直接从二级缓存中去获取即可。当B初始化完成进入一级缓存后,A 继续执行生命周期,当A完成了属性的注入后,就可以放入一级缓存了

spring2.6之前默认会解决循环依赖。在spring2.6之后需要通过配置开启解决循环依赖


依赖注入

依赖注入: Dependency Injection,它是 spring 框架核心 IOC 的具体实现,具体点就是依赖关系的维护称之为依赖注入
我们的程序在编写时, 通过控制反转, 把对象的创建交给了 spring,但是代码中不可能出现没有依赖的情况。

spring有4种依赖注入方式:

  1. Set方法注入
  2. 构造器注入
  3. 静态工厂的方法注入
  4. 实例工厂的方法注入
Set方法注入

首先我们有一个实体类User, 里面必须有set方法, 建议里面也有get方法, 这里为了方便展示只使用set方法, 在实际开发过程中应该同时有set和get方法

package org.example.bean;

import java.util.List;
import java.util.Map;

/**
 * @author TFY
 */

public class User {
    private  int id;
    private String name;
    private List<String> hobbies;
    private Map<String, String> map;

    public User() {
    }

    public User(int id, String name, List<String> hobbies, Map<String, String> map) {
        this.id = id;
        this.name = name;
        this.hobbies = hobbies;
        this.map = map;
    }

    /**
     * 设置
     * @param id
     */
    public void setId(int id) {
        this.id = id;
    }

    /**
     * 设置
     * @param name
     */
    public void setName(String name) {
        this.name = name;
    }
   
    /**
     * 设置
     * @param hobbies
     */
    public void setHobbies(List<String> hobbies) {
        this.hobbies = hobbies;
    }

    /**
     * 设置
     * @param map
     */
    public void setMap(Map<String, String> map) {
        this.map = map;
    }
}

然后我们在配置文件(applicationContext)中进行配置


<!--    spring容器中的每一个子元素都是一个bean对象-->
<!--    注入User类 id唯一标识 class对应类的全限定类名-->

    <bean id = "user" class="org.example.bean.User">
<!--      (主要)  属性注入: setter注入-->
        <property name="id" value="1"/>
        <property name="name" value="张三丰"/>

<!--        数组和list集合是相同的, set的话替换为set标签-->
        <property name="hobbies">
            <list>
                <value>1</value>
                <value>2</value>
                <value>3</value>
            </list>
        </property>

<!--        map类型-->
        <property name="map">
            <map>
<!--                键值对-->
                <entry key="1" value="巧克力"/>
                <entry key="2" value="草莓饼干"/>
            </map>
        </property>
    </bean>
</beans>

最后是测试方法

import org.example.bean.User;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class test {

    //由java管理
    @Test
    public void Test(){
        User user = new User();
        user.setName("金毛狮王谢逊");
        System.out.println(user);
    }

    //由spring框架管理
    @Test
    public void Test2(){
//        先读取spring配置文件
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
//        然后获取容器中的bean对象
        User user = context.getBean("user", User.class);
        System.out.println(user);
    }
}
构造器注入

我们可以在user实体类中加入构造函数如下

    public User(String name, int id) {
        this.name = name;
        this.id = id;
    }

配置文件修改如下

<!--    spring容器中的每一个子元素都是一个bean对象-->
<!--    注入User类 id唯一标识 class对应类的全限定类名-->
    <bean id = "user" class="org.example.bean.User">
        <constructor-arg name="name" value="小龙女"/>
        <constructor-arg name="id" value="2"/>
    </bean>

set和构造器注入有些麻烦, 于是我们进行简化, 在beans标签中添加如下即可

<!--   (重要) 简化配置, 只需要将实体类注入容器, 不需要多余的配置信息 , 获取的话需要new对象或者@Autowired-->
    <context:component-scan base-package ="org.example.bean"/>

之前都是使用 bean 的标签进行bean注入,但是实际开发中,我们一般都会使用注解!

注解注入

@Component

@Component("user")
// 相当于配置文件中 <bean id="user" class="当前注解的类"/>
public class User {
   public String name = "金毛狮王谢逊";
}
属性注入
  • 可以不用提供set方法,直接在直接名上添加@value(“值”)
@Component("user")
// 相当于配置文件中 <bean id="user" class="当前注解的类"/>
public class User {
   @Value("金毛狮王谢逊")
   // 相当于配置文件中 <property name="name" value="金毛狮王谢逊"/>
   public String name;
}
  • 如果提供了set方法,在set方法上添加@value(“值”)
@Component("user")
public class User {

   public String name;

   @Value("金毛狮王谢逊")
   public void setName(String name) {
       this.name = name;
  }
}

@Component三个衍生注解

为了更好的进行分层,Spring可以使用其它三个注解,功能一样,目前使用哪一个功能都一样。

  • @Controller:controller层
  • @Service:service层
  • @Repository:dao层

写上这些注解,就相当于将这个类交给Spring管理装配了!


Bean

在Spring框架中,Bean是指在Spring容器中管理的一个对象。Spring容器通过对Bean的管理,实现了依赖注入、面向切面编程、声明式事务管理等核心特性。Spring的Bean可以是普通Java对象、JavaBean、POJO(Plain Old Java Object)等类型的对象,还可以是数据源、连接池、JMS(Java消息服务)等非常复杂的对象。

在这里插入图片描述

Bean 的作用域

@scope(Singleton)

(1)Singleton:一个IOC容器只有一个

(2)Prototype:每次调用getBean()都会生成一个新的对象

(3)request:每个http请求都会创建一个自己的bean

(4)session:同一个session共享一个实例

(5)application:整个serverContext只有一个bean

(6)webSocket:一个websocket只有一个bean

Bean 生命周期

实例化 Instantiation->属性赋值 Populate->初始化 Initialization->销毁 Destruction
在这四步的基础上面,Spring 提供了一些拓展点:

  • Bean 自身的方法: 包括了 Bean 本身调用的方法和通过配置文件中的 init-method 和 destroy-method 指定的方法

  • Bean 级生命周期接口方法:包括了 BeanNameAware、BeanFactoryAware、InitializingBean 和 DiposableBean 这些接口的方法

  • 容器级生命周期接口方法:包括了 InstantiationAwareBeanPostProcessor 和 BeanPostProcessor 这两个接口实现,一般称它们的实现类为“后处理器”。

  • 工厂后处理器接口方法: 包括了 AspectJWeavingEnabler, ConfigurationClassPostProcessor, CustomAutowireConfigurer 等等非常有用的工厂后处理器接口的方法。工厂后处理器也是容器级的。在应用上下文装配配置文件之后立即调用。


Spring 事务

spring事务有编程式和声明式,我们一般使用声明式,在某个方法上增加@Transactional注解,这个方法中的sql会统一成功或失败。

原理是:

当一个方法加上@Transactional注解,spring会基于这个类生成一个代理对象并将这个代理对象作为bean,当使用这个bean中的方法时,如果存在@Transactional注解,就会将事务自动提交设为false,然后执行方法,执行过程没有异常则提交,有异常则回滚

事务的声明:

  • 编程式

spring事务失效场景

(1)事务方法所在的类没有加载到容器中

(2)事务方法不是public类型

(3)同一类中,一个没有添加事务的方法调用另外以一个添加事务的方法,事务不生效

(4)spring事务默认只回滚运行时异常,可以用rollbackfor属性设置

(5)业务自己捕获了异常,事务会认为程序正常秩序

spring事务的隔离级别

default:默认级别,使用数据库自定义的隔离级别

其它四种隔离级别与mysql一样 , 参考JAVA面试高级技术栈-04-MySql优化-CSDN博客

spring事务的传播行为

(1)支持当前事务,如果不存在,则新启一个事务

(2)支持当前事务,如果不存在,则抛出异常

(3)支持当前事务,如果不存在,则以非事务方式执行

(4)不支持当前事务,创建一个新事务

(5)不支持当前事务,如果已存在事务就抛异常

(6)不支持当前事务,始终以非事务方式执行


spring设计模式

BeanFactory用了工厂模式,AOP用了动态代理模式,RestTemplate用来模板方法模式,SpringMVC中handlerAdaper用来适配器模式,Spring里的监听器用了观察者模

具体可以参考这篇文章: 七种常用的设计模式-CSDN博客


SpringMVC工作原理

MVC模式: Model-View-Controller(数据-视图-控制器)软件架构模式, 目的是将用户的操作界面和业务逻辑进行分离

SpringMVC工作过程围绕着前端控制器DispatchServerlet,几个重要组件有HandleMapping(处理器映射器)、HandleAdapter(处理器适配器)、ViewReslover(试图解析器)

工作流程:

(1)DispatchServerlet接收用户请求将请求发送给HandleMapping

(2)HandleMapping根据请求url找到具体的handle和拦截器,返回给DispatchServerlet

(3)DispatchServerlet调用HandleAdapter,HandleAdapter执行具体的controller,并将controller返回的ModelAndView返回给DispatchServler

(4)DispatchServerlet将ModelAndView传给ViewReslover,ViewReslover解析后返回具体view

(5)DispatchServerlet根据view进行视图渲染,返回给用


springcloud主要解决什么问题?

解决服务之间的通信、容灾、负载平衡、冗余问题,能方便服务集中管理,常用组件有注册中心、配置中心、远程调用。服务熔断、网关

CAP理论

C:一致性,这里指的强一致性,也就是数据更新完,访问任何节点看到的数据完全一致

A:可用性,就是任何没有发生故障的服务必须在规定时间内返回合正确结果

P:容灾性,当网络不稳定时节点之间无法通信,造成分区,这时要保证系统可以继续正常服务。提高容灾性的办法就是把数据分配到每一个节点当中,所以P是分布式系统必须实现的,然后需要在C和A中取舍

为什么不能同时保证一致性和可用性呢?

当网络发生故障时,如果要保障数据一致性,那么节点相互间就只能阻塞等待数据真正同步时再返回,就违背可用性了。如果要保证可用性,节点要在有限时间内将结果返回,无法等待其它节点的更新消息,此时返回的数据可能就不是最新数据,就违背了一致性了

熔断限流的理解?

SprngCloud中用Hystrix组件来进行

  • 限流:限制并发的请求访问量,超过阈值则拒绝;

  • 降级:服务分优先级,牺牲非核心服务(不可用),保证核心服务稳定;从整体负荷考虑;

  • 熔断:依赖的下游服务故障触发熔断,避免引发本系统崩溃;系统自动执行和恢复降级

熔断

概念:应对微服务雪崩效应的一种链路保护机制。

在Spring Cloud框架里,熔断机制通过Hystrix实现。Hystrix会监控微服务间调用的状况,当失败的调用到一定阈值,缺省是5秒内20次调用失败,就会启动熔断机制

是对于消费者来讲,当对提供者请求时间过久时为了不影响性能就对链接进行熔断,

限流

是对于提供者来讲,为了防止某个消费者流量太大,导致其它更重要的消费者请求无法及时处理。限流可用通过拒绝服务、服务降级、消息队列延时处理、限流算法来实现

降级

服务降级是指在面对高并发请求的时候,根据实际业务使用情况以及流量,对一些服务和页面有策略的不处理或者用一种简单的方式进行处理,从而释放服务器资源的资源以保证核心业务的正常高效运行

服务降级和服务熔断区别
  • 触发原因不一样,服务熔断由链路上某个服务引起的,服务降级是从整体的负载考虑
  • 管理目标层次不一样,服务熔断是一个框架层次的处理,服务降级是业务层次的处理
    • 实现方式不一样,服务熔断一般是自我熔断恢复,服务降级相当于人工控制
  • 触发原因不同 服务熔断一般是某个服务(下游服务)故障引起,而服务降级一般是从整体负荷考虑;

一句话:

服务熔断是应对系统服务雪崩的一种保险措施,给出的一种特殊降级措施。而服务降级则是更加宽泛的概念,主要是对系统整体资源的合理分配以应对压力。

常用限流算法

计数器算法:使用redis的setnx和过期机制实现

漏桶算法:一般使用消息队列来实现,系统以恒定速度处理队列中的请求,当队列满的时候开始拒绝请求。

令牌桶算法:计数器算法和漏桶算法都无法解决突然的大并发,令牌桶算法是预先往桶中放入一定数量token,然后用恒定速度放入token直到桶满为止,所有请求都必须拿到token才能访问系统


Hystrix

在分布式环境中,许多服务依赖的一些服务中不可避免地会存在一些失败的情况。 Hystrix是这样一个框架,通过添加容忍延迟和容错逻辑,可帮助我们控制这些分布式服务之间的交互。 Hystrix通过隔离各个服务之间的访问点,减少服务之间的级联故障以及为我们提供回滚策略来实现这一点,所有这些都可以提高分布式系统的整体弹性。
Hystrix现在已经停止维护, 可以使用SpringCloudSentinel来代替
具体参考:sentinel (史上最全)-CSDN博客

目标

  • 通过第三方客户端库(通常通过网络)访问依赖关系,从而对延迟和故障进行保护和控制。
  • 停止复杂分布式系统中的级联故障。
  • 失败迅速并迅速恢复。
  • 可能的情况下回退并优雅地降级。
  • 启用近实时监控,警报和操作控制。

Spring Security OAuth2

OAuth 2.0是用于授权的行业标准协议。OAuth 2.0为简化客户端开发提供了特定的授权流,包括Web应用、桌面应用、移动端应用等。

OAuth2.0 相关名词解释

  • Resource owner(资源拥有者):拥有该资源的最终用户,他有访问资源的账号密码;
  • Resource server(资源服务器):拥有受保护资源的服务器,如果请求包含正确的访问令牌,可以访问资源;
  • Client(客户端):访问资源的客户端,会使用访问令牌去获取资源服务器的资源,可以是浏览器、移动设备或者服务器;
  • Authorization server(认证服务器):用于认证用户的服务器,如果客户端认证通过,发放访问资源服务器的令牌。

Spring Cloud Sleuth

Spring Cloud Sleuth是一个分布式追踪系统,用于在分布式系统中跟踪和解决请求的性能问题。它提供了一套API和工具,帮助开发人员在微服务架构中跟踪请求的流程,并可以追踪请求在不同微服务之间的传递。

Spring Cloud Sleuth通过在请求的不同环节中添加唯一的标识符,称为Trace ID和Span ID,来追踪请求的路径和性能。Trace ID是整个请求的唯一标识符,Span ID是每个环节中的唯一标识符。通过这些标识符,开发人员可以分析请求的流程和性能瓶颈,并进行优化。

注意事项

使用Spring Cloud Sleuth时,需要注意以下几点:

  1. 确保所有微服务都添加了Spring Cloud Sleuth的依赖,并配置了相同的Trace ID和Span ID。
  2. 需要注意微服务之间的调用关系,确保在调用方将Trace ID和Span ID传递给被调用方,并在被调用方正确接收和处理这些信息。
  3. 需要适当地配置日志输出,以便在日志中查看请求的Trace ID和Span ID,以及相关的性能信息。

Spring Cloud Sleuth

Spring Cloud Sleuth是一个分布式追踪系统,用于在分布式系统中跟踪和解决请求的性能问题。它提供了一套API和工具,帮助开发人员在微服务架构中跟踪请求的流程,并可以追踪请求在不同微服务之间的传递。

Spring Cloud Sleuth通过在请求的不同环节中添加唯一的标识符,称为Trace ID和Span ID,来追踪请求的路径和性能。Trace ID是整个请求的唯一标识符,Span ID是每个环节中的唯一标识符。通过这些标识符,开发人员可以分析请求的流程和性能瓶颈,并进行优化。

注意事项

使用Spring Cloud Sleuth时,需要注意以下几点:

  1. 确保所有微服务都添加了Spring Cloud Sleuth的依赖,并配置了相同的Trace ID和Span ID。
  2. 需要注意微服务之间的调用关系,确保在调用方将Trace ID和Span ID传递给被调用方,并在被调用方正确接收和处理这些信息。
  3. 需要适当地配置日志输出,以便在日志中查看请求的Trace ID和Span ID,以及相关的性能信息。
  4. 如果需要自定义Trace ID和Span ID的生成方式,可以实现自定义的Trace和Span生成器,并在配置文件中配置使用自定义生成器。
  • 19
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值