[笔记]SpringBoot实战(4)-Spring Boot Web&Data

系列笔记:

Spring Boot的 Web 开发

spring-boot-autoconfigure-x.x.x.jarorg.springframework.boot.autoconfigure.web包下,是与Web相关的自动配置类:

  • ServerPropertiesAutoConfiguration(SB2已找不到该类)和ServerProperties自动配置内散Servlet容器;
  • HttpEncodingAutoConfiguration 和 HttpProperties(SB2)/HttpEncodingProperties(SB1) 用来自动配置http 的编码;
  • MultipartAutoConfiguration和MultipartProperties用来自动配置上传文件的属性;
  • JacksonHttpMessageConvertersConfiguration用来自动配置mappingJackson2HttpMessageConverter 和 mappingJackson2XmlHttpMessageConverter ;
  • WebMvcAutoConfiguration 和 WebMvcProperties 配置 Spring MVC。

模板引擎

JSP 在内嵌的Servlet容器上运行有一些问题(内嵌Tomcat、Jetty不支持以jar形式运行JSP,Undertow不支持JSP)。

SpringBoot 提供了大量模板引擎,推荐使用 Thymeleaf,因为它提供了完美的SpringMVC支持。
Thymeleaf 是一个类库,它是一个 xml/xhtml/html5 的模板引擎,可以作为MVC的web应用的View层。还提供了额外的模块与SpringMVC集成,可以完全替代JSP。

读者个人的springboot2-learning.webflux项目有Thymeleaf的简单使用示例。

在Spring MVC中,若我们需要集成一个模板引擎的话,需要定义 ViewResolver,而 ViewResolver 需要定义一个VIew。
Thymeleaf 给我们提供了一个 SpringTemplateEngine 类,用来驱动在SpringMVC下使用Thymeleaf模板引擎,两外还提供了一个TemplateResolver用来设置通用的模板引擎(包含前缀、后缀等)。

而在SpringBoot下集成更简单,通过org.springframework.boot.autoconfigure.thymeleaf包下的ThymeleafAutoConfiguration类对集成所需要的Bean进行自动配置,包括 templateResolver、templateEngine和thymeleafViewResolver的配置。
通过 ThymeleafProperties 来配置 Thymeleaf,在application.proeprties 中,以spring.thymeleaf.xxx来配置(前文所述项目中有完整配置示例)。

Spring Boot Web自动配置

如果SpringBoot提供的SpringMVC默认配置不符合你的需求,则可以通过一个配置类(注解有@Configuration的类)加上@EnableWebMvc注解来实现完全自己控制的MVC配置。
当然,通常情况下,Spring Boot的自动配置是符合我们大多数需求的。在你既需要保留Spring Boot 提供的便利,又需要增加自己的额外的配置的时候,可以定义一个配置类并继承WebMvcConfigurerAdapter,无须使用@EnableWebMvc注解, 然后按照Spring MVC的配置方法来添加SpringBoot为我们所做的其他配置。

例如:

@Configuration
public class WebMvcConfig  extends WebMvcConfigurerAdapter {  
     @Override
       public void addViewControllers(ViewControllerRegistry registry) {
           registry.addViewController("/xx").setViewName("/xx");
       }
}

具体需要参考 书本 7.3 章
。。。。。。。。。。。。。。。。。。。。。。。。待后续补充

Websocket

具体需要参考 书本 7.6 章 Websocket, 广播、点低点传播、、 。。。。。。。。。。。。。。。。。。。。。。。。待后续补充

现代web应用

现代的B/S系统软件有下面几个特色:

  • 1.单页面应用

    单页面应用(singl-page application,简称SPA)指的是一种类似于原生客户端软件的更流畅的用户体验的页面。在单页面应用中,所有的资源(HTML、Javascript、CSS)都是按需动态加载到页面上的,且不需要服务端控制页面的转向。

  • 2.响应式设计

    响应式设计(Responsive web design,简称RWD)指的是不同的设备(电脑、平板、手机)访问相同的页面的时候,得到不同的页面视图,而得到的视图是适应当前屏幕的。当然就算在电脑上,我们通过拖动浏览器窗口的大小,也能得到合适的视图。

  • 3.数据导向

    数据导向是对于页面导向而言的,页面上的数据获得是通过消费后台的REST服务来实现的,而不是通过服务器渲染的动态页面(如JSP)来实现的,一般数据交换使用的格式是JSON。

Spring Boot的数据访问

Spring Data项目是Spring 用来解决数据访问问题的一揽子解决方案,Spring Data是一个伞形项目,包含了大量关系型数据库及非关系型数据库的数据访问解决方案。SpringData使我们可以快速且简单地使用普通的数据访问技术及新的数据访问技术。

Spring Data 为我们使用统一的API来对各种数据存储技术进行数据访问操作提供了支持。这是Spring通过提供Spring Data Commons项目来实现的,它是上述各种SpringData项目的依赖。Spring Data Commons 让我们在使用关系型或非关系型数据访问技术时都使用基于Spring的统一标准,该标准包含CRUD(创建、获取、更新、删除)、查询、排序和分页的相关的操作。

此处介绍下 Spring Data Commons 的一个重要概念: Spring Data Repository 抽象。使用Spring Data Repository 可以极大地减少数据访问层的代码。既然是数据访问操作的统一标准,那肯定是定义了各种各样和数据访问相关的接口,Spring Data Repository 抽象的根接口是Repository接口。

package org.springframework.data.repository;  
@Indexed
public interface Repository<T, ID> {  
}

从源码可以出,它技术领域类(JPA为实体类)和领域类的id类型作为类型参数。
它的子接口 CrudRepository 定义了和CRUD操作相关的内容。
CrudRepository 的子接口 PagingAndSortingRepository 定义了与分页和排序操作相关的内容。
不同的数据访问技术也有不同的Repository,如JpaRepository、MongoRepository。

还允许根据属性名进行计数、删除、查询方法等操作。

引入Docker

Docker是一个轻量级容器技术,类似于虚拟机技术(xen、kvm、vmware、virtualbox)。是直接运行在当前操作系统(Linux)之上,而不是运行在虚拟机中,但是也实现了虚拟机技术的资源隔离,性能远远高于虚拟机技术。

Docker 支持将软件编译成一个镜像(image),在这个镜像里做好对软件的各种配置,然后发布这个镜像,使用者可以运行这个镜像,运行中的镜像称之为容器(container),容器的启动是非常快的,一般都是以秒为单位。这个有点像我们平时安装ghost操作系统?系统安装好后软件都有了,虽然完全不是一种东西,但是思路是类似的。

Spring Data JPA、Spring Data REST

暂时不考虑,
。。。。。。。。。。。。。。。。。。。。。。。。待后续补充

声明式事务

所有的数据访问技术都有事务处理机制,这些技术提供了API用来开启事务、提交事务来完成数据操作,或者在发生错误的时候回滚数据。

而Spring的事务机制是用统一的机制来处理不同数据访问技术的事务处理。Spring 提供了一个 PlatformTransactionManager 接口,不同的数据访问技术的事务使用不同的接口实现。

Spring 支持声名式事务,即使用注解来选择需要使用事务的方法,它使用@Transactional注解(来自org.springframework.transaction.annotation包)在方法上表明该方法需要事务支持。这是一个基于AOP的实现操作,被注解的方法在被调用时,Spring 开启一个新的事务,当方法无异常运行结束后,Spring会提交这个事务。

Spring 提供了一个@EnableTransactionManagement注解在配置类上来开启声明式事务的支持。使用后,Spring 容器会自动扫描注解 @Transactional 的方法和类。

关于Spring事务隔离级别的介绍,可以查看Spring事务——事务的隔离级别一文。

@Transactional 不仅可以注解在方法上,也可以注解在类上。当注解在类上的时候意味着此类的所有public方法都是开启事务的。如果类级别和方法级别同时使用了@Transactional注解,则使用方法级别注解覆盖类级别注解。

Spring Data JPA 对所有的默认方法都开启了事务支持,且查询类事务默认启用 readOnly=true属性。

Spring 事务基于AOP实现

@Transactional 自调用失效问题,Spring 数据库事务实现原理是AOP,而AOP的原理是动态代理,在自调用过程中,是类自身的调用,而不是代理对象去调用,那么就不会产生AOP,这样Spring就不能把代码织入到约定的流程中。解决办法:1.使用一个service去调用另一个service,这样就是代理对象的调用;2.从Spring IoC容器中获取代理对象去启用AOP。

Spring 异常处理

默认 Spring 事务只在发生未被捕获RuntimeExcetpion时才回滚。

Spring AOP 异常捕获原理:@Transactional,它的切入点(PointCut)其实就是抛异常,在抛出异常的时候调用增强处理(Advice)中的方法将事务回滚掉,但是这个抛异常抛的不是普通的自Exception中继承过来的异常(unchecked exception 回滚)。也就是默认对RuntimeException()异常或是其子类进行事务回滚。虽然RuntimeException继承自Exception,但是切入点要更具体一些(直接抛出Exception默认是不会回滚的)。

被拦截的方法需显式抛出异常,并不能经任何处理,这样 AOP 代理才能捕获到方法的异常,才能进行回滚,默认情况下 AOP 只捕获RuntimeExcetpion的异常。但可以通过配置来捕获特定的异常并回滚。

当然如果要让所有 Exception 都回滚,在@Transactional(rollbackFor = Exception.class)上加个参数就好了。但是要注意的是:如果声明了@Transactional,但是又在方法里面自己捕获了异常,也就是try catch掉了,那就不会回滚了,因为切入点根本没捕获到,也谈不上调用增强处理中的方法了。

  • 解决方案1:例如service层处理事务,那么service中的方法中不做异常捕获,或者在catch语句中最后增加throw new RuntimeException()语句,以便让 AOP 捕获异常再去回滚,并且在service上层(webservice客户端,view层action)要继续捕获这个异常并处理。
  • 解决方案2:抛出 Exception,同时在事务声明中加上@Transactional(rollbackFor = Exception.class)
  • 解决方案3:在service层方法的catch语句中增加:TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();语句,手动回滚,这样上层就无需去处理异常。

Spring Boot 的事务支持

自动配置的事务管理器:
在使用JDBC作为数据访问技术的时候,SpringBoot为我们定义了PlatformTransactionManager的实现DataSourceTransactionManager的Bean,配置见 org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration类。
在使用JPA作为数据访问技术的时候,SpringBoot为我们了定义一个Platform TransactionManager 的实现 JpaTransactionManager 的 Bean,配置见 org.springframework.boot.autoconfigure.orm.jpa.JpaBaseConfiguration类。

自动开启注解事务的支持:
Spring Boot专门用于配置事务的类为:org.springframework.boot.autoconfigure.transaction.TransactionAutocomfigurating,此配置类依赖于JpaBaseConfiguration和DataSourceTransactionManagerAutoConfiguration。而在DataSourceTransactionManagerAutoConfiguration配置里还开启了对声名式事务的支持。

所以在Spring Boot中,无需显式开启使用@EnableTransactionManagement注解。

数据缓存 Cache

Spring定义了org.springframework.cache.CacheManagerorg.springframework.cache.Cache接口用来统一不同的缓存的技术。其中,CacheManager是Spring提供的各种缓存技术抽象接口,Cache 接口包含缓存的各种操作(增加、删除、获得缓存,我们一般不会直接和此接口打交道)。
针对不同的缓存技术,需要实现不同的CacheManager,在使用任意一个实现的CacheManager的时候,需注册实现的CacheManager的Bean。

Spring提供了4个注解来声明缓存规则(使用了注解式AOP)。
@Cacheable、@CachePut、@CacheEvit都有value属性,指定的是要使用的缓存名称;key属性指定的是数据在缓存中存储的键。

开启声明式缓存支持十分简单,只需在配置类上使用@EnableCaching注解即可。

Spring Boot的缓存支持

在Spring中使用缓存技术的关键是配置CacheManager,而Spring Boot为我们自动配置了多个CacheManager的实现。具体使用时只需在项目中导入想换缓存基础的依赖包,并在配置类使用@EnableCaching开启缓存支持即可。

Spring Boot 支持以spring.cache为前缀的属性来配置缓存(对应的安全配置类为CacheProperties)。

Spring Boot2 开始默认本地缓存为 Caffeine,废弃了Guava Cache,甚至已经移除相关的自动配置类。Caffeine 自动配置类为:CaffeineCacheConfiguration

Spring Boot 对redis的支持

Spring 对Redis的支持是通过Spring Data Redis 来实现的(只对Redis 2.6和2.8版本作过测试)。提供了连接相关的 ConnectionFactory 和 数据操作相关的 RedisTemplate。例如:

  • JedisConnectionFactory
  • JredisConnectionFactory (已不推荐使用)
  • LettuceConnectionFactory: (Lettuce性能优于jedis,官方推荐的,SpringBoot 默认使用的)
  • SrpConnectionFactory:(Spullara/redis-protocol) (已不推荐使用)

    lettuce与jedis的区别:

    • 使用jedis:当多线程使用同一个连接时,是线程不安全的。所以要使用连接池,为每个jedis实例分配一个连接。
    • 使用Lettuce:当多线程使用同一连接实例时,是线程安全的。

Spring Data Reids 提供了RedisTemplate(默认使用JdkSerializationRedisSerializer序列化,二进制形式不直观)和StringRedisTemplate(默认使用StringRedisSerializer序列化)两个模板来进行数据操作,其中StringRedisTemplate只针对键值都是字符型的数据进行操作。提供的主要数据访问方法:

  • opsForValue(): 操作只有简单属性的数据
  • opsForList(): 操作含有list的数据
  • opsForSet(): 操作含有set的数据
  • opsForZSet(): 操作含有ZSet(有序的set)的数据
  • opsForHash(): 操作含有hash的数据

可以这么使用:

@Repository
public class UserDao {  
    // Spring Boot 已自动配置,可直接注入
    @Autowired
    StringRedisTemplate stringRedisTemplate;
    @Autowired
    RedisTemplate<Object, Object> redisTemplate;
    // 使用@Resource注解指定stringRedisTemplate,可注入基于字符串的简单属性操作方法
    @Resource(name="stringRedisTemplate")
    ValueOperations<String,String> valOpsStr; 
    // 使用@Resource注解指定redisTemplate,可注入基于对象的简单属性操作方法
    @Resource(name="redisTemplate")
    ValueOperations<Object, Object> valOps; //4
    public void stringRedisTemplateDemo() {
        valOpsStr.set("xx", "yy");
    }
    public String getString() {
        return valOpsStr.get("xx");
    }
    public void save(User u) {
        valOps.set(u.getId(), u);
    }
    public User getUser() {
        return (User) valOps.get("1");
    }
}

RedisAutoConfiguration 为我们默认配置了LettuceConnectionConfigurationJedisConnectionConfiguration(需要引入依赖)、RedisTemplate、StringRedisTemplate等,让我们可以直接使用Redis作为数据存储。

Spring Boot 支持以spring.redis为前缀的属性来配置Redis(对应的安全配置类为RedisProperties)。

SpringBoot 自动配置的RedisTemplate使用的是JdkSerializationRedisSerializer序列化,其使用二进制形式存储数据,对于RedisClient查看很不直观,我们可以最积极配置RedisTemplate并定义Serializer。

import com.fasterxml.jackson.annotation.JsonAutoDetect;  
import com.fasterxml.jackson.annotation.PropertyAccessor;  
import com.fasterxml.jackson.databind.ObjectMapper;  
import org.springframework.boot.SpringApplication;  
import org.springframework.boot.autoconfigure.SpringBootApplication;  
import org.springframework.context.annotation.Bean;  
import org.springframework.data.redis.connection.RedisConnectionFactory;  
import org.springframework.data.redis.core.RedisTemplate;  
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;  
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.net.UnknownHostException;

/**
 * 简单redis使用示例
 */
@SpringBootApplication
public class Springboot2RedisApplication {

    public static void main(String[] args) {
        SpringApplication.run(Springboot2RedisApplication.class, args);
    }

    /**
     * 覆盖 SpringBoot reids 默认使用的序列化器(默认的为二进制,对可视化不友好)
     */
    @Bean
    @SuppressWarnings({"rawtypes", "unchecked"})
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory)
            throws UnknownHostException {
        RedisTemplate<Object, Object> template = new RedisTemplate<Object, Object>();
        template.setConnectionFactory(redisConnectionFactory);

        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        // 设置值(value)的序列化采用 Jackson2JsonRedisSerializer
        template.setValueSerializer(jackson2JsonRedisSerializer);
        // 设置值(key)的序列化采用 StringRedisSerializer
        template.setKeySerializer(new StringRedisSerializer());

        template.afterPropertiesSet();
        return template;
    }
}