列举一些重要的spring模块

  • spring core 主要提供IOC依赖注入功能
  • spring AOP 提供了面向切面的编程实现
  • spring JDBC java数据库连接
  • spring web 为创建web应用程序提供支持

spring IOC是什么?

  • IOC (inverse of control控制反转) 是一种设计思想,将原本在程序中手动创建对象的控制权交给spring框架来管理, 可以降低耦合方便功能复用.底层原理是java的反射机制,将bean的id和全类名对应起来,通过反射创建对象. IOC容器充当了工厂角色,使用静态集合类来保存bean的id和对象,直接通过id就可以获取map中的对象.

AOP是什么?

  • AOP( Aspect-Oriented Programming 面向切面编程) 能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(比如事务管理,日志管理等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,有利于实现未来的可扩展性和可维护性.
  • spring AOP的实现方式有两种,一种是JDK Proxy, 要求被代理对象必须实现接口. 另外一种是Cglib,要求被代理对象不能是最终类.

spring AOP中的几个名词

  • (1) 切面: 被抽取的公共模块.
    (2) 连接点:指业务中的方法.
    (3) 通知: 在切面的某个连接点上执行的动作,包括前置通知,后置通知,异常通知和环绕通知.
    (4) 切入点: 指要对哪些连接点进行拦截.通过切入点表达式指定拦截的方法。
    (5) 目标对象: 被一个或多个切面所通知的对象.
    (6) 织入:把增强应用到目标对象来创建新代理对象的过程.

依赖注入

  • ①就是指对象是被动接受依赖类而不是自己主动去找,换句话说就是指对象不是从容器中查找它依赖的类,而是在容器实例化对象的时候主动将它依赖的类注入给它。
    注入方式:setter方法注入和构造方法注入。

spring bean的作用域

  • singleton作用域: 唯一 bean 实例,Spring 中的 bean 默认都是单例的.

  • prototype作用域: 原型作用域, 每次调用bean时都会创建一个新实例.

  • request作用域: 每次http请求时都会创建一个新的bean,该作用域仅适用于 WebApplicationContext 环境。

  • session 作用域:同一个 Http Session 共享一个 Bean 对象,不同的 Session 拥有不同的 Bean 对象,仅适用于 WebApplicationContext 环境。

  • application 作用域:全局的 Web 作用域,类似于 Servlet 中的 Application.

springBean的生命周期

  • (1) Bean容器中找到配置文件中spring bean的定义。
    (2) Bean容器利用反射创建bean的实例,设置对象属性。
    (3) 若Bean实现了BeanNameAware接口,调用setBeanName()方法,传入bean的名字。
    (4) 如果Bean实现了BeanClassLoaderAware接口,调用setBeanClassLoader()方法,传入ClassLoader对象的实例。
    (5) 如果Bean实现了其他Aware接口,就调用相应的方法。
    (6) 如果有与加载这个Bean的Spring容器相关的BeanPostProcessor对象,执行postProcessBeforeInitialization()方法。
    (7) 如果Bean实现了InitializingBean接口,执行afterPropertiesSet()方法。
    (8) 如果 Bean 在配置文件中的定义包含 init-method 属性,执行指定的方法。
    (9) 如果有和加载这个 Bean的 Spring 容器相关的 BeanPostProcessor 对象,执行postProcessAfterInitialization() 方法
    (10) 当要销毁 Bean 的时候,如果 Bean 实现了 DisposableBean 接口,执行 destroy() 方法。
    (11) 当要销毁 Bean 的时候,如果 Bean 在配置文件中的定义包含 destroy-method 属性,执行指定的方法。

Spring和SpringBoot - 图1

spring bean的线程安全问题?

  • (1) 大部分时候我们并没有在系统中使用多线程,所以很少有人会关注这个问题。单例 bean 存在线程问题,主要是因为当多个线程操作同一个对象的时候,对这个对象的非静态成员变量的写操作会存在线程安全问题。

  • (2) 常见的有两种解决办法:
    ① 在Bean对象中尽量避免定义可变的成员变量(不太现实)。
    ② 在类中定义一个ThreadLocal成员变量,将需要的可变成员变量保存在 ThreadLocal 中(推荐的一种方式)。

spring解决循环依赖问题

  • (1) A依赖B,B也依赖A,或者A依赖自身,抛出的异常是BeanCurrentlyCreationException.

  • (2) 解决循环依赖的前置条件:
    ① 出现循环依赖的Bean必须是单例
    ② 依赖注入的方式不能全是构造器注入的方式。可以均使用setter方法注入或者一方使用setter方法注入,另外一方使用构造器注入。

  • (3) 如何解决?(类似两数之和问题,先在map中进行注册,若后续要用到,就直接取)
    ① Spring通过三级缓存解决了循环依赖,其中一级缓存为单例池(存放所有创建好的bean)(singletonObjects),二级缓存为早期曝光对象earlySingletonObjects(完成实例化,但还未进行属性注入以及初始化的对象),三级缓存为早期曝光对象工厂(singletonFactories)。
    ② 当A、B两个类发生循环引用时,在A完成实例化后,就使用实例化后的对象去创建一个对象工厂,并添加到三级缓存中,如果A被AOP代理,那么通过这个工厂获取到的就是A代理后的对象,如果A没有被AOP代理,那么这个工厂获取到的就是A实例化的对象。
    ③ 当A进行属性注入时,会去创建B,同时B又依赖了A,所以创建B的同时又会去调用getBean(a)来获取需要的依赖,此时的getBean(a)会从缓存中获取:
    1) 第一步,先获取到三级缓存中的工厂;
    2) 第二步,调用对象工厂的getObject方法来获取到对应的对象,得到这个对象后将其注入到B中。紧接着B会走完它的生命周期流程,包括初始化、后置处理器等。
    3) 当B创建完后,会将B再注入到A中,此时A再完成它的整个生命周期。至此,循环依赖结束!

  • (4) 为什么要使用三级缓存呢?二级缓存能解决循环依赖吗?
    ① 如果要使用二级缓存解决循环依赖,意味着所有Bean在实例化后就要完成AOP代理,这样违背了Spring设计的单一职责原则。

拦截器和过滤器的区别

  • ① 过滤器Filter依赖于servlet容器,在实现上基于函数回调,可以对几乎所有请求过滤,但过滤器实例只能在容器初始化时调用一次。一般用于修改字符编码,过滤掉request的一些参数,比如危险字符等。

  • ② 拦截器Interceptor依赖于web框架,在实现上基于java的反射机制,属于AOP的运用,调用时机是在controller方法执行前,后以及页面渲染完成前。一个拦截器实例在一个controller声明周期内可多次调用,但缺点是只能对controller请求进行拦截,如果访问的是静态资源,不会拦截。

  • ① 拦截器是基于java的反射机制的,而过滤器是基于函数回调。
    ② 拦截器不依赖与servlet容器,过滤器依赖于servlet容器。
    ③ 拦截器只能对action请求起作用,而过滤器则可以对几乎所有的请求起作用。
    ④ 拦截器可以访问action上下文、值栈里的对象,而过滤器不能访问。
    ⑤ 在action的生命周期中,拦截器可以多次被调用,而过滤器只能在容器初始化时被调用一次。
    ⑥ 拦截器可以获取IOC容器中的各个bean,而过滤器就不行,这点很重要,在拦截器里注入一个service,可以调用业务逻辑。

@Component 和 @Bean的区别

  • (1) 作用对象不同: @Component 注解作用于类,而@Bean注解作用于方法。
  • (2) @Component通常是通过类路径扫描来自动侦测以及自动装配到Spring容器中. @Bean 注解通常是我们在标有该注解的方法中定义产生这个 bean,@Bean告诉了Spring这是某个类的示例,当我需要用它的时候还给我。
  • (3) @Bean 注解比 Component 注解的自定义性更强,而且很多地方我们只能通过 @Bean 注解来注册bean。比如当我们引用第三方库中的类需要装配到 Spring容器时,则只能通过 @Bean来实现。

@Autowired和@Resource区别

  • (1) @Autowired与@Resource都可以用来装配bean. 都可以写在字段上,或写在setter方法上。
    (2) @Autowired默认按类型装配,默认情况下必须要求依赖对象必须存在,如果要允许null值,可以设置它的required属性为false,如果我们想使用名称装配可以结合@Qualifier注解进行使用。
    (3) @Resource(这个注解属于J2EE的),默认按照名称进行装配,名称可以通过name属性进行指定,当找不到与名称匹配的bean时才按照类型进行装配。但是需要注意的是,如果name属性一旦指定,就只会按照名称进行装配。

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

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

Spring中的动态代理

  • (1) Jdk动态代理底层原理
    ① 底层是利用拦截器加上反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvocationHandler来处理。

  • (2) Cglib动态代理底层原理
    ① Cglib底层是通过一个字节码处理框架ASM来转换字节码生成新的类。
    ② 要求被代理的类不能是最终类。

  • (3) Jdk动态代理和cglib的区别?
    ① Jdk动态代理只能对实现了接口的类生成代理,而不能针对没有实现任何接口的类。
    ② Cglib针对类实现代理,主要是针对指定类生成一个子类,覆盖其中的方法。因为是继承,所以要求该类不能是最终类。
    ③ 如果目标对象没有实现接口,spring会自动在jdk动态代理和cglib间转换。
    ④ 较低的jdk版本下,cglib的速度要快于jdk动态代理。但是在jdk1.7和jdk1.8之后jdk的速度反而高于cglib

  • (4) Jdk动态代理使用条件
    ① 被代理对象必须实现接口。
    ② 采用Proxy类。通过newProxyInstance()方法为生成代理对象。
    ③ 实现InvocationHandler接口,一般采用匿名内部类的形式创建。

  • (5) 如何强制使用cglib实现aop?
    ① 添加cglib库。
    ② 在spring配置文件中加入

什么是MVC?

MVC是一个架构模式,它分离了表现与交互。它被分为三个核心部件:模型、视图、控制器。视图是用户看到并与之交互的界面。模型表示业务数据,并提供数据给视图。控制器接受用户的输入并调用模型和视图去完成用户的需求. Spring MVC 下我们一般把后端项目分为 Service层(处理业务)、Dao层(数据库操作)、Controller层(控制层,返回数据给前台页面)

spring MVC的工作流程

Spring和SpringBoot - 图2

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

spring事务的隔离级别

  • ① ISOLATION_DEFAULT: 使用后端数据库默认的隔离级别,mysql采用可重复读,Oracle采用读已提交。其他四个与数据库隔离级别一样。
    ② ISOLATION_READ_UNCOMMITTED: 这是事务最低的隔离级别,允许读取尚未提交的数据变更,这种隔离级别会产生脏读,不可重复读和幻读。
    ③ ISOLATION_READ_COMMITTED: 读取并发事务已经提交的数据,产生不可重复读和幻读。
    ④ ISOLATION_REPEATABLE_READ: 对同一字段的多次读取结果都是一致的,产生幻读。
    ⑤ 5. ISOLATION_SERIALIZABLE 这是花费最高代价但是最可靠的事务隔离级别。事务被处理为顺序执行。除了防止脏读,不可重复读外,还避免了幻读。

srping事务不生效原因

  • (1) 数据库引擎设置问题,myisam不支持事务,需要改成innoDB.
    (2) 只有public方法才能使用事务。(由AOP决定)
    (3) Spring事务管理默认只对出现运行时异常进行回滚。
    (4) 确保业务和事务入口在同一个线程里面,否则事务不生效。
    同一个类中一个无事务的方法调用另一个有事务的方法,事务是不会起作用的。

Spring事务的7种传播机制

  • (1) REQUIRED(默认):支持使用当前事务,如果当前事务不存在,创建一个新事务。
    (2) SUPPORTS:支持使用当前事务,如果当前事务不存在,则不使用事务。
    (3) MANDATORY:支持使用当前事务,如果当前事务不存在,则抛出Exception。
    (4) REQUIRES_NEW:创建一个新事务,如果当前事务存在,把当前事务挂起.
    (5) NOT_SUPPORTED:无事务执行,如果当前事务存在,把当前事务挂起。
    (6) NEVER:无事务执行,如果当前有事务则抛出Exception。
    (7) NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则进行与REQUIRED类似的操作。

  • (8) 区别:
    ① REQUIRED如果外部有事务的话,内部事务出现异常,将导致一起回滚。
    ② REQUIRES_NEW创建一个新的不依赖于环境的内部事务,这个事务的提交或回滚不依赖于外部事务。当内部事务开始执行时, 外部事务将被挂起, 内务事务结束时, 外部事务将继续执行. 这个事务如果失败回滚,抛出的异常没有被外部事务捕获并处理,那么外部事务也会回滚。
    ③ NESTED开始一个嵌套的事务,它是已经存在事务的一个真正子事务,嵌套事务开始执行时,会取得一个savepoint,如果嵌套事务失败,将回滚到这个savepoint。嵌套事务是外部事务的一部分,只有外部事务结束后它才会被提交。如果外部事务失败回滚,那么这个子事务也会回滚。与REQUIRES_NEW的区别是REQUIRES_NEW完全是一个新事务,与外部事务相互独立,而NESTED是外部事务的子事务,依赖于外部事务。

spring用到了哪些设计模式

  • (1) 工厂设计模式 : Spring使用工厂模式通过 BeanFactory、ApplicationContext 创建 bean 对象。
    (2) 代理设计模式 : Spring AOP 功能的实现。
    (3) 单例设计模式 : Spring 中的 Bean 默认都是单例的。
    (4) 模板方法模式 : Spring 中 jdbcTemplate、hibernateTemplate 等以 Template 结尾的对数据库操作的类,它们就使用到了模板模式。
    (5) 包装器设计模式 : 我们的项目需要连接多个数据库,而且不同的客户在每次访问中根据需要会去访问不同的数据库。这种模式让我们可以根据客户的需求能够动态切换不同的数据源。
    (6) 观察者模式: Spring 事件驱动模型就是观察者模式很经典的一个应用。
    (7) 适配器模式 :Spring AOP 的增强或通知(Advice)使用到了适配器模式 ,比如选择cglib或jdk 代理时.

spring的通知有哪些类型

  • (1) 前置通知 before: 在某连接点前执行的通知.
    (2) 后置通知AfterReturning:在某连接点正常完成后执行的通知.
    (3) 异常通知AfterThrowing: 在连接点抛出异常时执行的通知.
    (4) 最终通知After:在连接点执行完成后执行,不管是正常执行完成还是异常,都会执行最终通知。
    (5) 环绕通知around: 包围一个连接点的通知.可以在方法调用前后完成自定义的行为.

BeanFactory和ApplicationContext的区别

  • (1) Beanfactory是spring里面最底层的接口,最简单的容器的功能,只提供了实例化对象和拿对象的功能;
    (2) ApplicationContext继承自Beanfactory接口,是spring中更高级的容器,提供了更多有用的功能,比如国际化、统一的资源文件读取方式等。
    (3) Beanfactory在启动的时候不会装载bean,而是从容器中取bean的时候才实例化。ApplicationContext在启动的时候就把所有的Bean全部实例化了。

JDK动态代理如何实现?

(1) 定义一个实现接口InvocationHandler 的类

(2)通过构造函数注入被代理类。

(3)实现invoke方法

(4)在主函数中获得被代理对象的类加载器

(5)使用Proxy.newProxyInstance()产生一个代理对象。

SpringBoot

springboot自动配置原理

  • (1) springboot的自动配置是通过@EnableAutoConfiguration注解实现的,然后该注解通过@Import注解导入了AutoConfigurationSelector类,目的是给容器导入组件,该类中可以查到getCandidateConfiguration(获取候选配置)函数,在该函数中又调用了loadFactoryNames()函数,这个函数就会遍历类路径下META-INF/spring.factories文件中的内容,并将其封装成properties对象,从properties中获取到以EnableAutoConfiguration为key对应的属性,并添加在IOC容器中。虽然spring.factories文件中存放了所有的自动配置类,但不一定都全部加载,满足@ConditionOnXXX的才能加载。

yaml和properties的区别

  • (1) 格式:yaml以空格的缩进来控制层级关系,相同的前缀只需要写一次。而properties文件中所有相同的前缀每一次都要书写。
    (2) 加载顺序:先加载yaml,后加载properties文件,如果相同的配置存在于两个文件中,最后会使用properties中的配置,因为后加载的优先级最高。

springboot和spring的区别

  • (1) 自动装配、简化配置、自带容器(springboot集成了tomcat容器)