基础知识

Spring框架了解吗?说说它的优缺点

Spring 是一种轻量级开发框架,旨在提高开发人员的开发效率以及系统的可维护性。

  • 轻量:Spring 是轻量的,基本的版本大约2MB
  • 控制反转:Spring通过控制反转实现了松散耦合,对象们给出它们的依赖,而不是创建或查找依赖的对象们
  • 面向切面的编程(AOP):Spring支持面向切面的编程,并且把应用业务逻辑和系统服务分开
  • 容器:Spring 包含并管理应用中对象的生命周期和配置
  • MVC框架:Spring的WEB框架是个精心设计的框架,是Web框架的一个很好的替代品
  • 事务管理:Spring 提供一个持续的事务管理接口,可以扩展到上至本地事务下至全局事务(JTA)
  • 异常处理:Spring 提供方便的API把具体技术相关的异常(比如由JDBC,Hibernate or JDO抛出的)转化为一致的unchecked 异常。

Spring框架 - 图1

Spring,Spring MVC,Spring Boot 之间什么关系?

Spring 包含了多个功能模块(上面刚刚提高过),其中最重要的是 Spring-Core(主要提供 IoC 依赖注入功能的支持) 模块, Spring 中的其他模块(比如 Spring MVC)的功能实现基本都需要依赖于该模块。
Spring MVC 是 Spring 中的一个很重要的模块,主要赋予 Spring 快速构建 MVC 架构的 Web 程序的能力。MVC 是模型(Model)、视图(View)、控制器(Controller)的简写,其核心思想是通过将业务逻辑、数据、显示分离来组织代码。
Spring Boot 只是简化了配置。

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

  1. 工厂模式:BeanFactory就是简单工厂模式的体现,用来创建对象的实例;
  2. 单例模式:Bean默认为单例模式。
  3. 代理模式:Spring的AOP功能用到了JDK的动态代理和CGLIB字节码生成技术;
  4. 模板方法:用来解决代码重复的问题。比如:RestTemplate, JmsTemplate, JpaTemplate。
  5. 观察者模式:定义对象间一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知被动更新,如Spring中listener的实现—ApplicationListener。

    IOC

    Spring IOC

  • 传统的开发方式 :往往是在类 A 中手动通过 new 关键字来 new 一个 B 的对象出来
  • 使用 IoC 思想的开发方式 :不通过 new 关键字来创建对象,而是通过 IoC 容器(Spring 框架) 来帮助我们实例化对象。我们需要哪个对象,直接从 IoC 容器里面过去即可。

    控制反转(IoC)有什么作用

  1. 对象之间的耦合度或者说依赖程度降低;
  2. 资源变的容易管理;比如你用 Spring 容器提供的话很容易就可以实现一个单例。

    IOC的底层实现原理

    XML解析、工厂模式、反射。

    IOC接口(BeanFactory和ApplicationContext

  3. IOC的思想就是基于IOC容器完成的,IOC容器底层就是对象工厂。

  4. Spring提供IOC容器的两种实现方式:
    1. BeanFactory:IOC容器基本实现,是Spring内部的使用接口,不提供开发人员进行使用

*加载配置文件时不会创建对象,在获取对象(使用)才去创建对象

  1. AppliacationContext: BeanFactory接口的子接口,提供更多更强大的功能,一般由开发人员进行使用

加载配置文件时就会把配置文件对象进行创建
*BeanFactory和ApplicationContext的优缺点分析:

BeanFactory的优缺点:

  • 优点:应用启动的时候占用资源很少,对资源要求较高的应用,比较有优势;
  • 缺点:运行速度会相对来说慢一些。而且有可能会出现空指针异常的错误,而且通过Bean工厂创建的Bean生命周期会简单一些。

ApplicationContext的优缺点:

  • 优点:所有的Bean在启动的时候都进行了加载,系统运行的速度快;在系统启动的时候,可以发现系统中的配置问题
  • 缺点:把费时的操作放到系统启动中完成,所有的对象都可以预加载,缺点就是内存占用较大。

    IOC接口的实现类

    ApplicationContext接口有实现类:
    image.png
    ClassPathXmlApplicationContext :在 ClassPath 中寻找 xml 配置文件
    FileSystemXmlApplicationContext : 需要一个 xml 配置文件在系统中的路径
    AnnotationConfigApplicationContext :基于注解来使用的,它不需要配置文件

    IOC和DI的关系

    IOC是控制反转,是Spring的一种核心思想,通俗的说就是我们不用自己创建实例对象,这些都交给Spring的bean工厂帮我们创建管理。
    DI是依赖注入,由容器动态的将某个依赖关系注入到组件之中,是一种实现的方式。依赖注入方式有构造函数注入和setter注入。
    IOC就是容器,DI就是注入这一行为,那么DI确实就是IOC的具体功能的实现。而IOC则是DI发挥的平台和空间。所以说。IOC和DI即是相辅相成的拍档。他们都是为了实现解耦而服务的。

    Spring Bean

    Spring框架中的单例bean是线程安全的吗?

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

    Bean管理的方式

    Bean管理指的就是两个操作:创建对象、注入属性。
  • 基于 xml 配置
  • 基于注解配置
  • 基于 Java API 配置

Spring 的 Java 配置是通过使用 @Bean 和 @Configuration 来实现。

普通Bean和工厂Bean

  1. 普通Bean :在配置文件中定义bean类型就是返回类型
  2. 工厂bean : 在配置文件定义bean类型可以和返回类型不一样

具体方法:
第一步,创建类,让这个类作为工厂Bean,实现接口FactoryBean
第二部,实现接口里的方法,在实现方法中定义返回的bean类型

  1. public class MyBean implements FactoryBean<Coures> {
  2. @Override
  3. public Course getObject() throws Exception {
  4. Course course = new Course();
  5. course.setCname("abc");
  6. return course;
  7. }
<bean id="myBean" class="com.community.MyBean">
</bean>
public void test3() {
    ApplicationContext context = 
        new ClassPathXmlApplication("bean.xml");
    Course course = context.getBean("myBean",Course.class);//返回类型不一致
}

Spring 中的 bean 的作用域

bean的作用域指的是创建Bean实例是单实例还是多实例,Spring中默认为单实例。
设置方法:
在Spring配置文件bean标签里面有属性scope用于设置是单实例还是多实例。

<bean id="myBean" class="com.community.MyBean" scope="singleton">
</bean>
  • singleton : 单实例,加载Spring配置文件的时候就会创建单实例对象
  • prototype:多实例,不是在加载Spring配置文件时候创建对象,而是在调用getBean()方法的时候创建
  • request: 每一次HTTP请求都会产生一个新的bean,该bean仅在当前HTTP request内有效。
  • session :在一个HTTP Session中,一个Bean定义对应一个实例。该作用域仅在基于web的Spring ApplicationContext情形下有效。

    Bean的生命周期

    生命周期:对象从创建到销毁的过程。
    过程:
  1. 通过构造器创建bean实例(无参数构造)
  2. 为bean的属性设置值和对其他bean引用(调用set方法)
  3. 把bean实例传递bean后置处理器方法postProcessBeforeInitialization
  4. 调用bean的初始化方法(需要进行配置初始化的方法)
  5. 把bean实例传递bean后置处理器方法postProcessAfterInitialization
  6. bean可以使用了(对象获取到了)
  7. 当容器关闭时候,调用bean的销毁的方法(需要进行配置销毁的方法)

*添加后置处理器的方法:实现接口BeanPostProcesser,再将bean注册到xml文件中

public class MyBeanPost implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("初始化之前");
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("初始化之后");
        return bean;
    }
}

Spring注解

使用@Autowired注解自动装配bean的过程是怎样的?

使用@Autowired注解来自动装配指定的bean。在使用@Autowired注解之前需要在Spring配置文件进行配置标签,然后在启动spring IoC时,容器就会自动装载了一个AutowiredAnnotationBeanPostProcessor后置处理器,当容器扫描到@Autowied、@Resource或@Inject时,就会在IoC容器自动查找需要的bean,并装配给该对象的属性。在使用@Autowired时,首先在容器中查询对应类型的bean:

  • 如果查询结果刚好为一个,就将该bean装配给@Autowired指定的属性;
  • 如果查询的结果不止一个,会抛出异常,需要配合@Qualifier注解根据名称来查找;
  • 如果配合@Qualifier注解根据名称来查找的结果为空,会抛出异常,可以将@Autowire注解的required属性设置为false。

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

  • @Autowired 是 Spring 提供的注解,@Resource 是 JDK 提供的注解。

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

    @Component 和 @Bean 的区别是什么?

  • @Component 注解作用于类,而@Bean注解作用于方法。

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

    @Component, @Controller, @Service, @Repository有何区别?

    这四个注解都可以将bean注入到spring容器中,根据不同的使用场景定义了特定功能的注解组件
    @Controller用于标注控制层组件
    @Service用于标注业务层组件
    @Repository用于标注数据访问层组件,即DAO组件
    @Component泛指组件,当组件不好归类的时候,我们可以使用这个注解进行标注

    Spring中有哪些常见的注解

  • @Component:用于指示类是组件。这些类用于自动注入,并在使用基于注解的配置时配置为bean。

  • @Controller:是一种特定类型的组件,用于MVC应用程序,主要与@RequestMapping注解一起使用。
  • @Repository:用于表示组件用作存储库和存储/检索/搜索数据的操作。我们可以将此注解应用于DAO实现类。
  • @Service:用于指示类是服务层。
  • @Required:此注解简单地说明作用的bean属性必须在配置时通过bean定义中的显式属性值或通过自动注入填充。如果作用的bean属性未填充,容器将抛出BeanInitializationException。
  • @ResponseBody:用于将对象作为response,通常用于将XML或JSON数据作为response发送。
  • @PathVariable:用于将动态值从URI映射到处理方法参数。
  • @Autowired:对自动注入的位置和方式提供了更细粒度的控制。它可以用于在setter方法上自动注入bean。就像@Required 注解一样,修饰setter方法、构造器、属性或者具有任意名称和/或多个参数的PN方法。
  • @Qualifier:当有多个相同类型的bean并且只需要将一个bean自动注入时,@Qualifier注解与@Autowired注释一起使用,通过指定将连接哪个bean来消除歧义。
  • @Scope:配置Spring bean的作用域。
  • @Configuration:表示Spring IOC容器可以将该类用作bean定义的源。
  • @ComponentScan:应用此注解时,将扫描包下的所有可用类。
  • @Bean:对于基于java的配置,用@Bean注解修饰的方法将返回一个在Spring应用程序上下文中注册为Bean的对象。
  • 用于配置切面和通知、@Aspect、@Before、@After、@Around、@Pointcut等的AspectJ注解。

    AOP

    Spring AOP

  1. 面向切面编程,利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可用性,同时提高了开发的效率。
  2. 通俗描述:实现在不修改源代码的情况下给程序动态统一添加额外功能的一种技术

image.png

AOP底层原理

AOP底层使用动态代理,有两种情况:

  • 有接口情况,使用JDK动态代理

创建接口实现类代理对象,增强类的方法

  • 没有接口情况,使用CGLIB动态代理

创建子类的代理对象,增强类的方法

AOP(JDK动态代理)

JDK动态代理,使用Proxy类里面的方法创建代理对象
(1)调用newProxyInstance()方法
方法有三个参数:
第一参数:类加载器
第二参数:增强方法所在的类,这个类实现的接口,支持多个接口
第三参数:实现这个接口的InvocationHandler,创建代理对象,写增强的部分

public interface InvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
}

实现类
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    // before do something;
    Object result = method.invoke(word, args);
    // after do something;
    return result;
}

生成代理对象:

Proxy.newProxyInstance(clazz.getClassLoader(), 
                       new Class<?>[]{clazz}, invocationHandler)

AOP常用术语

AOP核心概念:
image.png
Spring AOP 通知分类:
image.png
Spring AOP 织入时期:
image.png

Spring AOP and AspectJ AOP 有什么区别?

Spring AOP 基于动态代理方式实现;AspectJ 基于静态代理方式实现。
Spring AOP 仅支持方法级别的 PointCut;AspectJ 提供了完全的 AOP 支持,它还支持属性级别的 PointCut。

JDK动态代理和CGLIB动态代理的区别

Spring AOP中的动态代理主要有两种方式,JDK动态代理和CGLIB动态代理:

  • JDK动态代理只提供接口的代理,不支持类的代理。核心InvocationHandler接口和Proxy类,InvocationHandler 通过invoke()方法反射来调用目标类中的代码,动态地将横切逻辑和业务编织在一起;接着,Proxy利用 InvocationHandler动态创建一个符合某一接口的的实例, 生成目标类的代理对象。
  • 如果代理类没有实现 InvocationHandler 接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态的生成指定类的一个子类对象,并覆盖其中特定方法并添加增强代码,从而实现AOP。CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。

静态代理与动态代理区别在于生成AOP代理对象的时机不同,相对来说AspectJ的静态代理方式具有更好的性能,但是AspectJ需要特定的编译器进行处理,而Spring AOP则无需特定的编译器处理。
InvocationHandler 的 invoke(Object proxy,Method method,Object[] args):proxy是最终生成的代理实例; method 是被代理目标实例的某个具体方法; args 是被代理目标实例某个方法的具体入参, 在方法反射调用时使用。

事务

Spring事务有了解吗?

spring支持两种方式管理事务:

  1. 基于 TransactionTemplate 的编程式事务,实际中很少使用
  2. 基于 xml 配置或者注解 @Transactional 的声明式事务。

声明式事务管理实际是通过 AOP 实现的,其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。
另外,我们还可以通过 @Transactional 注解的 isolation 参数配置隔离级别、以及通过 propagation 参数配置传播行为

@Transactional(isolation = Isolation.READ_COMMITTED, 
               propagation = Propagation.REQUIRED)

Spring事务的实现方式和实现原理

Spring事务的本质其实就是数据库对事务的支持,没有数据库的事务支持,spring是无法提供事务功能的。真正的数据库层的事务提交和回滚是通过bin log或者redo log实现的。

Spring事务传播机制:

spring事务的传播行为说的是,当多个事务同时存在的时候,spring如何处理这些事务的行为。
PROPAGATION_REQUIRED:如果当前没有事务,就创建一个新事务,如果当前存在事务,就加入该事务,该设置是最常用的设置。
PROPAGATION_SUPPORTS:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就以非事务执行。
③ PROPAGATION_MANDATORY:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就抛出异常。
PROPAGATION_REQUIRES_NEW:创建新事务,无论当前存不存在事务,都创建新事务。
⑤ PROPAGATION_NOT_SUPPORTED:以非事务方式执行,如果当前存在事务,就把当前事务挂起。
⑥ PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。
⑦ PROPAGATION_NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则按REQUIRED属性执行。

说一下 spring 的事务隔离级别?

spring 有五大隔离级别,默认值为 ISOLATION_DEFAULT(使用数据库的设置),其他四个隔离级别和数据库的隔离级别一致:

  1. ISOLATION_DEFAULT:用底层数据库的设置隔离级别,数据库设置的是什么我就用什么;
  2. ISOLATION_READ_UNCOMMITTED:读未提交,最低的隔离级别,一个事务可以读取另一个事务更新但未提交的数据。(会出现脏读、不可重复读、幻读);
  3. ISOLATION_READ_COMMITTED:读已提交,一个事务提交后才能被其他事务读取到(会出现不可重复读、幻读),Oracle、SQL server 的默认级别;
  4. ISOLATION_REPEATABLE_READ:可重复读,对同一字段的多次读取结果都是一致的,除非数据被本身事务所修改(会出现幻读),MySQL 的默认级别;
  5. ISOLATION_SERIALIZABLE:可串行化,最高的隔离级别,可以防止脏读、不可重复读、幻读。

    Spring MVC

    什么是Spring MVC?简单介绍下你对Spring MVC的理解?

    MVC 是模型(Model)、视图(View)、控制器(Controller)的简写,其核心思想是通过将业务逻辑、数据、显示分离来组织代码。
    MVC 是一种设计模式,Spring MVC 是一款很优秀的 MVC 框架。Spring MVC 可以帮助我们进行更简洁的 Web 层的开发,并且它天生与 Spring 框架集成。Spring MVC 下我们一般把后端项目分为 Service 层(处理业务)、Dao 层(数据库操作)、Entity 层(实体类)、Controller 层(控制层,返回数据给前台页面)。

    SpringMVC 工作原理了解吗?

    image.png
    流程说明(重要):

  6. 客户端(浏览器)发送请求,直接请求到 DispatcherServlet。

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

    什么是springmvc拦截器以及如何使用它?

    Spring的处理程序映射机制包括处理程序拦截器,当你希望将特定功能应用于某些请求时,例如,检查用户主题时,这些拦截器非常有用。拦截器必须实现org.springframework.web.servlet包的HandlerInterceptor。此接口定义了三种方法:
  • preHandle:在执行实际处理程序之前调用。
  • postHandle:在执行完实际程序之后调用。
  • afterCompletion:在完成请求后调用。

    拦截器Interceptor与过滤器Filter的区别

    在Tomcat容器中,过滤器和拦截器触发时机不一样,过滤器是在请求进入容器后,但请求进入servlet之前进行预处理的。请求结束返回也是,是在servlet处理完后,返回给前端之前。过滤器包裹住servlet,servlet包裹住拦截器
    Spring框架 - 图8

    Mybatis

    MyBatis是什么?

    MyBatis 是一款优秀的持久层框架,一个半 ORM(对象关系映射)框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。

    为什么说Mybatis是半自动ORM映射工具?它与全自动的区别在哪里?

    Hibernate属于全自动ORM映射工具,使用Hibernate查询关联对象或者关联集合对象时,可以根据对象关系模型直接获取,所以它是全自动的。
    而Mybatis在查询关联对象或关联集合对象时,需要手动编写sql来完成,所以,称之为半自动ORM映射工具。

    #{}和${}的区别

  • {}是占位符,预编译处理,可以防止SQL注入;${}是拼接符,字符串替换,没有预编译处理,不能防止SQL注入。

  • Mybatis在处理#{}时,#{}传入参数是以字符串传入,会将SQL中的#{}替换为?号,调用PreparedStatement的set方法来赋值;Mybatis在处理${}时,是原值传入,就是把${}替换成变量的值,相当于JDBC中的Statement编译
  • {} 的变量替换是在DBMS 中,变量替换后,#{} 对应的变量自动加上单引号;${} 的变量替换是在 DBMS 外,变量替换后,${} 对应的变量不会加上单引号

    JDBC编程有哪些不足之处,MyBatis是如何解决这些问题的?

    1、频繁创建、释放数据库连接对象,容易造成系统资源浪费,影响系统性能。可以使用连接池解决这个问题。
    解决:在mybatis-config.xml中配置数据库连接池,使用连接池管理数据库连接。
    2、Sql语句写在代码中造成代码不易维护,实际应用sql变化的可能较大,sql变动需要改变java代码。
    解决:将Sql语句配置在XXXXmapper.xml文件中,与java代码分离。
    3、向sql语句传参数麻烦,因为sql语句的where条件不一定,可能多也可能少,占位符需要和参数一一对应。
    解决:Mybatis自动将java对象映射至sql语句。
    4、对结果集解析麻烦,sql变化导致解析代码变化,且解析前需要遍历,如果能将数据库记录封装成pojo对象解析比较方便。
    解决:Mybatis自动将sql执行结果映射至java对象。