深入浅出SpringBoot笔记

Spring Boot的优点

  • 创建独立的Spring应用程序
  • 嵌入式Tomcat、Jetty或者Undertow,无须部署war文件
  • 允许通过Maven来根据需要获取starter
  • 尽可能的自动配置Spring
  • 提供指标、健康检查和外部配置
  • 绝对没有代码生成,对XML没有要求配置

Spring Boot的参数配置优先级

1. 命令行参数
2. 来自java:comp/env的JNDI属性
3. Java系统属性(System.getProperties())
4. 操作系统环境变量
5. Randowm ValuePropertySource配置的 random.*属性值 
6. jar包外部的application-{profile}.properties或application.yaml 带(spring.profile)配置文件
7. jar包内部的application-{profile}.properties或application.yaml 带(spring.profile)配置文件
8. jar包外部的application-{profile}.properties或application.yaml 不带(spring.profile)配置文件
9. jar包内部的application-{profile}.properties或application.yaml 不带(spring.profile)配置文件


修改springMVC的视图解析地址
spring:
  mvc:
    view: 
      prefix: /MATE-INF/pages/
      suffix: .jsp 

全注解下的Spring Ioc

Spring使依赖的两个核心理念,一个是控制反转(Inversion of Control,IOC),另一个是面向切面编程(Aspect Oriented Programming,AOP)

Ioc容器简介

SpringIoc容器是一个管理Bean的容器,在Spring的定义中,它要求所有的Ioc容器都需要实现接口BeanFactory,它是一个顶级容器接口。 org.springframework.beans.factory.BeanFactory

由于BeanFactory的功能还不够强大,因此Spring在BeanFactroy的基础上,还设计了一个更为高级的接口ApplicationContext,它是BeanFactory的子接口之一,在Spring的体系中BeanFactroy和ApplicationContext是最为重要的接口设计。org.springframework.context.ApplicationContext
深入浅出SpringBoot笔记_第1张图片
ApplicationContext接口通过继承上级接口,进而继承BeanFactory接口,但是在BeanFactory的基础上,扩展了消息国际化(MessageSource)环境可配置接口(EnvironmentCapable)应用事件发布接口(ApplicationEventPublish)和资源模式解析接口(ResourcePatternResolver),所以它的功能强大。

@ComponentScan包扫描注解

几个重要的属性
1. basePackages:定义扫描的包名,在没有定义的情况下,它会扫描当前包和其子包下的路径。
2. basePackageClasses:定义扫描的类
3. includeFilters: 定义满足过滤器(Filter)条件的Bean才去扫描
4. excludeFilters:排除过滤器条件的Bean
注意: 3和4他们都需要通过一个注解@Filter去定义,它有一个type类型,这里可以定义为注解或者正则式等类型。

设置不被加载到IOC容器的bean,这样被Service注解了的类将不会被注册到IOC容器,
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Service;

@Configuration
@ComponentScan(excludeFilters = {
     @ComponentScan.Filter(classes = {
     Service.class})})

定义第三方的Bean

现实的Java应用往往需要引入许多来自第三方的包,并且很有可能希望把第三方包的类对象已放入到Spring Ioc容器中,这时@Bean注解就可以发挥作用了。
如果是第三方的类我们需要导入到IOC容器就使用@Bean注解,如果是自己写的类就用@Component注解等

依赖注入

  • @Autowried:它注入的机制最基本的一条是根据类型(by type),如果有一样的类型在按名称
  • @Quelifier:它将与@Autowried组合在一起,通过类型和名称一起找到Bean

带有参数的构造方法类的装配

@Component
public class User {
     
    
    private NameDynAnyPair nameDynAnyPair;

    public User(@Autowired @Qualifier("nameDynAnyPair") NameDynAnyPair nameDynAnyPair){
     
        this.nameDynAnyPair = nameDynAnyPair;
    }
}

生命周期

大致分为Bean的定义、Bean的初始化、Bean的生存期、Bean的销毁4个部分

  • Spring通过我们的配置,如@ComponetScan定义的扫描路径去找到带@Component的类,这个过程就是一个资源定位的过程。
  • 一旦找到了资源,那么它就开始解析,并且将定义的信息保存起来。注意,此时还没有初始化Bean,也就没有Bean的实例,它有的仅仅是Bean的定义
  • 然后就会把Bean定义发布到Spring Ioc容器中。此时,Ioc容器也只有Bean的定义,还是没有Bean的实例生成。
    深入浅出SpringBoot笔记_第2张图片
    完整流程 注意:只有实现了ApplicationContext接口的才能调动setApplicationContext方法
    深入浅出SpringBoot笔记_第3张图片

条件装配Bean

当配置文件中不包含某些配置的情况下,将不装配当前这个类到IOC容器中。

  1. 实现Condition接口,并且获取到配置文件将需要的配置获取到如果没有则不通过
  2. 在@Conditional中添加上实现类的Class
public class dataSourceCondition implements Condition {
     
    @Override
    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
     
    	// 获取到配置文件,并且判断是否符合需求
        Environment environment = conditionContext.getEnvironment();
        return environment.containsProperty("datasource.password") && environment.containsProperty("datasource.username");
    }
    
    
    // 当dataSourceCondition返回的是true才可以被装配
    @Conditional(dataSourceCondition.class)
    public DataSource getDataSource(){
     
        return new DataSource();
    }
}

Bean的作用域

在BeanFactory接口可以看到isSingleton和isPrototype两个方法,isSingleton如果返回true,则Bean在Ioc容器中以单例存在,这也是Spring Ioc容器的默认值;如果isPrototype方法返回true,则当我们每次获取Bean的时候,Ioc容器都会创建一个新的Bean,这显然存在很大的不同,这便是Spring Bean的作用域问题。

作用域类型 使用范围 作用域描述
singleton 所有Spring应用 默认值,Ioc容器只存在单例
prototype 所有Spring应用 每次从Ioc容器获取出来一个Bean,则创建一个新的Bean
session Spring Web应用 Http会话
application Spring Web应用 Web工程生命周期
request Spring Web应用 Web工程单次请求(request)

使用@scop来标准某个Bean使用哪种作用域

import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class dd{
     
....
}

/**		// 可以使用的枚举类型
	 * @see ConfigurableBeanFactory#SCOPE_PROTOTYPE
	 * @see ConfigurableBeanFactory#SCOPE_SINGLETON
	 * @see org.springframework.web.context.WebApplicationContext#SCOPE_REQUEST
	 * @see org.springframework.web.context.WebApplicationContext#SCOPE_SESSION
	 * @see #value
	 */

使用@Profile

// 如何使用
@Profile({
     "dev"})
public class test{
     

	@Profile("{test2}")
	public void t(){
     
	}
}

在Spring中存在两个参数可以提供给我们配置,以修改启动Profile机制,一个是spring.profiles.active,另一个是spring.profiles.default。在这两个属性都没有配置的情况下,Spring将不会启动Profile机制,这就意味着被@Profile标注的Bean将不会被Spring装配到Ioc容器中。
注意:如果是这样配置需要在给虚拟机配置:-Dspring.profiles.active=dev

按照Spring Boot的规则,假设把选项-Dspring.profiles.active配置的值记为{profile},则它会用application-{profile}.properties文件去替代原来默认的application.properties文件,然后启动SpringBoot程序

引入XML配置Bean

使用注解@@ImportResource

@Configuration
@ComponentScan(excludeFilters = {
     @ComponentScan.Filter(classes = {
     Service.class})})
@ImportResource({
     "classpath:Mybatis.xml"})
public class ConfigBean {
     
}

使用Spring EL

为了更加灵活,Spring还提供了表达式语音Spring LE。通过Spring EL可以拥有更为强大的运算规则来更好的配置Bean

最常用的当然是读取属性文件的值
@Value中的${
     ...}代表占位符,它会读取上下文的属性值装配到属性中
@Value("${user.name}")
private String name;



这里采用#{
     ...}代表启用Spring表达式,它将具有运算的功能
T(...)代表的是引入类
.currentTimeMillis() 是它的静态方法,也就是我们调用一次该方法为这个属性赋值。
@Value("#{T(java.lang.System).currentTimeMillis()}")
private Long initTime;


# 使用Spring EL赋值
@Value("#{'字符串'}")
private String str = null;

@Value("#{9.3E3}")
private Double d;

@Value("#{3.14}")
private  float pi;

有时候还可以获取其他Spring Bean的属性来给当前的Bean属性赋值
注意:这里的beanName是Spring Ioc容器的Bean名称ID,str是其属性,代表引用对应的Bean的属性给当前属性赋值。
@Value("#{beanName.str}")
private String otherBeanProp ;

Spring Aop

Aop术语

  • 连接点(join point): 对应的是具体被拦截的对象,因为Spring只能支持方法,所以被拦截的对象往往就是指特定的方法。Aop将通过动态代理技术把它织入对应的流程中。
  • 切点(point cat): 有时候,我们的切面不单单应用于单个方法,也可能是对个类的不同方法,这时
    可以通过正则式和指示器的规则去定义,从而适配连接点。切点就是提供这样的一个功能的概述。
  • 通知(advice): 就是按照约定的流程下的方法,分为前置通知(before advice)后置通知(after advice)环绕通知(around advice)事后返回通知(afterReturning advice)、和异常通知(afterThrowing advice),它会根据约定织入流程中。
  • 目标对象(target): 即被代理对象。
  • 引入(introduction): 是指引入新的类和其方法,增强现有Bean的功能
  • 织入(weaving): 它是一个通过动态代理技术,为原有服务对象生成代理对象,然后将与切点定义匹配的连接点拦截,并按约定将各类通知织入约定流程的过程。
  • 切面(spect): 是一个可以定义切点、各类通知和引入的内容,Spring AOP将通过它的信息来增强Bean的功能将对应的方法织入流程。

深入浅出SpringBoot笔记_第4张图片

AOP开发详情

1): 第一步导入aop包
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2): 连接点,可能需要被增强的方法们

public interface UserService {
     

    void printUser(User user);

}


@Service
public class UserServiceImpl implements UserService{
     
    @Override
    public void printUser(User user) {
     
        if(user == null){
     
            throw new RuntimeException("用户为空");
        }

        System.out.println(user);
    }
}

3): 定义切入点,需要注意的是要添加上@Aspect注解
	特别注意: 因为我们要操作的是Ioc容器中的Bean对象里的方法,使用我们的Aspect也要注册到Ioc容器中
@Aspect
@Component
public class MyAspect {
     

    @Pointcut("execution(* com.springboot.chapter4..*(..))")
    public void pointCut(){
     

    }

    @Before("pointCut()")
    public void before(){
     
        System.out.println("before...");
    }


    @Before("pointCut()")
    public void after(){
     
        System.out.println("after....");
    }

    @Before("pointCut()")
    public void afterReturning(){
     
        System.out.println("afterReturning...");
    }


    @Before("pointCut()")
    public void afterThrowing(){
     
        System.out.println("afterThrowing....");
    }

}

环绕通知

环绕通知(Around)是所有通知中最为强大的通知,强大也意味着难以控制。一般而言,使用它的环境是在你需要大幅度修改原有目标对象的服务逻辑时,否则都尽量使用其他的通知。

添加一个around方法
ProceedingJoinPoint: 这个参数的对象有proceed方法,通过这个方法可以回调原有目标对象方法

// 接着上面的代码走
    @Around("pointCut()")
    public void around(ProceedingJoinPoint jp) throws Throwable {
     
        System.out.println("around before...");
        jp.proceed();
        System.out.println("around after...");
    }

通知获取参数

有时候我们希望能够传递参数给通知,这也是允许的,我们只是需要在切点处加入对应的正则式就可以了。当然非环绕通知还可以使用一个连接点(JoinPoint)类型的参数。

参数解释:正则式pointCut() && args(user)中,pointCut()表示启用原来定义切点的规则,并且约定将连接点(目标对象方法)名称为user的参数传递进来。这里注意。JoinPoint类型的参数对于非环绕通知而言,Spring AOP会自动地把它传递到通知中;对于环绕通知而言,可以使用ProceedingJoinPonit类型的参数。

@Before("pointCut() && args(user)")
public void before(JoinPoint joinPoint,User user){
     
	System.out.printlen(user);

    Object[] args = joinPoint.getArgs();
    System.out.println(args);
    System.out.println("before...");
}

访问数据库

MyBatis配置

MyBatis是一个基于SqlSessionFactory构建的框架。对于SqlSessionFactory而言,它的作用是生成SqlSession对象,这个接口对象是Mysql操作的核心,而MyBatis-Spring的结合中甚至可以“擦除”这个对象,使其在代码中“消失”,这样做的意义是很重大的,因为SqlSession是一个功能性的代码,“擦除”它之后,就剩下了业务代码,这样就可以使得代码更具可读性。因为SqlSessionFactory的作用是单一的,只是为了创建核心接口SqlSession,所以在MyBatis应用的生命周期中理当只存在一个SqlSessinoFactory对象,并且往往会是单例模式。而构建SqlSessionFactory是通过配置类(Configuration)来完成,因此mybatis-spring-boot-starter,它会给予我们在配置文件(application.properties)进行Configuration配置的相关内容。

深入浅出SpringBoot笔记_第5张图片
MyBatis可配置的内容

  • properties(属性): 属性文件的实际应用中一般采用Spring进行配置,而不是MyBatis,所以这里不再介绍它的使用。
  • settings(设置): 它的配置将改变MyBatis的底层行为,可以配置映射规则,如自动映射和驼峰映射、执行器(Executor)类型、缓存等内容,比较复杂,具体配置项可参考 http://www.mybatis.org/mybatis-3/zh/configuration.html
  • typeAliases(类型别名): 因为使用类全限定名会比较长,所以MyBatis会对常用的类提供默认的别名,此外还允许我们通过typeAliases配置自定义的别名
  • typeHandlers(类型处理器): 这是MyBatis的重要配置之一,在MyBatis写入和读取数据库的过程中对于不同类型的数据(对于Java是JavaType,对于数据库则是JdbcType)进行自定义转换,在大部分的情况下我们不需要使用自定义的typeHandler,因为MyBatis自身就已经定义了比较多typeHandler
    MyBatis会自动识别javaType和jdbcType,从而实现各种类型的转换。Long类型转bigint类型
  • objcetFactory(对象工厂): 这是一个在MyBatis生成返回的POJO时会调用的工厂类。一般我们使用MyBatis默认提供的对象工厂类(DefaultObjectFactory)就可以了,而不需要任何配置。
  • plugins(插件): 有时候也称为拦截器,是MyBatis最强大也是最危险的组件,它通过动态代理和责任链模式来完成,可以修改MyBatis底层的实现功能。
  • databaseIdProvider(数据库厂商标识): 允许MyBatis配置多类型数据库支持
  • mapper(映射器): 是MyBatis最核心的组件,它提供SQL和POJO映射关系,这是MyBatis开发的核心

MyBatis常用配置: 具体的配置到官网查看

# 定义Mapper的XML路径
mybatis.mapper-locations:classpath:mapper/*Mapper.xml

#定义别名扫描的包,需要与@Alias联合使用
mybatis.type-aliases-package=cn.hg.pojo

# MyBatis配置文件,当你的配置比较复杂的时候,可以使用它,主配置文件
mybatis.config-location=classpath:Mybatis.xml

# 配置MyBatis插件(拦截器)
mybatis.configuration.interceptors=xxx

# 配置驼峰命名法
mybaits.configuration.map.mapUnderscoreToCamelCase=true

# 设置数据库默认隔离级别  读写提交
spring.datasource.tomcat.default-transaction-isolation: 2

数据库事务处理

Spring中,数据库事务是通过AOP技术来提供服务的

Spring声明式数据库事务约定

对于事务,需要通过标注告诉Spring在什么地方启用数据库事务功能。对于声明式事务,是使用@Transactional进行标注的。这个注解可以标注在类或者方法上,当它标注在类上时,代表这个类所有(public)非静态的方法都将启用事务功能。在@Transactional中,还允许配置许多的属性,如事务的隔离级别传播行为

事务隔离级别

隔离级别,因为互联网应用时刻对着高并发的环境,如商品库存,时刻都是多个线程共享的数据,这样就会在多线程的环境中扣减商品库存。对于数据库而言,就会出现多个事务同时访问同一记录的情况!这样引起数据出现不一致的情况,便是数据库的丢失更新(Lost Update)!!!
数据库事务的知识

  • Atomic(原子性): 事务中包含的操作被看作一个整体的业务单元,这个业务单元中的操作要么全部成功,要么全部失败,不会出现部分失败、部分成功的场景。
  • Consistency(一致性): 事务在完成时,必须使所有的数据都保持一致状态,在数据库中所有的修改都基于事务,保证了数据的完整性。
  • Islation(隔离性): 可能多个应用程序线程同时访问同一数据,这样数据库同样的数据就会在各个不同的事务中访问,这样会产生丢失更新。为了压制丢失更新的产生,数据库定义了隔离级别的概念,通过它的选择,可以在不同程度上压制丢失更新的发生。因为互联网的应用常常面对高并发的场景,所以隔离性是需要掌握的重点内容
  • Durability(持久性): 事务结束后,所以的数据会固化到一个地方,如保持到磁盘当中,即使断电重启也可以提供给应用程序访问。

详解隔离级别

Mysql数据库给我们提供了四种隔离级别

  1. 未提交读(read uncommitted)
    未提交读是最低的隔离级别,其含义是允许一个事务读取另一个事务没有提交的数据。未提交读是一种危险的隔离级别,所以一般在我们实际的开发中应用不广,但是它的优点在于并发能力高,适合那些对数据库一致性没有要求而追求高并发的场景,它的最大坏处是出现脏读。
    深入浅出SpringBoot笔记_第6张图片

  2. 读写提交(read committed)
    读写提交隔离级别,是指一个事务只能读取另一个事务已经提交的数据,不能读取未提交的数据
    深入浅出SpringBoot笔记_第7张图片

  3. 可重复读(Mysql数据库默认)
    可重复读的目标是克服读写提交中出现的不可重复读的现象,因为在读写提交的时候,可能出现一些值得变化,影响当前事务的执行,这时候数据库提出了可重复读的隔离级别。
    深入浅出SpringBoot笔记_第8张图片

  4. 串行化(Serializable)
    串行化是数据库最高的隔离级别,它会要求所有的SQL都会按照顺序执行,这样就克服了上隔离级别出现的各种问题,所以它能够完全保证数据的一致性。性能最差

  5. 合理的使用隔离级别
    深入浅出SpringBoot笔记_第9张图片

传播行为

在Spring事务机制对数据库存在7种传播行为,它是通过枚举Propagation定义的

public enum Propagation {
     

	REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED),

	SUPPORTS(TransactionDefinition.PROPAGATION_SUPPORTS),
	
	MANDATORY(TransactionDefinition.PROPAGATION_MANDATORY),
	
	REQUIRES_NEW(TransactionDefinition.PROPAGATION_REQUIRES_NEW),

 
	NOT_SUPPORTED(TransactionDefinition.PROPAGATION_NOT_SUPPORTED),

	
	NEVER(TransactionDefinition.PROPAGATION_NEVER),

	
	NESTED(TransactionDefinition.PROPAGATION_NESTED);


	private final int value;


	Propagation(int value) {
     
		this.value = value;
	}

	public int value() {
     
		return this.value;
	}

}
  • REQUIRED:需要事务,它是默认传播行为,如果当前存在事务,就沿用当前事务。否则新建一个事务运行子方法。
  • SUPPORTS:支持事务,如果当前存在事务,就沿用当前事务,如果不存在,则继续采用无事务的方式运行子方法
  • MANDATORY: 必须使用事务,如果当前没有事务,则会抛出异常,如果存在当前事务,就沿用当前事务
  • REQUIRES_NEW: 无论当前事务是否存在,都会创建新事务运行方法,这样新事务就可以拥有新的锁和隔离级别等特性,与当前事务相互独立
  • NOT_SUPPORTED: 不支持事务,当前存在事务时,将挂起事务,运行方法
  • NEVER: 不支持事务,当前存在事务时,则抛出异常,否则继续使用无事务机制运行
  • NESTED: 在当前方法调用子方法时,如果子方法发生异常,只回滚方法执行过的SQL,而不回滚当前方法的事务。

使用性能利器–Redis

RedisTemplate
应该说RedisTemplate是使用得最多的类,所以它是Spring操作Redis的重点内容。RedisTemplate是一个强大的类,首先它会自动从RedisConnectionFactory工厂中获取连接(前提是在配置文件配置了地址和端口),然后执行对应的Redis命令,在最后还会关闭Redis的连接。

序列化问题:默认情况下RedisTemplate中文数据到数据库会出现16进制的问题对应序列化器对应序列化器,Spring提供了RedisSerializer接口,它有两个方法。这两个方法,一个是serialize,它能把那些可以序列化的对象转换为二进制字符串;另一个是deserialize,它能够通过反序列化把二进制字符串转换为Java对象。
深入浅出SpringBoot笔记_第10张图片
RedisTemplate中序列化器属性
     如果自己要改的话: 将需要使用的类型设置为UTF-8的,获取直接使用StringRedisTemplate
     序列化主要是通过serializedeserialize来实现的

@Configuration
public class RedisConfig {
     

    public RedisTemplate redisTemplate(){
     
        RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
        
        //key序列化方式
        template.setKeySerializer(redisSerializer);
        //value序列化
        template.setValueSerializer(jackson2JsonRedisSerializer);
        //value hashmap序列化
        template.setHashKeySerializer(redisSerializer);
        template.setHashValueSerializer(jackson2JsonRedisSerializer);
        return redisTemplate;
    }
}
属性 描述 备注
defaultSerialize 默认序列化器 如果没有设置,则使用JdkSerializationRedisSerializer
keySerializer Redis键序列化器 如果没有设置,则使用默认序列化器
valueSerializer Redis值序列化器 如果没有设置,则使用默认序列化器
hashKeySerializer Redis散列结构field序列化器 如果没有设置,则使用默认序列化器
hashValueSerializer Redis散列结构value序列化器 如果没有设置,则使用默认序列化器
stringSerializer 字符串序列化器 RedisTemplate自动赋值为StringRedisSerializer对象
Spring 对 Redis数据类型操作的封装

Reids能支持7种类型的数据结构,这7种类型是字符串、散列、列表(链表)、集合有序集合、基数和地铁位。

操作接口 功能 获取方式 备注
GeoOperations 地理位置操作接口 redisTemplate.opsForGeo() 使用不多
HashOperations 散列操作接口 redisTemplate.opsForHash()
HyperLogLogOperations 基数操作接口 redisTemplate.opsForHyperLogLog() 使用不多
ListOperations 列表(链表)操作接口 redisTemplate.opsForList()
SetOperations 集合操作接口 redisTemplate.opsForSet()
ValueOperations 字符串操作接口 redisTemplate.opsForValue()
ZSetOperations 有序集合操作接口 redisTemplate.opsForZSet()

这样就可以通过各类的操作接口来操作不同的数据类型了,当然这需要你熟悉Redis的各种命令。有时候我们可能需要对某个键值对做连续的操作。这时Spring也提供支持,BoundXXXOperations接口。

使用SessionCallback接口实现一个多个操作

	// 这样就可以在一个链接的情况下多次操作redis
	template.execute(new SessionCallback<Object>() {
     
            @Override
            public  Object execute(RedisOperations ro) throws DataAccessException {
     
            
                ro.opsForValue().set("key","value");
                ro.opsForList().rightPush("items","d","c");
                return null;
            }
      });

Spring Boot 中配置Redis

pring:
  redis:
    host: 182.92.92.59
    port: 6379   
    
    # Redis连接超时时间,单位毫秒值
    timeout: 5000
	
	# 这里使用jedis做数据源,默认情况下redis-start整合的是lettuce
    jedis:
      pool:
        # 配置连接池属性
        max-idle: 10
        max-wait: 2000
        max-active: 10
        min-idle: 5
    

Rdis的一些特殊用法

Redis除了操作那些数据类型的功能外,还能支持事务、流水线、发布订阅和Lua语等功能,这些也是Redis常用的功能。在高并发的场景中,往往我们需要保证数据的一致性,这时考虑使用Redis事务或者利用Redis执行Lua的原子性来达到数据一致性的目的。

使用Redis事务

首先Redis是支持一定事务能力的NoSql,在Redis中使用事务,通常的命令组合是watch…multi…exec,也就是要一个Redis连接中执行多个命令,这时我们可以考虑使用SessionCallback接口来达到这个目的。其中,watch命令是可以监控Redis的一些键;multi命令是开始事务,开始事务后,该客户端的命令不会马上被执行,而是存放在一个队列里,这点是需要注意的地方,也就是在这时我们执行一些返回数据的命令,Redis也是不会马上执行的,而是把命令放到一个队列里,所以此时调用Redis命令,结果为null;exe命令的意义在于执行事务,只是它在队列执行前会判断被watch监控的Redis的键的数据是否发生过变化(即使赋予与之前相同的值也会被认为是改变过),如果它认为发生了变化,那么Redis就会取消事务,否则就会执行事务,Redis在执行事务时,要么全部执行,要么全部不执行,而且不会被其他客户端打断,这样就保证了Reids事务下数据的一致性。

深入浅出SpringBoot笔记_第11张图片
使用SessionCallback方法配合使用事务

@Autowired
   private RedisTemplate redisTemplate;
   
   public void demo(){
     
       this.redisTemplate.setKeySerializer(StringRedisSerializer.UTF_8);

       List<Object> item = (List<Object>) this.redisTemplate.execute(new SessionCallback() {
     
           @Override
           public Object execute(RedisOperations redisOperations) throws DataAccessException {
     
               redisOperations.multi();
               redisOperations.opsForValue().set("yes","nice");
               
               List exec = redisOperations.exec();
               return exec;
           }
       });
   }

使用Spring缓存注解操作Redis

缓存管理器和缓存的启用
Spring在使用缓存注解前,需要配置缓存管理器,缓存管理器将提供一些重要的信息,如缓存类型、超时时间等。Spring可以支持多种缓存的使用,因此它存在多缓存处理器,并提供了缓存处理器的接口CacheManager和与之相关的类。深入浅出SpringBoot笔记_第12张图片
在spring boot配置文件配置缓存信息
缓存管理器配置

spring:
	cache:
		cache-names: redisCache #如果由底层的缓存管理器支持创建,以逗号分隔的列表来缓存名称
		redis:
			cache-null-values: true # 是否允许Redis缓存空值
			key-prefix: rd_ # Redis的键前缀
			time-to-live: 600000ms # 缓存超时时间戳,0表示不设置超时时间
			use-key-prefix: true # 开启Redis前缀
		type: REDIS # 缓存类型,在默认的情况下,Spring会自动根据上下文探测	

这样就配置完了缓存管理器 spring.cache.type配置的是缓存类型,为Redis,Spring Boot会自动生成RedisCacheManager对象,而spring.cache.chache-names则是配置缓存名称

为了使用缓存管理器,需要在Spring Boot的配置文件中加入缓存的注解@EnableCaching

缓存注解

  1. @CachePut(value=“redisCache”,key="‘redis_user_’+#result.id ")
    表示将方法结果返回存放到缓存中
  2. @Cacheable(value=“redisCache”,key=“redis_user_’+#id”)
    表示先从缓存中通过定义的键查询,如果可以查询到数据,则返回,否则执行该方法,返回数据,并且将返回结果保持到缓存中
  3. @CacheEvict(value=“redisCache”,key=“redis_user_’+#id”,condition="#result != null")
    通过定义的键移除缓存,它有一个Boolean类型的配置项beforeInvocation,表示在方法之前或者之后移除缓存。

缓存的value都是"redisCache",因为在Spring Boot中配置了对应的缓存名称为"redisCache",这样就能够引用到对应的缓存了。

condition属性:这个属性的值是一个boolean类型,只有到值为true时才会执行缓存操作。

缓存的使用

// value为分类,缓存管理器会对对应的分类进行一些处理,建议将一些相关的操作统一到一个分类下  
//key则是缓存的名称,并且可以使用sqel语法,查看spring boot笔记 #root.  #result. #user ...
@Cacheable(value = "test",key = "'testInsert'")
public String testCacheable(){
     
	return "ok";
}

// vlaue为test,说明和上面的是一个分组下
// 名称为testPut
// 如果返回的数据为空,则不将数据缓存
// sync 是否使用同步方法,添加了锁支持
@CachePut(value="test",key="#root.methodName",condition = "#result != null",sync = true)
public String testCachePut(){
     
	return "ok";
}

// allEntries:将会删除所有组下的数据(value)
// key则需要指定将什么缓存的名称的数据删除,比如第一个的testInsert
@CacheEvict(value = "category",key = "'testInsert'",allEntries = true)
public String testCacheEvict(){
     
	return "ok";
}


@Caching(evict ={
     
	@CacheEvict(value = "category",key="'testInsert'"),
	@CacheEvict(value = "category",key="'testCachePut'")},)
public String testCaching(){
     
	return "ok";
}

自定义缓存管理器

@Bean
    public CacheManager cacheManager(RedisConnectionFactory factory) {
     
        RedisSerializer<String> redisSerializer = new StringRedisSerializer();
        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);
        // 配置序列化(解决乱码的问题),过期时间600秒
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofSeconds(600))
              .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))
                .disableCachingNullValues() // 允许有空值
                .disableKeyPrefix(); // 不设置前缀

        RedisCacheManager cacheManager = RedisCacheManager.builder(factory)
                .cacheDefaults(config) 
                .build();
        return cacheManager;

    }

初识Spring MVC

Spring Mvc一开始就定位于一个较为松散的组合,展示给用户的视图(View)、控制器返回的数据模型(Model)、定位视图的视图解析器(ViewResolver)、和处理适配器(HandlerAdapter)等内容都是独立的。

SpringMVC的执行流程
深入浅出SpringBoot笔记_第13张图片

HanlerMapping: 来获取到请求的路径来确定方法位置
HandlerExecutionChain: 里面有一个hanlder类,对应的有这个方法的参数
HandlerAdapter: 来解析出handler的数据
ModelAndView: 将数据和视图返回给控制器
ViewResolver: 获取到视图信息,并且找到对应的视图,给用户返回
View: 最终返回的视图

定制Spring MVC的初始化

为了支持对于Spring MVC的配置,Spring提供了接口WebMvcConfigurer,这是一个基于Java8的接口,所以其大部分方法都是default类型的,但是它们都是有空实现的,这样开发者只需要实现这个接口,重写需要定义的方法即可。
在Spring Boot中,自定义是通过配置类WebMvcAutoConfiguration定义的,它有一个静态内部类WebMvcAutoConfigurationAdapter,通过它Spring Boot就自动配置了Spring Mvc的初始化。

一些基础的配置项

# 异步请求超时时间(单位为毫秒)
spring.mvc.async.request-timeout=5000

# 是否使用请求参数(默认参数为"format")来确定请求的媒体类型
spring.mvc.contentnegotiation.favor-parameter=false

# 是否使用URL中的路径扩展来确定请求的媒体类型
spring.mvc.contentnegotiation.favor-path-extension=false

# 设置内容协商向媒体类型映射文件扩展名。例如,YML/YAML
spring.mvc.contentnegotiation.media-types.*=

# 当启用favor-parameter参数是,自定义参数名
spring.mvc.contentnegotiation.parameter-name=

# 日期格式配置,如 yyyy-MM-dd
spring.mvc.date-format=yyyy-MM-dd HH:mm:ss

# 是否让frameworkServlet doService方法支持TRACE请求
spring.mvc.dispatch-trace-request=false

# 是否启用 FrameworkServlet doService 方法支持OPTIONS请求
spring.mvc.dispatch-options-request=true

# 如果配置为false,那么它将忽略模型重定向的场景
spring.mvc.ignore-default-model-on-redirect=true

# 默认国际化选项,默认取 Accept-Language
spring.mvc.locale=Accept-Language

# 国际化解析器,如果需要固定可以使用fixed
spring.mvc.locale-resolver=accept_header

# 是否启用警告日志异常解决
spring.mvc.log-resolved-exception=false

# 启用Spring Web服务Servlet的优先顺序配置
spring.mvc.servlet.load-on-startup=-1

# 指定静态资源路径
spring.mvc.static-path-pattern=/**

# 如果请求找不到处理器,是否抛出NoHandlerFoundException异常
spring.mvc.throw-exception-if-no-handler-found=false

spring.mvc.view.prefix=/WEB-INF/pages/
spring.mvc.view.suffix=.jsp

# 设置国际化消息是否总是采用格式化,默认是false
spring.message.always-use-message-format=false

# 设置国际化属性名称,如果多个可以使用逗号分隔,默认为messages
spring.message.basename=message

# 设置国际化消息缓存超时秒数,默认为永久不过期,如果0表示每次都重新加载
spring.message.cache-duration=60

spring.message.encoding=UTF-8

处理器映射

Web工程使用了Spring MVC,那么它在启动阶段就会将注解@RequestMapping所配置的内容保存到处理器映射(HandlerMapper)机制中,然后等待请求的到来,通过拦截请求信息与HandlerMapping进行匹配,找到对应的处理器(它包含控制器的逻辑),并将处理器及其拦截器保存到HandlerExecutionChain对象中,返回给DispatcherServlet,这样DispatcherServlet就可以运行它们了。

自定义参数转换规则

Spring MVC提供的处理器会先以一套规则来实现参数的转换,而大部分的情况下开发者并不需要知道那些转换的细节。实际上处理器的转换规则还包括控制器返回后的处理器。

处理器获取参数逻辑
当一个请求来到时,在处理器执行的过程中,它首先会从HTTP请求和上下文环境来得到参数。如果是简单的参数它会以简单的转换器进行转换,而这些简单的转换器是Spring MVC自身已经提供了的。但是如果是转换HTTP请求体(Body),它就会调用HttpMessageConverter接口的方法对请求体的信息进行转换。

深入浅出SpringBoot笔记_第14张图片
在Spring MVC中,是通过WebDataBinder机制来获取参数的,它的主要作用是解析HTTP请求的上下文,然后在控制器的调用之前转换参数并且提供验证功能,为调用控制器方法做准备。当需要自定义转换规则时,只需要在注册机上注册自己的转换器就可以了。

Converter:是一个普通的转换器,例如一个Integer类型的控制器参数,而从HTTP对应的为字符串,对应的Converter就会将字符串转换为Integer类型。
Formatter:是一个格式化转换器,类似那些日期字符串就是通过它按照约定的格式转换为日期的。
GenericConverter:将HTTP参数转换为数组。会调用Coverter的转换

使用简单的Converter

@Component
public class StringToUserConverter implements Converter<String,User> {
     
    @Override
    public User convert(String userInfo) {
     
        User user = new User();
        
        // 前端传的数据要是这样 http://localhost?user=username_password 
        String[] split = userInfo.split("_");
        user.setUsername(split[0]);
        user.setPassword(split[1]);
        return user;
    }
}
数据校验

JSR-303验证主要是通过注解的方式进行的。在要使用的地方加上@Valid来启用验证机制

// 非空判断,message是提示信息
@NotNull(message="id不能为空")

// 只能是将来日期
@Future(message="需要一个将来日期")

// 日期格式化转换
@DateTimeFormat(pattern="yyyy-MM-dd")

// 浮点数最小值
@DecimalMin(value="0.0")

// 浮点数最大值
@DecimalMax(value="100000.00")

// 整数的最小值
@Min(value= 1,message="最小值")

// 整数的最大值
@Max(value=88,message="最大值")

// 限定范围整形
@Range(min = 1,max=888,message="范围在1-888")

// 邮箱验证
@Email(message="邮件格式错误")

// 字符串长度要达到20到30之间
@Size(min=20,max=30)

数据模型

控制器是业务逻辑核心内容,而控制器的核心内容之一就是对数据的处理。控制器是可以自定义模型视图(ModelAndView)的,其中模型是存放数据的地方,视图则是展示给用户的。
深入浅出SpringBoot笔记_第15张图片
如果在控制器方法的参数中使用ModelAndView、Model或者ModelMap作为参数类型,Spring MVC会自动创建数据模型对象。

视图和视图解析器

视图是渲染数据模型展示给用户的组件,在Spring MVC中分为逻辑视图非逻辑视图

视图的顶层接口View中有两个主要的方法getContentTyperender方法,getContentType是获取HTTP响应类型的,它可以返回的类型是文本、JSON数据或者文件等,render方法则是将数据模型渲染到视图的,这是视图的核心方法。在它的参数中,model是数据模型,实际就是控制器(或者由处理器自动绑定)返回的数据模型。这样render方法就可以把它渲染出来。渲染视图是比较复杂的过程,为了简化视图渲染的开发,在Spring MVC中已经给开发者提供了许多开发好的视图类。
深入浅出SpringBoot笔记_第16张图片

Spring MVC对文件上传的支持

首先,DispatcherServlet会使用适配器模式。
将HttpServletRequest接口对象转换MultipartHttpServletRequest对象。MultipartHttpServletRequest接口扩展了HttpServletRequest接口的所有方法,而且定义了一些操作文件的方法,可以直接使用MultipartFile获取

给控制器添加通知

在Spring AOP,可以通过通知来增强Bean的功能。同样地,Spring MVC也可以给控制器增加通知,于是在控制器方法的前后和异常发生时去执行不同的处理。

  • @ControllerAdvice: 定义一个控制器的通知类,允许定义一些关于增强控制器的各类通知和限定增强哪些控制器功能
  • @InitBinder: 定义控制器参数绑定规则,如转换规则、格式化等。
  • @ExceptionHandler: 定义控制器发生异常后的操作。一般来说,发生异常后,可以跳转到指定的友好页面,以避免用户使用的不友好

错误返回指定的页面

@ControllerAdvice
public class Demo {
     

   @ExceptionHandler(RuntimeException.class)
   public String error(Exception e){
     
       e.printStackTrace();
       return "error";  // 返回到error页面
   }
}

国际化

Spring MVC提供了国际化消息源机制,那就是MessageSource接口体系.
深入浅出SpringBoot笔记_第17张图片

使用
可以把两个属性(properties)文件放置在resources目录,只是这里要求有3个文件,且文件名messages.properties、message_zh_CN.properties和messages_us_US.properteis。messages.properties是默认的国际化文件,如果没有这个文件,则Spring MVC将不在启用国际化的消息机制;messages_zh_CN.properties则表示简体中文的国际话消息,对于messages_us_US.properties则是美国的国际话消息。这里以messages开头是配置了spring.messages.basename=messages


# 指定国际化区域,可以覆盖"Accept-Language"头信息
spring.mvc.locale=

# 国际化解析器,可以选择fixed或accept-header
# fixed代表固定的国际化,accept-header代表读取游览器的"Accept-Language"头信息
spring.mvc.locale-resolver=accept-header


# 这个两个配置使用的是AccpteHeaderLocaleResolver和FixedLocaleResolver

国际化实例----SessionLocaleResolver

第一步:在application.yaml编写message的内容
spring:
	message:
		encoding: UTF-8
		cache-dration: 600
		basename: messages # 设置基础名称

第二步:编写消息配置文件
messages.properties  # 必须存在的配置文件
messages_zh_CN.properties # 中国
messages_us_US.properties # 美国

第三步:添加国际化解析器和拦截器

@Configuration
public class MessageConfig implements WebMvcConfigurer{

    // 国际化拦截器
    private LocaleChangeInterceptor localeChangeInterceptor = null;


    // 国际化解析器
    @Bean(value = "localeResolver")
    public LocaleResolver localeResolver(){
        SessionLocaleResolver sessionLocaleResolver = new SessionLocaleResolver();
        // 默认选择中国的
        sessionLocaleResolver.setDefaultLocale(Locale.SIMPLIFIED_CHINESE);
        return sessionLocaleResolver;
    }

    // 创建国际化拦截器
    @Bean
    public LocaleChangeInterceptor getLocaleChangeInterceptor() {
        if(this.localeChangeInterceptor != null){
            return this.localeChangeInterceptor;
        }
        this.localeChangeInterceptor = new LocaleChangeInterceptor();
        this.localeChangeInterceptor.setParamName("language");
        return this.localeChangeInterceptor;
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(this.localeChangeInterceptor);
    }
}  

第四步:只要在请求地址上加是 http://localhost/hh?language=zh_CN  就会使用中国的文字

安全–Spring Security 认证和授权

为了提供安全的机制,Spring提供了其安全框架Spring Security,它是一个能够为基于Spring生态圈,提供安全访问控制解决方案的框架。

Spring Security的原理
一旦启用了Spring Security,Spring Ioc容器就会为你创建一个名称为SpringSecurityFilterChain的Spring Bean。它的类型为FilterChainProxy,事实上它也实现了Filter接口,只是它是一个特殊的拦截器。在Spring Security操作的过程中它会提供Servlet过滤器DelegatingFilterProxy,这个对象存在一个拦截器列表(List),列表上存在用户验证的拦截器跨站点请求伪造等拦截器等,这样它就可以提供多种拦截功能。

1. 导入jar包
<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-securityartifactId>
dependency>

2. 在引导类上添加,在spring boot的工程里面这一步也不是必须的
   @EnableWebSecurity
概述和简单安全认证

在JavaWeb工程中,一般使用Servlet过滤器(Filter)对请求进行拦截,然后在Filter中通过自己的验证逻辑决定是否放行请求。同样Spring Security也是基于这个原理,在进入DispatcherServlet前就可以对Spirng MVC的请求进行拦截。为了对请求进行拦截,Spring Security提供了过滤器DelegatingFilterProxy类


# Spring Security过滤器排序
spring.security.filter.order=-100

# 安全过滤器责任链拦截的分发类型
spring.security.filter.dispatcher-types=async,error,request

# 用户名,默认值为user
spring.security.user.name=user

# 用户密码
spring.security.user.password=hgbingqq1

# 用户角色
spring.security.user.roles=admin


# OAuth提供者详细配置信息
spring.security.oauth2.client.provider.*=

# OAuth客户端登记信息
spring.security.oauth2.client.registration.*=

使用WebSecurityConfigurerAdapter抽象类 来自定义

WebSecurityConfigurerAdapter中的默认存在的3个方法

public class SecurityConfig extends WebSecurityConfigurerAdapter{
     


   /**
     * 同来配置用户签名服务,主要是user-details机制,还可以给予用户赋予角色
     * @param  auth
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
     
		super.configure(auth);
    }

    /**
     * 用来配置Filter链
     * @param web
     * @throws Exception
     */
    @Override
    public void configure(WebSecurity web) throws Exception {
     
        super.configure(web);
    }


    /**
     * 用来配置拦截保护的请求,比如什么请求放行,什么请求需要验证
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
     
        super.configure(http);

    }   
}

自定义用户服务信息

1. 使用内存用户信息

@Configuration
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter{
     

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
     
        BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();

        // 使用内存存储
        auth.inMemoryAuthentication()
                // 使用Bcrypt的密码编辑,会将密码编译成密文
                .passwordEncoder(passwordEncoder)
                    .withUser("admin")
                    .password("$2a$10$wkTWqEH.6HohTe9TIy/OSeF5K46ImHcMaeA.ggtc026DX80NNb0Im")
                    .roles("p1","p2") // 用户权限  ROLE_p1 ROLE_p2
                .and()
                    .withUser("myuser")
                    .password("$2a$10$wkTWqEH.6HohTe9TIy/OSeF5K46ImHcMaeA.ggtc026DX80NNb0Im")
                    .roles("p1"); // 用户权限 ROLE_p1
    }
}

2. 使用数据库定义用户认证服务

@Service
public class MyUserDetail implements UserDetailsService{
     

    @Autowired
    private UserService userService;

    @Override
    public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
     
        QueryWrapper<User> wrapper = new QueryWrapper<>();
 
        wrapper.eq("user_name",userName);

        // 查询出用户信息
        User user = this.userService.getOne(wrapper);

        // 查询出用户的权限信息
        List<Role> roleList= this.userService.findRolesByUserName(userName);
        
        // 设置用户名称密码和权限信息
        return User.withUsername(user.getName()).password(user.getAvatar()).roles("p,p2").build();
    }
}

注册到AuthenticationManagerBuilder中


@Configuration
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter{
     

    @Autowired
    private MyUserDetail myUserDetail;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
     
        BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();

        // 使用内存存储
        auth.userDetailsService(MyUserDetail).passwordEncoder(this.passwordEncoder());
    }
    
    @Bean
    public PasswordEncoder passwordEncoder(){
     
        return new BCryptPasswordEncoder();
    }
}

深入浅出SpringBoot笔记_第18张图片

限制请求

// 正常配置
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{
     

    @Override
    protected void configure(HttpSecurity http) throws Exception {
     
        http.authorizeRequests()
                .antMatchers("/user/**")
                    // 当问题 /user下的资源的时候需要有 ROLE_USER 或者 ROLE_ADMIN权限
                    .hasAnyRole("USER","ADMIN")
                .anyRequest()
                    // 其他没有被拦截的就在登入后即可访问
                    .permitAll()
                .and()
                    // 对于没有配置权限的其他请求允许匿名访问
                    .anonymous()
                .and()
                    // 使用security提供的的登陆页面
                    .formLogin()
                .and()
                    // 启用HTTP基础验证
                    .httpBasic();
    }
}


// 强制开启https的协议
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{
     

    @Override
    protected void configure(HttpSecurity http) throws Exception {
     
       http.requiresChannel()
               .antMatchers("/user/**")
                    // 使用安全通道,限定为https请求
                    .requiresSecure()
            .and()
            .requiresChannel()
               .antMatchers("/admin/**")
                    // 不使用Https请求
                    .requiresInsecure()
            .and()
            .authorizeRequests()
                // 限定能够访问的角色
                .antMatchers("/user/**")
                    .hasAnyRole("USER","ADMIN")
                .antMatchers("/admin/**")
                    .hasAnyRole("ADMIN");
    }
}

// 自定义登录页面
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{
     

    @Override
    protected void configure(HttpSecurity http) throws Exception {
     
      http.authorizeRequests()
              .antMatchers("/admin/**")
                .hasAnyRole("ADMIN")
              .and()
                // 这里的rememberme方法的意思为启用了“记住我”,这个“记住我”的有效时间为1天,而游览器中将使用Cookie以键"remember-me-key"进行保持
                .rememberMe().tokenValiditySeconds(86400).key("remember-me-key")
              .and()
                .httpBasic()
              .and()
                // 通过签名可以访问任何地址
                .authorizeRequests().antMatchers("/**").permitAll()
              .and()
                // 设置了自定义的登入页面,和默认登入成功后的跳转页面
                .formLogin().loginPage("/login/page")
                .defaultSuccessUrl("/admin/welcome")
                .and()
                .logout()
                    // 设置了自定义的退出页面,会将Remember Me功能保持的信息清空
                    .logoutUrl("/logout/page")
                    .logoutSuccessUrl("/welcome");;
				
				// 当然这些页面也是要存在的,会通过InternalResourceView的前后缀拼接
    }
}

深入浅出SpringBoot笔记_第19张图片

你可能感兴趣的:(java)