Spring
Spring
介绍
Spring使Java编程对每个人来说都更快、更容易、更安全。
Spring对速度、简单性和生产力的关注使其成为世界上最流行的Java框架。
大版本变化
Spring 5.x : JDK >= 1.8 , Tomcat 9.x Spring 6.x : JDK >= 17 , Tomcat 10.x
IoC
容器:是一种为某种特定组件的运行提供必要支持的一个软件环境。
- 如 Tomcat:Servlet容器,实现TCP连接,解析HTTP协议 等服务
Spring核心:IoC容器
- 管理所有轻量级的JavaBean组件
- 组件的生命周期管理、配置和组装服务
- AOP支持
- 建立在AOP基础上的声明式事务服务
- 管理所有轻量级的JavaBean组件
IoC: Inversion of Control,直译为控制反转
- 问题:系统包含大量的组件,生命周期、依赖关系由自身维护,大大增加了系统的复杂度,导致紧密耦合
IoC模式下,控制权发生了反转,从应用程序转移到了IoC容器
组件不再由应用程序自己创建和配置,而是由IoC容器负责
为了能让组件在IoC容器中被“装配”出来,需要某种“注入”机制
- Service使用DataSource,并不会创建DataSource,等待外部通过setDataSource()方法来注入一个DataSource
IoC又称为依赖注入(DI:Dependency Injection),将组件的创建+配置与组件的使用相分离,并且,由IoC容器负责管理组件的生命周期。
- 因为IoC容器要负责实例化所有的组件,因此,有必要告诉容器如何创建组件,以及各组件的依赖关系。一种最简单的配置是通过XML文件来实现,
依赖注入方式:同时支持属性注入和构造方法注入,并允许混合使用。
- set()方法
- 构造方法
- 高度可扩展的,无侵入容器,应用程序的组件无需实现Spring的特定接口
- 应用程序组件既可以在Spring的IoC容器中运行,也可以自己编写代码自行组装配置;
- 测试的时候并不依赖Spring容器,可单独进行测试,大大提高了开发效率。
装配Bean
- 通过读取XML文件后使用反射完成
- ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
- ApplicationContext: IoC容器的引用
- getBean()
- ApplicationContext会一次性创建所有的Bean
- BeanFactory factory = new XmlBeanFactory(new ClassPathResource("application.xml"));
- 另一种IoC容器
- BeanFactory的实现是按需创建
- 另一种IoC容器
Annotation配置
XML
- 好处:直观观察Bean依赖
- 写起来繁琐
Annotation配置:Spring自动扫描Bean并组装它们;
@Component
@ComponentScan : 自动搜索当前类所在的包以及子包,把所有标注为@Component的Bean自动创建出来,并根据@Autowired进行装配。
@Autowired
ApplicationContext
- new AnnotationConfigApplicationContext(AppConfig.class)
- AppConfig, 配置类,标注了@Configuration
定制Bean
@Scope:
- 一般@Component 为一个一个单例(Singleton),在容器运行期间,我们调用getBean(Class)获取到的Bean总是同一个实例。
- @Scope,每次调用getBean(Class),容器都返回一个新的实例,这种Bean称为Prototype(原型)
注入List:Spring会自动把所有类型为Validator的Bean装配为一个List注入进来
@Autowired
List<Validator> validators;
可选注入:@Autowired(required = false)
- 找到一个类型为ZoneId的Bean,就注入,如果找不到,就忽略。
- 默认required为true,否则报:NoSuchBeanDefinitionException
创建第三方Bean
- 如果一个Bean不在我们自己的package管理之内,如不在 @ComponentScan内
- @Bean,单例
@Configuration
@ComponentScan
public class AppConfig {
// 创建一个Bean:
@Bean
ZoneId createZoneId() {
return ZoneId.of("Z");
}
}
初始化和销毁 : 在Bean的初始化和清理方法上标记@PostConstruct和@PreDestroy
使用别名
- 方式
- @Bean("z")
- @Bean + @Qualifier("z")
- 使用:@Autowired + @Qualifier("z")
- 异常:多个Bean未区分,NoUniqueBeanDefinitionException
- 其他方式:
- @Bean + @Primary:多个Bean会优先注入Primary
- 方式
使用FactoryBean
Resource
org.springframework.core.io.Resource
@Value("classpath:/logo.txt")
@Value("file:/path/to/logo.txt")
注入配置
- @PropertySource
- Spring容器看到@PropertySource("app.properties")注解后,自动读取这个配置文件,然后,我们使用@Value正常注入
- @Value("${app.zone:Z}")
- JavaBean,如@Component,读取Bean对象的属性值 get方法
- @Value("#{smtpConfig.host}")
- #{}表示从JavaBean读取属性
- ${key} 配置文件
条件装配
@Profile
- @Bean+ @Profile("!test"):表示非Test环境
- @Profile({ "test", "master" }) 多个配置
- 环境指定:
- -Dspring.profiles.active=test
- -Dspring.profiles.active=test,master
- @Bean+ @Profile("!test"):表示非Test环境
@Conditional
@ConditionalOnProperty
......
AOP
- AOP是Aspect Oriented Programming,即面向切面编程。
- OOP,Object Oriented Programming 面向对象编程,OOP把系统看作多个对象的交互,主要功能是数据封装、继承和多态;
- AOP把系统分解为不同的关注点
- 即 切面(Aspect)
- 在Java平台上,对于AOP的织入,有3种方式
- 编译期
- 类加载器
- 运行期
两种AOP实现
- AspectJ
- 基于编译的方式
- 在程序运行期是不会做任何事情
- SpringAOP
- 基于动态代理的实现AOP
- 运行时织入
- 依赖AspectJ,借用了AspectJ的一些功能 如 @AspceJ、@Before ·····
- AspectJ
切面(Aspect): 在哪干和干什么集合
- 横切关注点的模块化,如 LogAspect 日志组件
- 切入点和通知的集合
- @Aspect 注解的类就是切面
连接点(JoinPoint): 在哪里干
- 系统要在哪些点上织入AOP,如方法调用
- 连接点可能是类初始化、方法执行、方法调用、字段调用或处理异常等等
- Spring只支持方法执行连接点
切入点(Pointcut): 在哪里干的集合
- 指定一组 连接点,如表达式(某某类下的所有方法)
- Spring支持perl5正则表达式和AspectJ切入点模式,Spring默认使用AspectJ语法
- 指定一组 连接点,如表达式(某某类下的所有方法)
通知/增强(Advice) : 干什么
- 在连接点上执行的行为
- 定义了将会织入到 Joinpoint 的具体逻辑
- 包括前置通知(before advice)、后置通知(after advice)、环绕通知(around advice)
- 如注解 , 通过 @Before、@After、@Around 来区别在 JointPoint 之前、之后还是环绕执行的代码。
目标对象(Target Object): 对谁干
- 需要被织入横切关注点的对象,即该对象是切入点选择的对象,需要被通知的对象,从而也可称为被通知对象
- 由于Spring AOP 通过代理模式实现,从而这个对象永远是被代理对象
Spring的AOP实现就是基于JVM的动态代理。
- 由于JVM的动态代理要求必须实现接口,如果一个普通类没有业务接口,就需要通过CGLIB或者Javassist这些第三方库实现。
代理模式的实现方式
- 配置 @EnableAspectJAutoProxy
- class: @Component + @Aspect
Spring容器启动时为我们自动创建的注入了Aspect的子类,它取代了原始的UserService
- UserServiceAopProxy extends UserService
实际上是Spring使用CGLIB动态创建的子类
AspectJ的注入语法则
拦截器类型:
@Before:这种拦截器先执行拦截代码,再执行目标代码。如果拦截器抛异常,那么目标代码就不执行了;
@After:这种拦截器先执行目标代码,再执行拦截器代码。无论目标代码是否抛异常,拦截器代码都会执行;
@AfterReturning:和@After不同的是,只有当目标代码正常返回时,才执行拦截器代码;
@AfterThrowing:和@After不同的是,只有当目标代码抛出了异常时,才执行拦截器代码;
@Around:能完全控制目标代码是否执行,并可以在执行前后、抛异常后执行任意拦截代码,可以说是包含了上面所有功能。
注解装配AOP
- 使用非注解(基于方法规则,如表达式),如类似方式:@Before("execution(public * com.itranswarp.learnjava.service..(..))")
- 整个类,包,所有xx开头的方法:
- 非精准打击误伤面更大
- 后续新增的Bean,如果不清楚现有的AOP装配规则,容易被强迫装配。
- 整个类,包,所有xx开头的方法:
- 注解(定义注解,基于注解装配AOP)
- 既简单,又能明确标识AOP装配,是使用AOP推荐的方式
- 自定义注解,显示的装配,匹配该注解为其实现AOP
- 使用非注解(基于方法规则,如表达式),如类似方式:@Before("execution(public * com.itranswarp.learnjava.service..(..))")
避坑
- 由于Spring通过CGLIB实现代理类,我们要避免直接访问Bean的字段,以及由final方法带来的“未代理”问题。
ORM
JdbcTemplate
- 对JDBC操作的一个简单封装,它的目的是尽量减少手动编写try(resource) {...}的代码
- 主要通过RowMapper实现了JDBC结果集到Java对象的转换。
声明式事务
JavaEE
- JDBC事物
- 分布式事务JTA(Java Transaction API)
PlatformTransactionManager
- Spring为了同时支持JDBC和JTA两种事务模型,进行了抽象
- JDBC事务:DataSourceTransactionManager
- 声明式事务
- 配置,@EnableTransactionManagement
- PlatformTransactionManager,创建@Bean DataSourceTransactionManager
- 方法上实现事务:@Transactional;
- Claas上也添加@Transactional,所有public方法都具有事务支持
- 回滚:抛出RuntimeException
- 回滚:@Transactional(rollbackFor = {RuntimeException.class, IOException.class})
- 为了简化代码,我们强烈建议业务异常体系从RuntimeException派生,这样就不必声明任何特殊异常即可让Spring的声明式事务正常工作
- class BusinessException extends RuntimeException
- LoginException extends BusinessException
- Spring为了同时支持JDBC和JTA两种事务模型,进行了抽象
原理:AOP代理,自动创建Bean的Proxy实现
事务边界
- 以下事务边界就是addBonus()方法开始和结束
@Transactional
public void addBonus(long userId, int bonus) { // 事务开始
...
} // 事务结束
事务传播
- 事务方法1 调用 事务方法2
- 默认事务传播级别REQUIRED
- 事务方法2会检测判定,加入事务方法1 的事务中
- 默认事务传播级别REQUIRED
- Spring的声明式事务为事务传播定义了几个级别
- REQUIRED,它的意思是,如果当前没有事务,就创建一个新事务,如果当前有事务,就加入到当前事务中执行。
- SUPPORTS:表示如果有事务,就加入到当前事务,如果没有,那也不开启事务执行。这种传播级别可用于查询方法,因为SELECT语句既可以在事务内执行,也可以不需要事务;
- REQUIRES_NEW:表示不管当前有没有事务,都必须开启一个新的事务执行。如果当前已经有事务,那么当前事务会挂起,等新事务完成后,再恢复执行;
- ······
- 事务方法1 调用 事务方法2
Spring是如何传播事务的
- 最终也是通过执行JDBC事务来实现功能的
- Connection,setAutoCommit,commit,rollback
一个事务方法,如何获知当前是否存在事务?
- ThreadLocal,Spring总是把JDBC相关的Connection和TransactionStatus实例绑定到ThreadLocal;
- 如果一个事务方法从ThreadLocal未取到事务,那么它会打开一个新的JDBC连接,同时开启一个新的事务,否则,它就直接使用从ThreadLocal获取的JDBC连接以及TransactionStatus。
- 事务能正确传播的前提是,方法调用是在一个线程内才行。
- 无法跨线程传播
Spring MVC
启动
- DispatcherServlet,web.xml 配置 ,org.springframework.web.servlet.DispatcherServlet
- WebApplicationContext :IoC容器,完成所有Bean的初始化,并将容器绑到ServletContext上
- IoC容器中获取所有@Controller的Bean
- ModelAndView 视图渲染
- servlet-mapping :/*
- 请求全部由DispatcherServlet接收
- 根据配置转发到指定Controller的指定方法处理
- DispatcherServlet,web.xml 配置 ,org.springframework.web.servlet.DispatcherServlet
应用配置:@EnableWebMvc :如:
- DataSource
- JdbcTemplate
- PlatformTransactionManager
- WebMvcConfigurer: 添加静态文件目录
- ViewResolver:添加模版引擎
Filter
│ ▲
▼ │
┌───────┐
│Filter1│
└───────┘
│ ▲
▼ │
┌───────┐
┌ ─ ─ ─│Filter2│─ ─ ─ ─ ─ ─ ─ ─ ┐
└───────┘
│ │ ▲ │
▼ │
│ ┌─────────────────┐ │
│DispatcherServlet│<───┐
│ └─────────────────┘ │ │
│ ┌────────────┐
│ │ │ModelAndView││
│ └────────────┘
│ │ ▲ │
│ ┌───────────┐ │
│ ├───>│Controller1│────┤ │
│ └───────────┘ │
│ │ │ │
│ ┌───────────┐ │
│ └───>│Controller2│────┘ │
└───────────┘
└ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘
Filter是Servlet规范定义的标准组件
- web.xml配置Filter,filter-mapping
- @Component 注册Filter
- XXX implements Filter, doFilter
Interceptor
│ ▲
▼ │
┌───────┐
│Filter1│
└───────┘
│ ▲
▼ │
┌───────┐
│Filter2│
└───────┘
│ ▲
▼ │
┌─────────────────┐
│DispatcherServlet│<───┐
└─────────────────┘ │
│ ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┼ ─ ─ ─ ┐
│ │
│ │ ┌────────────┐ │
│ │ Render │
│ │ └────────────┘ │
│ ▲
│ │ │ │
│ ┌────────────┐
│ │ │ModelAndView│ │
│ └────────────┘
│ │ ▲ │
│ ┌───────────┐ │
├─┼─>│Controller1│────┤ │
│ └───────────┘ │
│ │ │ │
│ ┌───────────┐ │
└─┼─>│Controller2│────┘ │
└───────────┘
和Filter相比,Interceptor拦截范围不是后续整个处理流程,而是仅针对Controller拦截
ModelAndView并渲染后,后续处理就脱离了Interceptor的拦截范围。
- @Order(1) + @Component
- LoggerInterceptor implements HandlerInterceptor
- boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
- void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
- Controller方法正常返回后执行
- void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
- 无论Controller方法是否抛异常都会执行
注册拦截器
- @Bean WebMvcConfigurer createWebMvcConfigurer
- registry.addInterceptor
处理异常
- 在Controller中,Spring MVC还允许定义基于@ExceptionHandler注解的异常处理方法
- @ExceptionHandler(RuntimeException.class)
- 仅作用于当前的Controller
CORS
- 全称Cross-Origin Resource Sharing
- 跨域访问能否成功,取决于B站是否愿意给A站返回一个正确的Access-Control-Allow-Origin响应头,所以决定权永远在提供API的服务方手中。
- Controller类
- @CrossOrigin(origins = "http://domain:8080")
- 全局
- @Bean WebMvcConfigurer addCorsMappings
- registry.allowedOrigins
国际化
- i18n,internationalization
- Spring MVC应用程序通过MessageSource和LocaleResolver,配合View实现国际化。
WebSocket
- 一种基于HTTP的长链接技术
- Java的Servlet规范从3.1开始支持WebSocket,所以,必须选择支持Servlet 3.1或更高规范的Servlet容器,才能支持WebSocket。
- Spring
- 配置Bean @Bean WebSocketConfigurer createWebSocketConfigurer
- new WebSocketConfigurer
- Spring提供了TextWebSocketHandler和BinaryWebSocketHandler分别处理文本消息和二进制消息
- ChatHandler extends TextWebSocketHandler
- 成功建立连接: afterConnectionEstablished()
- 任何原因导致WebSocket连接中断时: afterConnectionClosed
- 每个WebSocket会话以WebSocketSession表示,且已分配唯一ID
异步处理
- Servlet从3.0规范开始添加了异步支持,允许对一个请求进行异步处理。
- Spring
- org.springframework.web.servlet.DispatcherServlet
<async-supported>true</async-supported> </servlet>
- Controller
- Callable
- DeferredResult
- org.springframework.web.servlet.DispatcherServlet
- Filter
<async-supported>true</async-supported> </filter>
- 对应Http,async请求
- 一个声明为支持
<async-supported>
的Filter既可以过滤async处理请求,也可以过滤正常的同步处理请求 - 未声明
<async-supported>
的Filter无法支持async请求
- 一个声明为支持