0、Spring,Spring MVC,Spring Boot 之间什么关系?
Spring 包含了多个功能模块,其中最重要的是 Spring-Core(主要提供 IoC 依赖注入功能的支持) 模块, Spring 中的其他模块(比如 Spring MVC)的功能实现基本都需要依赖于该模块。
Spring MVC 是 Spring 中的一个很重要的模块,主要赋予 Spring 快速构建 MVC 架构的 Web 程序的能力。MVC 是模型(Model)、视图(View)、控制器(Controller)的简写,其核心思想是通过将业务逻辑、数据、显示分离来组织代码。
使用 Spring 进行开发各种配置过于麻烦比如开启某些 Spring 特性时,需要用 XML 或 Java 进行显式配置。于是,Spring Boot 诞生了!
Spring 旨在简化 J2EE 企业应用程序开发。Spring Boot 旨在简化 Spring 开发(减少配置文件,开箱即用!)。Spring Boot 只是简化了配置,如果你需要构建 MVC 架构的 Web 程序,你还是需要使用 Spring MVC 作为 MVC 框架,只是说 Spring Boot 帮你简化了 Spring MVC 的很多配置,真正做到开箱即用!
J2SE全称是java 2 Standard Edition(标准版), J2SE 包含那些构成Java语言核心的类。比如:数据库连接、接口定义、输入/输出、网络编程
J2EE全称是java 2 enterprise edition(企业版),现在最新称谓是java EE 5,两个是同样的东东,Enterprise Edition(企业版) J2EE 包含J2SE 中的类,并且还包含用于开发企业级应用的类。比如:EJB、servlet、JSP、XML、事务控制 。
J2SE包含于J2EE中,简单点说,J2EE是J2SE在企业应用需求上的扩张,语法是以J2SE为基础,通过添加新的API,框架技术来实现大规模,高复杂度的企业级项目的开发。
一、Spring
1、AOP
概念;几个名词解释;JDK VS Cglib;通知(5)
AOP实现的关键在于 代理模式,AOP代理主要分为静态代理和动态代理。静态代理的代表为AspectJ;动态代理则以Spring AOP为代表。
将 Advice 应用于目标对象后创建的对象称为代理。在客户端对象的情况下,目标对象和代理对象是相同的。
Advice + Target Object = Proxy
技术实现:Spring AOP,AspectJ(xml和注解)
Spring AOP 和 AspectJ AOP 有什么区别
Spring AOP 属于运行时增强,而 AspectJ 是编译时增强。
Spring AOP 基于代理(Proxying),而 AspectJ 基于字节码操作(Bytecode Manipulation)。
2、IOC
概述;比如淘宝。优点:第一,资源集中管理,实现资源的可配置和易管理。第二,降低了使用资源双方的依赖程度,也就是我们说的耦合度。
什么是DI?DI 是 IoC 的技术实现,就是注入属性,必须在创建对象的基础上进行。DI(dependency injection):依赖注入,只需在程序中提供要使用的对象名称即可,至于对象如何在容器中创建、赋值、查找都由容器内部实现。在Spring创建对象的过程中,把对象依赖的属性注入到对象中。依赖注入主要有两种方式:构造器注入和属性注入。
DI实现方式:xml和注解
xml:使用标签和属性完成。包括set注入(property
标签)和构造器注入( constructor-arg
标签)。
若类中的属性为引用类型,如 Student 类中有自定义类 School,可采用 ref 标签属性引用。
<bean id="myStudent" class="com.springdemo.pag2.Student"> <property name="name" value="Kurisu" /> <property name="age" value="19" /> <property name="school" ref="mySchool" /> </bean> <bean id="myStudent" class="com.springdemo.pag3.Student"> <constructor-arg name="name" value="Kurisu" /> <constructor-arg name="age" value="19" /> </bean>
-
从XML中读取配置文件。
-
将bean标签解析成 BeanDefinition,如解析 property 元素, 并注入到 BeanDefinition 实例中。
-
将 BeanDefinition 注册到容器 BeanDefinitionMap 中。
-
BeanFactory 根据 BeanDefinition 的定义信息创建实例化和初始化 bean。
单例bean的初始化以及依赖注入一般都在容器初始化阶段进行,只有懒加载(lazy-init为true)的单例bean是在应用第一次调用getBean()时进行初始化和依赖注入。
// AbstractApplicationContext // Instantiate all remaining (non-lazy-init) singletons. finishBeanFactoryInitialization(beanFactory);
多例bean 在容器启动时不实例化,即使设置 lazy-init 为 false 也没用,只有调用了getBean()才进行实例化。
loadBeanDefinitions
采用了模板模式,具体加载 BeanDefinition
的逻辑由各个子类完成。
3、Bean
什么是bean?Bean 代指的就是那些被 IoC 容器所管理的对象。
将一个类声明为 Bean 的注解有哪些?
作用域(2+4);生命周期(4大类:实例化 Instantiation;属性赋值 Populate;初始化 Initialization;销毁 Destruction)
单例 Bean 的线程安全问题了解吗?
大部分时候我们并没有在项目中使用多线程,所以很少有人会关注这个问题。单例 Bean 存在线程问题,主要是因为当多个线程操作同一个对象的时候是存在资源竞争的。常见的有两种解决办法:
-
在 Bean 中尽量避免定义可变的成员变量。
-
在类中定义一个
ThreadLocal
成员变量,将需要的可变成员变量保存在ThreadLocal
中(推荐的一种方式)。
不过,大部分 Bean 实际都是无状态(没有实例变量)的(比如 Dao、Service),这种情况下, Bean 是线程安全的。
@Component
,@Repository
,@Service
,@Controller
@Repository
: 对应持久层即 Dao 层,主要用于数据库相关操作。
都是使用注解定义 Bean。@Bean 是使用 Java 代码装配 Bean,@Component 是自动装配 Bean。
@Component 注解用在类上,表明一个类会作为组件类,并告知Spring要为这个类创建bean,每个类对应一个 Bean。
@Bean 注解用在方法上,表示这个方法会返回一个 Bean。@Bean 需要在配置类中使用,即类上需要加上@Configuration注解。
Spring常用的注入方式有:属性注入, 构造方法注入, set 方法注入
-
构造器注入:利用构造方法的参数注入依赖
-
set方法注入:调用setter的方法注入依赖
-
属性注入:在字段上使用@Autowired/Resource注解
其中,基于属性注入的方式,容易导致Spring 初始化失败。因为在Spring在初始化的时候,可能由于属性在被注入前就引用而导致空指针异常,进而导致容器初始化失败。如果可能的话,尽量使用构造器注入。@Autowired默认是按照类型匹配(ByType),因此有可能会出现两个相同的类型bean,进而导致Spring 装配失败。
@Autowired 和 @Resource 的区别是什么?
-
@Autowired
是 Spring 提供的注解,@Resource
是 JDK 提供的注解。 -
@Autowired
默认的注入方式为byType
(根据类型进行匹配),@Resource
默认注入方式为byName
(根据名称进行匹配)。 -
当一个接口存在多个实现类的情况下,
@Autowired
和@Resource
都需要通过名称才能正确匹配到对应的 Bean。Autowired
可以通过@Qualifier
注解来显式指定名称,@Resource
可以通过name
属性来显式指定名称。 -
@Reference
是Dubbo框架中的注解,用于注入Dubbo中的远程服务对象。它的使用方式有两种:-
根据类型自动装配:如果@Reference注解修饰的属性的类型在Dubbo中只有一个对应的服务对象,那么Dubbo会将该服务对象自动注入到该属性中。
-
根据名称自动装配:如果@Reference注解修饰的属性的类型在Dubbo中有多个对应的服务对象,那么Dubbo会根据属性名称或者注解中指定的名称来选择要注入的服务对象。
-
上述两种自动装配的依赖注入并不适合简单值类型,如int、boolean、long、String以及Enum等,对于这些类型,Spring容器也提供了@Value注入的方式。
@Value和@Autowired、@Resource类似,也是用来对属性进行注入的,只不过@Value是用来从Properties文件中来获取值的,并且@Value可以解析SpEL(Spring表达式)。
BeanFactory和FactoryBean的区别?
BeanFactory:管理Bean的容器,Spring中生成的Bean都是由这个接口的实现来管理的。
FactoryBean:通常是用来创建比较复杂的bean,一般的bean 直接用xml配置即可,但如果一个bean的创建过程中涉及到很多其他的bean 和复杂的逻辑,直接用xml配置比较麻烦,这时可以考虑用FactoryBean,可以隐藏实例化复杂Bean的细节。
BeanFactory和ApplicationContext有什么区别?
BeanFactory和ApplicationContext是Spring的两大核心接口,都可以当做Spring的容器。其中ApplicationContext是BeanFactory的子接口。
-
功能上的区别。ApplicationContext接口作为BeanFactory的派生,除了提供BeanFactory所具有的功能外,还提供了更完整的框架功能,如继承MessageSource、支持国际化、统一的资源文件访问方式、同时加载多个配置文件等功能。
-
加载方式的区别。BeanFactroy采用的是延迟加载形式来注入Bean的,即只有在使用到某个Bean时(调用getBean()),才对该Bean进行加载实例化。而ApplicationContext是在容器启动时,一次性创建了所有的Bean。
-
创建方式的区别。BeanFactory通常以编程的方式被创建,ApplicationContext还能以声明的方式创建,如使用ContextLoader。
4、事务
两种事务管理;事务失效(6)
1、@Transactional 应用在非 public 修饰的方法上
如果
Transactional
注解应用在非public
修饰的方法上,Transactional将会失效。之所以会失效是因为在Spring AOP 代理时,
TransactionInterceptor
(事务拦截器)在目标方法执行前后进行拦截,DynamicAdvisedInterceptor
(CglibAopProxy 的内部类)的 intercept 方法或JdkDynamicAopProxy
的 invoke 方法会间接调用AbstractFallbackTransactionAttributeSource
的computeTransactionAttribute
方法,获取Transactional 注解的事务配置信息。此方法会检查目标方法的修饰符是否为 public,不是 public则不会获取
@Transactional
的属性配置信息。注意:
protected
、private
修饰的方法上使用@Transactional
注解,虽然事务无效,但不会有任何报错,这是我们很容犯错的一点。2、@Transactional 注解属性 propagation 设置错误
这种失效是由于配置错误,若是错误的配置以下三种 propagation,事务将不会发生回滚。
TransactionDefinition.PROPAGATION_SUPPORTS
:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
TransactionDefinition.PROPAGATION_NOT_SUPPORTED
:以非事务方式运行,如果当前存在事务,则把当前事务挂起。
TransactionDefinition.PROPAGATION_NEVER
:以非事务方式运行,如果当前存在事务,则抛出异常。3、@Transactional 注解属性 rollbackFor 设置错误
rollbackFor
可以指定能够触发事务回滚的异常类型。Spring默认抛出了未检查unchecked
异常(继承自RuntimeException
的异常)或者Error
才回滚事务;其他异常不会触发回滚事务。如果在事务中抛出其他类型的异常,但却期望 Spring 能够回滚事务,就需要指定 rollbackFor 属性,如果未指定 rollbackFor 属性则事务不会回滚。4、同一个类中方法调用,导致 @Transactional 失效
开发中避免不了会对同一个类里面的方法调用,比如有一个类Test,它的一个方法A,A再调用本类的方法B(不论方法B是用public还是private修饰),但方法A没有声明注解事务,而B方法有。则外部调用方法A之后,方法B的事务是不会起作用的。这也是经常犯错误的一个地方。
同一个类中,没有加事务的方法调用加了事务的方法,将会导致事务的失效。
那为啥会出现这种情况?其实这还是由于使用
Spring AOP
代理造成的,因为 只有当事务方法被 当前类以外的代码 调用时,才会由Spring
生成的代理对象来管理。5、异常被你的 catch“吃了”导致 @Transactional 失效
这种情况是最常见的一种
@Transactional
注解失效场景,@Transactional private Integer A() throws Exception { int insert = 0; try { CityInfoDict cityInfoDict = new CityInfoDict(); cityInfoDict.setCityName("2"); cityInfoDict.setParentCityId(2); /** * A 插入字段为 2的数据 */ insert = cityInfoDictMapper.insert(cityInfoDict); /** * B 插入字段为 3的数据 */ b.insertB(); } catch (Exception e) { e.printStackTrace(); } }
如果B方法内部抛了异常,而A方法此时try catch了B方法的异常,那这个事务还能正常回滚吗?
答案:不能!
会抛出异常:
org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only因为当
ServiceB
中抛出了一个异常以后,ServiceB
标识当前事务需要rollback
。但是ServiceA
中由于你手动的捕获这个异常并进行处理,ServiceA
认为当前事务应该正常commit
。此时就出现了前后不一致,也就是因为这样,抛出了前面的UnexpectedRollbackException
异常。6、数据库引擎不支持事务
这种情况出现的概率并不高,事务能否生效数据库引擎是否支持事务是关键。常用的MySQL数据库默认使用支持事务的
innodb
引擎。一旦数据库引擎切换成不支持事务的myisam
,那事务就从根本上失效了。
属性名 | 说明 |
---|---|
propagation | 事务的传播行为,默认值为 REQUIRED。7个 |
isolation | 事务的隔离级别,默认值采用 DEFAULT。5个 |
timeout | 事务的超时时间,默认值为-1(不会超时)。如果超过该时间限制但事务还没有完成,则自动回滚事务。 |
readOnly | 指定事务是否为只读事务,默认值为 false。 |
rollbackFor | 用于指定能够触发事务回滚的异常类型,并且可以指定多个异常类型。 |
正确的事务传播行为可能的值如下 :
1.TransactionDefinition.PROPAGATION_REQUIRED
使用的最多的一个事务传播行为,我们平时经常使用的@Transactional
注解默认使用就是这个事务传播行为。如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
2.TransactionDefinition.PROPAGATION_REQUIRES_NEW
创建一个新的事务,如果当前存在事务,则把当前事务挂起。也就是说不管外部方法是否开启事务,Propagation.REQUIRES_NEW
修饰的内部方法会新开启自己的事务,且开启的事务相互独立,互不干扰。
3.TransactionDefinition.PROPAGATION_NESTED
:
如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED
。
4.TransactionDefinition.PROPAGATION_MANDATORY
如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。(mandatory:强制性)
-
TransactionDefinition.PROPAGATION_SUPPORTS
: 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。 -
TransactionDefinition.PROPAGATION_NOT_SUPPORTED
: 以非事务方式运行,如果当前存在事务,则把当前事务挂起。 -
TransactionDefinition.PROPAGATION_NEVER
: 以非事务方式运行,如果当前存在事务,则抛出异常。
@Transactional
的工作机制是基于 AOP 实现的,AOP 又是使用动态代理实现的。如果目标对象实现了接口,默认情况下会采用 JDK 的动态代理,如果目标对象没有实现了接口,会使用 CGLIB 动态代理。如果一个类或者一个类中的 public 方法上被标注@Transactional
注解的话,Spring 容器就会在启动的时候为其创建一个代理类,在调用被@Transactional
注解的 public 方法的时候,实际调用的是代理类的方法,也就是TransactionInterceptor
类中的 invoke()
方法。
若同一类中的其他没有 @Transactional
注解的方法内部调用有 @Transactional
注解的方法,有@Transactional
注解的方法的事务会失效。这是由于Spring AOP
代理的原因造成的,因为只有当 @Transactional
注解的方法在类以外被调用的时候,Spring 事务管理才生效。
-
定义一个事件: 实现一个继承自
ApplicationEvent
,并且写相应的构造函数; -
定义一个事件监听者:实现
ApplicationListener
接口,重写onApplicationEvent()
方法; -
使用事件发布者发布消息: 可以通过
ApplicationEventPublisher
的publishEvent()
方法发布消息。
5、设计模式
二、Spring Boot
1、@SpringBootApplication
-
@EnableAutoConfiguration
:启用 SpringBoot 的自动配置机制
-
-
@AutoConfigurationPackage
:给容器中导入一个组件 Registrar.class,而 Registrar 给容器中批量导入组件,通过 registerBeanDefinitions 方法中的 PackageImports 方法,将主程序类下的所有包(子包)下的组件导入到容器中。
-
-
-
@Import(EnableAutoConfigurationImportSelector.class)
:使用@Import导入的类会被Spring加载到IOC容器中.AutoConfigurationImportSelector
类实现了ImportSelector
接口,也就实现了这个接口中的selectImports
方法,该方法主要用于获取所有符合条件的类的全限定类名,这些类需要被加载到 IoC 容器中。
-
-
获取需要自动装配的所有配置类,读取
META-INF/spring.factories
,可发现META-INF/spring.factories
下的 xxxAutoConfiguration 被读取到了,不光是这个依赖下的META-INF/spring.factories
被读取到,所有 Spring Boot Starter 下的META-INF/spring.factories
都会被读取到。到这里可能面试官会问你:“
spring.factories
中这么多配置,每次启动都要全部加载么?”。很明显,这是不现实的。经过 filter 筛选后,根据条件装配
@ConditionalOnXXX
注解,只有满足该注解的 xxxAutoConfiguration 才会被加载,因此所需大大减小。
-
@ComponentScan
: 扫描被@Component
(@Service
,@Controller
)注解的 bean,注解默认会扫描该类所在的包下所有的类。也可以自定义不扫描某些 bean -
@SpringBootConfiguration
:内部是@Configuration
:声明是一个配置类,允许在 Spring 上下文中注册额外的 bean 或导入其他配置类
2、启动流程
参考 https://www.cnblogs.com/Narule/p/14253754.html
0.启动main方法开始。main里面调用SpringApplication.run()启动整个spring-boot程序,该方法所在类需要使用@SpringBootApplication注解,以及@ImportResource注解(if need)。
1.初始化配置:首先创建 SpringApplication 对象,在 Spring Application 中通过类加载器,(loadFactories)读取classpath下所有的spring.factories配置文件,创建一些初始配置对象;通知监听者应用程序启动开始,创建环境对象 environment,用于读取环境配置 如 application.yml
2.创建应用程序上下文-createApplicationContext,创建 bean工厂(beanfactory )对象
3.刷新上下文(启动核心)
3.1 配置工厂对象,包括上下文类加载器,对象发布处理器,beanFactoryPostProcessor
3.2 注册并实例化bean工厂发布处理器,并且调用这些处理器,对包扫描解析(主要是class文件)
3.3 注册并实例化bean发布处理器 beanPostProcessor
BeanFactoryPostProcessors 和 BeanPostProcessors是有区别的
BeanFactoryPostProcessors 是工厂发布处理器,定义什么是bean,知道哪些是bean类,解析class文件,包括注解解析,成员对象依赖解析等;BeanPostProcessors主要在BeanFactoryPostProcessors调用完之后工作
一般在bean对像的创建之前或之后,BeanFactory调用这些bean处理器拦截处理,Spring代理对象的创建也是通过beanPostProcessor处理器来实现
bean发布处理器生产AOP代理对象
BeanFactoryPostProcessor是在容器实例化任何Bean之前调用的,而BeanPostProcessor是在容器实例化Bean之后调用的(在Bean被初始化之前或之后进行自定义修改。)。因此,BeanFactoryPostProcessor可以修改容器本身以及Bean定义,而BeanPostProcessor只能修改实例化的Bean。
3.4 初始化一些与上下文有特别关系的bean对象(创建tomcat服务器)
3.5 实例化所有bean工厂缓存的bean对象(剩下的)
3.6 发布通知-通知上下文刷新完成(启动tomcat服务器)
4.通知监听者-启动程序完成
启动中,大部分对象都是BeanFactory对象通过反射创建
三、SpringMVC
1、异常处理
-
在类上面加 @ControllerAdvice
-
在类中的方法上加 @ExceptionHandler
SpringMVC使用 ExceptionHandlerExceptionResolver
来支持这种自定义异常处理机制。它是一个异常处理器,它用于处理在 MVC 组件中抛出的异常。这种异常处理方式下,会给所有或者指定的 Controller织入异常处理的逻辑(AOP),当 Controller中的方法抛出异常的时候,由被@ExceptionHandler
注解修饰的方法进行处理。
ExceptionHandlerExceptionResolver
中有个参数exceptionHandlerCache
,再缓存中存放对应的错误和处理的方法。然后从ExceptionHandlerMethodResolver
去找该 exception 对应的异常处理方法。检查缓存中是否有该异常类型所对应的处理方法,如果找到了,则调用该方法来处理异常。否则,异常会被传递到上层处理器来处理。具体实现是ExceptionHandlerMethodResolver
中的getMappedMethod
方法
private final Map<Class<?>, ExceptionHandlerMethodResolver> exceptionHandlerCache = new ConcurrentHashMap(64);
https://pict-picgo.oss-cn-hangzhou.aliyuncs.com/picture3/202209191600196.png
其他方法
1.自定义实现 HandlerExceptionResolver
接口处理异常;可以作为默认的全局异常处理规则。 catch 到 @Controller 方法执行时发生的异常,处理后返回 ModelAndView 作为结果视图,因此可以通过它来定制异常视图。
2.基于注解的处理:SimpleMappingExceptionResolver是Spring MVC提供的简单异常处理器
2、Spring MVC 的拦截器
拦截器(类)需要实现 HandlerInterceptor 接口,拦截器是全局的,可对多个 Controller 做拦截处理,一个项目中可有零个或多个拦截器,常用在用户登录、权限检查、记录日志等。拦截器使用的是 AOP 的思想,对请求统一拦截处理。
有以下几点:
-
功能相同:拦截器和 Filter 都能实现相应的功能
-
容器不同:Filter属于Servlet技术;Interceptor属于SpringMVC技术
-
使用便利性不同:拦截器提供了三个方法(preHandle、postHandle、afterCompletion),分别在不同的时机执行(在 Controller 方法处理之前被执行;控制器方法执行之后执行;请求处理完成之后执行);过滤器仅提供一个方法
-
过滤器实现 Filter 接口,拦截器实现 HandlerInterceptor 接口
-
过滤器过滤 servlet 请求响应,拦截器则是拦截普通类方法执行
转发和重定向:
转发是指服务器接收到用户请求后,将该请求发送到另一个 URL 地址,然后将该 URL 地址的响应返回给用户。转发是在服务器端完成的,用户并不知道请求已经被转发到了另一个地址。转发通常用于处理请求的过程中需要进行一些额外处理的情况,比如在处理请求前需要进行身份验证或者数据处理。
重定向是指服务器接收到用户请求后,将该请求返回给用户一个新的 URL 地址,让用户再次发送请求到该地址。重定向是在客户端完成的,用户会在浏览器上看到地址被重定向到了另一个网页。重定向通常用于需要将用户请求转移到一个新的地址,比如网站的旧版网址已被废弃,需要将用户重定向到新的网址。
总的来说,转发和重定向都是将用户请求转移到另一个 URL 地址的方式,只是实现方式和目的略有不同。在实际应用中,应根据具体情况选择转发和重定向的方式。
3、SpringMVC 内部处理请求流程
1、用户发起请求 doSome.do,请求被SpringMVC 前端控制器 DispatcherServlet捕获。
2、中央调度器 DispatcherServlet 接收用户的 doSome.do 请求,然后转发给处理器映射器。处理器映射器是指实现了 HandlerMapping 接口的类(可有多个),处理器映射器可根据用户请求,从 SpringMVC 容器中获取处理器类的对象(也就是 Spring 中的 getBean 方法),然后将获取到的处理器对象放入到处理器执行链(HandlerExecutionChain)中保存,这个类中,同时还保存了拦截器,保存形式为 List 集合。
3、DispatcherServlet 将处理器执行链 HandlerExecutionChain 中的处理器对象交给处理器适配器对象(可有多个)。处理器适配器指的是 SpringMVC 框架中实现了 HandlerAdapter 接口的类,用于执行处理器类中的处理器方法。
4、执行完处理器方法后,返回 ModelAndView,DispatcherServlet 将返回的 ModelAndView 交给视图解析器对象进行处理。视图解析器指的是实现了 ViewResoler 接口的类(可有多个),作用是根据 SpringMVC 配置文件中的视图解析器配置,使用前缀后缀创建 View 对象,View 对象是一个接口,用来表示视图,框架中 jsp、html 用 View 和其实现类来表示视图。
5、DispatcherServlet 获取第 4 步执行的结果 View,调用 View 类的方法,将 Model 数据添加到 request 作用域,执行对象视图的 forward 响应浏览器,请求结束。
-
请求被SpringMVC 前端控制器
DispatcherServlet
捕获 -
DispatcherServlet
对请求URL进行解析(核心方法doDispatch()
),得到请求资源标识符(URI)然后根据该URI,调用HandlerMapping
获得该Handler(Controller)
配置的所有相关的对象(包括Handler对象以及Handler对象对应的拦截器),最后以HandlerExecutionChain执行链对象的形式返回。 -
DispatcherServlet
根据获得的Handler,选择一个合适的HandlerAdapter(执行目标方法的反射工具)
,如果成功获得HandlerAdapter,此时将开始执行拦截器的preHandler(…)方法【正向】 -
提取Request中的模型数据,填充Handler入参==(也就是给形参赋值)==,开始执行Handler(Controller)方法,处理请求。
-
根据你的配置,Spring将帮你做一些额外的工作:
a) HttpMessageConveter: 将请求消息(如Json、xml等数据)转换成一个对象,将对象转换为指定的响应信息
b) 数据转换:对请求消息进行数据转换。如String转换成Integer、Double等
c) 数据格式化:对请求消息进行数据格式化。 如将字符串转换成格式化数字或格式化日期等
d) 数据验证: 验证数据的有效性(长度、格式等),验证结果存储到BindingResult或Error中
-
-
Handler执行完成后,向
DispatcherServlet
返回一个ModelAndView
对象,==(1061行,mv=ha.handler)==此时将开始执行拦截器的postHandle(...)方法【逆向】。 -
根据返回的
ModelAndView
,选择一个适合的ViewResolver
(视图解析器),==(有三种,redirect,forward和thymeleaf)== -
ViewResolver
结合Model和View,来渲染视图,渲染视图完毕执行拦截器的afterCompletion(…)方法【逆向】。 -
由
DispatcherServlet
响应给客户端
4、什么是REST?
REST,英文全称,Resource Representational State Transfer,对资源的访问状态的变化通过url的变化表述出来。 通俗来讲,就是通过URL就知道要什么资源,通过HTTP method就知道要干什么,通过HTTP status code就知道结果如何。
使用REST有什么优势呢?
第一,风格统一了,不会出现delUser/deleteUser/removeUser
各种命名的代码了。
第二,面向资源,一目了然,具有自解释性。
第三,充分利用 HTTP 协议本身语义。
Spring MVC中的常用注解
@Controller:用于标识此类的实例是一个控制器。
@RequestMapping:映射Web请求(访问路径和参数)。
@ResponseBody:注解返回数据而不是返回页面
@RequestBody:注解实现接收 http 请求的 json 数据,将 json 数据转换为 java 对象。
@PathVariable:获得URL中路径变量中的值
@RestController:@Controller+@ResponseBody
@ExceptionHandler标识一个方法为全局异常处理的方法。
5、@RestController
@RestController
注解是@Controller和
@ResponseBody
的合集,表示这是个控制器 bean,并且是将函数的返回值直 接填入 HTTP 响应体中,是 REST 风格的控制器。
单独使用 @Controller
不加 @ResponseBody
的话一般使用在要返回一个视图的情况,这种情况属于比较传统的 Spring MVC 的应用,对应于前后端不分离的情况。@Controller
+@ResponseBody
返回 JSON 或 XML 形式数据
四、Mybatis
1、Mybatis和Hibernate的区别?
-
数据库扩展性的区别。Hibernate与数据库具体的关联在XML中,所以HQL对具体是用什么数据库并不是很关心。MyBatis由于所有sql都是依赖数据库书写的,所以扩展性、迁移性比较差。
-
sql的优化上,Mybatis要比Hibernate方便很多。由于Mybatis的sql都是写在xml里,因此优化sql比Hibernate方便很多。而Hibernate的sql很多都是自动生成的,无法直接维护sql;总之写sql的灵活度上Hibernate不及Mybatis。
-
Hibernate的开发难度大于MyBatis,主要由于Hibernate比较复杂,庞大,学习周期比较长。
-
Hibernate属于全自动ORM映射工具,使用Hibernate查询关联对象或者关联集合对象时,可以根据对象关系模型直接获取,所以它是全自动的。而Mybatis在查询关联对象或关联集合对象时,需要手动编写sql来完成,所以,称之为半自动ORM映射工具。
2、#{}和${}的区别
${}
是 Properties 文件中的变量占位符,需要单引号
#{}
是 sql 的参数占位符,#{ } 被解析成预编译语句,会将 sql 中的#{}
替换为?
,预编译之后可以直接执行
sql注入:是一种注入攻击,它通过将任意代码插入数据库查询,使得攻击者完全控制数据库服务器。 使用#{}可以有效的防止SQL注入,MyBatis
启用了预编译功能,在SQL执行前,会先将上面的SQL发送给数据库进行编译;执行时,直接使用编译好的SQL,替换占位符“?”就可以了。因为SQL注入只能对编译过程起作用,所以这样的方式就很好地避免了SQL注入的问题。
3、二级缓存
一级缓存是SqlSession级别的缓存
二级缓存是mapper级别的缓存:可以使缓存在各个SqlSession之间共享。当多个用户在查询数据的时候,只要有任何一个SqlSession拿到了数据就会放入到二级缓存里面,其他的SqlSession就可以从二级缓存加载数据。二级缓存默认不开启
MyBatis常见面试题总结
分页插件:
-
PageHelper:PageHelper 是一个 MyBatis 分页插件,在代码中使用 PageHelper.startPage() 方法来实现分页。
-
RowBounds:RowBounds 是 MyBatis 中定义的一个对象,用于指定查询结果的起始位置和数量,从而实现分页功能。它需要在查询方法中显式传递 RowBounds 对象,同时需要在 SQL 语句中使用 LIMIT 或 OFFSET 关键字来指定分页参数。
mybatis和mybatis-plus:使用 MyBatis-Plus 可以通过继承 BaseMapper 接口来自动生成常用的 CRUD 操作。
在使用MyBatis-Plus进行分页查询时,需要先创建一个Page对象,并将查询参数和分页参数设置到Page对象中,然后将Page对象传递给MyBatis的查询方法进行查询。
总的来说,MyBatis-Plus 在 MyBatis 的基础上提供了更多的功能和便利性,可以提高开发效率和代码质量。但是,如果需要更加灵活和自定义的 ORM(Object-Relational Mapping,即对象关系映射) 操作,MyBatis 可能更加适合。
恒生使用自家的querypage进行分页查询
@Override
public QueryPage<Deal> getAllDealRequest(Integer pageNo, Integer pageSize) {
String sqlList = "select r.id, r.environment, r.contact_person_name, u.username, r.employee_id, r.deal_process, r.deal_duration, r.deal_status, r.deal_result" +
" FROM repo_deal_request r LEFT JOIN repo_user u ON r.employee_id = u.employee_id "
+ " ORDER BY deal_status DESC limit ?,?";
String sqlCount = "select count(*) from repo_deal_request ";
UfBaseException exc = new UfBaseException(ErrorConsts.ERR_BASE_DAO);
QueryPage<Deal> queryPage = new QueryPage<Deal>();
queryPage.setCurrentPage(pageNo);
queryPage.setTotal(this.jdbcTemplate.queryForObject(() -> exc, sqlCount, new Object[]{}, Integer.class));
queryPage.setRows(this.jdbcTemplate.query(() -> exc, sqlList, new PreparedStatementSetter() {
@Override
public void setValues(PreparedStatement ps) throws SQLException {
ps.setInt(1, (pageNo - 1) * pageSize);
ps.setInt(2, pageSize);
}
}, new RowMapper<Deal>() {
@Override
public Deal mapRow(ResultSet rs, int rowNum) throws SQLException {
Deal deal = new Deal();
deal.setId(rs.getInt(1));
deal.setEnvironment(rs.getString(2));
deal.setContactPersonName(rs.getString(3));
deal.setUsername(rs.getString(4));
deal.setEmployeeId(rs.getString(5));
deal.setDealProcess(rs.getString(6));
deal.setDuration(rs.getString(7));
deal.setDealStatus(rs.getString(8));
deal.setDealResult(rs.getString(9));
return deal;
}
}));
return queryPage;
}