写点什么

Java 面试八股文中,常问的那些 spring 高频题目解析,网易架构师深入讲解 Java 开发

用户头像
Java高工P7
关注
发布于: 30 分钟前
  • spring aop:提供了面向切面的编程实现,让你可以自定义拦截器、切点等。

  • spring Web:提供了针对 Web 开发的集成特性,例如文件上传,利用 servlet listeners 进行 ioc 容器初始化和针对 Web 的 ApplicationContext。

  • spring test:主要为测试提供支持的,支持使用 JUnit 或 TestNG 对 Spring 组件进行单元测试和集成测试。


Spring 框架中都用到了那些设计模式


  • 工厂模式:BeanFactory 就是简单工厂模式的体现,用来创建对象的实例。

  • 单例模式:Bean 默认为单例模式。

  • 代理模式:SpringAOP 功能用到了 JDK 的动态代理和 CGLIB 字节码生成技术。

  • 模板方法:用来解决代码重复的问题,比如:RestTemplate, JmsTemplate, JpaTemplate、refresh。

  • 观察者模式:定义对象键是一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到被制动更新,如 Spring 中 listener 的实现–ApplicationListener。


Spring IOC


==========


IOC 是什么?


IOC(Inversion of Control)即控制反转,简单说就是把原来代码需要实现的对象创建、依赖反转给容器来帮忙实现,需要创建一个容器并且需要一种描述让容器指导要创建对象之间的关系,在 Spring 中管理对象及其依赖关系是通过 Spring 的 IOC 容器实现的。


IOC 的实现方式有依赖注入和依赖查找是,由于依赖查找用的很少,因此 IOC 也叫做依赖注入。依赖注入指对象被动地接受依赖类而不用自己主动去找,对象不是从容器中查找它依赖的类,而是在容器实例化对象时,主动将它所依赖的类注入给它。


依赖注入更加准确地描述了 IOC 的设计理念,所谓依赖注入(Dependency Injection),即组件之间的依赖关系有容器在应用系统运行期来决定,也就是由容器动态地将某种依赖关系的目标对象实例注入到应用程序中的各个关联的组件之中,组件不做定位查询,只提供普通的 Java 方法让容器去决定依赖关系。


举例:一个 Car 类需要一个 Engine 的对象,那么一般需要手动 new 一个 Engine,利用 IOC 就只需要定义一个私有的 Engine 类型的成员变量,容器会在运行时创建一个 Engine 的实例对象并将引用自动注入给成员变量。



控制反转是:关于一个对象如何获取他所依赖的对象的引用,这个责任的反转。


IOC 容器初始化的过程


基于 xml 的容器初始化


当创建一个


ClassPathXmlApplicationContext 时,构造方法做了两件事


  • 调用父容器的构造方法为容器设置好 Bean 资源资源加载器

  • 调用父类的 setConfigLocations 方法设置 Bean 信息的定位路径


ClassPathXmlApplicationContext 通过调用父类 AbstractApplicationContext 的 refresh 方法启动整个 IOC 容器对 Bean 定义的载入过程,refresh 是一个模板方法,规定了 IOC 容器的启动流程,在创建 IOC 容器前如果已有容器存在,需要把已有的容器销毁,保证在 refresh 方法后使用的是新创建的 IOC 容器


容器创建后通过 loadBeanDefinitions 方法加载 Bean 配置资源,该方法做两件事:


调用资源加载器的方法获取需要加载的资源;


真正执行加载功能,由子类 XmlBeanDefinitionReader 实现。加载资源时:


  • 首先解析配置文件路径,读取配置文件内容,然后通过 xml 解析器将 Bean 配置信息转换成文档对象;

  • 之后按照 Spring Bean 定义规则对文档对象进行解析。


Spring IOC 容器中注解解析的 Bean 信息放在一个 HashMap 集合中,key 是字符串,值是 BeanDefinition,注册过程中需要 synchronized 保证线程安全,当配置文件的 Bean 被解析且被注册到 IOC 容器中后,初始化就算真正完成了,Bean 定义信息已经可以使用且可被检索。Spring IoC 容器的作用就是对这些注册的 Bean 定义信息进行处理和维护,注册的 Bean 定义信息是控制反转和依赖注入的基础。



测试类



基于注解的容器初始化


分为两种:


  • 直接将注解 Bean 注解到容器中,可以在初始化容器时注册,也可以在容器创建之后手动注册,然后刷新容器使其对注册的注解 Bean 进行处理。

  • 通过扫描指定包及其子包的所有类处理,在初始化注解容器时指定要自动扫描的路径。



或者


在 xml 中开启注解装配


context:annotation-config</context:annotation-config>


然后使用


ClassPathXmlApplicationContext 加载 xml 配置文件


依赖注入的实现方法


=========


构造方法注入


IOC Service Provider 会检查被注入对象的构造方法取得它所需要的依赖对象列表,进而为其注入相应的对象。这种方法的优点是在对象构造完成后就处于就绪状态,可以马上使用。缺点是当依赖对象较多时,构造方法的参数列表会比较长,构造方法无法被继承,无法设置默认值。对于非必需的依赖处理可能需要引入多个构造方法,参数数量的变动可能会造成维护的困难。



setter 方法注入


当前对象要只需要为其依赖对象对应的属性添加 setter 方法,就可以通过 setter 方法将依赖对象注入中。setter 方法注入在描述性上要比构造方法注入强,并且可以被继承,允许设置默认值。缺点是无法在对象构造完成后马上进入就绪状态。



构造器依赖注入和 Setter 方法注入的区别



两种依赖方式都可以使用,构造器注入和 Setter 方法注入。最好的解决方案是用构造器参数实现强制依赖,setter 方法实现可选依赖。


接口注入


必须实现某个接口,接口提供方法来为其注入依赖对象。使用少,因为它强制要求被注入对象实现不必要接口,侵入性强,已在 Spring4 中废除。


依赖注入的过程?


getBean 方法获取 Bean 实例,该方法调用 doGetBean,doGetBean 真正实现从 IOC 容器获取 Bean 的功能,也是触发依赖注入的地方。


具体创建 Bean 对象的过程是由 ObjectFactory 的 createBean 完成的,该方法主要通过 createBeanInstance 方法生成 Bean 包含的 Java 对象实例和 populateBean 方法对 Bean 属性的依赖注入进行处理。


在 populateBean 方法中,注入过程主要分为两种情况:


1.属性值类型不需要强制转换时,不需要强制类型转换时,不需要解析属性值,直接进行依赖注入。


2.属性类型需要强制转换时。


  • 首先解析属性值;

  • 然后对解析后的属性值进行依赖注入。

  • 依赖注入的过程就是将 Bean 对象实例设置到它所依赖的 Bean 对象的属性上。

  • 真正的依赖注入是通过 setPropertyValues 方法实现的,该方法使用了委派模式。


BeanWrapperImpl 类负责对完成初始化的 Bean 对象进行依赖注入


  • 对于非集合类型属性,使用 JDK 反射,通过属性的 setter 方法为属性设置注入后的值;

  • 对于集合类型的属性,将属性值解析为目标类型的集合直接赋值给属性。


当容器对 Bean 的定位、载入、解析和依赖注入完成后就不再需要手动创建对象,IOC 容器会自动为我们创建对象并且依赖注入。


Bean 的生命周期


=========



  • Spring 容器读取 XML 配置文件中 bean 定义并实例化 Bean;

  • Spring 根据 Bean 的定义设置属性值;

  • 如果该 Bean 实现了 BeanNameAware 接口,Spring 将 Bean 的 id 传递给 setBeanName()方法;

  • 如果该 Bean 实现了 BeanFactoryAware 接口,Spring 将 BeanFactory 传递给 setBeanFactory()方法;

  • 如果 bean 实现了 ApplicationContextAware 接口,Spring 将调用 setApplicationContext()方法,将 bean 所在的应用上下文的引用传入进来;

  • 如果 bean 实现了 BeanPostProcessor 接口,Spring 将调用它们的 post-ProcessBeforeInitialization()方法;

  • 如果 bean 实现了 InitializingBean 接口,Spring 将调用它们的 after-PropertiesSet()方法。类似地,如果 bean 使用 initmethod 声明了初始化方法,该方法也会被调用。


此时 Bean 就已经准备就绪了,可以被应用程序使用了,他们一直驻留在容器中,直到容器被销毁。


  • 果 bean 实现了 DisposableBean 接口,Spring 将调用它的 destroy()接口方法。同样,如果 bean 使用 destroy-method 声明了销毁方法,该方法也会被调用。


自己定制初始化和注销方法


XML 方式通过配置 bean 标签中的 init-Method 和 destory-Method 指定自定义初始化和销毁方法。



注解方式通过 @PreConstruct 和 @PostConstruct 注解指定自定义初始化和销毁方法。



Bean 的作用范围


通过 scope 属性指定 bean 的作用范围,包括:


  • singleton:单例模式,是默认作用域,不管收到多少 Bean 请求每个容器中只有一个唯一的 Bean 实例

  • prototype:原型模式,和 singleton 相反,每次 Bean 请求都会创建一个新的实例

  • request:每次 HTTP 请求都会创建一个新的 Bean 并把它放到 request 域中,在请求完成之后 Bean 会失效并被垃圾收集器回收


session:和 request 类似,确保每个 session 中有一个 Bean 实例,session 过期后 Bean 会随之失效


  • global session:当应用部署在 Portlet 容器时,如果想让 Portlet 公用全局存储变量,那么该变量需要存储在 global session 中


Bean 线程安全性


Spring 的单例 Bean 不是线程安全的


Spring 中的 Bean 默认是单例模式,Spring 框架中没有对单例 Bean 进行多线程的封装处理。


实际上大部分时候 spring bean 无状态的(比如 dao 类),所有某种程度上来说 bean 也是安全的,但如果 bean 有状态的话(比如 view model 对象),那就要开发者自己去保证线程安全了,最简单的就是改变 bean 的作用域,把“singleton”变更为“prototype”,这样请求 bean 相当于 new Bean()了,所以就可以保证线程安全了。


  • 有状态:有数据存储功能

  • 无状态:不会保存数据


Spring 如何处理线程并发问题


只有无状态的 Bean 才会在多线程下共享,在 Spring 中,绝大部分 Bean 都可以声明为 singleton 作用域,,因为 Spring 对一些 Bean 中非线程安全状态采用 ThreadLocal 进行处理,解决线程安全问题。


ThreadLocal 和线程同步机制都是为了解决多线程中相同变量的访问冲突问题。同步机制采用了“时间换空间”的方式,仅提供一份变量,不同的线程在访问前需要获取锁,没获得锁的线程则需要排队。而 ThreadLocal 采用了“空间换时间”的方式。


ThreadLocal 为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据访问的冲突,因为每一个线程都拥有自己的变量副本,从而就没有必要对该变量进行同步,ThreadLocal 提供了线程安全的共享对象,在编写多线程代码时,可以把不安全的变量封装进 ThreadLocal。


扩展问题


====


创建 Bean 的方式


xml


  • 默认无参构造方法,只需要指明 bean 标签中的 id 和 class 属性,如果没有无参构造方***报错。

  • 静态工厂方法,通过 bean 标签中的 class 属性指明静态工厂,factory-method 属性指明静态工厂方法。

  • 实例工厂方法,通过 ean 标签中的 factory-bean 属性指明实例工厂,factory-method 属性指明实例工厂方法。


注解


@Component 把当前类对象存入 Spring 容器中,相当于在 xml 中配置一个 bean 标签。value 属性指定 bean 的 id,默认使用当前类的首字母小写的类名。


@Controller,@Service,@Repository 三个注解都是 @Component 的衍生注解,作用及属性都是一模一样的。只是提供了更加明确语义,@Controller 用于表现层,@Service 用于业务层,@Repository 用于持久层。如果注解中有且只有一个 value 属性要赋值时可以省略 value。


如果想将第三方的类变成组件又没有源代码,也就没办法使用 @Component 进行自动配置,这种时候就要使用 @Bean 注解。被 @Bean 注解的方法返回值是一个对象,将会实例化,配置和初始化一个新对象并返回,这个对象由 Spring 的 IoC 容器管理。


name 属性用于给当前 @Bean 注解方法创建的对象指定一个名称,即 bean 的 id。当使用注解配置方法时,如果方法有参数,Spring 会去容器查找是否有可用 bean 对象,查找方式和 @Autowired 一样。


BeanFactory、FactoryBean 和 ApplicationContext 的区别?


==============================================


BeanFactory(”低级容器")


BeanFactory 是一个 Bean 工厂,使用简单工厂模式,是 Spring IOC 容器的顶级接口,可以理解为含有 Bean 集合的工厂,作用是管理 Bean,包括实例化、定位、配置对象及建立这些对象间的依赖。BeanFactory 实例化后并不会自动实例化 Bean,只有当 Bean 被使用时才实例化与装配依赖关系,属于延迟加载,适合多例模式。


FactoryBean


FactoryBean 是一个工厂 Bean,使用了工厂方法模式,作用是生产其他 Bean 实例,可以通过实现该接口,提供一个工厂方法来自定义实例化 Bean 的逻辑,FactoryBean 接口有 BeanFactory 中的配置实现,,这些对象本身就是用于创建对象的工厂,如果一个 Bean 实现了这个接口,那么它就是创建对象的工厂 Bean,而不是 Bean 实例本身。


ApplicationContext(”高级容器“)


ApplicationContext 是 BeanFactory 的子接口,扩展了 BeanFactory 的功能,提供了支持国际化的文本消息,统一的资源文件读取方式,事件传播以及应用层的特别配置等。该接口定义了一个 refresh 方法,用于刷新整个容器,即重新加载/刷新所有的 bean。容器会在初始化时对配置的 Bean 进行预实例化,Bean 的依赖注入在容器初始化时就已经完成,属于立即加载,适合单例模式,一般推荐使用。


实现类:


  • ClassPathApplicationContext:只能读取路径下的配置文件(推荐使用)。

  • FileSystemXmlApplicationContext:可以读取任意硬盘路径的配置文件,你可以点击根目录下的文件的属性查看他在本地硬盘的位置。

  • AnnotationConfigApplicationContext:读取注解配置类。


Spring AOP


==========


AOP 是什么?


AOP(Aspect-Oriented Programming)即面向切面编程,简单地说就是将代码中重复的部分抽取出来,在需要执行的时候使用动态代理技术,在不修改源码的基础上对方法进行增强,减少了系统的重复代码,降低了模块间的耦合度,同时提高了系统的可维护性。


常见的场景包括:权限认证、自动缓存、错误处理、日志、调试和事务。


动态代理


====


JDK


Spring 根据类是否实现接口来判断动态代理的方式,如果实现接口会使用 JDK 动态代理,核心是 InvocationHandler 接口和 Proxy 类,JDK 动态代理主要通过重组字节码实现,首先获得被代理对象的引用和所有接口,生成新的类必须实现代理类的所有接口,动态生成 Java 代码后编译生成新的.class 文件并重新加载到 JVM 运行。JDK 代理直接写 Class 字节码。


InvocationHandler 的 invoke(Object proxy,Method method,Object[] args):proxy 是最终生成的代理实例; method 是被代理目标实例的某个具体方法; args 是被代理目标实例某个方法的具体入口, 在方法反射调用时使用。


CGLIB


  • 如果代理类没有实现 InvocationHandler 接口,那么 Spring AOP 会选择使用 CGLIB 来动态代理目标类。

  • CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态的生成指定类的一个子类对象,并覆盖其中的特定方法并添加增强代码。

  • 采用 ASM 框架写字节码,生成代理类的效率低,但是 CGLIB 调用方法的效率高,因为 JDK 使用反射调用方法。

  • CGLIB 使用 FastClass 机制为代理类和被代理类各生成一个子类,这个类会为代理类或者被代理类的方法生成一个 index,这个 index 可以作为参数直接定位要调用的方法。

  • 如果没有实现接口会使用 CGLIB 是运行时动态生成某个类的子类,如果某个类被标记为 final,不能使用 CGLIB。


AOP 相关术语


Aspect:切面是通知和切点的结合。通知和切点共同定义了切点的全部内容,在 Spring AOP 中,切面可以使用通用类(基于模式的风格)或者在普通类中以 @AspectJ 注解来实现。


Joinpoint:指连接点,在 SpringAOP 中,一个连接点总是代表一个方法的执行。应用可能有数以千计应用通知。这些时机被称为连接点。连接点是在应用执行过程中能够插入切面的一个点。这个点可以是调用方法时、抛出异常时、甚至修改一个字段时。切面代码可以利用这些点插入到应用的正常流程之中,并添加新的行为。


Advice:通知,指切面对于某个连接点所产生的动作,包括:


  • 前置通知(Before)

  • 后置通知(After)

  • 返回后通知(AfterReturning)

  • 异常通知(AfterThrowing)

  • 环绕通知(Around)


Pointcut:切入点的定义会匹配通知所要织入的一个或多个连接点。我们通常使用明确的类和方法名称,或是利用正则表达式定义所匹配的类和方法名称来指定这些切点。


Proxy:代理,Spring AOP 中有 JDK 动态代理和 CGLib 代理,目标对象实现了接口时采用 JDK 动态代理,反之采用 CGLib 代理。


Target:代理的目标对象,指一个或多切面所通知的对象。


We


《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
浏览器打开:qq.cn.hn/FTe 免费领取
复制代码


aving :织入,指把增强应用到目标对象来创建代理对象的过程。


  • 编译期:切面在目标类编译时被织入。AspectJ 的织入编译器是以这种方式织入切面的。

  • 类加载期:切面在目标类加载到 JVM 时被织入。需要特殊的类加载器,它可以在目标类被引入应用之前增强该目标类的字节码。AspectJ5 的加载时织入就支持以这种方式织入切面。

  • 运行期:切面在应用运行的某个时刻被织入。一般情况下,在织入切面时,AOP 容器会为目标对象动态地创建一个代理对象。SpringAOP 就是以这种方式织入切面。


AOP 的过程


SpringAOP 由 BeanPostProcessor 后置处理器开始,这个后置处理器是一个监听器,可以监听容器触发的 Bean 生命周期事件,向容器注册后置处理器以后,容器中管理的 Bean 就具备了接收 IOC 容器回调事件的能力。BeanPostProcessor 的调用发生在 Spring IoC 容器完成 Bean 实例对象的创建和属性的依赖注入后,为 Bean 对象添加后置处理器的入口是 initializeBean 方法。


AOP 工作中心在于如何将增强编织到目标对象的连接点上,这里包含两个动作


  • 如何通过 pointcut 和 advice 定位到特定的 joinpoint 上

  • 如何在 advice 中编写代码



扩展问题


====


AOP 有哪些实现方式


AOP 实现的关键在于代理模式,AOP 代理主要分为静态代理和动态代理。静态代理的代表是 AspectJ,动态代理则以 SpringAOP 为代表。


SpringAOP 和 AspectJ AOP 有什么区别


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

  • Spring AOP 使用的动态代理,所谓的动态代理就是说 AOP 框架不会去修改字节码,而是每次运行时,在内存中临时为方法生成一个 AOP 对象,这个 AOP 对象包含了目标对象的全部方法,并且在特定的切点做了增强处理。


Spring 注解


========


基于 Spring 注解配置


基于 Java 的配置,允许在少量 Java 注解的帮助下,进行大部分 Spring 的配置而非通过 xml 文件。


  • @Configuration:用于指定当前类是一个 Spring 配置类,标记类可以当做一个 Bean 的定义,被 IOC 容器使用。


value 属性用于指定配置类的字节码文件。


  • @Bean:表示此方法要返回一个对象,作为一个 Bean 注册到 Spring 应用上下文中。

用户头像

Java高工P7

关注

还未添加个人签名 2021.11.08 加入

还未添加个人简介

评论

发布
暂无评论
Java面试八股文中,常问的那些spring高频题目解析,网易架构师深入讲解Java开发