Java面试题总结 | Java面试题总结11- Spring模块(持续更新)

Spring

文章目录

什么是Spring

Spring就是一个轻量级的框架,主要是为了解决企业级程序开发的复杂性而创建的,他的核心思想为AOP和IOC

SpringMVC、Spring、SrpingBoot的区别

Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器框架。Spring使你能够编写更干净、更可管理、并且更易于测试的代码。

Spring MVC是Spring的一个模块,一个web框架。通过Dispatcher Servlet, ModelAndView 和 View Resolver,开发web应用变得很容易。主要针对的是网站应用程序或者服务开发——URL路由、Session、模板引擎、静态Web资源等等。

Spring配置复杂,繁琐,所以推出了Spring boot,约定优于配置,简化了spring的配置流程。

Spring Cloud构建于Spring Boot之上,是一个关注全局的服务治理框架。

Spring、SpringMVC和SpringBoot看这一篇就够了! - 腾讯云开发者社区-腾讯云 (tencent.com)

SSM和SpringBoot的区别

SSM(Spring+SpringMVC+MyBatis)框架集由Spring、SpringMVC、MyBatis三个开源框架整合而成,常作为数据源较简单的web项目的框架。

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

SpringMVC分离了控制器、模型对象、分派器以及处理程序对象的角色,这种分离让它们更容易进行定制。

MyBatis是一个支持普通SQL查询,存储过程和高级映射的优秀持久层框架。

而spring boot你可以看做一个启动、配置、快速开发的辅助框架,本身针对的是微服务。

IOC容器

BeanFactory: 采用的是延迟加载形式来注入 Bean 的,即只有在使用到某个 Bean 时(调用getBean()),才对该 Bean 进行加载实例化。这样,我们就不能发现一些存在的 Spring 的配置问题。如果 Bean 的某一个属性没有注入, BeanFacotry 加载后,直至第一次使用调用 getBean 方法才会抛出异常。

ApplicationContext,它是在容器启动时,一次性创建了所有的Bean。这样,在容器启动时,我们就可以发现 Spring 中存在的配置错误,这样有利于检查所依赖属性是否注入。 ApplicationContext 启动后预载入所有的单实例 Bean,通过预载入单实例 bean ,确保当你需要的时候,你就不用等待,因为它们已经创建好了。

其中 ApplicationContext 是 BeanFactory 的子接口。

容器的作用:包含了各种 Bean 的定义,读取 bean 配置文档,管理 bean 的加载、实例化,控制 bean 的生命周期,维护 bean 之间的依赖关系

Spring Boot Starter有什么用

和自动配置一样,Spring Boot Starter的目的也是简化配置,而Spring Boot Starter解决的是依赖管理配置复杂的问题,有了它,当我需要构建一个Web应用程序时,不必再遍历所有的依赖包,一个一个地添加到项目的依赖管理中,而是只需要一个配置spring-boot-starter-web, 同理,如果想引入持久化功能,可以配置spring-boot-starter-data-jpa

SpringBoot启动原理

@SpringBootConfiguration 
将当前类申明为配置类,同时还可以使用@bean注解将类以方法的形式实例化到spring容器
@EnableAutoConfiguration
他的作用就是扫描当前包以及子包,将有@Component@Controller@Service@Repository等注解的类注册到容器中,以便调用
@ComponentScan

首先,Spring Boot项目创建完成会默认生成一个名为 *Application 的入口类,我们是通过该类的main方法启动Spring Boot项目的。在main方法中,通过SpringApplication的静态方法,即run方法进行SpringApplication类的实例化操作,然后再针对实例化对象调用另外一个run方法来完成整个项目的初始化和启动

SpringApplication调用的run方法的大致流程,如下图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PHXflyPO-1683166124042)(D:/学习/JAVA/面经/面试题整理版本.assets/springboot-1.jpg)]

其中,SpringApplication在run方法中重点做了以下操作:

  • 获取监听器和参数配置;
  • 打印Banner信息;
  • 创建并初始化容器;
  • 监听器发送通知。

说说你对AOP的理解

AOP:面向切面编程思想,将那些与业务无关,却被业务模块所公用的逻辑封装起来,然后通过配置的方式,声明这些代码在什么地方、什么时候使用。减少了重复代码、降低了模块之间的耦合度。它的应用场景有事务、日志管理等。在项目中权限检查用到了

spring用到的设计模式

  • 工厂模式:BeanFactory /ApplicationContext就是简单工厂模式的体现,用来创建对象的实例;
  • 单例模式:Bean 默认为单例模式。
  • 代理模式:能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码降低模块间的耦合度,并有利于未来的可拓展性和可维护性
  • 模板方法:用来解决代码重复的问题。比如. RestTemplate,jdbcTemplate, JpaTemplate 等以 Template 结尾的对数据库操作的类,它们就使用到了模板模式

Spring是怎么解决循环依赖的?

首先,需要明确的是spring对循环依赖的处理有三种情况:

  1. 构造器的循环依赖:这种依赖spring是处理不了的,直接抛出BeanCurrentlylnCreationException异常。
  2. 单例模式下的setter循环依赖:通过“三级缓存”处理循环依赖。
  3. 非单例循环依赖:无法处理。

接下来,我们具体看看spring是如何处理第二种循环依赖的。

Spring单例对象的初始化大略分为三步:

  1. createBeanInstance:实例化,其实也就是调用对象的构造方法实例化对象;
  2. populateBean:填充属性,这一步主要是多bean的依赖属性进行填充;
  3. initializeBean:调用spring xml中的init 方法。

从上面讲述的单例bean初始化步骤我们可以知道,循环依赖主要发生在第一步、第二步。也就是构造器循环依赖和field循环依赖。 Spring为了解决单例的循环依赖问题,使用了三级缓存。

/** Cache of singleton objects: bean name –> bean instance */
private final Map singletonObjects = new ConcurrentHashMap(256);
/** Cache of singleton factories: bean name –> ObjectFactory */
private final Map> singletonFactories = new HashMap>(16);
/** Cache of early singleton objects: bean name –> bean instance */
private final Map earlySingletonObjects = new HashMap(16);

这三级缓存的作用分别是:

  • singletonFactories : 进入实例化阶段的单例对象工厂的cache (三级缓存);
  • earlySingletonObjects :完成实例化但是尚未初始化的,提前暴光的单例对象的Cache (二级缓存);
  • singletonObjects:完成初始化的单例对象的cache(一级缓存)。

我们在创建bean的时候,会首先从cache中获取这个bean,这个缓存就是sigletonObjects。主要的调用方法是:

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    
    
    Object singletonObject = this.singletonObjects.get(beanName);
    //isSingletonCurrentlyInCreation()判断当前单例bean是否正在创建中
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
    
    
        synchronized (this.singletonObjects) {
    
    
            singletonObject = this.earlySingletonObjects.get(beanName);
            //allowEarlyReference 是否允许从singletonFactories中通过getObject拿到对象
            if (singletonObject == null && allowEarlyReference) {
    
    
                ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                if (singletonFactory != null) {
    
    
                    singletonObject = singletonFactory.getObject();
                    //从singletonFactories中移除,并放入earlySingletonObjects中。
                    //其实也就是从三级缓存移动到了二级缓存
                    this.earlySingletonObjects.put(beanName, singletonObject);
                    this.singletonFactories.remove(beanName);
                }
            }
        }
    }
    return (singletonObject != NULL_OBJECT ? singletonObject : null);
}

从上面三级缓存的分析,我们可以知道,Spring解决循环依赖的诀窍就在于singletonFactories这个三级cache。这个cache的类型是ObjectFactory,定义如下:

public interface ObjectFactory<T> {
    
    
    T getObject() throws BeansException;
}

这个接口在AbstractBeanFactory里实现,并在核心方法doCreateBean()引用下面的方法:

protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
    
    
    Assert.notNull(singletonFactory, "Singleton factory must not be null");
    synchronized (this.singletonObjects) {
    
    
        if (!this.singletonObjects.containsKey(beanName)) {
    
    
            this.singletonFactories.put(beanName, singletonFactory);
            this.earlySingletonObjects.remove(beanName);
            this.registeredSingletons.add(beanName);
        }
    }
}

这段代码发生在createBeanInstance之后,populateBean()之前,也就是说单例对象此时已经被创建出来(调用了构造器)。这个对象已经被生产出来了,此时将这个对象提前曝光出来,让大家使用。

这样做有什么好处呢?让我们来分析一下“A的某个field或者setter依赖了B的实例对象,同时B的某个field或者setter依赖了A的实例对象”这种循环依赖的情况。A首先完成了初始化的第一步,并且将自己提前曝光到singletonFactories中,此时进行初始化的第二步,发现自己依赖对象B,此时就尝试去get(B),发现B还没有被create,所以走create流程,B在初始化第一步的时候发现自己依赖了对象A,于是尝试get(A),尝试一级缓存singletonObjects(肯定没有,因为A还没初始化完全),尝试二级缓存earlySingletonObjects(也没有),尝试三级缓存singletonFactories,由于A通过ObjectFactory将自己提前曝光了,所以B能够通过ObjectFactory.getObject拿到A对象(虽然A还没有初始化完全,但是总比没有好呀),B拿到A对象后顺利完成了初始化阶段1、2、3,完全初始化之后将自己放入到一级缓存singletonObjects中。此时返回A中,A此时能拿到B的对象顺利完成自己的初始化阶段2、3,最终A也完成了初始化,进去了一级缓存singletonObjects中,而且更加幸运的是,由于B拿到了A的对象引用,所以B现在hold住的A对象完成了初始化。

说说你对MVC的理解

Model(模型)、View(视图)、Controller(控制器)。Model代表的是数据,View代表的是用户界面,Controller代表的是数据的处理逻辑,它是Model和View这两层的桥梁。将软件分层的好处是,可以将对象之间的耦合度降低,便于代码的维护。

spring mvc执行流程

客户端发起HTTP请求,web服务接收到这个请求,如果DispatcherServlet的请求映射,web容器讲这个请求转交给DispatcherServlet处理

DispatcherServlet根据请求的信息以及HanlderMapping的配置找到处理请求的处理器Handler

通过HandlerAdapter对这个Handler请求处理器进行封装,在用统一的适配器接口调用handler

处理器完成业务逻辑之后返回,返回一个ModelAndView给到DisapatcherServlet,然后有ViewResoler完成逻辑视图名到真实视图对象的解析工作

最后DispatcherServlet使用这个视图对象对ModelAndView中的模型数据进行渲染

  1. 客户端(浏览器)发送请求,直接请求到 DispatcherServlet
  2. DispatcherServlet 根据请求信息调用 HandlerMapping,解析请求对应的 Handler
  3. 解析到对应的 Handler(也就是我们平常说的 Controller 控制器)后,开始由 HandlerAdapter 适配器处理。
  4. HandlerAdapter 会根据 Handler来调用真正的处理器开处理请求,并处理相应的业务逻辑。
  5. 处理器处理完业务后,会返回一个 ModelAndView 对象,Model 是返回的数据对象,View 是个逻辑上的 View
  6. ViewResolver 会根据逻辑 View 查找实际的 View
  7. DispaterServlet 把返回的 Model 传给 View(视图渲染)。
  8. View 返回给请求者(浏览器)

什么是XSS攻击

XSS攻击全称跨站脚本攻击,是一种在web应用中的计算机安全漏洞,它允许恶意web用户将代码植入到提供给其它用户使用的页面中。

Spring Boot Starter有什么用

和自动配置一样,Spring Boot Starter的目的也是简化配置,而Spring Boot Starter解决的是依赖管理配置复杂的问题,有了它,当我需要构建一个Web应用程序时,不必再遍历所有的依赖包,一个一个地添加到项目的依赖管理中,而是只需要一个配置spring-boot-starter-web, 同理,如果想引入持久化功能,可以配置spring-boot-starter-data-jpa

  • 引入模块所需的相关jar包
  • 自动配置各自模块所需的属性

原理

利用starter实现自动化配置只需要两个条件——maven依赖、配置文件,这里简单介绍下starter实现自动化配置的流程。
引入maven实质上就是导入jar包,spring-boot启动的时候会找到starter jar包中的resources/META-INF/spring.factories文件,根据spring.factories文件中的配置,找到需要自动配置的类

Spring Boot 在启动的时候会干这几件事情:

① Spring Boot 在启动时会去依赖的 Starter 包中寻找 resources/META-INF/spring.factories 文件,然后根据文件中配置的 Jar 包去扫描项目所依赖的 Jar 包。

② 根据 spring.factories 配置加载 AutoConfigure 类

③ 根据 @Conditional 注解的条件,进行自动配置并将 Bean 注入 Spring Context

其实就是 Spring Boot 在启动的时候,按照约定去读取 Spring Boot Starter 的配置信息,再根据配置信息对资源进行初始化,并注入到 Spring 容器中。这样 Spring Boot 启动完毕后,就已经准备好了一切资源,使用过程中直接注入对应 Bean 资源即可。

常用的starter

spring-boot-starter-web 用于构建Web,包含 RESTful 风格框架、SpringMVC和默认的嵌入式容器Tomcat
spring-boot-starter-test 用于测试
spring-boot-starter-data-jpa 带有Hibermate的Spring Data JPA
spring-boot-starter-jdbc 传统的JDBC
spring-boot-starter-thymeleaf 支持Thymeleaf模板
spring-boot-starter-mail 支持Java Mail、Spring Email 发送邮件
spring-boot-starter-integration Spring框架创建的一个API,面向企业应用集成(EAI)
spring-boot-starter-mobile SpringMVC的扩展,用来简化手机上的Web应用程序开发
spring-boot-starter-data-redis 通过Spring Data Redis、Redis Client使用Redis
spring-boot-starter-validation Bean Validation是一个数据验证的规范,Hibernate Validator是一个数据验证框架
spring-boot-starter-websocket 相对于非持久的协议HTTP,Websocket 是一个持久化的协议
spring-boot-starter-web-services SOAP Web Services
spring-boot-starter-hateoas 为服务添加HATEOAS功能
spring-boot-starter-security 用Spring Security进行身份验证和授权
spring-boot-starter-data-rest 用Spring Data REST公布简单的REST服务

SpringCloud解决了哪些问题?

Spring Cloud 是一套分布式服务治理框架,它本身不提供具体功能性的操作,只专注于服务之间的通信、熔断和监控等。因此,需要很多组件来共同支持一套功能。

1、与分布式系统相关的复杂性 – 包括网络问题,延迟开销,带宽问题,安全问题。

2、处理服务发现的能力 – 服务发现允许集群中的进程和服务找到彼此并进行通信。

3、解决冗余问题 – 冗余问题经常发生在分布式系统中。

4、负载平衡 – 改进跨多个计算资源(例如计算机集群,网络链接,中央处理单元)的工作负载分布。

5、减少性能问题 – 减少因各种操作开销导致的性能问题。

1.Eureka:服务注册于发现。

2.Feign:基于动态代理机制,根据注解和选择的机器,拼接请求 url 地址,发起请求。

3.Ribbon:实现负载均衡,从一个服务的多台机器中选择一台。

4.Hystrix:提供线程池,不同的服务走不同的线程池,实现了不同服务调用的隔离,避免了服务雪崩的问题。

5.Zuul:网关管理,由 Zuul 网关转发请求给对应的服务。

微服务的缺点和优点

缺点:

  • 1.运维要求较高;
  • 2.分布式的复杂性;
  • 3.接口调整成本高;
  • 4.学习难度曲线加大:需要掌握一系列的微服务开发技术
  • 5.处理分布式事务较棘手
  • 6.多服务运维难度,随着服务的增加,运维的压力也在增大

优点

1.每个微服务都很小,这样能聚焦一个指定的业务功能或业务需求。

2.微服务能够被小团队单独开发,这个小团队是2到5人的开发人员组成。

3.微服务是松耦合的,是有功能意义的服务,无论是在开发阶段或部署阶段都是独立的。

4.微服务能使用不同的语言开发。

5.微服务易于被一个开发人员理解,修改和维护,这样小团队能够更关注自己的工作成果。无需通过合作才能体现价值。

6.微服务允许你利用融合最新技术。

7.微服务只是业务逻辑的代码,不会和HTML,CSS 或其他界面组件混合。

将一个类声明为 Bean 的注解有哪些?

  • @Component :通用的注解,可标注任意类为 Spring 组件。如果一个 Bean 不知道属于哪个层,可以使用@Component 注解标注。
  • @Repository : 对应持久层即 Dao 层,主要用于数据库相关操作。
  • @Service : 对应服务层,主要涉及一些复杂的逻辑,需要用到 Dao 层。
  • @Controller : 对应 Spring MVC 控制层,主要用户接受用户请求并调用 Service 层返回数据给前端页面。

@Autowired 和 @Resource 的区别是什么?

  • @Autowired 是 Spring 提供的注解,@Resource 是 JDK 提供的注解。
  • Autowired 默认的注入方式为byType(根据类型进行匹配),@Resource默认注入方式为 byName(根据名称进行匹配)。
  • 当一个接口存在多个实现类的情况下,@Autowired@Resource都需要通过名称才能正确匹配到对应的 Bean。Autowired 可以通过 @Qualifier 注解来显示指定名称,@Resource可以通过 name 属性来显示指定名称。

Bean的生命周期

  • Bean 容器找到配置文件中 Spring Bean 的定义,实例化Bean实例
  • 利用 set()方法设置对象属性值。
  • 如果实现了其他 .Aware接口,就调用相应的方法。
  • 如果有和加载这个 Bean 的 Spring 容器相关的 BeanPostProcessor 对象,执行postProcessBeforeInitialization() 方法
  • 如果 Bean 在配置文件中的定义包含 init-method 属性,执行指定的方法。
  • 如果这个 Bean 实现了 BeanPostProcessor 接口,将会调用postProcessAfterInitialization(Object obj, String s)方法;由于这个方法是在 Bean 初始化结束时调用的,所以可以被应用于内存或缓存技术;
  • 当 Bean 不再需要时,会经过清理阶段,如果 Bean 实现了 DisposableBean 这个接口,会调用其实现的 destroy()方法;
  • 最后,如果这个 Bean 的 Spring 配置中配置了 destroy-method 属性,会自动调用其配置的销毁方法

单例 Bean 的线程安全问题了解吗?

大部分时候我们并没有在项目中使用多线程,所以很少有人会关注这个问题。单例 Bean 存在线程问题,主要是因为当多个线程操作同一个对象的时候是存在资源竞争的。

常见的有两种解决办法:

  1. 在 Bean 中尽量避免定义可变的成员变量。
  2. 在类中定义一个 ThreadLocal 成员变量,将需要的可变成员变量保存在 ThreadLocal 中(推荐的一种方式)。

不过,大部分 Bean 实际都是无状态(没有实例变量)的(比如 Dao、Service),这种情况下, Bean 是线程安全的

bean默认是单例模式,如何创建多个实例

有十个类,里面都有一个b对象,那么spring上下文中有几个b对象(因为spring的单例模式,所以是1个)想要多个怎么办?更改@Scope

@Scope注解是springIoc容器中的一个作用域,在 Spring IoC 容器中具有以下几种作用域:基本作用域singleton(单例)prototype(多例),Web 作用域(reqeust、session、globalsession),自定义作用域

@RequestBody@ResponseBody

用来接收前端传递给后端的json字符串中的数据,并把数据绑定到对应的java对象上

@ResponseBody注解告诉控制器,返回的对象需要自动化序列成JSON,并通过HttpResponseBody返回给客户端

Bean原理

1.读取Bean的定义信息
通过BeanDefinitionReader这个接口解析xml配置、配置类或其他的一些方式定义的类,得到BeanDefinition(Bean定义信息)

2.实例化Bean
通过BeanPostProcessor这个接口(增强器)可以对我们的BeanDefinition进行一些修改,然后BeanFactory通过反射实例化Bean对象,但是此时的Bean对象还没有进行初始化,没有填充属性等操作。

3.初始化Bean
(1)自定义属性赋值是用 set 方法赋值(populateBean())
(2)容器对象的属性赋值是用实现Aware接口的方式来赋值(invokeAwareMethods()),如BeanNameAware
(3)调用BeanPostProcessor的前置处理方法
(4)调用init初始化方法:init-method
(5)调用BeanPostProcessor的后置处理方法(AOP在这里实现)
(6)获得一个完整的对象,并将对象放入map中(通过Context.getBean()可以获取到Bean对象并使用)

4.销毁Bean
Spring容器关闭时会调用DisposableBean的Destory()方法
如果你在这个Bean中配置了destory-method属性,会自动调用指定的销毁方法

Bean的整体创建流程

  1. 利用该类的构造方法来实例化得到一个对象(但是如果一个类中有多个构造方法, Spring则会进行选择,这个叫做推断构造方法,下文在详细介绍)

  2. 得到一个对象后,Spring会判断该对象中是否存在被@Autowired注解了的属 性,把这些属性找出来并由Spring进行赋值(依赖注入)

  3. 依赖注入后,Spring会判断该对象是否实现了BeanNameAware接口、 BeanClassLoaderAware接口、BeanFactoryAware接口,如果实现了,就表示当前 对象必须实现该接口中所定义的setBeanName()、setBeanClassLoader()、 setBeanFactory()方法,那Spring就会调用这些方法并传入相应的参数

  4. Aware回调后,Spring会判断该对象中是否存在某个方法被@PostConstruct注解 了,如果存在,Spring会调用当前对象的此方法(初始化前),上面的代码中初始化了一个adminUser的数据。

  5. 紧接着,Spring会判断该对象是否实现了InitializingBean接口,如果实现了,就 表示当前对象必须实现该接口中的afterPropertiesSet()方法,那Spring就会调用当前对象中的afterPropertiesSet()方法(初始化)

  6. 最后,Spring会判断当前对象需不需要进行AOP,如果不需要那么Bean就创建完 了,如果需要进行AOP,则会进行动态代理并生成一个代理对象做为Bean(初始化 后)

关于第6步控制台没办法打出日志,因为初始后涉及到spring的源码操作,但是可以通过断点看一下,提一句例子中UserService是被LogAspect切面切的。

这样,一个Bean就创建完了,如果当前Bean是单例Bean,那么会把该Bean对象存入一个Map<String, Object>,Map的key为beanName,value为Bean对象。这样下次getBean时就可 以直接从Map中拿到对应的Bean对象了。(实际上,在Spring源码中,这个Map就 是单例池)

如果当前Bean是原型Bean,那么后续没有其他动作,不会存入一个Map,下次 getBean时会再次执行上述创建过程,得到一个新的Bean对象。

(1条消息) Spring: Bean的创建原理解析_Freedom3568的博客-CSDN博客_spring创建bean的原理

bean的创建方式

Spring支持如下三种方式创建Bean

1:调用构造器创建Bean

2:调用静态工厂方法创建Bean

3:调用实例工厂方法创建Bean

请求的时候,接口怎么知道参数类型,怎么识别参数,类到接口的流程

参数的绑定

1.表单提交的数据都是k=v格式的 username=Adair &password=123456

2.SpringMVC的参数绑定过程是把表单提交的请求参数,作为控制器中方法的参数进行绑定的

3.要求:提交表单的name和参数的名称是相同的。

4.使用@RequestParam绑定请求参数值
在处理方法参数处使用@RequestParam可以把请求参数传递给请求方法,其中:
– value:请求参数的参数名
– required:该参数是否必须,默认为true,
– defaultValue:请求参数的默认值,表示请求参数中必须包含对应的参数,若不存在,将抛出异常。

怎么样把某个请求映射到特定的方法上面

直接在方法上面加上注解@RequestMapping,并且在这个注解里面写上要拦截的路径

SpringBoot配置类

配置类:在springboot中被@Configuration或者@SpringBootConfiguration标注的类称之为配置类。

在配置类可以定义很多@Bean的方法,可以让这些@Bean修饰的方式让spring框架加载到ioc容器中去。

一个springboot项目中的加载的bean有那些呢?

程序员自己编写的开发的bean ,比如加了:@Service,@Mapper,@Controller,@RestController,@Component,@Repository的类,都会加载到ioc容器中。

如果一个类加了注解就能够加载到ioc容器中去吗?

不能,需要@ComponentScan扫包才行。刚好springboot的注解是一个复合注解其中就包含了@ComponentScan注解,然后springbooot启动类启动会去扫包把这些加了注解的bean全部加ioc容器中

思考为什么会存在配置类?

它其实就一种额外扩展和加载bean的一种机制。

可以方便进行额外的扩展和引用第三方的bean。如果没有这种机制,那么所以的中间件比如:mybatis,redis,jpa,kafka等这些初始化并且放入到ioc容器中必须全部要自己取编写。

springboot为了解放程序开发人员,所以提供starter机制,而这些个starter机制里面的原理全部都是:配置类+@Bean

如果官方提供的满足不了,当然你可以自己取定义配置类和@Bean方法进行加载到ioc容器中,进行额外扩展。

Mapper和xml的绑定

接口与文件的绑定关系

Mapper接口XML文件的绑定:通过 XML里mapper 标签的 namespace值(Mapper 接口的 包路径.接口名)绑定。

img

img

方法名的绑定关系

Mapper 接口的方法名与 XML 文件中标签的 id 值绑定。

img

img

例如select语句

查询语句是 MyBatis 中最常用的元素之一,简单查询的 select 元素是非常简单的。比如:

<mapper namespace="cn.mybatis.mydemo.mapper.StudentMapper">
    <select id="getStudent" parameterType="long" resultType="student">
        SELECT id,name,address FROM Student WHERE id =#{id}
    </select>
</mapper>

namespace是对应接口的全限定名,于是 MyBatis 上下文就可以通过它找到对应的接口。

<select> 元素表明这是一条查询语句
 id 标识了这条 SQL,id对应的是接口的某个方法
resultType="student" 表示返回的是一个 Student 类型的返回值。而 student是配置文件 mybatis-config.xml 配置的别名,指代的是 com.mybatis.domain.Student
parameterType="long" 说明传递给 SQL 的是一个 long 型的参数
#{id} 表示传递进去的参数

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SbWu3LAP-1683166124043)(D:/学习/JAVA/面经/面试题整理版本.assets/image-20220926140232743.png)]

SpringBoot常用的注解

@SpringBootApplication:SpringBoot的核心注解,用于开始自动配置,该注解里面包含了SpringBootConfiguration:声明为SpringBoot的应用的配置类、EnableAutoConfiguration:开启自动配置,配置各种组件、ComponentScan:用于扫描指定的包和组件

还有@import、@Conditional、@Autowired

  • @Component :通用的注解,可标注任意类为 Spring 组件。如果一个 Bean 不知道属于哪个层,可以使用@Component 注解标注。

  • @Repository : 对应持久层即 Dao 层,主要用于数据库相关操作。

  • @Service : 对应服务层,主要涉及一些复杂的逻辑,需要用到 Dao 层。

  • @Controller : 对应 Spring MVC 控制层,主要用于接受用户请求并调用 Service 层返回数据给前端页面。

  • @RestController注解是@Controller@ResponseBody的合集,表示这是个控制器 bean,并且返回JSON格式,是RestFul风格

  • @Scope:声明bean的作用域

  • @GetMapping(“users”) 等价于@RequestMapping(value="/users",method=RequestMethod.GET)

  • @Transient :声明不需要与数据库映射的字段,在保存的时候不需要保存进数据库 。

RestController和Controller的区别

@RestController注解,相当于@Controller+@ResponseBody两个注解的结合,返回json数据不需要在方法前面加@ResponseBody注解了,但使用@RestController这个注解,就不能返回jsp,html页面,视图解析器无法解析jsp,html页面

如果只是使用@RestController注解Controller,则Controller中的方法无法返回jsp页面,或者html,配置的视图解析器不起作用,返回的内容就是Return 里的内容。如果需要返回到指定页面,则需要用 @Controller配合视图解析器才行。

Spring框架的核心

AOP:面向切面的编程思想,将与业务无关,但被业务所共用的逻辑或者责任封装起来,然后通过配置的方式,声明这些代码在什么地方,什么时候进行调用。减少了重复代码,降低了模块之间的耦合度

IOC:控制反转的意思,传统的是程序员来创建A类所依赖的B类对象,而SpringIOC的思想为,创建好的B类对象交给Spring容器去管理,由Spring容器来控制依赖关系和生命周期,降低耦合

DI:是IOC的实现,动态的将某个以来关系注入到组件中

spring事务传播

分为三类:

  1. 第一类:支持当前事务的
    1. 如果当前没有事务,则新建一个事务,如果当前有事务,那么则加入到这个事务当中
    2. 支持当前事务,如果当前没有事务,则以非事务的方式进行
    3. 使用当前事务,如果没有事务则抛出异常
  2. 第二类:不支持当前事务的
    1. 以非事务的方式进行,如果当前存在事务,则把当前事务挂起
    2. 以非事务的方式进行,如果当前存在事务则抛出异常
    3. 创建一个新事务,如果当前存在事务,则把当前事务挂起
  3. 第三类:嵌套事务,如果当前存在事务,则在嵌套事务内执行,如果不存在事务则新建事务

Spring AOP不能对哪些类进行增强?

参考答案

  1. Spring AOP只能对IoC容器中的Bean进行增强,对于不受容器管理的对象不能增强。
  2. 由于CGLib采用动态创建子类的方式生成代理对象,所以不能对final修饰的类进行代理。

JDK动态代理和CGLIB有什么区别?

参考答案

JDK动态代理

这是Java提供的动态代理技术,可以在运行时创建接口的代理实例。Spring AOP默认采用这种方式,在接口的代理实例中织入代码。

CGLib动态代理

采用底层的字节码技术,在运行时创建子类代理的实例。当目标对象不存在接口时,Spring AOP就会采用这种方式,在子类实例中织入代码。

既然有没有接口都可以用CGLIB,为什么Spring还要使用JDK动态代理?

参考答案

在性能方面,CGLib创建的代理对象比JDK动态代理创建的代理对象高很多。但是,CGLib在创建代理对象时所花费的时间比JDK动态代理多很多。所以,对于单例的对象因为无需频繁创建代理对象,采用CGLib动态代理比较合适。反之,对于多例的对象因为需要频繁的创建代理对象,则JDK动态代理更合适。Spring事务

Spring 事务的本质其实就是数据库对事务的支持,没有数据库的事务支持, spring 是无法提供事务功

能的。真正的数据库层的事务提交和回滚是通过 binlog 或者 redo log 实现的。

Spring 事务的种类:

spring 支持编程式事务管理和声明式事务管理两种方式:

1 编程式事务管理使用TransactionTemplate。

2 声明式事务管理建立在 AOP 之上的。其本质是通过 AOP 功能,对方法前后进行拦截,将事务处理的功能编织到拦截的方法中,也就是在目标方法开始之前加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。

声明式事务最大的优点就是不需要在业务逻辑代码中掺杂事务管理的代码, 只需在配置文件中做相关的

事务规则声明或通过@Transactional 注解的方式,便可以将事务规则应用到业务逻辑中。

声明式事务管理要优于编程式事务管理,这正是 spring 倡导的非侵入式的开发方式,使业务代码不受污

染,只要加上注解就可以获得完全的事务支持。唯一不足地方是,最细粒度只能作用到方法级别,无法

做到像编程式事务那样可以作用到代码块级别。

Spring 事务中的隔离级别有哪⼏种

Spring的事务是基于数据库事务的基础上,如果数据库没有事务机制,那么Spring也就没有事务;

public enum Isolation {
    
    
    DEFAULT(-1),
    READ_UNCOMMITTED(1),
    READ_COMMITTED(2),
    REPEATABLE_READ(4),
    SERIALIZABLE(8);

    private final int value;

    private Isolation(int value) {
    
    
        this.value = value;
    }

    public int value() {
    
    
        return this.value;
    }
}

定义了五个表示隔离级别的常量:

(1) TransactionDefinition.ISOLATION_DEFAULT: 使⽤后端数据库默认的隔离级别, Mysql 默认采⽤的 REPEATABLE_READ 隔离级别;Oracle 默认采⽤的READ_COMMITTED 隔离级别.

(2) TransactionDefinition.ISOLATION_READ_UNCOMMITTED: 最低的隔离级别,允许读取尚未提交的数据变更, 可能会导致脏读、幻读或不可重复读

(3) TransactionDefinition.ISOLATION_READ_COMMITTED: 允许读取并发事务已经提交的数据, 可以阻止脏读,但是幻读或不可重复读仍有可能发生

(4) TransactionDefinition.ISOLATION_REPEATABLE_READ: 对同⼀字段的多次读取结果都是⼀致的,除非数据是被本身事务自己所修改, 可以阻止脏读和不可重复读,但幻读仍有可能发⽣。

(5) TransactionDefinition.ISOLATION_SERIALIZABLE: 最⾼的隔离级别,完全服从 ACID 的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会⽤到该级别。

Spring 事务中哪⼏种事务传播⾏为?

Spring在TransactionDefinition接口中规定了7种类型的事务传播行为,它们规定了事务方法和事务方法发生嵌套调用时如何进行传播:

    int PROPAGATION_REQUIRED = 0;
    int PROPAGATION_SUPPORTS = 1;
    int PROPAGATION_MANDATORY = 2;
    int PROPAGATION_REQUIRES_NEW = 3;
    int PROPAGATION_NOT_SUPPORTED = 4;
    int PROPAGATION_NEVER = 5;
    int PROPAGATION_NESTED = 6;

支持当前事务的情况:

(1)TransactionDefinition.PROPAGATION_REQUIRED: 如果当前存在事务, 则加入该事务;如果当前没有事务,则创建⼀个新的事务。使用的最多的一个事务传播行为,我们平时经常使用的@Transactional注解默认使用就是这个事务传播行为。

(2) TransactionDefinition.PROPAGATION_SUPPORTS: 如果当前存在事务,则加⼊该事务;如果当前没有事务,则以非事务的方式运行。

(3) TransactionDefinition.PROPAGATION_MANDATORY: 如果当前存在事务,则加⼊该事务;如果当前没有事务,则抛出异常。(mandatory:强制性)

不支持当前事务的情况:

(1)TransactionDefinition.PROPAGATION_REQUIRES_NEW: 创建⼀个新的事务,如果当前存在事务,则把当前事务挂起。

(2)TransactionDefinition.PROPAGATION_NOT_SUPPORTED: 以非事务的方式运行,如果当前存在事务,则把当前事务挂起。

(3)TransactionDefinition.PROPAGATION_NEVER: 以非事务的方式运行,如果当前存在事务,则抛出异常。

其他情况:

(1)TransactionDefinition.PROPAGATION_NESTED: 如果当前存在事务,则创建⼀个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED

说说Spring Boot的自动装配

使用Spring Boot时,我们只需引入对应的Starters,Spring Boot启动时便会自动加载相关依赖,配置相应的初始化参数,以最快捷、简单的形式对第三方软件进行集成,这便是Spring Boot的自动配置功能。Spring Boot实现该运作机制锁涉及的核心部分如下图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rgELCTQk-1683166124044)(D:/学习/JAVA/面经/面试题整理版本.assets/springboot-2.jpg)]

整个自动装配的过程是:Spring Boot通过@EnableAutoConfiguration注解开启自动配置,加载spring.factories中注册的各种AutoConfiguration类,当某个AutoConfiguration类满足其注解@Conditional指定的生效条件(Starters提供的依赖、配置或Spring容器中是否存在某个Bean等)时,实例化该AutoConfiguration类中定义的Bean(组件等),并注入Spring容器,就可以完成依赖框架的自动配置。

通过EnableAutoConfigurations注解开始自动配置,首先会从spring.factories中寻找有没有AutoConfiguration类满足Conditional注解的生效条件,有的话,就是实例化该AutoConfiguration类中定义的bean组件,然后加载到spring容器就实现了spring的自动装配

Bean的生命周期

  • Bean 容器找到配置文件中 Spring Bean 的定义,实例化Bean实例
  • 利用 set()方法设置对象属性值。
  • 如果实现了其他 .Aware接口,就调用相应的方法。
  • 如果有和加载这个 Bean 的 Spring 容器相关的 BeanPostProcessor 对象,执行postProcessBeforeInitialization() 方法
  • 如果 Bean 在配置文件中的定义包含 init-method 属性,执行指定的方法。
  • 如果这个 Bean 实现了 BeanPostProcessor 接口,将会调用postProcessAfterInitialization(Object obj, String s)方法;由于这个方法是在 Bean 初始化结束时调用的,所以可以被应用于内存或缓存技术;
  • 当 Bean 不再需要时,会经过清理阶段,如果 Bean 实现了 DisposableBean 这个接口,会调用其实现的 destroy()方法;
  • 最后,如果这个 Bean 的 Spring 配置中配置了 destroy-method 属性,会自动调用其配置的销毁方法

单例 Bean 的线程安全问题了解吗?

大部分时候我们并没有在项目中使用多线程,所以很少有人会关注这个问题。单例 Bean 存在线程问题,主要是因为当多个线程操作同一个对象的时候是存在资源竞争的。

常见的有两种解决办法:

  1. 在 Bean 中尽量避免定义可变的成员变量。
  2. 在类中定义一个 ThreadLocal 成员变量,将需要的可变成员变量保存在 ThreadLocal 中(推荐的一种方式)。

不过,大部分 Bean 实际都是无状态(没有实例变量)的(比如 Dao、Service),这种情况下, Bean 是线程安全的

公平锁和非公平锁只有两处不同:

  1. 非公平锁在调用 lock 后,首先就会调用 CAS 进行一次抢锁,如果这个时候恰巧锁没有被占用,那么直接就获取到锁返回了。
  2. 非公平锁在 CAS 失败后,和公平锁一样都会进入到 tryAcquire 方法,在 tryAcquire 方法中,如果发现锁这个时候被释放了(state == 0),非公平锁会直接 CAS 抢锁,但是公平锁会判断等待队列是否有线程处于等待状态,如果有则不去抢锁,乖乖排到后面。

公平锁和非公平锁就这两点区别,如果这两次 CAS 都不成功,那么后面非公平锁和公平锁是一样的,都要进入到阻塞队列等待唤醒。

相对来说,非公平锁会有更好的性能,因为它的吞吐量比较大。当然,非公平锁让获取锁的时间变得更加不确定,可能会导致在阻塞队列中的线程长期处于饥饿状态。

说说@Autowired和@Resource注解的区别

@Autowied是Spring提供的注解,@Resource是JDK提供的注解。@Autowied是只能按类型注入,@Resource默认按名称注入,也支持按类型注入。@Autowired按类型装配依赖对象,默认情况下它要求依赖对象必须存在,如果允许null值,可以设置它required属性为false,如果我们想使用按名称装配,可以结合@Qualifier注解一起使用

Spring Boot 实现热部署

Spring-boot-devtools

pom.xml 中直接添加依赖即可。

<dependency>

<groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId>

<scope>provided</scope>

<optional>true</optional></dependency

MyBatis的分页原理

1、通过page对象作为分页依据

2、通过count来进行查询总条数的限制

3、对原sql通过limit来进行分页的效果

img

Mybatis$和#的区别

$设置参数时,Mybatis只是创建普通的sql语句,然后再执行sql语句的时候将Mybatis参数直接拼入到sql里

使用#的时候,Myabatis会创建预编译的sql语句,然后在执行sql的时候Mybatis会为预编译sql中的占位符进行赋值。

预编译可以防止sql注入,执行效率更高

Mybatis的批量插入

<insert id="addByOneSQL">
    insert into user (username,address,password) values
    <foreach collection="users" item="user" separator=",">
        (#{user.username},#{user.address},#{user.password})
    </foreach>
</insert>

MyBatis 是如何进行分页的?分页插件的原理是什么?

答:(1) MyBatis 使用 RowBounds 对象进行分页,它是针对 ResultSet 结果集执行的内存分页,而非物理分页;(2) 可以在 sql 内直接书写带有物理分页的参数来完成物理分页功能,(3) 也可以使用分页插件来完成物理分页。

分页插件的基本原理是使用 MyBatis 提供的插件接口,实现自定义插件,在插件的拦截方法内拦截待执行的 sql,然后重写 sql,根据 dialect 方言,添加对应的物理分页语句和物理分页参数。

举例: select _ from student ,拦截 sql 后重写为: select t._ from (select \* from student)t limit 0,10

Mybatis缓存机制

MyBatis的缓存分为一级缓存和二级缓存。

一级缓存:

一级缓存也叫本地缓存,它默认会启用,并且不能关闭。一级缓存存在于SqlSession的生命周期中,即它是SqlSession级别的缓存。在同一个 SqlSession 中查询时,MyBatis 会把执行的方法和参数通过算法生成缓存的键值,将键值和查询结果存入一个Map对象中。如果同一个SqlSession 中执行的方法和参数完全一致,那么通过算法会生成相同的键值,当Map 缓存对象中己经存在该键值时,则会返回缓存中的对象。

二级缓存:

二级缓存存在于SqlSessionFactory 的生命周期中,即它是SqlSessionFactory级别的缓存。若想使用二级缓存,需要在如下两处进行配置。

在MyBatis 的全局配置settings 中有一个参数cacheEnabled,这个参数是二级缓存的全局开关,默认值是true ,初始状态为启用状态。

MyBatis 的二级缓存是和命名空间绑定的**,即二级缓存需要配置在Mapper.xml 映射文件中**。在保证二级缓存的全局配置开启的情况下,给Mapper.xml 开启二级缓存只需要在Mapper. xml 中添加如下代码:

<cache />

二级缓存具有如下效果:

  • 映射语句文件中的所有SELECT 语句将会被缓存。
  • 映射语句文件中的所有时INSERT 、UPDATE 、DELETE 语句会刷新缓存。
  • 缓存会使用Least Rece ntly U sed ( LRU ,最近最少使用的)算法来收回。
  • 根据时间表(如no Flush Int erv al ,没有刷新间隔),缓存不会以任何时间顺序来刷新。
  • 缓存会存储集合或对象(无论查询方法返回什么类型的值)的1024 个引用。
  • 缓存会被视为read/write(可读/可写)的,意味着对象检索不是共享的,而且可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。

什么是Tomcat

Tomcat 本身内含了一个 HTTP 服务器,所以也可以被当作一个 Web 服务器来使用

tomcat是一个中间件,在B/S架构中,浏览器发出的http请求经过tomcat中间件,转发到最终的目的服务器上,响应消息再通过tomcat返回给浏览器。

tomcat所做的事情主要有:开启监听端口监听用户的请求,解析用户发来的http请求然后访问到你指定的应用系统,然后你返回的页面经过tomcat返回给用户

Apache,Nginx和Tomcat的区别:

Apache全称是 Apache Http Server Project, Tomcat全称是 Apache Tomcat。

Apache和 Nginx用于处理静态资源, tomcat用来处理动态资源。

Apache和Nginx相比,Nginx适合做前端服务器,适合做负载均衡。

一般情况下,使用的时候,都是 Apache+Tomcat一起使用或者 Nginx+tomcat一起使用。

过滤器和拦截器的区别

对于过滤器和拦截器的区别, 知乎@Kangol LI 的回答很不错。

  • 过滤器(Filter):当你有一堆东西的时候,你只希望选择符合你要求的某一些东西。定义这些要求的工具,就是过滤器。
    ?q=架构&spm=1001.2101.3001.7020)中,浏览器发出的http请求经过tomcat中间件,转发到最终的目的服务器上,响应消息再通过tomcat返回给浏览器。

tomcat所做的事情主要有:开启监听端口监听用户的请求,解析用户发来的http请求然后访问到你指定的应用系统,然后你返回的页面经过tomcat返回给用户

Apache,Nginx和Tomcat的区别:

Apache全称是 Apache Http Server Project, Tomcat全称是 Apache Tomcat。

Apache和 Nginx用于处理静态资源, tomcat用来处理动态资源。

Apache和Nginx相比,Nginx适合做前端服务器,适合做负载均衡。

一般情况下,使用的时候,都是 Apache+Tomcat一起使用或者 Nginx+tomcat一起使用。

过滤器和拦截器的区别

对于过滤器和拦截器的区别, 知乎@Kangol LI 的回答很不错。

  • 过滤器(Filter):当你有一堆东西的时候,你只希望选择符合你要求的某一些东西。定义这些要求的工具,就是过滤器。
  • 拦截器(Interceptor):在一个流程正在进行的时候,你希望干预它的进展,甚至终止它进行,这是拦截器做的事情

猜你喜欢

转载自blog.csdn.net/qq_43167873/article/details/130481617