Spring基础
什么是Spring框架?
Spring 是一款开源的轻量级 Java 开发框架,旨在提高开发人员的开发效率以及系统的可维护性。
我们一般说 Spring 框架指的都是 Spring Framework,它是很多模块的集合,使用这些模块可以很方便地协助我们进行开发,比如说 Spring 支持 IoC(Inversion of Control:控制反转) 和 AOP(Aspect-Oriented Programming:面向切面编程)、可以很方便地对数据库进行访问、可以很方便地集成第三方组件(电子邮件,任务,调度,缓存等等)、对单元测试支持比较好、支持 RESTful Java 应用程序的开发。
Spring 提供的核心功能主要是 IoC 和 AOP。学习 Spring ,一定要把 IoC 和 AOP 的核心思想搞懂!
Spring包含的模块有哪些?
Spring4.x 版本:
Spring5.x 版本:
Spring5.x 版本中 Web 模块的 Portlet 组件已经被废弃掉,同时增加了用于异步响应式处理的 WebFlux 组件。
Spring的各个模块的依赖关系如下:
Core Container
Spring 框架的核心模块,也可以说是基础模块,主要提供 IoC 依赖注入功能的支持。Spring 其他所有的功能基本都需要依赖于该模块,我们从上面那张 Spring 各个模块的依赖关系图就可以看出来。
-
spring-core :Spring 框架基本的核心工具类。
-
spring-beans :提供对 bean 的创建、配置和管理等功能的支持。
-
spring-context :提供对国际化、事件传播、资源加载等功能的支持。
-
spring-expression :提供对表达式语言(Spring Expression Language) SpEL 的支持,只依赖于 core 模块,不依赖于其他模块,可以单独使用。
AOP
-
spring-aspects :该模块为与 AspectJ 的集成提供支持。
-
spring-aop :提供了面向切面的编程实现。
-
spring-instrument :提供了为 JVM 添加代理(agent)的功能。 具体来讲,它为 Tomcat 提供了一个织入代理,能够为 Tomcat 传递类文 件,就像这些文件是被类加载器加载的一样。没有理解也没关系,这个模块的使用场景非常有限。
Data Access/Integration
-
spring-jdbc :提供了对数据库访问的抽象 JDBC。不同的数据库都有自己独立的 API 用于操作数据库,而 Java 程序只需要和 JDBC API 交互,这样就屏蔽了数据库的影响。
-
spring-tx :提供对事务的支持。
-
spring-orm : 提供对 Hibernate、JPA 、iBatis 等 ORM 框架的支持。
-
spring-oxm :提供一个抽象层支撑 OXM(Object-to-XML-Mapping),例如:JAXB、Castor、XMLBeans、JiBX 和 XStream 等。
-
spring-jms : 消息服务。自 Spring Framework 4.1 以后,它还提供了对 spring-messaging 模块的继承
Spring Web
-
spring-web :对 Web 功能的实现提供一些最基础的支持。
-
spring-webmvc : 提供对 Spring MVC 的实现。
-
spring-websocket : 提供了对 WebSocket 的支持,WebSocket 可以让客户端和服务端进行双向通信。
-
spring-webflux :提供对 WebFlux 的支持。WebFlux 是 Spring Framework 5.0 中引入的新的响应式框架。与 Spring MVC 不同,它不需要 Servlet API,是完全异步。
Messaging
spring-messaging 是从 Spring4.0 开始新加入的一个模块,主要职责是为 Spring 框架集成一些基础的报文传送应用
Spring Test
Spring 团队提倡测试驱动开发(TDD)。有了控制反转 (IoC)的帮助,单元测试和集成测试变得更简单。
Spring 的测试模块对 JUnit(单元测试框架)、TestNG(类似 JUnit)、Mockito(主要用来 Mock 对象)、PowerMock(解决 Mockito 的问题比如无法模拟 final, static, private 方法)等等常用的测试框架支持的都比较好。
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 的很多配置,真正做到开箱即用!
Spring IoC
谈谈自己对于Spring IoC的了解
IoC(Inversion of Control:控制反转) 是一种设计思想,而不是一个具体的技术实现。IoC 的思想就是将原本在程序中手动创建对象的控制权,交由 Spring 框架来管理。不过, IoC 并非 Spring 特有,在其他语言中也有应用。
为什么叫控制反转?
- 控制 :指的是对象创建(实例化、管理)的权力
- 反转 :控制权交给外部环境(Spring 框架、IoC 容器)
将对象之间的相互依赖关系交给 IoC 容器来管理,并由 IoC 容器完成对象的注入。这样可以很大程度上简化应用的开发,把应用从复杂的依赖关系中解放出来。 IoC 容器就像是一个工厂一样,当我们需要创建一个对象的时候,只需要配置好配置文件/注解即可,完全不用考虑对象是如何被创建出来的。
在实际项目中一个 Service 类可能依赖了很多其他的类,假如我们需要实例化这个 Service,你可能要每次都要搞清这个 Service 所有底层类的构造函数,这可能会把人逼疯。如果利用 IoC 的话,你只需要配置好,然后在需要的地方引用就行了,这大大增加了项目的可维护性且降低了开发难度。
在Spring中,IoC容器是Spring用来实现IoC的载体,IoC容器实际上就是个Map(key, value), Map中存放的是各种对象。
Spring时代我们一般通过XML文件来配置Bean,后来开发人员觉得XML文件来配置不太好,于是SpringBoot注解配置就慢慢开始流行起来。
什么是Spring Bean?
简单来说,Bean 代指的就是那些被 IoC 容器所管理的对象。
我们需要告诉 IoC 容器帮助我们管理哪些对象,这个是通过配置元数据来定义的。配置元数据可以是 XML 文件、注解或者 Java 配置类。
下图简单地展示了 IoC 容器如何使用配置元数据来管理对象。
将一个类声明为Bean的注解有哪些?
-
@Component:通用的注解,可标注任意类为Spring组件。如果一个 Bean 不知道属于哪个层,可以使用@Component注解标注。 -
@Repository: 对应持久层即 Dao 层,主要用于数据库相关操作。 -
@Service: 对应服务层,主要涉及一些复杂的逻辑,需要用到 Dao 层。 -
@Controller: 对应 Spring MVC 控制层,主要用于接受用户请求并调用Service层返回数据给前端页面
@Component和@Bean的区别是什么?
@Component注解作用于类,而@Bean注解作用于方法。@Component通常是通过类路径扫描来自动侦测以及自动装配到 Spring 容器中(我们可以使用@ComponentScan注解定义要扫描的路径从中找出标识了需要装配的类自动装配到 Spring 的 bean 容器中)。@Bean注解通常是我们在标有该注解的方法中定义产生这个 bean,@Bean告诉了 Spring 这是某个类的实例,当我需要用它的时候还给我。@Bean注解比@Component注解的自定义性更强,而且很多地方我们只能通过@Bean注解来注册 bean。比如当我们引用第三方库中的类需要装配到Spring容器时,则只能通过@Bean来实现。
@Bean注解使用示例:
上面的代码相当于下面的 xml 配置
<beans>
<bean id="transferService" class="com.acme.TransferServiceImpl"/>
</beans>
下面这个例子是通过 @Component 无法实现的。
注入Bean的注解有哪些?
Spring 内置的 @Autowired 以及 JDK 内置的 @Resource 和 @Inject 都可以用于注入 Bean。
@Autowired 和@Resource使用的比较多一些。
@Autowired 和 @Resource 的区别是什么?
Autowired 属于 Spring 内置的注解,默认的注入方式为byType(根据类型进行匹配),也就是说会优先根据接口类型去匹配并注入 Bean (接口的实现类)。
这会有什么问题呢? 当一个接口存在多个实现类的话,byType这种方式就无法正确注入对象了,因为这个时候 Spring 会同时找到多个满足条件的选择,默认情况下它自己不知道选择哪一个。
这种情况下,注入方式会变为 byName(根据名称进行匹配),这个名称通常就是类名(首字母小写)。就比如说下面代码中的 smsService 就是我这里所说的名称,这样应该比较好理解了吧。
举个例子,SmsService 接口有两个实现类: SmsServiceImpl1和 SmsServiceImpl2,且它们都已经被 Spring 容器所管理。
我们还是建议通过 @Qualifier 注解来显式指定名称而不是依赖变量的名称。
@Resource属于 JDK 提供的注解,默认注入方式为 byName。如果无法通过名称匹配到对应的 Bean 的话,注入方式会变为byType。
@Resource 有两个比较重要且日常开发常用的属性:name(名称)、type(类型)。
如果仅指定 name 属性则注入方式为byName,如果仅指定type属性则注入方式为byType,如果同时指定name 和type属性(不建议这么做)则注入方式为byType+byName。
简单总结一下:
@Autowired是 Spring 提供的注解,@Resource是 JDK 提供的注解。Autowired默认的注入方式为byType(根据类型进行匹配),@Resource默认注入方式为byName(根据名称进行匹配)。- 当一个接口存在多个实现类的情况下,
@Autowired和@Resource都需要通过名称才能正确匹配到对应的 Bean。Autowired可以通过@Qualifier注解来显式指定名称,@Resource可以通过name属性来显式指定名称。
Bean的作用域有哪些?
- singleton: IoC容器只有唯一的bean实例。Spring中的bean默认都是单例的,是对单例设计模式的应用。
- prototype: 每次获取都会创建一个新的bean实例。也就是说,连续
getBean()两次,得到的是不同的Bean实例。 - request (仅 Web 应用可用): 每一次 HTTP 请求都会产生一个新的 bean(请求 bean),该 bean 仅在当前 HTTP request 内有效。
- session (仅 Web 应用可用) : 每一次来自新 session 的 HTTP 请求都会产生一个新的 bean(会话 bean),该 bean 仅在当前 HTTP session 内有效。
- application/global-session(仅Web应用可用): 每个Web应用在启动时创建一个Bean(应用Bean),该bean仅在当前应用启动时间内有效。
- websocket(仅Web应用可用): 每一次WebSocket会话产生一个新的bean。
如何配置bean的作用域呢?
xml方式:
<bean id="..." class="..." scope="singleton"></bean>
注解方式:
单例Bean的线程安全问题了解吗?
大部分时候我们并没有在项目中使用多线程,所以很少有人会关注这个问题。单例 Bean 存在线程问题,主要是因为当多个线程操作同一个对象的时候是存在资源竞争的。
常见的有两种解决办法:
- 在 Bean 中尽量避免定义可变的成员变量。
- 在类中定义一个
ThreadLocal成员变量,将需要的可变成员变量保存在ThreadLocal中(推荐的一种方式)。
不过,大部分 Bean 实际都是无状态(没有实例变量)的(比如 Dao、Service),这种情况下, Bean 是线程安全的
Bean的生命周期了解么?
-
Bean 容器找到配置文件中 Spring Bean 的定义。
-
Bean 容器利用 Java Reflection API 创建一个 Bean 的实例。
-
如果涉及到一些属性值 利用
set()方法设置一些属性值。 -
如果 Bean 实现了
BeanNameAware接口,调用setBeanName()方法,传入 Bean 的名字。 -
如果 Bean 实现了
BeanClassLoaderAware接口,调用setBeanClassLoader()方法,传入ClassLoader对象的实例。 -
如果 Bean 实现了
BeanFactoryAware接口,调用setBeanFactory()方法,传入BeanFactory对象的实例。 -
与上面的类似,如果实现了其他
*.Aware接口,就调用相应的方法。 -
如果有和加载这个 Bean 的 Spring 容器相关的
BeanPostProcessor对象,执行postProcessBeforeInitialization()方法 -
如果 Bean 实现了
InitializingBean接口,执行afterPropertiesSet()方法。 -
如果 Bean 在配置文件中的定义包含 init-method 属性,执行指定的方法。
-
如果有和加载这个 Bean 的 Spring 容器相关的
BeanPostProcessor对象,执行postProcessAfterInitialization()方法 -
当要销毁 Bean 的时候,如果 Bean 实现了
DisposableBean接口,执行destroy()方法。 -
当要销毁 Bean 的时候,如果 Bean 在配置文件中的定义包含 destroy-method 属性,执行指定的方法。
图示:
Spring AoP
谈谈自己对于AOP的了解
AOP(Aspect-Oriented Programming:面向切面编程)能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。
Spring AOP 就是基于动态代理的,如果要代理的对象,实现了某个接口,那么 Spring AOP 会使用 JDK Proxy,去创建代理对象,而对于没有实现接口的对象,就无法使用 JDK Proxy 去进行代理了,这时候 Spring AOP 会使用 Cglib 生成一个被代理对象的子类来作为代理,如下图所示:
当然你也可以使用 AspectJ !Spring AOP 已经集成了 AspectJ ,AspectJ 应该算的上是 Java 生态系统中最完整的 AOP 框架了。
Java中动态代理和动态绑定的区别?
那么举一个动态代理的例子
当需要在不修改原有代码的情况下,为一个对象添加一些额外的行为时,可以使用Java动态代理。以下是一个简单的例子:
假设我们有一个接口UserService,它有一个方法saveUser用于保存用户信息。我们现在想在saveUser方法执行前后,分别打印日志。我们可以使用动态代理来实现这个功能:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class UserServiceProxy implements InvocationHandler {
private UserService userService;
public UserServiceProxy(UserService userService) {
this.userService = userService;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Before invoking " + method.getName());
Object result = method.invoke(userService, args);
System.out.println("After invoking " + method.getName());
return result;
}
public static void main(String[] args) {
UserService userService = new UserServiceImpl();
UserService proxy = (UserService) Proxy.newProxyInstance(
userService.getClass().getClassLoader(),
userService.getClass().getInterfaces(),
new UserServiceProxy(userService)
);
proxy.saveUser();
}
}
interface UserService {
void saveUser();
}
class UserServiceImpl implements UserService {
public void saveUser() {
System.out.println("Saving user...");
}
}
在上面的代码中,我们定义了一个UserServiceProxy类,它实现了Java的InvocationHandler接口。在invoke方法中,我们在调用userService的saveUser方法前后打印了日志。我们通过Proxy.newProxyInstance方法创建了一个代理对象,并把它转换成了UserService类型,最终调用了saveUser方法。当代理对象的saveUser方法被调用时,实际上会先调用UserServiceProxy类的invoke方法,然后再调用userService的saveUser方法。这样我们就实现了在原有代码不变的情况下,为saveUser方法添加了额外的逻辑。
AOP切面编程涉及到的一些专业术语:
Spring AOP和AspectJ AOP有什么区别?
Spring AOP 属于运行时增强,而 AspectJ 是编译时增强。 Spring AOP 基于代理(Proxying),而 AspectJ 基于字节码操作(Bytecode Manipulation)。
Spring AOP 已经集成了 AspectJ ,AspectJ 应该算的上是 Java 生态系统中最完整的 AOP 框架了。AspectJ 相比于 Spring AOP 功能更加强大,但是 Spring AOP 相对来说更简单,
如果我们的切面比较少,那么两者性能差异不大。但是,当切面太多的话,最好选择 AspectJ ,它比 Spring AOP 快很多。
AspectJ定义的通知类型有哪些?
-
Before(前置通知):目标对象的方法调用之前触发
-
After (后置通知):目标对象的方法调用之后触发
-
AfterReturning(返回通知):目标对象的方法调用完成,在返回结果值之后触发
-
AfterThrowing(异常通知) :目标对象的方法运行中抛出 / 触发异常后触发。AfterReturning 和 AfterThrowing 两者互斥。如果方法调用成功无异常,则会有返回值;如果方法抛出了异常,则不会有返回值。
-
Around (环绕通知):编程式控制目标对象的方法调用。环绕通知是所有通知类型中可操作范围最大的一种,因为它可以直接拿到目标对象,以及要执行的方法,所以环绕通知可以任意的在目标对象的方法调用前后搞事,甚至不调用目标对象的方法
多个切面的执行顺序如何控制?
通常使用@Order 注解直接定义切面顺序
实现Ordered 接口重写 getOrder 方法。
Spring MVC
说说自己对于Spring MVC了解?
MVC 是模型(Model)、视图(View)、控制器(Controller)的简写,其核心思想是通过将业务逻辑、数据、显示分离来组织代码。
想要真正理解Spring MVC,我们先来看看Model 1和Model 2这两个没有Sprng MVC的时代。
Model 1时代
很多学 Java 后端比较晚的朋友可能并没有接触过 Model 1 时代下的 JavaWeb 应用开发。在 Model1 模式下,整个 Web 应用几乎全部用 JSP 页面组成,只用少量的 JavaBean 来处理数据库连接、访问等操作。
这个模式下 JSP 即是控制层(Controller)又是表现层(View)。显而易见,这种模式存在很多问题。比如控制逻辑和表现逻辑混杂在一起,导致代码重用率极低;再比如前端和后端相互依赖,难以进行测试维护并且开发效率极低。
Model 2时代
学过 Servlet 并做过相关 Demo 的朋友应该了解“Java Bean(Model)+ JSP(View)+Servlet(Controller) ”这种开发模式,这就是早期的 JavaWeb MVC 开发模式。
- Model:系统涉及的数据,也就是 dao 和 bean。
- View:展示模型中的数据,只是用来展示。
- Controller:处理用户请求都发送给 ,返回数据给 JSP 并展示给用户。
Model2 模式下还存在很多问题,Model2 的抽象和封装程度还远远不够,使用 Model2 进行开发时不可避免地会重复造轮子,这就大大降低了程序的可维护性和复用性。
于是,很多 JavaWeb 开发相关的 MVC 框架应运而生比如 Struts2,但是 Struts2 比较笨重。
Spring MVC时代
随着 Spring 轻量级开发框架的流行,Spring 生态圈出现了 Spring MVC 框架, Spring MVC 是当前最优秀的 MVC 框架。相比于 Struts2 , Spring MVC 使用更加简单和方便,开发效率更高,并且 Spring MVC 运行速度更快。
MVC 是一种设计模式,Spring MVC 是一款很优秀的 MVC 框架。Spring MVC 可以帮助我们进行更简洁的 Web 层的开发,并且它天生与 Spring 框架集成。Spring MVC 下我们一般把后端项目分为 Service 层(处理业务)、Dao 层(数据库操作)、Entity 层(实体类)、Controller 层(控制层,返回数据给前台页面)。
Spring MVC的核心组件有哪些?
记住了下面这些组件,也就记住了 SpringMVC 的工作原理。
DispatcherServlet:核心的中央处理器,负责接收请求、分发,并给予客户端响应。HandlerMapping:处理器映射器,根据 uri 去匹配查找能处理的Handler,并会将请求涉及到的拦截器和Handler一起封装。HandlerAdapter:处理器适配器,根据HandlerMapping找到的Handler,适配执行对应的Handler;Handler:请求处理器,处理实际请求的处理器。ViewResolver:视图解析器,根据Handler返回的逻辑视图 / 视图,解析并渲染真正的视图,并传递给DispatcherServlet响应客户端
SpringMVC工作原理了解吗?
流程说明(重要):
- 客户端(浏览器)发送请求,
DispatcherServlet拦截请求。 DispatcherServlet根据请求信息调用HandlerMapping。HandlerMapping根据 uri 去匹配查找能处理的Handler(也就是我们平常说的Controller控制器) ,并会将请求涉及到的拦截器和Handler一起封装。DispatcherServlet调用HandlerAdapter适配执行Handler。Handler完成对用户请求的处理后,会返回一个ModelAndView对象给DispatcherServlet,ModelAndView顾名思义,包含了数据模型以及相应的视图的信息。Model是返回的数据对象,View是个逻辑上的View。ViewResolver会根据逻辑View查找实际的View。DispaterServlet把返回的Model传给View(视图渲染)。- 把
View返回给请求者(浏览器)
统一异常处理怎么做?
推荐使用注解的方式统一异常处理,具体会使用到 @ControllerAdvice + @ExceptionHandler 这两个注解 。
@ControllerAdvice
@ResponseBody
public class GlobalExceptionHandler {
@ExceptionHandler(BaseException.class)
public ResponseEntity<?> handleAppException(BaseException ex, HttpServletRequest request) {
//......
}
@ExceptionHandler(value = ResourceNotFoundException.class)
public ResponseEntity<ErrorReponse> handleResourceNotFoundException(ResourceNotFoundException ex, HttpServletRequest request) {
//......
}
}
这种异常处理方式下,会给所有或者指定的 Controller 织入异常处理的逻辑(AOP),当 Controller 中的方法抛出异常的时候,由被@ExceptionHandler 注解修饰的方法进行处理。
ExceptionHandlerMethodResolver 中 getMappedMethod 方法决定了异常具体被哪个被 @ExceptionHandler 注解修饰的方法处理异常。
从源代码看出: getMappedMethod()会首先找到可以匹配处理异常的所有方法信息,然后对其进行从小到大的排序,最后取最小的那一个匹配的方法(即匹配度最高的那个)。
Spring框架中用到了哪些设计模式
-
工厂设计模式 : Spring 使用工厂模式通过
BeanFactory、ApplicationContext创建 bean 对象。 -
代理设计模式 : Spring AOP 功能的实现。
-
单例设计模式 : Spring 中的 Bean 默认都是单例的。
-
模板方法模式 : Spring 中
jdbcTemplate、hibernateTemplate等以 Template 结尾的对数据库操作的类,它们就使用到了模板模式。 -
包装器设计模式 : 我们的项目需要连接多个数据库,而且不同的客户在每次访问中根据需要会去访问不同的数据库。这种模式让我们可以根据客户的需求能够动态切换不同的数据源。
-
观察者模式: Spring 事件驱动模型就是观察者模式很经典的一个应用。
-
适配器模式 : Spring AOP 的增强或通知(Advice)使用到了适配器模式、spring MVC 中也是用到了适配器模式适配
Controller。
Spring 事务
Spring管理事务的方式有几种?
-
编程式事务 : 在代码中硬编码(不推荐使用) : 通过
TransactionTemplate或者TransactionManager手动管理事务,实际应用中很少使用,但是对于你理解 Spring 事务管理原理有帮助。 -
声明式事务 : 在 XML 配置文件中配置或者直接基于注解(推荐使用) : 实际是通过 AOP 实现(基于
@Transactional的全注解方式使用最多)
Spring事务中哪几种事务传播行为?
事务传播行为是为了解决业务层方法之间互相调用的事务问题。
Spring 事务中哪几种事务传播行为?
事务传播行为是为了解决业务层方法之间互相调用的事务问题。
当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。
正确的事务传播行为可能的值如下:
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:强制性)
这个使用的很少。
若是错误的配置以下 3 种事务传播行为,事务将不会发生回滚:
TransactionDefinition.PROPAGATION_SUPPORTS: 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。TransactionDefinition.PROPAGATION_NOT_SUPPORTED: 以非事务方式运行,如果当前存在事务,则把当前事务挂起。TransactionDefinition.PROPAGATION_NEVER: 以非事务方式运行,如果当前存在事务,则抛出异常。
Spring事务中的隔离级别有哪几种?
和事务传播行为这块一样,为了方便使用,Spring 也相应地定义了一个枚举类:Isolation
下面我依次对每一种事务隔离级别进行介绍:
TransactionDefinition.ISOLATION_DEFAULT:使用后端数据库默认的隔离级别,MySQL 默认采用的REPEATABLE_READ隔离级别 Oracle 默认采用的READ_COMMITTED隔离级别.TransactionDefinition.ISOLATION_READ_UNCOMMITTED:最低的隔离级别,使用这个隔离级别很少,因为它允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读TransactionDefinition.ISOLATION_READ_COMMITTED: 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生TransactionDefinition.ISOLATION_REPEATABLE_READ: 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。TransactionDefinition.ISOLATION_SERIALIZABLE: 最高的隔离级别,完全服从 ACID 的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。
@Transactional(rollbackFor = Exception.class)注解了解吗
Exception 分为运行时异常 RuntimeException 和非运行时异常。事务管理对于企业应用来说是至关重要的,即使出现异常情况,它也可以保证数据的一致性。
当 @Transactional 注解作用于类上时,该类的所有 public 方法将都具有该类型的事务属性,同时,我们也可以在方法级别使用该标注来覆盖类级别的定义。如果类或者方法加了这个注解,那么这个类里面的方法抛出异常,就会回滚,数据库里面的数据也会回滚。
在 @Transactional 注解中如果不配置rollbackFor属性,那么事务只会在遇到RuntimeException的时候才会回滚,加上 rollbackFor=Exception.class,可以让事务在遇到非运行时异常时也回滚。
Spring Data JPA
JPA 重要的是实战,这里仅对小部分知识点进行总结。
如何使用JPA在数据库中非持久化一个字段?
假如我们有下面一个类:
如果我们想让secrect 这个字段不被持久化,也就是不被数据库存储怎么办?我们可以采用下面几种方法:
-
static String transient1; // not persistent because of static -
final String transient2 = "Satish"; // not persistent because of final -
transient String transient3; // not persistent because of transient -
@Transient String transient4; // not persistent because of @Transient
一般使用后面两种方式比较多
JPA 的审计功能是做什么的?有什么用?
审计功能主要是帮助我们记录数据库操作的具体行为比如某条记录是谁创建的、什么时间创建的、最后修改人是谁、最后修改时间是什么时候。
-
@CreatedDate: 表示该字段为创建时间字段,在这个实体被 insert 的时候,会设置值 -
@CreatedBy:表示该字段为创建人,在这个实体被 insert 的时候,会设置值@LastModifiedDate、@LastModifiedBy同理。
实体之间的关联关系注解有哪些?
@OneToOne: 一对一。@ManyToMany:多对多。@OneToMany: 一对多。@ManyToOne:多对一。
Spring Security
有哪些控制请求访问权限的方法?
-
permitAll():无条件允许任何形式访问,不管你登录还是没有登录。 -
anonymous():允许匿名访问,也就是没有登录才可以访问。 -
denyAll():无条件决绝任何形式的访问。 -
authenticated():只允许已认证的用户访问。 -
fullyAuthenticated():只允许已经登录或者通过 remember-me 登录的用户访问。 -
hasRole(String): 只允许指定的角色访问。 -
hasAnyRole(String): 指定一个或者多个角色,满足其一的用户即可访问。 -
hasAuthority(String):只允许具有指定权限的用户访问 -
hasAnyAuthority(String):指定一个或者多个权限,满足其一的用户即可访问。 -
hasIpAddress(String): 只允许指定 ip 的用户访问
hasRole和hasAuthority有区别吗?
代码上来说,hasRole 和 hasAuthority 写代码时前缀不同,但是最终执行是一样的;设计上来说,role 和 authority 这是两个层面的权限设计思路,一个是角色,一个是权限,角色是权限的集合。
如何对密码进行加密?
如果我们需要保存密码这类敏感数据到数据库的话,需要先加密再保存。
Spring Security 提供了多种加密算法的实现,开箱即用,非常方便。这些加密算法实现类的父类是 PasswordEncoder ,如果你想要自己实现一个加密算法的话,也需要继承 PasswordEncoder。
PasswordEncoder 接口一共也就 3 个必须实现的方法。
如何优雅更换系统使用的加密算法?
如果我们在开发过程中,突然发现现有的加密算法无法满足我们的需求,需要更换成另外一个加密算法,这个时候应该怎么办呢?
推荐的做法是通过 DelegatingPasswordEncoder 兼容多种不同的密码加密方案,以适应不同的业务需求。
从名字也能看出来,DelegatingPasswordEncoder 其实就是一个代理类,并非是一种全新的加密算法,它做的事情就是代理上面提到的加密算法实现类。在 Spring Security 5.0之后,默认就是基于 DelegatingPasswordEncoder 进行密码加密的。
Spring&Spring Boot常用注解总结
1. @SpringBootApplication
这个注解是Spring Boot项目的基石,创建SpringBoot项目之后会默认在主类加上。
我们可以把 @SpringBootApplication看作是 @Configuration、@EnableAutoConfiguration、@ComponentScan 注解的集合。
-
@EnableAutoConfiguration:启用 SpringBoot 的自动配置机制 -
@ComponentScan: 扫描被@Component(@Repository,@Service,@Controller)注解的 bean,注解默认会扫描该类所在的包下所有的类。 -
@Configuration:允许在 Spring 上下文中注册额外的 bean 或导入其他配置类
2. Spring Bean相关
2.1 @Autowired
自动导入对象到类中,被注入进的类同样要被Spring容器管理。比如:Service类注入到Controller类中。
2.2 @Component,@Repository,@Service, @Controller
我们一般使用 @Autowired 注解让 Spring 容器帮我们自动装配 bean。要想把类标识成可用于 @Autowired 注解自动装配的 bean 的类,可以采用以下注解实现:
@Component:通用的注解,可标注任意类为Spring组件。如果一个 Bean 不知道属于哪个层,可以使用@Component注解标注。@Repository: 对应持久层即 Dao 层,主要用于数据库相关操作。@Service: 对应服务层,主要涉及一些复杂的逻辑,需要用到 Dao 层。@Controller: 对应 Spring MVC 控制层,主要用于接受用户请求并调用 Service 层返回数据给前端页面。
2.3 @RestController
@RestController注解是@Controller和@ResponseBody的合集,表示这是个控制器 bean,并且是将函数的返回值直接填入 HTTP 响应体中,是 REST 风格的控制器。
Guide 哥:现在都是前后端分离,说实话我已经很久没有用过@Controller。如果你的项目太老了的话,就当我没说。
单独使用 @Controller 不加 @ResponseBody的话一般是用在要返回一个视图的情况,这种情况属于比较传统的 Spring MVC 的应用,对应于前后端不分离的情况。@Controller +@ResponseBody 返回 JSON 或 XML 形式数据
关于@RestController 和 @Controller的对比,请看这篇文章:@RestController vs @Controlleropen in new window。
2.4 @Scope
声明 Spring Bean 的作用域,使用方法:
-
singleton : 唯一 bean 实例,Spring 中的 bean 默认都是单例的。
-
prototype : 每次请求都会创建一个新的 bean 实例。
-
request : 每一次 HTTP 请求都会产生一个新的 bean,该 bean 仅在当前 HTTP request 内有效。
-
session : 每一个 HTTP Session 会产生一个新的 bean,该 bean 仅在当前 HTTP session 内有效
2.5 @Configuration
一般用来声明配置类,可以使用 @Component注解替代,不过使用@Configuration注解声明配置类更加语义化。
3. 处理常见的HTTP请求类型
-
GET :请求从服务器获取特定资源。举个例子:
GET /users(获取所有学生) -
POST :在服务器上创建一个新的资源。举个例子:
POST /users(创建学生) -
PUT :更新服务器上的资源(客户端提供更新后的整个资源)。举个例子:
PUT /users/12(更新编号为 12 的学生) -
DELETE :从服务器删除特定的资源。举个例子:
DELETE /users/12(删除编号为 12 的学生) -
PATCH :更新服务器上的资源(客户端提供更改的属性,可以看做作是部分更新),使用的比较少,这里就不举例子了。
3.1 GET请求
3.2 POST请求
3.3 PUT请求
3.4 DELETE请求
3.5 PATCH请求
4. 前后端传值
4.1 @PathVariable 和 @RequestParam
4.2 @RequestBody
用于读取 Request 请求(可能是 POST,PUT,DELETE,GET 请求)的 body 部分并且Content-Type 为 application/json 格式的数据,接收到数据之后会自动将数据绑定到 Java 对象上去。系统会使用HttpMessageConverter或者自定义的HttpMessageConverter将请求的 body 中的 json 字符串转换为 java 对象。
我用一个简单的例子来给演示一下基本使用!
我们有一个注册的接口:
UserRegisterRequest对象:
我们发送 post 请求到这个接口,并且 body 携带 JSON 数据:
{"userName":"coder","fullName":"shuangkou","password":"123456"}
这样我们的后端就可以直接把 json 格式的数据映射到我们的 UserRegisterRequest 类上。
👉 需要注意的是:一个请求方法只可以有一个@RequestBody,但是可以有多个@RequestParam和@PathVariable。 如果你的方法必须要用两个 @RequestBody来接收数据的话,大概率是你的数据库设计或者系统设计出问题了!
5. 读取配置信息
很多时候我们需要将一些常用的配置信息比如阿里云 oss、发送短信、微信认证的相关配置信息等等放到配置文件中。
下面我们来看一下 Spring 为我们提供了哪些方式帮助我们从配置文件中读取这些配置信息。
我们的数据源application.yml内容如下:
5.1 @Value(常用)
使用 @Value("${property}") 读取比较简单的配置信息:
5.2 @ConfigurationProperties(常用)
通过@ConfigurationProperties读取配置信息并与 bean 绑定。
你可以像使用普通的 Spring bean 一样,将其注入到类中使用。
5.3 @PropertySource(不常用)
@PropertySource读取指定 properties 文件
6. 参数校验
数据的校验的重要性就不用说了,即使在前端对数据进行校验的情况下,我们还是要对传入后端的数据再进行一遍校验,避免用户绕过浏览器直接通过一些 HTTP 工具直接向后端请求一些违法数据。
JSR(Java Specification Requests) 是一套 JavaBean 参数校验的标准,它定义了很多常用的校验注解,我们可以直接将这些注解加在我们 JavaBean 的属性上面,这样就可以在需要校验的时候进行校验了,非常方便!
校验的时候我们实际用的是 Hibernate Validator 框架。Hibernate Validator 是 Hibernate 团队最初的数据校验框架,Hibernate Validator 4.x 是 Bean Validation 1.0(JSR 303)的参考实现,Hibernate Validator 5.x 是 Bean Validation 1.1(JSR 349)的参考实现,目前最新版的 Hibernate Validator 6.x 是 Bean Validation 2.0(JSR 380)的参考实现。
SpringBoot 项目的 spring-boot-starter-web 依赖中已经有 hibernate-validator 包,不需要引用相关依赖。如下图所示(通过 idea 插件—Maven Helper 生成):
注:更新版本的 spring-boot-starter-web 依赖中不再有 hibernate-validator 包(如2.3.11.RELEASE),需要自己引入 spring-boot-starter-validation 依赖。
👉 需要注意的是: 所有的注解,推荐使用 JSR 注解,即javax.validation.constraints,而不是org.hibernate.validator.constraints
6.1 一些常用的字段验证的注解
-
@NotEmpty被注释的字符串的不能为 null 也不能为空 -
@NotBlank被注释的字符串非 null,并且必须包含一个非空白字符 -
@Null被注释的元素必须为 null -
@NotNull被注释的元素必须不为 null -
@AssertTrue被注释的元素必须为 true -
@AssertFalse被注释的元素必须为 false -
@Pattern(regex=,flag=)被注释的元素必须符合指定的正则表达式 -
@Email被注释的元素必须是 Email 格式。 -
@Min(value)被注释的元素必须是一个数字,其值必须大于等于指定的最小值 -
@Max(value)被注释的元素必须是一个数字,其值必须小于等于指定的最大值 -
@DecimalMin(value)被注释的元素必须是一个数字,其值必须大于等于指定的最小值 -
@DecimalMax(value)被注释的元素必须是一个数字,其值必须小于等于指定的最大值 -
@Size(max=, min=)被注释的元素的大小必须在指定的范围内 -
@Digits(integer, fraction)被注释的元素必须是一个数字,其值必须在可接受的范围内 -
@Past被注释的元素必须是一个过去的日期 -
@Future被注释的元素必须是一个将来的日期 -
......
6.2 验证请求体(RequestBody)
我们在需要验证的参数上加上了@Valid注解,如果验证失败,它将抛出MethodArgumentNotValidException。
6.3 验证请求参数(Path Variables和Request Parameters)
一定一定不要忘记在类上加上 @Validated 注解了,这个参数可以告诉 Spring 去校验方法参数。
@RestController
@RequestMapping("/api")
@Validated
public class PersonController {
@GetMapping("/person/{id}")
public ResponseEntity<Integer> getPersonByID(@Valid @PathVariable("id") @Max(value = 5,message = "超过 id 的范围了") Integer id) {
return ResponseEntity.ok().body(id);
}
}
全局处理Controller异常
相关注解:
@ControllerAdvice:注解定义全局异常处理类@ExceptionHandler:注解声明异常处理方法
如何使用呢?拿我们在第 5 节参数校验这块来举例子。如果方法参数不对的话就会抛出MethodArgumentNotValidException,我们来处理这个异常。
8.JPA相关
8.1 创建表
@Entity声明一个类对应一个数据库实体。
@Table 设置表名
8.2 创建主键
@Id :声明一个字段为主键。
使用@Id声明之后,我们还需要定义主键的生成策略。我们可以使用 @GeneratedValue 指定主键生成策略。
1.通过 @GeneratedValue直接使用 JPA 内置提供的四种主键生成策略来指定主键生成策略。
JPA 使用枚举定义了 4 种常见的主键生成策略,如下:
@GeneratedValue注解默认使用的策略是GenerationType.AUTO
一般使用 MySQL 数据库的话,使用GenerationType.IDENTITY策略比较普遍一点(分布式系统的话需要另外考虑使用分布式 ID)。
2.通过 @GenericGenerator声明一个主键策略,然后 @GeneratedValue使用这个策略
等价于:
jpa提供的主键生成策略有如下几种:
8.3 设置字段类型
@Column声明字段
示例:
设置属性 userName 对应的数据库字段名为 user_name,长度为 32,非空
设置字段类型并且加默认值,这个还是挺常用的。
8.4 指定不持久化特定字段
@Transient :声明不需要与数据库映射的字段,在保存的时候不需要保存进数据库 。
如果我们想让secrect 这个字段不被持久化,可以使用 @Transient关键字声明。
除了 @Transient关键字声明, 还可以采用下面几种方法:
一般使用注解的方式比较多。
8.5 声明大字段
@Lob:声明某个字段为大字段。
更详细的声明:
8.6 创建枚举类型的字段
可以使用枚举类型的字段,不过枚举字段要用@Enumerated注解修饰。
8.7 增加审计功能
只要继承了AbstractAuditBase的类都会默认加上下面四个字段。
我们对应的审计功能对应地配置类可能是下面这样的(Spring Security项目):
简单介绍一下上面涉及到的一些注解:
-
@CreatedDate: 表示该字段为创建时间字段,在这个实体被 insert 的时候,会设置值 -
@CreatedBy:表示该字段为创建人,在这个实体被 insert 的时候,会设置值@LastModifiedDate、@LastModifiedBy同理。
@EnableJpaAuditing:开启 JPA 审计功能
8.8 删除/修改数据
@Modifying注解提示JPA该操作是修改操作,注意还要配合@Transactional注解使用
8.9 关联关系
@OneToOne声明一对一关系@OneToMany声明一对多关系@ManyToOne声明多对一关系@ManyToMany声明多对多关系
9. 事务@Transactional
在要开启事务的方法上使用@Transactional注解即可!
我们知道 Exception 分为运行时异常 RuntimeException 和非运行时异常。在@Transactional注解中如果不配置rollbackFor属性,那么事务只会在遇到RuntimeException的时候才会回滚,加上rollbackFor=Exception.class,可以让事务在遇到非运行时异常时也回滚。
@Transactional 注解一般可以作用在类或者方法上。
- 作用于类:当把
@Transactional注解放在类上时,表示所有该类的 public 方法都配置相同的事务属性信息。 - 作用于方法:当类配置了
@Transactional,方法也配置了@Transactional,方法的事务会覆盖类的事务配置信息
10. json数据处理
10.1 过滤json数据
@JsonIgnoreProperties 作用在类上用于过滤掉特定字段不返回或者不解析。
@JsonIgnore一般用于类的属性上,作用和上面的@JsonIgnoreProperties 一样。
10.2 格式化json数据
@JsonFormat一般用来格式化 json 数据。
比如:
10.3 扁平化对象
未扁平化之前:
使用@JsonUnwrapped 扁平对象之后:
11. 测试相关
@ActiveProfiles一般作用于测试类上, 用于声明生效的 Spring 配置文件。
@Test声明一个方法为测试方法
@Transactional被声明的测试方法的数据会回滚,避免污染测试数据。
@WithMockUser Spring Security 提供的,用来模拟一个真实用户,并且可以赋予权限。