Java面试之Spring知识点总结

定义

  • Spring是一个轻量级的开源的IOC和AOP容器框架。是为Java应用程序提供基础性服务的一套框架,目的是用于简化企业应用程序的开发,它使得开发者只需要关心业务需求。

优点

  • spring属于低侵入式设计,代码的污染极低
  • spring的DI机制将对象之间的依赖关系交由框架处理,减低组件的耦合性
  • Spring提供了AOP技术,支持将一些通用任务,如安全、事务、日志、权限等进行集中式管理,从而提供更好的复用。
  • spring对于主流的应用框架提供了集成支持。

IOC(Inversion of Control )

  • 定义

    • 当对象A实例化和运行时,如果需要对象B的话,IOC容器会主动创建一个对象B注入到对象A所需要的地方,对象A获得依赖对象B的过程,由主动行为变成了被动行为,即把创建对象交给了IOC容器处理,控制权颠倒过来了,这就是控制反转的由来!
  • IOC和DI的关系

    • IoC的一个重点是在系统运行中,动态的向某个对象提供它所需要的其他对象。这一点是通过DI(Dependency Injection,依赖注入)来实现的
  • DI(Dependency Injection)

    • 定义

      • 组件之间依赖关系由容器在运行期决定,由容器动态的将某个依赖关系注入到组件之中。
    • 依赖注入

      • 手动装配

        • 构造方法注入:被注入的对象可以通过在其构造方法中声明参数列表,让 IoC 容器知道它需要依赖哪些对象

          <bean id="user" class="com.bean.User">
                 <constructor-arg index="0"><value>张三</value></constructor-arg>
                 <constructor-arg index="1"><value>21</value></constructor-arg>
                 <constructor-arg index="2" ref="address"></constructor-arg>
             </bean>
          
      • setter注入:为其需要依赖的对象增加 setter 方法,可以通过 setter 方法将其依赖的对象注入到对象中

<bean id="user" class="com.bean.User">
        <property name="name" value="张三"></property>
        <property name="age" value="21"></property>
        <property name="student" ref="address"></property>
    </bean>
	- 自动装配

		- 原因:由于在手动配置xml过程中,常常发生字母缺漏和大小写等错误,而无法对其进行检查,使得开发效率降低。采用自动装配将避免这些错误,并且使配置简单化。
		- 自动装配


			- xml实现自动装配

				- byName:Spring会自动寻找一个与该属性名称相同或id相同的Bean,注入进来

					- <!--    让address根据name自动装配-->
				- byType:Spring会自动寻找一个与该属性类型相同的Bean,注入进来。

					- <!--    让address根据type自动装配-->
				- constructor:Spring会寻找与该Bean的构造函数各个参数类型相匹配的Bean,通过构造函数注入进来

					- <!--    让address根据constructor自动装配-->
				- autodetect:Spring容器会首先尝试构造器注入,然后尝试按类型注入

					- <!--    让address根据constructor自动装配-->
			- 注解实现自动装配

				- @Autowired

					- 可以通过 @Autowired 自动装配 bean,它可以在 setter 方法,构造函数或字段中使用
					- 默认情况下,@Autowired将执行相关检查,以确保属性已经装配正常。当Spring无法找到匹配的Bean装配,它会抛出异常。要解决这个问题,可以通过 @Autowired 的“required”属性设置为false来禁用此检查功能。
					- @Qualifier注解在@Autowired下方使用,用来控制哪个bean自动装配到该字段。

				- @Resource
				- @Autowired 和 @Resource的区别

					- @Resource 默认情况下是按照名称进行注入,如果没有找到相同名称的Bean,则会按照类型进行注入;由J2EE提供,需要导入包javax.annotation.Resource。
					- @Autowired 根据类型注入,@Autowired @Qualifier("userService") 是直接按照名程进行注入
					- @Autowired是spring自带的注解,@Resourc是Java原生的注解,更具移植性。
  • 优点

    • 降低组件之间的耦合性
    • 提高程序的灵活性和可维护性
  • 缺点

    • 生成一个对象的步骤变复杂了
    • 使用反射创建对象,在效率上有些损耗,启动慢

AOP(Asepct Orentid Programming)

  • 定义

    • 面向切面编程,作为面向对象的一种补充,用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取并封装为一个可重用的模块,这个模块被命名为“切面”(Aspect)。
  • 实现方式

    • 静态代理(AspectJ)

      • 方式

        • AspectJ是静态代理,也称为编译时增强,AOP框架会在编译阶段生成AOP代理类,并将AspectJ(切面)织入到Java字节码中,运行的时候就是增强之后的AOP对象。
      • 优点

        • AspectJ的静态代理方式具有更好的性能
      • 缺点

        • AspectJ专门的编译器用来生成遵守Java字节编码规范的Class文件
    • 动态代理(Spring AOP)

      • JDK代理

        • 方式

          • JDK动态代理只提供接口的代理,不支持类的代理,要求被代理类实现接口。JDK动态代理的核心是InvocationHandler接口和Proxy类,在获取代理对象时,使用Proxy类来动态创建目标类的代理类(即最终真正的代理类,这个类继承自Proxy并实现了我们定义的接口),当代理对象调用真实对象的方法时, InvocationHandler 通过invoke()方法反射来调用目标类中的代码,动态地将横切逻辑和业务编织在一起
        • 优点

          • Java本身支持,不用担心依赖问题,随着版本稳定升级
          • 代码实现简单
        • 缺点

          • 目标类必须实现某个接口,换言之,没有实现接口的类是不能生成代理对象的
          • 代理的方法必须都声明在接口中,否则,无法代理
          • 执行速度性能相对cglib较低
      • CGLIB代理

        • 方式

          • 在运行时动态的生成指定类的一个子类对象,并覆盖其中特定方法并添加增强代码,从而实现AOP。
        • 优点

          • 代理的类无需实现接口
          • 执行速度相对JDK动态代理较高
        • 缺点

          • 字节码库需要进行更新以保证在新版java上能运行
          • 动态创建代理对象的代价相对JDK动态代理较高
          • CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。
  • 优点

    • 减少系统中的重复代码,降低了模块间的耦合度,提高系统的可维护性。
  • 应用场景

    • AOP可用于权限认证、日志、事务处理。

Bean生命周期

  • 与普通java对象的区别

    • 于普通的Java对象,当new的时候创建对象,当它没有任何引用的时候被垃圾回收机制回收。而由Spring IoC容器托管的对象,它们的生命周期完全由容器控制。
  • 生命周期

    • (1)实例化Bean

      • 对于BeanFactory容器,当客户向容器请求一个尚未初始化的bean时,或初始化bean的时候需要注入另一个尚未初始化的依赖时,容器就会调用createBean进行实例化。 对于ApplicationContext容器,当容器启动结束后,便实例化所有的bean
      • 实例化对象被包装在BeanWrapper对象中,BeanWrapper提供了设置对象属性的接口,从而避免了使用反射机制设置属性
    • (2)设置对象属性(依赖注入)

      • 实例化后的对象被封装在BeanWrapper对象中,并且此时对象仍然是一个原生的状态,并没有进行依赖注入
      • 紧接着,Spring根据BeanDefinition中的信息进行依赖注入。并且通过BeanWrapper提供的设置属性的接口完成依赖注入
    • (3)注入Aware接口

      • 紧接着,Spring会检测该对象是否实现了xxxAware接口,并将相关的xxxAware实例注入给bean。

        • 如果这个Bean实现了BeanNameAware接口,会调用它实现的setBeanName(String beanId)方法,此处传递的是Spring配置文件中Bean的ID
        • 如果这个Bean实现了BeanFactoryAware接口,会调用它实现的setBeanFactory(),传递的是Spring工厂本身(可以用这个方法获取到其他Bean)
        • 如果这个Bean实现了ApplicationContextAware接口,会调用setApplicationContext(ApplicationContext)方法,传入Spring上下文
      • Spring提供了广泛的Aware回调接口,让bean向容器表明它们需要某种基础设施依赖。

    • (4)BeanPostProcessor

      • 通过实现BeanPostProcessor接口,对所有的bean进行一个初始化之前和之后的代理
    • (5)InitializingBean与init-method

      • 可以针对某个具体的bean进行配置
    • (6)DisposableBean和destroy-method

      • 和init-method一样,通过给destroy-method指定函数,就可以在bean销毁前执行指定的逻辑。
    • 参考博客

      • https://www.zhihu.com/question/38597960

Bean作用域

  • singleton:在spring IoC容器仅存在一个Bean实例,Bean以单例方式存在。singleton是bean作用域范围的默认值。

    • 线程安全问题:如果单例Bean,是一个无状态Bean,也就是线程中的操作不会对Bean的成员执行查询以外的操作,那么这个单例Bean是线程安全的。比如Spring mvc 的 Controller、Service、Dao等,这些Bean大多是无状态的,只关注于方法本身。

      • 如何解决?

        • 最浅显的解决办法就是将有状态的bean的作用域由“singleton”改为“prototype”。
        • 采用ThreadLocal解决线程安全问题,为每个线程提供一个独立的变量副本,不同线程只操作自己线程的副本变量。
  • prototype:每次从容器中请求Bean时,都会创建一个新的Bean实例并返回。

    • 线程安全问题:对于原型Bean,每次创建一个新对象,也就是线程之间并不存在Bean共享,自然是不会有线程安全的问题。
  • request:Spring IOC容器为每个HTTP请求创建一个新的Bean实例,在单个HTTP请求中都会复用这一个对象。

  • session:同一个HTTP Session共享一个Bean,不同Session使用不同的Bean。在session过期后,bean会随之失效。

  • global-session:所有Session共享一个Bean实例。

设计模式

  • 简单工厂

    • 简单工厂模式的实质是由一个工厂类根据传入的参数,动态决定应该创建哪一个产品类。 通过BeanFactory和ApplicationContext来创建对象。
    • Spring中的BeanFactory就是简单工厂模式的体现,根据传入一个唯一的标识来获得Bean对象。
  • 单例模式:

    • 保证一个类仅有一个实例,并提供一个访问它的全局访问点。
    • Spring下默认的bean均为singleton,可以通过 scope=“singleton”来指定;spring提供了单例对象的全局访问点:BeanFactory。
  • 观察者模式

    • 定义对象间的一种一对多的依赖关系,当一个对象的状态发生变化时,所有依赖它的对象都得到通知并自动更新。
    • Spring 事件驱动模型
  • 动态代理

    • 提供了对目标对象额外的访问方式,即通过代理对象访问目标对象,这样可以在不修改原目标对象的前提下,提供额外的功能操作,扩展目标对象的功能
    • Spring AOP就是基于动态代理的,如果要代理的对象实现了某个接口,那么Spring AOP会使用JDK Proxy,去创建代理对象,而对于没有实现接口的对象,Spring AOP会使用Cglib,这时候Spring AOP会使用Cglib生成一个被代理对象的子类来作为代理
  • 模板方法模式

    • 它定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。 模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤的实现方式。
    • Spring 中 jdbcTemplate、hibernateTemplate 等以 Template 结尾的对数据库操作的类,它们就使用到了模板模式。
  • 策略模式

    • 例如Resource的实现类,针对不同的资源文件,实现了不同方式的资源获取策略

事务

  • Spring事务底层是基于数据库事务和AOP机制实现的,以Spring事务隔离级别为准,因为他重写了数据库的隔离级别;当使用 @Transactional(isolation = Isolation.DEFAULT)时,才会使用数据库设置的隔离级别。

  • 实现方式

    • 声明式:只需在配置文件中做相关的事务规则声明或通过@Transactional注解的方式,便可以将事务规则应用到业务逻辑中,减少业务代码的污染。声明式事务管理建立在AOP之上的。其本质是通过AOP功能,对方法前后进行拦截,将事务处理的功能编织到拦截的方法中,也就是在目标方法开始之前启动一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。

    • 编程式:在代码中调用beginTransaction()、commit()、rollback()等事务管理相关的方法。

    • 对比

      • 声明式事务最大的优点就是不需要在业务逻辑代码中掺杂事务管理的代码
      • 声明式事务最细粒度只能作用到方法级别,无法做到像编程式事务那样可以作用到代码块级别
  • 隔离级别

    • READ-UNCOMMITTED(读取未提交)
    • READ-COMMITTED(读取已提交)
    • REPEATABLE-READ(可重复读)(默认)
    • SERIALIZABLE(可串行化)
  • 事务传播机制

    • 定义

      • Spring事务的传播机制指当多个事务同时存在的时候,spring如何处理这些事务的行为
    • 分类

      • PROPAGATION_REQUIRED

        • 如果当前没有事务,则自己新建一个事务,如果当前存在事务,则加入这个事务
      • SUPPORTS

        • 当前存在事务,则加入当前事务,如果当前没有事务,就以非事务方法执行
      • REQUIRES_NEW

        • 新建事务,如果当前存在事务,则把当前事务挂起
    • 参考博客:https://blog.csdn.net/zhouxukun123/article/details/79874797

Spring,SpringMVC,Springboot的区别

  • Spring:Spring是一个IOC容器,用来管理Bean,使用依赖注入实现控制反转,可以很方便的整合各种框架,提供AOP机制弥补OOP的代码重复问题、更方便将不同类不同方法中的共同处理抽取成切面、自动注入给方法执行,比如日志、异常等。
  • Spring MVC:Spring MVC是Spring对Web框架的一个解决方案,提供了一个总的前端控制器Servlet,用来接收请求,然后定义了一套路由策略(url到handle的映射)及适配执行handler,将handler结果使用视图解析技术生成视图展现给前端。
  • Spring Boot:Spring Boot是spring提供的一个快速开发工具包,让程序员能更方便、更快速的开发spring + Spring MVC应用,简化了配置(约定了默认配置),整合了一系列的解决方案(starter机制) 、redis、mongodb、es,可以开箱即用

Spring 的启动流程

  • TODO

BeanFactory和ApplicationContext的区别?

  • BeanFactory是Spring里面最底层的接口,是IoC的核心,定义了IoC的基本功能,包含了各种Bean的定义、加载、实例化,依赖注入和生命周期管理。ApplicationContext接口作为BeanFactory的子类,除了提供BeanFactory所具有的功能外,还包括继承MessageSource,支持国际化;载入多个(有继承关系)上下文(即同时加载多个配置文件) ;提供在监听器中注册bean的事件。
  • BeanFactroy采用的是延迟加载形式来注入Bean的,只有在使用到某个Bean时(调用getBean()),才对该Bean进行加载实例化。ApplicationContext,它是在容器启动时,一次性创建了所有的Bean。

循环依赖问题

  • 描述:类与类之间的依赖关系形成了闭环,就会导致循环依赖问题的产生。比如A类依赖了B类,B类依赖了C类,而最后C类又依赖了A类,这样就形成了循环依赖问题。

  • 分类

    • 通过构造方法进行依赖注入时产生循环依赖问题。

      • 解决方案

        • setter方法
        • @Lazy注解
      • 报错:BeanCurrentlyInCreationException

    • 通过setter方法进行依赖注入且是在原型模式下产生循环依赖问题。

      • Spring无法解决
      • 报错:BeanCurrentlyInCreationException
    • 通过setter方法进行依赖注入且是在单例模式下产生的循环依赖问题。

      • Spring通过三级缓存解决单例模式setter注入的循环依赖问题

        • 1:使用context.getBean(A.class),旨在获取容器内的单例A(若A不存在,就会走A这个Bean的创建流程),显然初次获取A是不存在的,因此走A的创建之路~
        • 2.实例化A(注意此处仅仅是实例化),并将它放进缓存(此时A已经实例化完成,已经可以被引用了)
        • 3.初始化A:@Autowired依赖注入B(此时需要去容器内获取B
        • 4.为了完成依赖注入B,会通过getBean(B)去容器内找B。但此时B在容器内不存在,就走向B的创建之路~
        • 5.实例化B,并将其放入缓存。(此时B也能够被引用了)
        • 6.初始化B,@Autowired依赖注入A(此时需要去容器内获取A)
        • 7.初始化B时会调用getBean(A)去容器内找到A,上面我们已经说过了此时候因为A已经实例化完成了并且放进了缓存里,所以这个时候去看缓存里是已经存在A的引用了的,所以getBean(A)能够正常返回
        • 8.B初始化成功(此时已经注入A成功了,已成功持有A的引用了),return(注意此处return相当于是返回最上面的getBean(B)这句代码,回到了初始化A的流程中~)。
        • 9.因为B实例已经成功返回了,因此最终A也初始化成功
        • 10.到此,B持有的已经是初始化完成的A,A持有的也是初始化完成的B,完美~

缓存

  • 一级缓存

    • /** 单例缓存池,这里存储的bean是已经完成了实例化,属性注入,初始化等一系列操作的bean */
      private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
  • 二级缓存

    • /** 存储的提前暴露的早期对象,即只是调用了构造函数生成的bean对象 */
      private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
  • 三级缓存

    • /** 单例对象工厂缓存,存储了每个beanName所在的生产工厂 */
      private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

JWT鉴权

  • 定义

    • JWT全称JSON Web Token,是一种基于JSON的,用于在网络上声明某种主张的令牌(Token)
  • 组成

    • 头信息(header)

      • 包含两部分,typ:代表token的类型,这里使用的是JWT类型。 alg:代表使用的签名算法,例如HMAC SHA256或RSA.
      • {
        “typ”: “JWT”,
        “alg”: "HS256
        }
    • 载荷(payload)

      • 具体的传输内容,包括一些标准属性,iss: 该JWT的签发者,exp: 过期时间戳,iat: 签发时间戳,jti: JWTID等等。也可以添加其他需要传递的内容数据。
      • {
        “iss”: “kkk”,
        “iat”: 1548818203,
        “exp”: 1548818212,
        “sub”: "test.com
        }
    • 签名(signature)

      • 服务器端生成私钥secret,使用header中指定的算法对base64Url编码后的header+“.”+base64Url编码后的payload、secret生成签名
    • 算出签名之后, Header、Payload、Signature 三个部分拼成一个字符串,每个部分之间用"点"(.)分隔,就可以返回给用户。

  • 过程

    • 用户发起登录请求
    • 服务端创建一个加密后的JWT信息,作为Token返回
    • 客户端后续每次与服务器通信,都要带上这个Token,可以把它放在Cookie里面自动发送,但是这样不能跨域,所以更好的做法是放在HTTP请求头信息Authorization里面。
    • 服务端通过拦截器拦截,拿到Token之后进行解密,解密之后获取用户信息,与redis或数据库中的用回信息校验是否合法,合法则验证通过;解密失败说明Token无效或者已过期。
  • 为什么使用JWT?

    • 由于Http协议本身是无状态的,那么服务器是怎么识别两次请求是不是来自同一个客户端呢,传统用户识别是基于seeion和cookie实现的。

    • 缺点

      • 而且session是保存在内存中,单台服务器部署如果登陆用户过多占用服务器资源也多,做集群必须得实现session共享的话,集群数量又不易太多,否则服务器之间频繁同步session也会非常耗性能。当然也可以引入持久层,将session保存在数据库或者redis中,保存数据库的话效率不高,存redis效率高,但是对redis依赖太重,如果redis挂了,影响整个应用。
    • 过程

      • (1)用户向服务器发送用户名和密码请求
        (2)用户进行校验,校验通过后创建session会话,并将用户相关信息保存到session中
        (3)服务器将sessionId回写到用户浏览器cookie中
        (4)用户以后的请求,都会携带cookie发送到服务器
        (5)服务器得到cookie中的sessionId,从session集合中找到该用户的session回话,识别用户
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

赴前尘

喜欢我的文章?请我喝杯咖啡吧!

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

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

打赏作者

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

抵扣说明:

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

余额充值