Spring总结
Spring Framework:非侵入式、组件化
Core Container
Spring 框架的核心模块,也可以说是基础模块,主要提供 IoC 依赖注入功能的支持。Spring 其他所有的功能基本都需要依赖于该模块
- spring-core:Spring 框架基本的核心工具类。
- spring-beans:提供对 bean 的创建、配置和管理等功能的支持。
- spring-context:提供对国际化、事件传播、资源加载等功能的支持。
- spring-expression:提供对表达式语言(Spring Expression Language) SpEL 的支持,只依赖于 core 模块,不依赖于其他模块,可以单独使用。
AOP
- spring-aspects:该模块为与 AspectJ 的集成提供支持。
- spring-aop:提供了面向切面的编程实现。
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 框架集成一些基础的报文传送应用。
MVC 是模型(Model)、视图(View)、控制器(Controller)的简写,其核心思想是通过将业务逻辑、数据、显示分离来组织代码。
IOC
IoC(控制反转,Inversion of Control):
-
控制:指的是对象创建(实例化、管理)的权力
-
反转:控制权交给外部环境(Spring 框架、IoC 容器)
反转了什么?【对象的创建权力/对象和对象关系的维护权力】交给第三方容器管理
IoC容器管理所有java对象的实例化与初始化,控制对象与对象之间的依赖关系
在实际项目中一个 Service 类可能依赖了很多其他的类,假如我们需要实例化这个 Service,你可能要每次都要搞清这个 Service 所有底层类的构造函数,这可能会把人逼疯。如果利用 IoC 的话,你只需要配置好,然后在需要的地方引用就行了,这大大增加了项目的可维护性且降低了开发难度。
IoC容器根据Bean的定义信息【xml中的bean定义信息转化为BeanDefinition,被BeanDefinitionReader读取】,通过BeanFactory工厂+反射来实例化对象,然后进行初始化形成最终的对象Bean
Spring Bean:IoC容器管理的Java对象
spring用反射创建对象到Map<String,BeanDefinition> beanDifinitionMap
中:IoC 容器实际上就是个 Map(key,value),Map 中存放的是各种对象。
DI(Dependency Injection,依赖注入):Spring创建对象的过程中,将对象属性通过配置进行注入
自动装配:
@Autowire 自动装配:属性、setter方法、构造方法、构造方法形参上都可以【与@Qualifier(value=“”)联合可在同类型的多个类中精准选定bean】
@Resource 也可完成属性注入,属性、setter方法【属于JDK扩展包中的,JDK8以外的需要引入jakarta.annotation-api依赖】
Autowire 默认采用byType,若想按名称则需与Qualifier一起用
Resource 默认采用byName,未指定name时将属性名当作name,找不到的话则按类型
bean生命周期:
bean无参构建(实例化bean对象)
给bean设置相关属性 set方法
检查Aware相关接口并设置相关依赖【BeanNameAware。。。】
bean后置处理器(初始化之前) BeanPostProcessor接口的postProcessBeforeInitialization()
如果 Bean 实现了InitializingBean接口,执行afterPropertiesSet()方法。
bean对象初始化(调用初始化方法)init-method设置
bean后置处理器(初始化之后)BeanPostProcessor接口的postProcessAfterInitialization()
Spring 中 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
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
**Bean是线程安全的吗?**prototype 作用域下,每次获取都会创建一个新的 bean 实例,不存在资源竞争问题,所以不存在线程安全问题。singleton 作用域下,IoC 容器中只有唯一的 bean 实例,可能会存在资源竞争问题(取决于 Bean 是否有状态)。如果这个 bean 是有状态的话,那就存在线程安全问题(有状态 Bean 是指包含可变的成员变量的对象)。
对于有状态单例 Bean 的线程安全问题,常见的有两种解决办法:
- 在 Bean 中尽量避免定义可变的成员变量。
- 在类中定义一个
ThreadLocal
成员变量,将需要的可变成员变量保存在ThreadLocal
中(推荐的一种方式)。
AOP
(案例:日志记录)需要把附加的日志功能从业务代码中抽离出来,困难在于日志功能中有方法的参数,不能简单的抽出来搞个工具类就行
代理模式:通过调用代理对象的方法,来让代理对象调用目标方法
动态代理:对日志功能进行统一管理
public class ProxyFactory {
//目标对象
private Object target;
public ProxyFactory(Object target){
this.target = target;
}
public Object getProxy(){
ClassLoader classLoader = target.getClass().getClassLoader();
Class<?>[] interfaces = target.getClass().getInterfaces();
InvocationHandler invocationHandler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("before");
Object result = method.invoke(target, args);
System.out.println("after");
return result;
}
};
return Proxy.newProxyInstance(classLoader,interfaces,invocationHandler);
}
}
横向关注点:分散在各个模块中解决相同的问题(日志管理…)
通知(增强):添加的功能(通知方法)
切面:封装通知方法的类
连接点:spring允许使用通知的地方
切入点:实际去实现增强的方法,通过切入点定位到特定的连接点(连接点=数据库中的记录,切入点=SQL语句)
JDK动态代理:拜把兄弟(有接口,代理对象与目标对象实现同样的接口)
cglib动态代理:认干爹(无接口,代理对象继承目标对象)
Spring AOP 就是基于动态代理的,如果要代理的对象,实现了某个接口,那么 Spring AOP 会使用 JDK Proxy,去创建代理对象,而对于没有实现接口的对象,就无法使用 JDK Proxy 去进行代理了,这时候 Spring AOP 会使用 Cglib 生成一个被代理对象的子类来作为代理
AspectJ:AOP框架,Spring借用了它的注解(本质上静态代理,将代理逻辑“织入”到目标对象的字节码文件中,实现了动态的效果)
需要开启aop中的自动
<aop:aspectj-autoproxy/>
@Aspect
@Component
public class LogAspect {
//切入点表达式 也可写为"execution(* org.annoaop.CalculatorImpl.*(..))"
@Before(value = "execution(public void org.annoaop.CalculatorImpl.*(..))")
public void beforeMethod(){
System.out.println("before:");
}
//返回目标方法的结果
@AfterReturning(value = "execution(public void org.annoaop.CalculatorImpl.*(..))",returning = "result")
public void afterReturningMethod(Object result){
System.out.println("returning:" + result);
}
//发生异常后触发
@AfterThrowing(value = "execution(public void org.annoaop.CalculatorImpl.*(..))")
public void afterReturningMethod(Throwable ex){
System.out.println("error:" + ex);
}
//在后置返回、后置异常之后发生
@After(value = "execution(public void org.annoaop.CalculatorImpl.*(..))")
public void afterMethod(JoinPoint joinpoint){
String methodName = joinpoint.getSignature().getName(); //通过joinpoint可以得到目标方法相关信息
System.out.println("after:" + methodName);
}
@Around(value = "execution(public void org.annoaop.CalculatorImpl.*(..))")
public void aroundMethod(ProceedingJoinPoint proceed){
try {
System.out.println("around-before");
proceed.proceed(); //由此来执行目标方法
System.out.println("around-after-returning");
} catch (Throwable e) {
System.out.println("around-ex");
throw new RuntimeException(e);
}finally {
System.out.println("around-after");
}
}
}
切入点重用:
@PointCut(value = "execution(public void org.annoaop.CalculatorImpl.*(..))")
public void pointCut(){}
@Before(value = "pointCut()") //同一切面类内重用切入点,不同类时前面需要加包名
xxx
切面优先级:内外嵌套顺序(外面>里面的,或者用@Order来指定)
xml配置:
<aop:config>
//配置切面类
<aop:aspect ref="logAspect">
//配置切面点
<aop:pointcut id="pointcut" expression="execution(public void org.annoaop.CalculatorImpl.*(..))"/>
//前置通知
<aop:before method="beforeMethod" point-ref="pointcut"/>
</aop:aspect ref="logAspect">
</aop:config>
Spring MVC
DispatcherServlet
:核心的中央处理器,负责接收请求、分发,并给予客户端响应。HandlerMapping
:处理器映射器,根据 uri 去匹配查找能处理的Handler
,并会将请求涉及到的拦截器和Handler
一起封装。HandlerAdapter
:处理器适配器,根据HandlerMapping
找到的Handler
,适配执行对应的Handler
;Handler
:请求处理器,处理实际请求的处理器。ViewResolver
:视图解析器,根据Handler
返回的逻辑视图 / 视图,解析并渲染真正的视图,并传递给DispatcherServlet
响应客户端
- 客户端(浏览器)发送请求,
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
@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)
ExceptionHandlerMethodResolver
中 getMappedMethod
方法决定了异常具体被哪个被 @ExceptionHandler
注解修饰的方法处理异常。
@Nullable
private Method getMappedMethod(Class<? extends Throwable> exceptionType) {
List<Class<? extends Throwable>> matches = new ArrayList<>();
//mappedMethods 中存放了异常和处理异常的方法的对应关系
for (Class<? extends Throwable> mappedException : this.mappedMethods.keySet()) {
if (mappedException.isAssignableFrom(exceptionType)) {
matches.add(mappedException);
}
}
// 不为空说明有方法处理异常
if (!matches.isEmpty()) {
// 按照匹配程度从小到大排序
matches.sort(new ExceptionDepthComparator(exceptionType));
// 返回处理异常的方法
return this.mappedMethods.get(matches.get(0));
}
else {
return null;
}
}
首先找到可以匹配处理异常的所有方法信息,然后对其进行从小到大的排序,最后取最小的那一个匹配的方法(即匹配度最高的那个)
事务
编程式事务:在代码中硬编码(不推荐使用) : 通过 TransactionTemplate
或者 TransactionManager
手动管理事务
声明式事务:在 XML 配置文件中配置或者直接基于注解(推荐使用) : 实际是通过 AOP 实现(基于@Transactional
的全注解方式使用最多)
传播行为:解决业务层方法之间互相调用的事务问题。
正确的事务传播行为可能的值如下:
1.TransactionDefinition.PROPAGATION_REQUIRED
如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。【@Transactional
注解默认使用就是这个事务传播行为。】
2.TransactionDefinition.PROPAGATION_REQUIRES_NEW
不管外部方法是否开启事务,Propagation.REQUIRES_NEW
修饰的内部方法会新开启自己的事务,且开启的事务相互独立,互不干扰。
3.TransactionDefinition.PROPAGATION_NESTED
如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则创建一个新的事务
4.TransactionDefinition.PROPAGATION_MANDATORY
(mandatory:强制性)
如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。这个使用的很少。
若是错误的配置以下 3 种事务传播行为,事务将不会发生回滚:
TransactionDefinition.PROPAGATION_SUPPORTS
: 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。TransactionDefinition.PROPAGATION_NOT_SUPPORTED
: 以非事务方式运行,如果当前存在事务,则把当前事务挂起。TransactionDefinition.PROPAGATION_NEVER
: 以非事务方式运行,如果当前存在事务,则抛出异常
隔离级别:
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 的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别
脏读(读取未提交数据):A事务读取B事务尚未提交的数据,此时如果B事务发生错误并执行回滚操作,那么A事务读取到的数据就是脏数据
不可重复读(前后读取同一数据内容不一致):事务A在执行读取操作,由于整个事务A处理时间长,前后读取同一条数据需要经历很长的时间 。而在事务A第一次读取数据,比如此时读取了小明的年龄为20岁,事务B执行更改操作,将小明的年龄更改为30岁,此时事务A第二次读取到小明的年龄时,发现其年龄是30岁,和之前的数据不一样了,也就是数据不重复了,系统无法读取到重复的数据,成为不可重复读。
幻读(前后读取的数据总量不一致):事务A在执行读取操作,需要两次统计数据的总量,前一次查询数据总量后,此时事务B执行了新增数据的操作并提交后,这个时候事务A读取的数据总量和之前统计的不一样,就像产生了幻觉一样,平白无故的多了几条数据,成为幻读。
@Transactional(rollbackFor = Exception.class)注解了解吗?
在 @Transactional
注解中如果不配置rollbackFor
属性,那么事务只会在遇到RuntimeException
的时候才会回滚,加上 rollbackFor=Exception.class
,可以让事务在遇到非运行时异常时也回滚
作用于类:当把@Transactional
注解放在类上时,表示所有该类的 public 方法都配置相同的事务属性信息。
作用于方法:当类配置了@Transactional
,方法也配置了@Transactional
,方法的事务会覆盖类的事务配置信息。
SpringBoot(TODO)
Spring Boot 通过@EnableAutoConfiguration
开启自动装配,通过 SpringFactoriesLoader 最终加载META-INF/spring.factories
中的自动配置类实现自动装配,自动配置类其实就是通过@Conditional
按需加载的配置类,想要其生效必须引入spring-boot-starter-xxx
包实现起步依赖
Spring security
permitAll()
:无条件允许任何形式访问,不管你登录还是没有登录。anonymous()
:允许匿名访问,也就是没有登录才可以访问。denyAll()
:无条件决绝任何形式的访问。authenticated()
:只允许已认证的用户访问。fullyAuthenticated()
:只允许已经登录或者通过 remember-me 登录的用户访问。hasRole(String)
: 只允许指定的角色访问。hasAnyRole(String)
: 指定一个或者多个角色,满足其一的用户即可访问。hasAuthority(String)
:只允许具有指定权限的用户访问hasAnyAuthority(String)
:指定一个或者多个权限,满足其一的用户即可访问。hasIpAddress(String)
: 只允许指定 ip 的用户访问。
使用 hasAuthority
更具有一致性,你不用考虑要不要加 ROLE_
前缀,数据库什么样这里就是什么样!而 hasRole
则不同,代码里如果写的是 admin
,框架会自动加上 ROLE_
前缀,所以数据库就必须是 ROLE_admin
。
常用注解
1.SpringBootApplication
@SpringBootApplication = @Configuration+
@EnableAutoConfiguration+@ComponentScan
@EnableAutoConfiguration
:启用 SpringBoot 的自动配置机制
@ComponentScan
:扫描被@Component
(@Repository
,@Service
,@Controller
)注解的 bean,注解默认会扫描该类所在的包下所有的类。
@Configuration
:允许在 Spring 上下文中注册额外的 bean 或导入其他配置类
2.Bean
@RestController
=@Controller
+@ResponseBody
返回 JSON 或 XML 形式
@Controller
的话一般是用在要返回一个视图的情况,这种情况属于比较传统的 Spring MVC 的应用,对应于前后端不分离的情况。
@Scope(“singleton”)
singleton : 唯一 bean 实例,Spring 中的 bean 默认都是单例的。
prototype : 每次请求都会创建一个新的 bean 实例。
request : 每一次 HTTP 请求都会产生一个新的 bean,该 bean 仅在当前 HTTP request 内有效。
session : 每一个 HTTP Session 会产生一个新的 bean,该 bean 仅在当前 HTTP session 内有效
3.HTTP
@GetMapping(“users”) =
@RequestMapping(value=“/users”,method=RequestMethod.GET)
@GetMapping("/klasses/{klassId}/teachers")
public List<Teacher> getKlassRelatedTeachers(
@PathVariable("klassId") Long klassId,
@RequestParam(value = "type", required = false) String type ) {
...
}
请求的 url 是:/klasses/123456/teachers?type=web
那么我们服务获取到的数据就是:klassId=123456,type=web
public ResponseEntity signUp(@RequestBody @Valid UserRegisterRequest userRegisterRequest)
Request请求的body 部分并且Content-Type 为 application/json 格式的数据
系统会使用HttpMessageConverter
或者自定义的HttpMessageConverter
将请求的 body 中的 json 字符串转换为 java 对象。
4.读取配置信息
@Value("${cui}")
String cui;
@ConfigurationProperties(prefix = "library")
class LibraryProperties
5.JSR( Specification Requests)
验证请求体
public ResponseEntity<Person> getPerson(@RequestBody @Valid Person person)
验证请求参数【一定不要忘记在类上加上 @Validated
注解】
@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);
}
}
6.测试
@ActiveProfiles
一般作用于测试类上, 用于声明生效的 Spring 配置文件。
@Test
声明一个方法为测试方法
@Transactional
被声明的测试方法的数据会回滚,避免污染测试数据。
@WithMockUser
Spring Security 提供的,用来模拟一个真实用户,并且可以赋予权限。