Spring 开发基础


这是一篇面向Ruby语言编程者的培训文档,主要介绍Spring框架的基本概念和日常开发过程中的应用。

Spring

Spring是一款用于Java应用开发的开源框架,特点是设计灵活,主要包括7个可自由搭配的组件:

  1. SpringCore:核心容器提供Spring框架的基本功能,包括Bean管理、公用工具和字节码增强工具等。核心容器的主要组件是BeanFactory,使用控制反转(IOC)模式将应用程序的配置和依赖性规范与实际的应用程序代码分开。新的版本,SpringCore和SpringBean分开,使各自职责更单一。
  2. SpringContext:Spring上下文是一个配置文件(applicationContext.xml),向Spring框架提供上下文信息。核心接口是ApplicationContext,基于此接口,可向所有模块提供诸如读取配置文件的实现、读取BeanFactory、推送Spring事件等能力。
  3. SpringAOP:通过配置管理特性,AOP(Aspect Oriented Programming)模块提供面向切面的编程的能力,以解决诸如日志记录、权限验证、事务控制、错误处理等相对固定、独立的问题。使用者只需要通过定义切面(@Aspect)、切点(@PointCut)和行为(@Before@After@AfterThrowing等)三个概念完成对代码的织入以达到动态增强的效果。
  4. SpringDAO:DAO提供了数据访问层的封装,可用该结构来管理异常处理和不同数据库抛出的错误消息。异常层次结构简化了错误处理,并且极大地降低了需要编写的异常代码数量。
  5. SpringORM:ORM层提供了对象/关系映射工具,JPA、Hibernate、MyBatis等框架都可以基于此模块集成到Spring,所有这些都遵从Spring的通用事务(spring-tx)和DAO异常层次结构。
  6. SpringWeb:Web包提供了基础的针对Web开发的集成特性,例如多方文件上传,WebApplicationContext初始化等。基于Servlet API,SpringWeb可以集成Struts等框架。
  7. SpringMVC:MVC框架是一个全功能的构建Web应用程序的MVC实现。通过可自由接入的Interceptor、Filter,以及Databind等接口,其核心类DispatcherServlet,可以实现url映射、请求拦截及数据绑定(参数数据反序列化、字段校验)等任务。controller完成处理后,通过ModelAndView进行返回数据处理。

spring_modules

spring-mvc

SpringBoot

SpringBoot是基于Spring框架搭建的一个,以快速构建、独立运行为目的应用构建框架。它和Spring框架的最大区别是,Spring是一款为编写应用设计的框架,而SpringBoot是为构建应用设计的框架。它更多是提供各类灵活可用的构建工具,如基于main函数的web应用启动器、内嵌的tomcat容器等。虽然我们大部分情况下都是构建Web应用,但SpringBoot并不是只为Web设计的,它可以作为一个单纯的计算应用,可以作为一个文件处理应用。使用什么样的组件,取决于用户需要SpringBoot构建什么样的应用。基于这种小而精的理念,SpringBoot在构建微服务方面尤其受欢迎。

springboot-spring

SpringBoot在需要使用这些具有不同功能的模块时,不需要使用者自己完成各个模块的加载,而是会根据starter完成自动加载。因为SpringBoot启动时,会加载classPath和extLibrary下jar包的META-INF文件夹,读取spring.factories文件,并根据声明项读取相关的类完成ApplicationContext的初始化。根据这个特点,我们也可以实现自己的starter。 springboot-starter

SpringCloud

SpringCloud是基于SpringBoot的一个分布式微服务架构。每个用户自实现的SpringBoot可以提供Web服务,而SpringCloud框架则在SpringBoot的基础上,根据配置,提供服务网关(ZUUL)、配置中心(Config)、服务发现(Eureka)、负载均衡(Ribbon)、降级熔断(Hystrix)、RPC调用(Feign)等功能。

spring-cloud

Web项目中常用Spring组件

Spring框架集成了许多通用组件,只需要简单的配置就可以使用。SpringBoot项目下,配置文件默认为application.yml,与Spring框架相关的通用配置项在spring配置下,不同环境通过application-{env}.yml这样的配置文件区别。application.yml同时可以代替掉传统的各类properties配置文件,但如果配置项太多,还是建议单独写配置文件。

  1. 开发通用:RestTemplate、RedisTemplate、MongoTemplate、ScheduledTask、KafkaClient等
  2. Mybatis/Mybatis-Plus:ORM实现框架,配置项写在mybatis/mybatis-plus下,核心配置项是mapper-locations,指定mapper文件位置
  3. 日志组件:springboot默认使用logback框架作为日志组件,它是一个slf4j的实现。默认配置文件为logback-spring.xml,主要需要设定日志appender,分ConsoleAppenderRollingFileAppender两种
  4. 测试组件:除了传统Java程序的Junit框架,SpringBoot在此基础上提供了SpringBootTest,来提供单元测试使用的容器环境,我们的项目采用的这种方式。测试提供的容器和应用运行时的容器没有区别,如果不希望在测试时形成真实的数据库变更,有时需要配合一些错误或Mock工具完成测试。常用的Mock工具有EasyMock、Mockito等

IOC和DI

IOC(Inversion of Control),控制反转是软件设计中的一种方法论,也是在Java开发群体中被广泛认可的提倡的一种设计。而DI,在Java的演变历史中,是IOC的一种具体实现。DI(Dependency Injection)的核心是,如果A依赖B,则由B自己完成实例化,并通过setter或constructor的方式,为A提供自己的实例。

Spring的IOC容器实现是一个Map<String, BeanDefinition>,key通过beanName唯一标识一个Bean实例,而value则描述一个可实例化的BeanDefinition对象接口。SpringBoot项目启动,会对ApplicationContext/WebApplicationContext进行初始化,由context根据配置文件或Bean注解,去扫描所有应该创建的实例,然后循环解决每个实例间的依赖,直到找到无依赖或相互依赖的Bean,然后反射创建对象,再通过setter/constructor完成依赖注入。如果对源码感兴趣,可以关注AbstractApplicationContext(可视作容器,获取bean通过此类调用)、AbstractBeanFactoryDefaultListableBeanFactory (Map容器本身)、DefaultSingletonBeanRegistry(解决循环依赖,完成实例化)。

常用注解

Spring从2.0开始,提供各类注解来简化配置,开发中常用的注解有如下一些:

  1. @Bean@Component@Service@Repository:这几个注解都是标识一个类是一个由IOC容器创建、管理的Bean,通常@Bean@Component用来标识一些普通的Bean或提供公共能力的组件,@Service标识业务处理的服务类,@Repository标识数据访问类
  2. @Autowired@Resource@Value:将受容器管理的Bean注入到当前类中。@Autowired自动根据对象类型注入,@Resource默认根据名称注入,@Value用来注入配置文件中的属性。只有注入对象是受IOC管理的Bean才会生效。
  3. @Configuration:标识配置类,可用于代替xml文件。容器初始化时会去扫描配置类下所有方法,并将声明的Bean注册到容器中
  4. @Controller@RestController:标识Http请求的处理类,@RestController是在@Controller的基础上,增加了@ResponseBody,使得此注解下所有方法的返回结果会被解析为json格式
  5. @RequestMapping@RequestParam@PathVariable@RequestBody:Http请求的解析注解,@RequestMapping标识uri,@RequestParam绑定query参数,@PathVariable绑定路径参数,@RequestBody绑定请求体。注解在不指定名称的情况下,默认使用参数名称绑定。如果不使用注解,SpringMVC会尝试自动根据参数的名称和数据类型进行绑定
  6. @Entity@Table@Column@Id:ORM相关注解,分别标识实体、数据表、列、主键,用来解决实体和实际表间的映射问题
  7. @Query@Select@Update@Delete:注解式声明DAO查询语句,免去xml编写
  8. @Transactional:声明Spring事务的注解,利用AOP在修饰代码块的数据库访问中添加事务,以提供事务提交和回滚的能力
  9. @Async:配合线程池配置使用,可以利用线程池内的线程启动异步任务
  10. @Aspect@PointCut:声明切面、切点,实现AOP
// 容器初始化时,加载@Configuration,代替配置文件
@Configuration
// 引入其它配置文件,其它文件只需要声明配置项内容,不需要添加@Configuration
@Import({GlobalConfig.class, RedisConfig.class})
public class Config {
    // 读取配置文件中的 spring.application.name 属性值,注入到name字段中
    @Value("${spring.application.name}")
    String name;

    @Bean
    RestTemplate restTemplate(){
        // 初始化一个名为restTemplate的bean
        return new RestTemplate();
    }
}

Spring中的Bean

Bean这个词在Java相关的资料中可能随处可见,我们不再去深究它的起源和演变,我们只说说什么是标准的Spring中的Bean。Spring中的Bean是可以交给IOC容器创建、管理、销毁的Java实例,因此它需要具备以下特性:

  1. 无状态,或者说状态都由容器管理。Bean的所有可变属性应该由@Autowired@Resource@Value的方式注入,如果需要定义可被业务代码修改的属性,必须考虑线程安全问题
  2. Bean有单例、原型两种常见模式,默认为单例。Web场景下,还有Request、Session、GlobalSession模式,对应Bean的生命周期为单次请求、Session范围和全局Session范围
  3. Bean通过name属性唯一标识,通常情况下,Bean的类型可以做到这件事,但如果同一个类有多个实现时,必须指定Bean的名字

Spring数据库连接池和事务

数据库连接池随应用启动而创建并完成初始化,每个线程在访问数据库时都向连接池申请连接,完成连接和线程的绑定,使用结束再归还。基于常见的Web应用线程模型(如tomcat),每个http请求创建一个新的线程去执行。在线程调用到有@Transactional注解修饰的代码块,Spring执行事务增强。事务切面会从ThreadLocal对象中,获取当前线程的连接(如果已完成绑定),或从连接池申请一个连接并写入到当前线程ThreadLocal对象以完成连接和线程绑定,之后执行事务开始语句。如果线程的调用链中,存在多个@Transactional,Spring会根据其传递性插入不同的语句。根据这个模型,如果线程的调用链中新起了一个线程,新起的线程会获取一个新的连接并开启新的事务去完成自己的数据库操作,所以“主”线程的提交、回滚等操作对其“子”线程没有任何影响。

Spring的事务是基于AOP实现的,而AOP的默认实现是JDK的Proxy。Proxy实现动态代理时,只能增强接口,所以有以下限制:

  1. @Transactional只对public函数生效,因为接口声明的函数都是public的
  2. 只能通过外部调用触发事务,同一个类内部互相调用,不能触发事务
  3. 未处理和指定声明的异常才能触发回滚

Spring事务传递性,TransactionDefinition:

  • PROPAGATION_REQUIRED:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务
  • PROPAGATION_REQUIRES_NEW:创建一个新的事务,如果当前存在事务,则把当前事务挂起
  • PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行
  • PROPAGATION_NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起
  • PROPAGATION_NEVER:以非事务方式运行,如果当前存在事务,则抛出异常
  • PROPAGATION_MANDATORY:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常
  • PROPAGATION_NESTED:如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价PROPAGATION_REQUIRED

一次简单的CURD开发流程

Controller:用于接收和处理HTTP请求,使用@Controller@RestController修饰,使用@RequestMapping指定uri。Controller在SpringMVC中默认是单例实现的,即所有请求到来时,调用到的是同一个对象的方法。因此Controller中,不被Spring容器管理的属性不是线程安全的,如果有必要增加这些属性,需要考虑其线程安全问题。

Service:主要用于处理复杂的业务逻辑,向上为Controller层提供服务,向下调用DAL层的接口完成数据操作,事务处理通常也在这一层。使用@Service修饰,需要声明一个接口xxxService供上层调用,再编写一个实现类xxxServiceImpl。

DAL/Repository:完成数据库增删查改实现,向上为Service层提供服务。使用@Repository修饰,需要声明一个接口xxxDAO/xxxRepository,再编写实现类xxxDAOImpl。

ExceptionHandler:SpringWeb提供@ExceptionHandler@ControllerAdvice来协助完成全局异常处理。Java提供异常处理机制,在编码过程中,通常我们都需要一个随处可用的异常来使得程序及时感知一些不符合逻辑或未知错误,RuntimeException是一个更好的选择,因为它不像Exception一样,需要显式处理。基础架构组提供了BizException来统一处理,如果编码过程中发现这个类不能满足需求,我们也可以自己定义一些继承自RuntimeException的异常类,并使用@ExceptionHandler声明对此类异常的处理流程。

可能Service层和DAL层,每个组件都要先声明一个接口再完成实现类的方式大家不太明白为什么要这样做,这是硬性要求吗?对此我是这样理解的:

  1. Spring框架的AOP是基于Proxy的,而JDK的Proxy需要基于接口实现动态代理。所以不声明接口的话,Spring的许多特性,比如事务等,需要使用其它方式实现。
  2. 这不是硬性要求,但是这是提倡的做法。稍大一点的项目,在编程过程中,先约定好interface,再做实现可以很好的切分职责,独立开发。比如A、B两个后端同学协作开发一个项目,A对B负责的领域不太熟悉,这时候先让B在自己的领域服务上提供一个接口定义,A在有此接口定义的情况下可以自由开发、测试,不会因为没有实现导致自己开发过程中的编译错误。
  3. 其实第二点说的核心,就是面向接口编程的思想。无论是前后端交互,还是后端服务间交互,甚至一个项目中不同模块间的交互,所有的约定,在Java中都可以看作一个接口。有的RPC框架,如阿里的HSF,就是基于接口声明完成调用的。此外,接口因为其轻量的特性,还可以单独提取出来发布成二方包,供其它人使用。

© Cheng all right reserved,powered by GitbookModified At: 2021-12-10 14:58:15

results matching ""

    No results matching ""