跳至主要內容

Spring

酷风大约 11 分钟

Spring

介绍

Spring 5.x : JDK >= 1.8 , Tomcat 9.x Spring 6.x : JDK >= 17 , Tomcat 10.x

IoC

  • 容器:是一种为某种特定组件的运行提供必要支持的一个软件环境。

    • 如 Tomcat:Servlet容器,实现TCP连接,解析HTTP协议 等服务
  • Spring核心:IoC容器

    1. 管理所有轻量级的JavaBean组件
      1. 组件的生命周期管理、配置和组装服务
      2. AOP支持
      3. 建立在AOP基础上的声明式事务服务
  • IoC: Inversion of Control,直译为控制反转

    • 问题:系统包含大量的组件,生命周期、依赖关系由自身维护,大大增加了系统的复杂度,导致紧密耦合
  • IoC模式下,控制权发生了反转,从应用程序转移到了IoC容器

  • 组件不再由应用程序自己创建和配置,而是由IoC容器负责

  • 为了能让组件在IoC容器中被“装配”出来,需要某种“注入”机制

    • Service使用DataSource,并不会创建DataSource,等待外部通过setDataSource()方法来注入一个DataSource
  • IoC又称为依赖注入(DI:Dependency Injection),将组件的创建+配置与组件的使用相分离,并且,由IoC容器负责管理组件的生命周期。

    • 因为IoC容器要负责实例化所有的组件,因此,有必要告诉容器如何创建组件,以及各组件的依赖关系。一种最简单的配置是通过XML文件来实现,
  • 依赖注入方式:同时支持属性注入和构造方法注入,并允许混合使用。

  1. set()方法
  2. 构造方法
  • 高度可扩展的,无侵入容器,应用程序的组件无需实现Spring的特定接口
  1. 应用程序组件既可以在Spring的IoC容器中运行,也可以自己编写代码自行组装配置;
  2. 测试的时候并不依赖Spring容器,可单独进行测试,大大提高了开发效率。

装配Bean

  • 通过读取XML文件后使用反射完成
  • ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
    • ApplicationContext: IoC容器的引用
    • getBean()
    • ApplicationContext会一次性创建所有的Bean
  • BeanFactory factory = new XmlBeanFactory(new ClassPathResource("application.xml"));
    • 另一种IoC容器
      • BeanFactory的实现是按需创建

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
  • @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 ·····
  • 切面(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装配规则,容易被强迫装配。
    • 注解(定义注解,基于注解装配AOP)
      • 既简单,又能明确标识AOP装配,是使用AOP推荐的方式
      • 自定义注解,显示的装配,匹配该注解为其实现AOP
  • 避坑

    • 由于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
  • 原理:AOP代理,自动创建Bean的Proxy实现

  • 事务边界

    • 以下事务边界就是addBonus()方法开始和结束
    @Transactional
    public void addBonus(long userId, int bonus) { // 事务开始
       ...
    } // 事务结束
  • 事务传播

    • 事务方法1 调用 事务方法2
      • 默认事务传播级别REQUIRED
        • 事务方法2会检测判定,加入事务方法1 的事务中
    • Spring的声明式事务为事务传播定义了几个级别
      • REQUIRED,它的意思是,如果当前没有事务,就创建一个新事务,如果当前有事务,就加入到当前事务中执行。
      • SUPPORTS:表示如果有事务,就加入到当前事务,如果没有,那也不开启事务执行。这种传播级别可用于查询方法,因为SELECT语句既可以在事务内执行,也可以不需要事务;
      • REQUIRES_NEW:表示不管当前有没有事务,都必须开启一个新的事务执行。如果当前已经有事务,那么当前事务会挂起,等新事务完成后,再恢复执行;
      • ······
  • 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的指定方法处理
  • 应用配置:@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
    • Filter
      • <async-supported>true</async-supported> </filter>
      • 对应Http,async请求
        • 一个声明为支持<async-supported>的Filter既可以过滤async处理请求,也可以过滤正常的同步处理请求
        • 未声明<async-supported>的Filter无法支持async请求

参考

Spring开发 - 廖雪峰的官方网站open in new window
Spring基础 - Spring核心之面向切面编程(AOP)open in new window