JAVA框架知识整理

框架知识整理

SpringBoot、SpringMVC、Spring的区别和他们的作用?

  • SpringBoot是一个微服务框架,其简化了Spring应用的创建、运行、测试、部署。使开发人员无需过多的关注XML配置。里面整合了许多框架例如SpringMVC、Spring Security和Spring Data JPA。并且内置了Tomcat
  • SpringMVC是web层的mvc框架,用于处理响应请求,获取表单参数,进行表单校验等。是一个基于Java的实现了MVC设计模式的请求驱动类型的轻量级Web框架,通过把Model,View,Controller分离,将web层进行职责解耦,把复杂的web应用分成逻辑清晰的几部分,简化开发,减少出错,方便组内开发人员之间的配合。
  • Spring是一个轻量级的IoC和AOP容器框架。是为Java应用程序提供基础性服务的一套框架,目的是用于简化企业应用程序的开发,它使得开发者只需要关心业务需求。常见的配置方式有三种:基于XML的配置、基于注解的配置、基于Java的配置。

Spring的IOC和AOP

IOC

IOC:控制反转,把传统上由程序代码直接操控的对象的调用权交给了容器,通过容器来实现对象组件的装配和管理。即对组件对象控制权的转移,从程序代码本身转移到了外部容器。一个对象依赖的其它对象会通过被动的方式传递进来。最直观的表达就是,IOC让对象的创建不用去new了,可以由spring自动生产,使用java的反射机制,根据配置文件在运行时动态的去创建对象以及管理对象,并调用对象的方法的。

  • 作用:管理对象的创建和依赖关系的维护;解耦,由容器去维护具体的对象;托管了类的生产过程。
  • 优点:将对象之间的相互依赖关系交给Ioc容器来管理,并由IOC容器完成对象的注入,这样可以很大程度上简化应用的开发。Ioc就像一个工厂一样,需要创建对象只需要配置好文件或者注解即可。完全不用考虑对象是怎么创建出来的。
    初始化过程:XML-》Resource-》BeanDefinition-》BeanFactory
  • Spring的IOC有三种注入方式 :构造器注入、setter方法注入、根据注解注入。

AOP

AOP:面向切面编程,能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可扩展性和可维护性。如果没有AOP的话,在做一些日志管理,可能需要给每个方法都加上日志处理,如果我们需要进行一些修改的逻辑,就必须得一个一个来。通过AOP的动态代理,可以在指定位置执行对应流程。这样就可以将一些横向的功能抽离出来形成一个独立的模块,然后在指定位置插入这些功能。这样的思想,被称为面向切面编程,亦即AOP。

  • 基于动态代理实现,如果要代理的对象实现了某个接口,那么Spring AOP就会使用JDK动态代理去创建代理对象。对于没有实现接口的对象,无法使用JDK动态代理,则转为使用CGlib动态代理生成一个被代理对象的子类在作为处理。
  • AspectJ也是一个AOP框架,是静态代理的增强,所谓静态代理,就是AOP框架会在编译阶段生成AOP代理类,因此也称为编译时增强,他会在编译阶段将AspectJ(切面)织入到Java字节码中,运行的时候就是增强之后的AOP对象。静态代理与动态代理的区别就是生成AOP代理对象的时机不同,一个是编译期,另一个是运行期。静态代理性能较好但需要特定的编译器进行处理。
  • AOP的动态代理:
    • JDK的动态代理只提供接口的代理,不支持类的代理。核心InvocationHandler接口和Proxy类,InvocationHandler通过invoke()方法反射来调用目标类的代码,动态的将横切逻辑和业务编织在一起。接着Proxy利用InvocationHandler动态创建一个符合某一接口的实例,生成目标类的代理对象。
    • 如果代理类没有实现InvocationHandler接口,那么Spring AOP会选择使用CGLib(Code Genreation Library)来动态代理目标类。CGLib是一个代码生成的类库,可以在运行时动态的生成指定类的一个子类对象,并覆盖其中特定方法并添加增强代码,从而实现AOP。CGLib是使用继承的方式做的动态代理,因此如果某个类被标记为final,那么其无法使用CGLib进行动态代理。

AOP的相关概念:

  • 通知advise:用于描述切面的目标,即切面必须要完成的工作,通知定义了切面是什么以及何时使用。有五种:前置通知、后置通知、返回通知、异常通知、环绕通知。
  • 切点pointcut:一个切面并不需要通知应用的所有连接点,切点有助于缩小切面所通知的连接点范围。如果通知定义了切面的何时和什么,则切点就是定义了何地。即定义了需要执行在哪些连接点上执行通知。
  • 切面Aspect:通知和切点的结合,通知和切点共同定义了切面的全部内容:是什么,在何时和在何处完成其功能。
  • 连接点join point:在应用执行过程中能够插入切面的一个点,这个点可以是调用方法时,抛出异常时,修改一个字段时。切面代码利用这些点插入到应用的政策流程之中,并添加新的行为
  • 引入Introduction:允许我们向现有的类添加新方法或属性。
  • 织入Weaving:把切面应用到目标对象并创建新的代理对象的过程。切面在指定的连接点被织入到目标对象中,在目标对象的生命周期中有很多个点可以进行织入:
    • 编译期:切面在目标类编译时被织入。这种方式需要有特殊的编译器。
    • 类加载期:切面在目标类加载到JVM时被织入。这种方式需要特殊的类加载器,它可以在目标类被引入应用之前增强该目标类的字节码。
    • 运行期:切面在应用运行的某个时刻被织入。一般情况下,在织入切面时,AOP容器会为目标对象动态的创建一个代理对象。

什么是依赖注入(DI)

依赖注入(DI)指的是组件之间的依赖关系由容器在运行期决定,即由同期动态的将某个依赖关系注入到组件之中。依赖注入的目的是为了提升组件重用的频率,并为系统搭建一个灵活、可扩展的平台,以及对类的解耦。

IOC的一个重点是在系统运行中,动态的向某个对象提供它所需要的其他对象,这一点就是通过依赖注入实现的。

例:ClassA中用到了ClassB的对象b,一般情况下,需要在A的代码中显示的new出来一个B的对象。采用依赖注入技术之后,A的代码只需要定义一个私有的B对象,不需要直接new来获得这个对象,而是通过相关的容器控制程序来将B的对象在外部new出来并注入到A来的引用中。

依赖注入与控制反转的区别是:依赖注入更倾向于实现,而控制反转更倾向于一个法则。

  • 控制反转:创建对象实例的控制权从代码控制剥离到IOC容器控制。
  • 依赖注入:创建对象实例时,为这个对象注入属性值或其他对象的实现。

注入的方式

构造器注入、字段注入

Spring中Bean的作用域

作用域限定了Spring Bean的作用范围,在Spring配置文件定义Bean时,通过声明scope配置项,可以灵活定义Bean的作用范围。

  • singleton(单例):唯一bean实例,IOC只会创建并返回一个bean。Spring中的bean默认都是单例的。
  • prototype(原型):每次请求都会创建一个新的bean实例。
  • request:只有HTTP请求才会创建,每一次请求都会创建一个新的bean,该bean只在当前HTTP request内有效,适用于WebApplicationContext环境。
  • session:只有HTTP请求才会创建,每一次请求都会创建一个新的bean,该bean只在当前HTTP session内有效。
  • global-session:全局session作用域,Spring5中没有。

Spring中单例Bean的线程安全问题

单例Bean存在线程安全问题:当前多个线程操作同一个对象的时候,对该对象的非静态成员变量的写操作会发生线程安全问题。

解决方案:

  • 把bean的作用域改成prototype:在类或接口上加上@Scope(“prototype”),表示每次调用该接口都会生成一个新的Bean。
  • 在类中定义一个TreadLocal成员变量,将不安全的可变成员变量保存在ThreadLocal中。TreadLocal意味每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突,因为每一个线程都有自己的变量副本。

Spring中Bean的生命周期

  • 实例化Bean:Spring启动,查找并加载需要被Spring管理的bean,进行Bean的实例化。

    • 对于BeanFactory容器,当客户向容器请求一个尚未初始化的bean时,或初始化bean的时候需要注入另一个尚未初始化的依赖时,容器就会调用createBean进行实例化。
    • 对于ApplicationContext容器,当容器启动结束后便实例化所有的bean。
    • 容器通过获取BeanDefinition对象中的信息进行实例化,并且只是简单的实例化,并没有完成依赖注入。实例化Bean被包装在BeanWrapper中,BeanWrapper提供了设置对象属性的接口,从而避免了使用反射机制设置属性。
  • 设置Bean属性:Bean实例化后对将Bean的引入和值注入到Bean的属性中。Spring根据BeanDefinition中的信息和BeanWrapper提供的设置属性接口进行依赖注入。

  • 注入Aware接口:检测该对象是否实现了xxxAware接口,并将相关的接口注入给Bean。

    • 如果Bean实现了BeanNameAware接口的话,Spring将Bean的Id传递给setBeanName()方法
    • 如果Bean实现了BeanFactoryAware接口的话,Spring将调用setBeanFactory()方法,将BeanFactory容器实例传入。
    • 如果Bean实现了ApplicationContextAware接口的话,Spring将调用Bean的setApplicationContext()方法,将bean所在应用上下文引用传入进来。
  • 进行自定义处理:当经过上述的步骤后,Bean已经被正确的构造,如果想要对象被使用前添加一些自定义的处理,通过BeanPostProcessor接口实现。

    • postProcessBeforeInitialzation( Object bean, String beanName ) 当前正在初始化的bean对象会被传递进来,我们就可以对这个bean作任何处理。 这个函数会先于InitialzationBean执行,因此称为前置处理。 所有Aware接口的注入就是在这一步完成的。
    • postProcessAfterInitialzation( Object bean, String beanName ) 当前正在初始化的bean对象会被传递进来,我们就可以对这个bean作任何处理。 这个函数会在InitialzationBean完成后执行,因此称为后置处理。
  • InitializingBean与init-method

    • 如果Bean实现了InitializingBean接口,Spring将调用他们的afterPropertiesSet()方法。类似的,如果bean使用init-method声明了初始化方法,该方法也会被调用。Spring为了降低对客户代码的侵入性,给bean的配置提供了init-method属性,该属性指定了在这一阶段需要执行的函数名。Spring便会在初始化阶段执行我们设置的函数。init-method本质上仍然使用了InitializingBean接口。(可以重写这些方法)
  • DisposableBean和destroy-method

    • 和init-method一样,通过给destroy-method指定函数,就可以在bean销毁前执行指定的逻辑。 (可以重写这些方法)

Spring框架中用到的设计模式

  • 工厂模式:通过BeanFactory和ApplicationContext来创建bean对象。
  • 代理模式:Spring AOP的实现。
  • 单例模式:Spring中的bean默认都是单例的。
    • 减少了新生成实例的消耗:Spring通过反射或者CGLIB动态代理去生成bean都是很耗性能的操作,给对象分配内存也会涉及复杂的算法。
    • 减少jvm的垃圾回收:由于不会给每个请求都新生成bean实例,所以自然回收的对象就少了。
    • 单例获取bean操作除了第一次生成之外,其余都是从缓存里获取。
  • 模板方法模式:Spring中的jdbcTemplate、hibernateTemplate等以Template结尾的对数据库进行操作的类。
  • 观察者模式:定义对象见一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知被制动更新,如Spring中listener的实现–ApplicationListener。Spring事件模型。
  • 适配器模式:Spring AOP的增强或通知(Advice)使用到了适配器模式、Spring MVC中也是用到了适配器模式适配Controller。

Spring中的事务

事务:逻辑上的一组操作,组成这组操作的各个逻辑单元要么一起成功,要么一起失败。

事务管理方式

  • 编程式事务:在代码中硬编码TransactionTemplate(不推荐)
  • 声明式事务:在配置文件中配置,分为基于XML的声明式事务和基于注解的声明式事务。是建立在AOP上的,本质通过AOP功能对方法前后进行拦截,将事务处理的功能编织到拦截的方法中。即在目标方法开始之前加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。声明式事务的最大优点就是不需要在业务逻辑代码中掺杂事务管理的代码,只需要在配置文件中做相关的配置或通过@Transactional注解,便可以将事务规则应用到业务逻辑中。唯一不足的是最细粒度只能到方法级别,而不能像编程式事务到代码块的级别。

事务的传播

当多个事务同时存在的时候,Spring如何处理这些事务的行为:

  • PROPAGETION_REQUIRED:如果当前没有事务,就创建一个新的事务;如果有事务则加入该事务。是最常见的设置。
  • PROPAGATION_SUPPORTS:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就以非事务执行。
  • PROPAGATION_MANDATORY:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就抛出异常。
  • PROPAGATION_REQUIRES_NEW:创建新事务,无论当前存不存在事务,都创建新事务。
  • PROPAGATION_NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
  • PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。
  • PROPAGATION_NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则按REQUIRED属性执行。

Spring的通知机制

  • 前置通知(Before advice):在某连接点(join point)之前执行的通知,但这个通知不能阻止连接点前的执行(除非它抛出一个异常)。
  • 返回后通知(After returning advice):在某连接点(join point)正常完成后执行的通知:例如,一个方法没有抛出任何异常,正常返回。
  • 抛出异常后通知(After throwing advice):在方法抛出异常退出时执行的通知。
  • 后通知(After (finally) advice):当某连接点退出的时候执行的通知(不论是正常返回还是异常退出)。
  • 环绕通知(Around Advice):包围一个连接点(join point)的通知,如方法调用。这是最强大的一种通知类型。 环绕通知可以在方法调用前后完成自定义的行为。它也会选择是否继续执行连接点或直接返回它们自己的返回值或抛出异常来结束执行。 环绕通知是最常用的一种通知类型。大部分基于拦截的AOP框架,例如Nanning和JBoss4,都只提供环绕通知。

Spring的三级缓存与循环依赖

Spring在启动的过程中,使用到了三个map,称为三级缓存。

  • singletonObjects:一级缓存
  • earlySingletonObjects:二级缓存
  • singletonFactories:三级缓存

Spring的启动过程

  1. 加载配置文件
  2. 解析配置文件转化beanDefination,获取到bean的所有属性、依赖及初始化用到的各类处理器等
  3. 创建beanFactory并初始化所有的单例bean
  4. 注册所有的单例bean并返回可用的容器,一般为扩展的applicationContext

一级缓存

在启动过程的第三步(创建beanFactory并初始化所有单例bean)中,所有单例的bean初始化完成后会存放在一个Map(singletonObjects)中,beanName为key,单例bean为value。这就是一级缓存

在第三步单例bean初始化的过程如下:

  • bean标记为创建中

  • new出bean对象

  • 如果支持循环依赖则生成三级缓存,可以提前暴露bean

  • 填充bean属性,解决属性依赖

  • 初始化bean,处理Aware接口并执行各类bean后处理器,执行初始化方法,如果需要生成aop代理对象

  • 如果之前解决了aop的循环依赖,则缓存中放置了提前生成的代理对象,然后使用原始bean继续执行初始化,所以需要再返回最终bean前,把原始bean置换为代理对象返回。

  • 此时bean已经可以被使用,进行bean注册并注册销毁方法

  • 将bean放入容器中(一级缓存),移除创建中标记及二三级缓存

循环依赖问题

假设初始化A时,发现A依赖于B,即A初始化到了第2步,此时B还没有初始化,则需要暂停A去初始化B,那么此时需要一个可以标记A(创建中)的Map,提前的暴露正在创建的bean提供给其他的bean依赖。如果在初始化A所依赖的bean B时,发现B也需要注入一个A的依赖,则B可以从创建中的beanMap中直接获取A(创建中)对象去注入,然后完成B的初始化,返回给正在注入属性的A,最终A也完成初始化。

如果配置不允许循环依赖,就用不到上述缓存了。一般来说复杂的的业务,例如一个service中需要注入其他的service,service互相引用就会出现循环依赖。所以出现循环依赖就去解决,不出现就不解决,虽然支持循环依赖,但是只有在出现循环依赖时才真正暴露早期对象,否则只暴露一个获取bean的方法,并没有真正暴露bean,因为这个方法不会被执行到。这块的实现就是三级缓存,只缓存了一个单例bean工厂。

这个bean工厂不仅可以暴露早期bean还可以暴露代理bean,如果存在aop代理,则依赖的应该是代理对象,而不是原始的bean。

二级缓存

三级缓存中提到的出现循环依赖才去解决,也就是说出现循环依赖时,才会执行工厂的getObject生成(获取)早期的依赖,这个时候就需要给它换个地方存储了,因为暴露的不是工厂而是对象,所以需要使用一个新的缓存去保存暴露的早期对象(earlySingletonObjects),同时移除提前暴露的工厂,也不需要在多重循环依赖时每次去执行getObject。

Spring Bean的自动装配

Spring中对象无须自己查找或者创建与其关联的其他对象,由容器负责把需要相互协作的对象引用赋给各个对象。

  • 基于xml
    • no:默认的方式是不进行自动装配,通过手动设置ref属性来进行装配Bean,Bean依赖关系必须通过property元素定义
    • byName:根据属性名自动装配。BeanFactory查找容器中的全部Bean,找出id与属性的setter方法入参匹配的Bean。
    • byType:表示根据property的数据类型进行自动装配。BeanFactory查找容器中的全部Bean。
      • 如果正好有一个与依赖属性类型相同的Bean,就自动装配这个属性。
      • 如果有多个这样的Bean,Spring无法决定注入哪个Bean,就抛出一个UnsatisfiedDependencyException异常。
      • 如果没有匹配的Bean,就什么都不会发生,属性不会被设置。
    • constructor:使用构造方法完成对象注入,其实也是根据构造方法的参数类型进行对象查找,相当于采用byType的方式。即Spring会寻找与参数数据类型相同的bean,通过构造函数将其注入。
    • default:表示默认采用上一级标签的自动装配的取值。如果存在多个配置文件的话,那么每一个配置文件的自动装配方式都是独立的。
  • 基于注解@Autowired
    • 使用@Autowired注解之前需要在Spring配置文件进行配置,
    • 在启动Spring时,容器自动装在了一个AutowiredAnnotationBeanPostProcessor后置处理器,当容器扫描到@Autowired,@Resource或者@Inject时,就会在IOC容器自动查找需要的bean,并装配给该对象属性。
    • 使用@Autowired首先会在容器中查询对应类型的bean(类型就是bean class,名称就是bean id):
      • 如果查询结果刚好为一个,就将该bean进行装配。
      • 如果查询结果不止一个,那么@Autowired会根据名称来查找。
      • 如果查不到,则抛出异常,解决方法就是@Autowired(required = false)
    • @Autowired可用于构造函数,成员变量,setter方法。开发中也用在service或者mapper接口上

注意@Autowired和@Resource之间的区别

  • @Autowired默认是按照类型装配注入的,查询到多个结果会按照名称来查找。默认情况下它要求依赖对象必须存在(可以设置它required属性为false)。
  • @Resource默认是按照名称来装配注入的,只有当找不到与名称匹配的bean才会按照类型来装配注入。

@Component和@Bean的区别

作用对象不同:@Component作用于类,而@Bean作用于方法

  • @Component注解通常是通过类路径扫描来自动侦测以及自动装配到Spring容器中(我们可以使用@ComponentScan注解定义要扫描的路径)。

  • @Bean注解通常是在标有该注解的方法中定义产生这个bean,告诉Spring这是某个类的实例,当我需要用它的时候还给我。@Bean注解比@Component注解的自定义性更强,而且很多地方只能通过@Bean注解来注册bean。比如当引用第三方库的类需要装配到Spring容器的时候,就只能通过@Bean注解来实现。

    例如

    @Configuration
    public class AppConfig {
        @Bean
        public TransferService transferService() {
            return new TransferServiceImpl();
        }
    }
    

    等价于

    <beans>
    	<bean id="transferService" class="com.hty.TransferServiceImpl"/>
    beans>
    

    如果说一个方法中有switch语句且其中调用了其他的方法,用@Component是无法实现的。

将一个类声明为Spring的bean的注解有哪些

一般使用@Autowired注解去自动装配bean,而想要把一个类标识为可以用@Autowired注解自动装配的bean,可以采用以下的注解实现。

  • @Component:通用,可标注任意类为Spring的Bean,如果一个类不知道属于哪一个层,可以使用@Component注解标注。@Component一般用于各种组件中。
  • @Repository:对应DAO层,主要用于数据库的相关操作。
  • @Service:对应服务层,主要涉及一些复杂的业务逻辑,需要用到DAO层。
  • @Controller:对应Spring MVC的控制层,主要用于接受用户的请求并调用Service层的方法返回数据给前端页面。
  • @Configuration:标注一个类是配置类,用来替换XML。

@Required和@Qualifier

@Required:表明bean的属性必须在配置的时候进行配置,通过一个bean定义的显示属性值或者通过自动装配,没有则抛异常BeanInitializationException。

@Qualifier:在Controller中需要注入service,如果该service有多个实现类如何区分开这两个impl呢,就用该注解表明即可。

*SpringMVC的执行流程

  • 用户发送请求到前端控制器DispatcherServlet。
  • DispatcherServlet收到请求后,调用HandlerMapping处理器映射器,请求获取Handler。
  • HandlerMapping根据请求url找到具体的处理器,生成处理器对象及处理器拦截器(有则生成),一并返回给DispatcherServlet。
  • DispathcerServlet调用HandlerAdapter处理器适配器。
  • HandlerAdapter经过适配调用具体的Handler(Controller)进行执行,Handler执行完成返回ModelAndView给DispatcherServlet。
  • DispatcherServlet将ModelAndView传给ViewResolver视图解析器进行解析。
  • ViewResolver解析后返回具体的View给DispatcherServlet。
  • DispatcherServlet对View进行渲染视图(将模型数据填充到视图中),然后响应用户。

SpringMVC的主要组件

  • 前端控制器DispatcherServlet:接受请求、响应结果。相当于转发器
  • 处理器映射器HandlerMapping:根据请求的URL来查找Handler
  • 处理器适配器HandlerAdapter:负责执行Handler
  • 视图解析器ViewResolver:进行视图的解析,根据视图逻辑名解析成真正的View
  • 视图View(JSP,Thymeleaf)

SpringMVC解决了什么问题

SpringMVC是一个基于MVC的框架,主要解决了前端页面和后台代码分离的问题,以及一个请求对应一个方法。

SpringMVC如何重定向和转发,如何解决乱码?

转发:返回值前面加forward

重定向:返回值前面加redirect

POST:配置一个CharacterEncodingFilter过滤器,设置成utf-8;

GET:修改tomcat配置文件添加编码与工程编码一致

SpringMVC的控制器是否单例?有什么问题?如何解决?

是单例,在多线程下会有线程安全的问题。不要直接在Controller中写同步逻辑,而是在具体的Service中写,不要在Controller层中写字段。

SpringMVC开发常用注解

@RequestMapping:用于处理请求url映射的注释,可用于类或者方法上。用于类上则表示类中所有的响应请求方法都是以该地址作为父路径。

  • value:指明请求的实际地址,例如value = “/list”。其中如果是以数组的形式,则表示多个请求映射到一个方法上。也可以以正则表达式的形式。
  • method:指明请求的方法类型,例如GET、POST、DELETE、PUT等。
  • params:指定请求地址中必须包含某些参数值,才让该方法处理。可以有多个值,并且支持与非或运算。
  • consumes:指定处理请求的提交内容类型(Content-Type),例如application/json,text/html等。
  • produces:指定返回的内容类型,仅当request请求头中的Accept类型中包含该指定类型才返回。
  • headers:指定request中必须包含某些指定的header值,才能让该方法处理请求。

@PathVariable:将URL中的占位符参数绑定到方法的参数上,例如@RequestMapping(“show5/{id}/{name}”) 用该注解可以获取。

@RequestParam:将URL中后面的参数绑定到方法的参数上,例如一url:localhost:8080/hty?name=hty 使用该注解可以获取,如果不确定后面有无参数,可以加个required = false。

@RequestBody:实现接受HTTP请求的JSON数据,用于方法参数上。只能用于@PostMapping

@ResponseBody:将controller方法对象转换为json对象响应给前端

@RestController:@ResponseBody + @Controller,即所有的方法默认返回json

@GetMapping:方法返回对应的请求是GET,POST,DELETE,PUT同理(少用)

SpringBoot循环依赖

循环依赖是什么?

Bean A 依赖 B,Bean B 依赖 A这种情况下出现循环依赖,即Bean A → Bean B → Bean A。
更复杂的间接依赖造成的循环依赖如下:Bean A → Bean B → Bean C → Bean D → Bean E → Bean A。当存在该依赖的时候,Spring加载Bean时无法决定先创建哪个Bean而产生异常。

如何解决

  • 进行代码重构
  • 在配置文件中给相互依赖的两个bean中任意一个加上lazy-init属性
  • 在注入的bean的@Autowire注解后加入@Lazy注解
  • 使用Setter/Field注入(给Bean A中的setterB加@Autowired)
  • 使用@PostConstruct
  • 三级缓存:
    • A执行doGetBean、查询缓存、createBean创建实例,实例化完成放入三级缓存singleFactories中,接着执行populateBean方法装配属性,但是发现有一个属性时B的对象
    • 此时调用B的doGetBean、查询缓存、createBean去创建实例,实例化完成之后放入三级缓存singleFactories中,执行populateBean装配属性,但是此时又发现有一个属性是A的对象。
    • 此时调用doGetBean来创建A的实例,但是执行到getSingleton查询缓存的时候,从三级缓存中查询到了A的实例此时直接返回A,不用执行后续的流程去创建A了,此时B完成了属性装配,此时完整的B对象放入以及缓存singletonObjects中。
    • B创建完成了,A自然完成了属性装配,也创建完成放入了一级缓存singletonObjects中

MyBatis优点

  • 减少代码量,上手简单。
  • 不会对应用程序或者数据库的现有设计造成影响,SQL写在XML里,从程序代码中彻底分离,降低了耦合度。
  • 提供XML标签,支持编写动态SQL语句。
  • 提供映射标签,支持对象与数据库的ORM字段关系映射。

MyBatis中#{}和${}的区别是什么?

  • #{}是预编译处理,而${}是字符串替换。
  • Mybatis在处理#{}时,会将sql中的#{}替换为?号,调用PreparedStatement的set方法来赋值。
  • Mybatis在处理 时,把 {}时,把 时,把{}替换成变量的值。
  • 使用#{}能有效地防止SQL注入,提高系统的安全性。

xml映射文件和Mapper接口工作原理是什么

接口的全限定名就是映射文件中的namespace值,接口的方法名就是映射文件中Mapper的Statement的id,接口方法参数就是传给sql的参数。Mapper接口没有实现类,当调用接口方法时,接口全限定类名+方法名拼接成字符串作为key值,可以唯一定位一个MapperStatement。
在Mybatis中每一个、、、标签,都会被解析为一个MapperStatement对象。Mapper接口里的方法,是不能重载的,因为是使用 全限名+方法名 的保存和寻找策略。Mapper 接口的工作原理是JDK动态代理,Mybatis运行时会使用JDK动态代理为Mapper接口生成代理对象proxy,代理对象会拦截接口方法,转而执行MapperStatement所代表的sql,然后将sql执行结果返回。

Mybatis的一二级缓存

MyBatis的缓存:在执行一次SQL查询或者SQL更新之后,这条SQL语句并不会消失,而是被MyBatis缓存起来,当再次执行相同的SQL语句,就会直接从缓存中进行提取,而不是再次执行SQL。其中MyBatis分一级二级缓存,一级是sqlSession级别的,二级是表级别的(一个mapper一个缓存)。

一级缓存:基于 PerpetualCache 的 HashMap 本地缓存,其存储作用域为 sqlSession,当 Session flush 或 close 之后,该 Session 中的所有缓存清空,默认打开一级缓存。

  • 在同一个SqlSession中, Mybatis 会把执行的方法和参数通过算法生成缓存的键值,将键值和结果存放在一个Map中,如果后续的键值一样,则直接从 Map 中获取数据;
  • 不同的SqlSession之间的缓存是相互隔离的;
  • 用一个SqlSession,可以通过配置使得在查询前清空缓存;
  • 任何的UPDATE,INSERT,DELETE语句都会清空缓存。
  • 失效的情况:
    • sqlSession不同,即使SQL语句相同依然会执行两次。
    • sqlSession相同,但查询条件不同。
    • sqlSession相同,查询条件也相同,但两次查询之间执行了增删改操作(清空缓存)。
    • sqlSeesion对象相同,两次查询条件相同,中间无增删改但是调用了clearCache()方法。
  • MyBatis认为,对于两次查询,如果以下条件都完全一样,那么就认为它们是完全相同的两次查询。
    • 传入的statementId。
    • 查询时要求的结果集中的结果范围.
    • 这次查询所产生的最重要传递给PreparedStatement的SQL语句字符串,以及要设置的参数值。

二级缓存:多个sqlSession需要共享缓存,则需要开启二级缓存,开启二级缓存后,会使用CachingExecutor装饰Executor,进入一级缓存的查询流程前,先在CachingExecutor进行二级缓存的查询。

  • 二级缓存默认是不开启的,需要手动开启,实现二级缓存MyBatis要求返回的POJO必须是可序列化的,开启二级缓存需要在MyBatis的配置文件中进行配置:

    <settings>
    	<setting name = "cacheEnabled" value = "true" />
    settings>
    

    开启二级缓存还需要在Mapper的XML配置文件中加入标签。

  • 失效条件:

    • 第一次SqlSession未提交,此时SQL语句产生的查询结果还没有放入二级缓存中,此时第二次SqlSession在查询的时候是感受不到二级缓存的存在的。
    • 如果出现了UPDATE、INSERT、DELETE操作,缓存将被清空

慎用缓存:多表操作下,在某张表中做了刷新缓存的操作,在另一张表中的缓存是仍然有效的,这样会导致查询结果不正确。

MyBatis执行过程

  • 读取Mybatis配置文件:mybatis-config.xml为Mybatis的全局配置文件,配置了MyBatis的运行环境等信息,例如数据库连接信息
  • 加载映射文件:SQL映射文件,该文件中配置了操作数据库的SQL语句,需要在MyBatis配置文件中加载
  • 构造会话工厂SqlSessionFactory:通过MyBatis的环境等配置信息构建
  • 创建会话对象:由SqlSessionFactory来创建SqlSession对象,该对象中包含了执行SQL的所有方法
  • Executor执行器:Mybatis底层定义了一个Executor接口来操作数据库,它根据SqlSession传递的参数动态的生成需要执行的SQL语句,同时负责查询缓存的维护
  • MappedStatement对象:在Executor接口的执行方法中有一个MappedStatement类型的参数,该参数是对映射信息的封装,用于存储要映射的SQL语句的id、参数等信息
  • 输入参数映射:输入参数类型可以使Map、List等集合类型,也可以是基本数据类型和POJO类型。该过程类似JDBC对preparedStatement对象设置参数的过程
  • 输出结果映射

在mapper中如何传递多个参数

  • 顺序传参,即#{0} #{1}

  • @Param注解传参法

  • Map传参法

  • Java Bean传参法:

    <select id="selectUser" parameterType="com.hty.pojo.User" resultMap="UserResultMap">
    

RESTful

一种网络应用程序的设计风格和开发方式,基于HTTP,可以使用XML或者JSON格式定义。

  • 每一个URI代表一种资源
  • 客户端使用GET、POST、PUT、DELETE四个操作方式对服务端的资源进行操作。GET用来获取、POST用来新建或者更新、PUT用来更新、DELETE用来删除资源

你可能感兴趣的:(java,spring,前端)