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使依赖的两个核心理念,一个是控制反转(Inversion of Control,IOC),另一个是面向切面编程(Aspect Oriented Programming,AOP)
SpringIoc容器是一个管理Bean的容器,在Spring的定义中,它要求所有的Ioc容器都需要实现接口BeanFactory,它是一个顶级容器接口。 org.springframework.beans.factory.BeanFactory
由于BeanFactory的功能还不够强大,因此Spring在BeanFactroy的基础上,还设计了一个更为高级的接口ApplicationContext,它是BeanFactory的子接口之一,在Spring的体系中BeanFactroy和ApplicationContext是最为重要的接口设计。org.springframework.context.ApplicationContext
ApplicationContext接口通过继承上级接口,进而继承BeanFactory接口,但是在BeanFactory的基础上,扩展了消息国际化(MessageSource)、环境可配置接口(EnvironmentCapable)、应用事件发布接口(ApplicationEventPublish)和资源模式解析接口(ResourcePatternResolver),所以它的功能强大。
几个重要的属性
1. basePackages:定义扫描的包名,在没有定义的情况下,它会扫描当前包和其子包下的路径。
2. basePackageClasses:定义扫描的类
3. includeFilters: 定义满足过滤器(Filter)条件的Bean才去扫描
4. excludeFilters:排除过滤器条件的Bean
注意
: 3和4他们都需要通过一个注解@Filter
去定义,它有一个type类型,这里可以定义为注解或者正则式等类型。
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})})
现实的Java应用往往需要引入许多来自第三方的包,并且很有可能希望把第三方包的类对象已放入到Spring Ioc容器中,这时@Bean注解就可以发挥作用了。
如果是第三方的类我们需要导入到IOC容器就使用@Bean注解,如果是自己写的类就用@Component注解等
带有参数的构造方法类的装配
@Component
public class User {
private NameDynAnyPair nameDynAnyPair;
public User(@Autowired @Qualifier("nameDynAnyPair") NameDynAnyPair nameDynAnyPair){
this.nameDynAnyPair = nameDynAnyPair;
}
}
大致分为Bean的定义、Bean的初始化、Bean的生存期、Bean的销毁4个部分
注意:只有实现了ApplicationContext接口的才能调动setApplicationContext方法
当配置文件中不包含某些配置的情况下,将不装配当前这个类到IOC容器中。
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();
}
}
在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({
"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程序
使用注解@@ImportResource
@Configuration
@ComponentScan(excludeFilters = {
@ComponentScan.Filter(classes = {
Service.class})})
@ImportResource({
"classpath:Mybatis.xml"})
public class ConfigBean {
}
为了更加灵活,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 ;
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的功能将对应的方法织入流程。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是一个基于SqlSessionFactory构建的框架。对于SqlSessionFactory而言,它的作用是生成SqlSession对象,这个接口对象是Mysql操作的核心,而MyBatis-Spring的结合中甚至可以“擦除”这个对象,使其在代码中“消失”,这样做的意义是很重大的,因为SqlSession是一个功能性的代码,“擦除”它之后,就剩下了业务代码,这样就可以使得代码更具可读性。因为SqlSessionFactory的作用是单一的,只是为了创建核心接口SqlSession,所以在MyBatis应用的生命周期中理当只存在一个SqlSessinoFactory对象,并且往往会是单例模式。而构建SqlSessionFactory是通过配置类(Configuration)来完成,因此mybatis-spring-boot-starter,它会给予我们在配置文件(application.properties)进行Configuration配置的相关内容。
properties(属性)
: 属性文件的实际应用中一般采用Spring进行配置,而不是MyBatis,所以这里不再介绍它的使用。settings(设置)
: 它的配置将改变MyBatis的底层行为,可以配置映射规则,如自动映射和驼峰映射、执行器(Executor)类型、缓存等内容,比较复杂,具体配置项可参考 http://www.mybatis.org/mybatis-3/zh/configuration.htmltypeAliases(类型别名)
: 因为使用类全限定名会比较长,所以MyBatis会对常用的类提供默认的别名,此外还允许我们通过typeAliases配置自定义的别名typeHandlers(类型处理器)
: 这是MyBatis的重要配置之一,在MyBatis写入和读取数据库的过程中对于不同类型的数据(对于Java是JavaType,对于数据库则是JdbcType)进行自定义转换,在大部分的情况下我们不需要使用自定义的typeHandler,因为MyBatis自身就已经定义了比较多typeHandlerobjcetFactory(对象工厂)
: 这是一个在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在什么地方启用数据库事务功能。对于声明式事务,是使用@Transactional
进行标注的。这个注解可以标注在类或者方法上,当它标注在类上时,代表这个类所有(public)非静态的方法都将启用事务功能。在@Transactional
中,还允许配置许多的属性,如事务的隔离级别和传播行为。
隔离级别,因为互联网应用时刻对着高并发的环境,如商品库存,时刻都是多个线程共享的数据,这样就会在多线程的环境中扣减商品库存。对于数据库而言,就会出现多个事务同时访问同一记录的情况!这样引起数据出现不一致的情况,便是数据库的丢失更新(Lost Update)!!!
数据库事务的知识
Atomic(原子性)
: 事务中包含的操作被看作一个整体的业务单元,这个业务单元中的操作要么全部成功,要么全部失败,不会出现部分失败、部分成功的场景。Consistency(一致性)
: 事务在完成时,必须使所有的数据都保持一致状态,在数据库中所有的修改都基于事务,保证了数据的完整性。Islation(隔离性)
: 可能多个应用程序线程同时访问同一数据,这样数据库同样的数据就会在各个不同的事务中访问,这样会产生丢失更新。为了压制丢失更新的产生,数据库定义了隔离级别的概念,通过它的选择,可以在不同程度上压制丢失更新的发生。因为互联网的应用常常面对高并发的场景,所以隔离性是需要掌握的重点内容
。Durability(持久性)
: 事务结束后,所以的数据会固化到一个地方,如保持到磁盘当中,即使断电重启也可以提供给应用程序访问。Mysql数据库给我们提供了四种隔离级别
未提交读(read uncommitted)
未提交读是最低的隔离级别,其含义是允许一个事务读取另一个事务没有提交的数据。未提交读是一种危险的隔离级别,所以一般在我们实际的开发中应用不广,但是它的优点在于并发能力高,适合那些对数据库一致性没有要求而追求高并发的场景,它的最大坏处是出现脏读。
读写提交(read committed)
读写提交隔离级别,是指一个事务只能读取另一个事务已经提交的数据,不能读取未提交的数据
可重复读(Mysql数据库默认)
可重复读的目标是克服读写提交中出现的不可重复读的现象,因为在读写提交的时候,可能出现一些值得变化,影响当前事务的执行,这时候数据库提出了可重复读的隔离级别。
串行化(Serializable)
串行化是数据库最高的隔离级别,它会要求所有的SQL都会按照顺序执行,这样就克服了上隔离级别出现的各种问题,所以它能够完全保证数据的一致性。性能最差
在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,而不回滚当前方法的事务。RedisTemplate
应该说RedisTemplate是使用得最多的类,所以它是Spring操作Redis的重点内容。RedisTemplate是一个强大的类,首先它会自动从RedisConnectionFactory工厂中获取连接(前提是在配置文件配置了地址和端口),然后执行对应的Redis命令,在最后还会关闭Redis的连接。
序列化问题:默认情况下RedisTemplate中文数据到数据库会出现16进制的问题对应序列化器对应序列化器,Spring提供了RedisSerializer接口,它有两个方法。这两个方法,一个是serialize,它能把那些可以序列化的对象转换为二进制字符串;另一个是deserialize,它能够通过反序列化把二进制字符串转换为Java对象。
RedisTemplate中序列化器属性
如果自己要改的话: 将需要使用的类型设置为UTF-8的,获取直接使用StringRedisTemplate
序列化主要是通过serialize
和deserialize
来实现的
@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对象 |
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接口。
// 这样就可以在一个链接的情况下多次操作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;
}
});
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
Redis除了操作那些数据类型的功能外,还能支持事务、流水线、发布订阅和Lua语等功能,这些也是Redis常用的功能。在高并发的场景中,往往我们需要保证数据的一致性,这时考虑使用Redis事务或者利用Redis执行Lua的原子性来达到数据一致性的目的。
首先Redis是支持一定事务能力的NoSql,在Redis中使用事务,通常的命令组合是watch…multi…exec,也就是要一个Redis连接中执行多个命令,这时我们可以考虑使用SessionCallback接口来达到这个目的。其中,watch命令是可以监控Redis的一些键;multi命令是开始事务,开始事务后,该客户端的命令不会马上被执行,而是存放在一个队列里,这点是需要注意的地方,也就是在这时我们执行一些返回数据的命令,Redis也是不会马上执行的,而是把命令放到一个队列里,所以此时调用Redis命令,结果为null;exe命令的意义在于执行事务,只是它在队列执行前会判断被watch监控的Redis的键的数据是否发生过变化(即使赋予与之前相同的值也会被认为是改变过),如果它认为发生了变化,那么Redis就会取消事务,否则就会执行事务,Redis在执行事务时,要么全部执行,要么全部不执行,而且不会被其他客户端打断,这样就保证了Reids事务下数据的一致性。
@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在使用缓存注解前,需要配置缓存管理器,缓存管理器将提供一些重要的信息,如缓存类型、超时时间等。Spring可以支持多种缓存的使用,因此它存在多缓存处理器,并提供了缓存处理器的接口CacheManager和与之相关的类。
在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
缓存的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一开始就定位于一个较为松散的组合,展示给用户的视图(View)
、控制器返回的数据模型(Model)
、定位视图的视图解析器(ViewResolver)
、和处理适配器(HandlerAdapter)
等内容都是独立的。
HanlerMapping: 来获取到请求的路径来确定方法位置
HandlerExecutionChain: 里面有一个hanlder类,对应的有这个方法的参数
HandlerAdapter: 来解析出handler的数据
ModelAndView: 将数据和视图返回给控制器
ViewResolver: 获取到视图信息,并且找到对应的视图,给用户返回
View: 最终返回的视图
为了支持对于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
接口的方法对请求体的信息进行转换。
在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)
的,其中模型是存放数据的地方,视图则是展示给用户的。
如果在控制器方法的参数中使用ModelAndView、Model或者ModelMap作为参数类型,Spring MVC会自动创建数据模型对象。
视图是渲染数据模型展示给用户的组件,在Spring MVC中分为逻辑视图
和非逻辑视图
。
视图的顶层接口View
中有两个主要的方法getContentType
和render
方法,getContentType是获取HTTP响应类型的,它可以返回的类型是文本、JSON数据或者文件等,render方法则是将数据模型渲染到视图的,这是视图的核心方法。在它的参数中,model是数据模型,实际就是控制器(或者由处理器自动绑定)返回的数据模型。这样render方法就可以把它渲染出来。渲染视图是比较复杂的过程,为了简化视图渲染的开发,在Spring MVC中已经给开发者提供了许多开发好的视图类。
首先,DispatcherServlet会使用适配器模式。
将HttpServletRequest接口对象转换MultipartHttpServletRequest对象。MultipartHttpServletRequest接口扩展了HttpServletRequest接口的所有方法,而且定义了一些操作文件的方法,可以直接使用MultipartFile获取
在Spring AOP,可以通过通知来增强Bean的功能。同样地,Spring MVC也可以给控制器增加通知,于是在控制器方法的前后和异常发生时去执行不同的处理。
错误返回指定的页面
@ControllerAdvice
public class Demo {
@ExceptionHandler(RuntimeException.class)
public String error(Exception e){
e.printStackTrace();
return "error"; // 返回到error页面
}
}
Spring MVC提供了国际化消息源机制,那就是MessageSource接口体系.
使用:
可以把两个属性(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
第一步:在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提供了其安全框架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中的默认存在的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();
}
}
// 正常配置
@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的前后缀拼接
}
}