Java Caching定义了5个核心接口分别是CachingProvider, CacheManager, Cache, Entry 和 Expiry。html
CachingProvider定义了建立、配置、获取、管理和控制多个CacheManager。一个应用能够在运行期访问多个CachingProvider。前端
CacheManager定义了建立、配置、获取、管理和控制多个惟一命名的Cache,这些Cache存在于CacheManager的上下文中。一个CacheManager仅被一个CachingProvider所拥有。java
Cache是一个相似Map的数据结构并临时存储以Key为索引的值。一个Cache仅被一个CacheManager所拥有。mysql
Entry是一个存储在Cache中的key-value对。git
Expiry 每个存储在Cache中的条目有一个定义的有效期。一旦超过这个时间,条目为过时的状态。一旦过时,条目将不可访问、更新和删除。缓存有效期能够经过ExpiryPolicy设置。github
Spring从3.1开始定义了org.springframework.cache.Cache
和org.springframework.cache.CacheManager接口来统一不一样的缓存技术;
并支持使用JCache(JSR-107)注解简化咱们开发;
Cache接口为缓存的组件规范定义,包含缓存的各类操做集合;
Cache接口下Spring提供了各类xxxCache的实现;如RedisCache,EhCacheCache , ConcurrentMapCache等;
每次调用须要缓存功能的方法时,Spring会检查检查指定参数的指定的目标方法是否已经被调用过;若是有就直接从缓存中获取方法调用后的结果,若是没有就调用方法并缓存结果后返回给用户。下次调用直接从缓存中获取。
使用Spring缓存抽象时咱们须要关注如下两点;
一、肯定方法须要被缓存以及他们的缓存策略
二、从缓存中读取以前缓存存储的数据web
初试缓存Cache:redis
启动类:spring
package com.mikey.cache; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cache.annotation.EnableCaching; @MapperScan(value = "com.mikey.cache.mapper") @SpringBootApplication @EnableCaching//开启缓存 public class Springboot01CacheApplication { public static void main(String[] args) { SpringApplication.run(Springboot01CacheApplication.class, args); } }
配置文件:sql
spring.datasource.url=jdbc:mysql://localhost:3306/spring_cache spring.datasource.username=root spring.datasource.password=root #spring.datasource.driver-class-name=com.mysql.jdbc.Driver mybatis.configuration.multiple-result-sets-enabled=true logging.level.com.mikey.cache.mapper=debug
Mapper:
package com.mikey.cache.mapper; import com.mikey.cache.bean.Employee; import org.apache.ibatis.annotations.*; import org.springframework.stereotype.Component; /** * @author Mikey * @Title: * @Description: * @date 2018/10/25 22:40 * @Version 1.0 */ @Component @Mapper public interface EmployeeMapper { @Select("select * from employee where id=#{id}") public Employee getEmpById(Integer id); @Update("update employee set lastName=#{lastName},email=#{email},gender=#{gender},d_id=#{d_id} where id=#{id}") public void updateEmp(Employee employee); @Delete("Delete from employee where id=#{id}") public void deleteEmpById(Integer id); @Insert("insert employee(lastName,email,gender,d_id) values(#{lastName},#{email},#{gender},#{dId}") public void insertEmployee(Employee employee); }
Service:
package com.mikey.cache.service; import com.mikey.cache.bean.Employee; import com.mikey.cache.mapper.EmployeeMapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; /** * @author Mikey * @Title: * @Description: * @date 2018/10/25 22:58 * @Version 1.0 */ @Service public class EmployeeService { @Autowired EmployeeMapper employeeMapper; /** * 将方法的运行结果进行缓存 * @param id * @return */ // @Cacheable(cacheNames = "emp",key = "#id") @Cacheable(cacheNames = "emp",condition = "#id>0",unless = "#result==null") public Employee getEmp(Integer id){ System.out.println("查询"+id+"号员工"); Employee employee=employeeMapper.getEmpById(id); return employee; } }
Controller:
package com.mikey.cache.controller; import com.mikey.cache.bean.Employee; import com.mikey.cache.service.EmployeeService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * @author Mikey * @Title: * @Description: * @date 2018/10/25 23:00 * @Version 1.0 */ @RestController public class EmployeeController { @Autowired EmployeeService employeeService; @RequestMapping("/emp/{id}") public Employee getEmployee(@PathVariable("id") Integer id){ return employeeService.getEmp(id); } }
将方法的运行结果进行缓存;之后再要相同的数据,直接从缓存中获取,不用调用方法; CacheManager管理多个Cache组件的,对缓存的真正CRUD操做在Cache组件中,每个缓存组件有本身惟一一个名字; 原理: 1、自动配置类;CacheAutoConfiguration 2、缓存的配置类 org.springframework.boot.autoconfigure.cache.GenericCacheConfiguration org.springframework.boot.autoconfigure.cache.JCacheCacheConfiguration org.springframework.boot.autoconfigure.cache.EhCacheCacheConfiguration org.springframework.boot.autoconfigure.cache.HazelcastCacheConfiguration org.springframework.boot.autoconfigure.cache.InfinispanCacheConfiguration org.springframework.boot.autoconfigure.cache.CouchbaseCacheConfiguration org.springframework.boot.autoconfigure.cache.RedisCacheConfiguration org.springframework.boot.autoconfigure.cache.CaffeineCacheConfiguration org.springframework.boot.autoconfigure.cache.GuavaCacheConfiguration org.springframework.boot.autoconfigure.cache.SimpleCacheConfiguration【默认】 org.springframework.boot.autoconfigure.cache.NoOpCacheConfiguration 三、哪一个配置类默认生效:SimpleCacheConfiguration;
4、给容器中注册了一个CacheManager:ConcurrentMapCacheManager 5、能够获取和建立ConcurrentMapCache类型的缓存组件;他的做用将数据保存在ConcurrentMap中; 运行流程: @Cacheable: 1、方法运行以前,先去查询Cache(缓存组件),按照cacheNames指定的名字获取; (CacheManager先获取相应的缓存),第一次获取缓存若是没有Cache组件会自动建立。 2、去Cache中查找缓存的内容,使用一个key,默认就是方法的参数; key是按照某种策略生成的;默认是使用keyGenerator生成的,默认使用SimpleKeyGenerator生成key; SimpleKeyGenerator生成key的默认策略; 若是没有参数;key=new SimpleKey(); 若是有一个参数:key=参数的值 若是有多个参数:key=new SimpleKey(params); 3、没有查到缓存就调用目标方法; 4、将目标方法返回的结果,放进缓存中 @Cacheable标注的方法执行以前先来检查缓存中有没有这个数据,默认按照参数的值做为key去查询缓存, 若是没有就运行方法并将结果放入缓存;之后再来调用就能够直接使用缓存中的数据; 核心: 1)、使用CacheManager【ConcurrentMapCacheManager】按照名字获得Cache【ConcurrentMapCache】组件 2)、key使用keyGenerator生成的,默认是SimpleKeyGenerator 几个属性: cacheNames/value:指定缓存组件的名字;将方法的返回结果放在哪一个缓存中,是数组的方式,能够指定多个缓存; key:缓存数据使用的key;能够用它来指定。默认是使用方法参数的值 1-方法的返回值 编写SpEL; #i d;参数id的值 #a0 #p0 #root.args[0] getEmp[2] keyGenerator:key的生成器;能够本身指定key的生成器的组件id key/keyGenerator:二选一使用; cacheManager:指定缓存管理器;或者cacheResolver指定获取解析器 condition:指定符合条件的状况下才缓存; ,condition = "#id>0" condition = "#a0>1":第一个参数的值》1的时候才进行缓存 unless:否认缓存;当unless指定的条件为true,方法的返回值就不会被缓存;能够获取到结果进行判断 unless = "#result == null" unless = "#a0==2":若是第一个参数的值是2,结果不缓存; sync:是否使用异步模式
package com.mikey.cache.config; import org.springframework.cache.interceptor.KeyGenerator; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.lang.reflect.Method; import java.util.Arrays; /** * @author Mikey * @Title: * @Description: * @date 2018/10/26 15:21 * @Version 1.0 */ @Configuration public class MyCacheConfig { @Bean("myKeyGenerator") public KeyGenerator keyGenerator(){ return new KeyGenerator(){ @Override public Object generate(Object target, Method method, Object... params) { return method.getName()+"["+ Arrays.asList(params).toString()+"]"; } }; } }
注意:使用异步不支持unless
/** * @CachePut:既调用方法,又更新缓存数据;同步更新缓存 * 修改了数据库的某个数据,同时更新缓存; * 运行时机: * 一、先调用目标方法 * 二、将目标方法的结果缓存起来 * * 测试步骤: * 一、查询1号员工;查到的结果会放在缓存中; * key:1 value:lastName:张三 * 二、之后查询仍是以前的结果 * 三、更新1号员工;【lastName:zhangsan;gender:0】 * 将方法的返回值也放进缓存了; * key:传入的employee对象 值:返回的employee对象; * 四、查询1号员工? * 应该是更新后的员工; * key = "#employee.id":使用传入的参数的员工id; * key = "#result.id":使用返回后的id * @Cacheable的key是不能用#result * 为何是没更新前的?【1号员工没有在缓存中更新】 * */ @CachePut(/*value = "emp",*/key = "#result.id") public Employee updateEmp(Employee employee){ System.out.println("updateEmp:"+employee); employeeMapper.updateEmp(employee); return employee; }
参考:http://www.bubuko.com/infodetail-2378163.html
key:指定要清除的数据
allEntries = true:指定清除这个缓存中全部的数据
beforeInvocation = false:缓存的清除是否在方法以前执行
默认表明缓存清除操做是在方法执行以后执行;若是出现异常缓存就不会清除
beforeInvocation = true:
表明清除缓存操做是在方法运行以前执行,不管方法是否出现异常,缓存都清除
@CacheEvict(value="emp",beforeInvocation = true/*key = "#id",*/) public void deleteEmp(Integer id){ System.out.println("deleteEmp:"+id); //employeeMapper.deleteEmpById(id); int i = 10/0; }
// @Caching 定义复杂的缓存规则 @Caching( cacheable = { @Cacheable(/*value="emp",*/key = "#lastName") }, put = { @CachePut(/*value="emp",*/key = "#result.id"), @CachePut(/*value="emp",*/key = "#result.email") } ) public Employee getEmpByLastName(String lastName){ return employeeMapper.getEmpByLastName(lastName); }
/* * Copyright 2002-2015 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.cache.annotation; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * {@code @CacheConfig} provides a mechanism for sharing common cache-related * settings at the class level. * * <p>When this annotation is present on a given class, it provides a set * of default settings for any cache operation defined in that class. * * @author Stephane Nicoll * @author Sam Brannen * @since 4.1 */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface CacheConfig { /** * Names of the default caches to consider for caching operations defined * in the annotated class. * <p>If none is set at the operation level, these are used instead of the default. * <p>May be used to determine the target cache (or caches), matching the * qualifier value or the bean names of a specific bean definition. */ String[] cacheNames() default {}; /** * The bean name of the default {@link org.springframework.cache.interceptor.KeyGenerator} to * use for the class. * <p>If none is set at the operation level, this one is used instead of the default. * <p>The key generator is mutually exclusive with the use of a custom key. When such key is * defined for the operation, the value of this key generator is ignored. */ String keyGenerator() default ""; /** * The bean name of the custom {@link org.springframework.cache.CacheManager} to use to * create a default {@link org.springframework.cache.interceptor.CacheResolver} if none * is set already. * <p>If no resolver and no cache manager are set at the operation level, and no cache * resolver is set via {@link #cacheResolver}, this one is used instead of the default. * @see org.springframework.cache.interceptor.SimpleCacheResolver */ String cacheManager() default ""; /** * The bean name of the custom {@link org.springframework.cache.interceptor.CacheResolver} to use. * <p>If no resolver and no cache manager are set at the operation level, this one is used * instead of the default. */ String cacheResolver() default ""; }
完整文件:
package com.atguigu.cache.service; import com.atguigu.cache.bean.Employee; import com.atguigu.cache.mapper.EmployeeMapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.annotation.*; import org.springframework.stereotype.Service; @CacheConfig(cacheNames="emp"/*,cacheManager = "employeeCacheManager"*/) //抽取缓存的公共配置 @Service public class EmployeeService { @Autowired EmployeeMapper employeeMapper; /** * 将方法的运行结果进行缓存;之后再要相同的数据,直接从缓存中获取,不用调用方法; * CacheManager管理多个Cache组件的,对缓存的真正CRUD操做在Cache组件中,每个缓存组件有本身惟一一个名字; * * * 原理: * 一、自动配置类;CacheAutoConfiguration * 二、缓存的配置类 * org.springframework.boot.autoconfigure.cache.GenericCacheConfiguration * org.springframework.boot.autoconfigure.cache.JCacheCacheConfiguration * org.springframework.boot.autoconfigure.cache.EhCacheCacheConfiguration * org.springframework.boot.autoconfigure.cache.HazelcastCacheConfiguration * org.springframework.boot.autoconfigure.cache.InfinispanCacheConfiguration * org.springframework.boot.autoconfigure.cache.CouchbaseCacheConfiguration * org.springframework.boot.autoconfigure.cache.RedisCacheConfiguration * org.springframework.boot.autoconfigure.cache.CaffeineCacheConfiguration * org.springframework.boot.autoconfigure.cache.GuavaCacheConfiguration * org.springframework.boot.autoconfigure.cache.SimpleCacheConfiguration【默认】 * org.springframework.boot.autoconfigure.cache.NoOpCacheConfiguration * 三、哪一个配置类默认生效:SimpleCacheConfiguration; * * 四、给容器中注册了一个CacheManager:ConcurrentMapCacheManager * 五、能够获取和建立ConcurrentMapCache类型的缓存组件;他的做用将数据保存在ConcurrentMap中; * * 运行流程: * @Cacheable: * 一、方法运行以前,先去查询Cache(缓存组件),按照cacheNames指定的名字获取; * (CacheManager先获取相应的缓存),第一次获取缓存若是没有Cache组件会自动建立。 * 二、去Cache中查找缓存的内容,使用一个key,默认就是方法的参数; * key是按照某种策略生成的;默认是使用keyGenerator生成的,默认使用SimpleKeyGenerator生成key; * SimpleKeyGenerator生成key的默认策略; * 若是没有参数;key=new SimpleKey(); * 若是有一个参数:key=参数的值 * 若是有多个参数:key=new SimpleKey(params); * 三、没有查到缓存就调用目标方法; * 四、将目标方法返回的结果,放进缓存中 * * @Cacheable标注的方法执行以前先来检查缓存中有没有这个数据,默认按照参数的值做为key去查询缓存, * 若是没有就运行方法并将结果放入缓存;之后再来调用就能够直接使用缓存中的数据; * * 核心: * 1)、使用CacheManager【ConcurrentMapCacheManager】按照名字获得Cache【ConcurrentMapCache】组件 * 2)、key使用keyGenerator生成的,默认是SimpleKeyGenerator * * * 几个属性: * cacheNames/value:指定缓存组件的名字;将方法的返回结果放在哪一个缓存中,是数组的方式,能够指定多个缓存; * * key:缓存数据使用的key;能够用它来指定。默认是使用方法参数的值 1-方法的返回值 * 编写SpEL; #i d;参数id的值 #a0 #p0 #root.args[0] * getEmp[2] * * keyGenerator:key的生成器;能够本身指定key的生成器的组件id * key/keyGenerator:二选一使用; * * * cacheManager:指定缓存管理器;或者cacheResolver指定获取解析器 * * condition:指定符合条件的状况下才缓存; * ,condition = "#id>0" * condition = "#a0>1":第一个参数的值》1的时候才进行缓存 * * unless:否认缓存;当unless指定的条件为true,方法的返回值就不会被缓存;能够获取到结果进行判断 * unless = "#result == null" * unless = "#a0==2":若是第一个参数的值是2,结果不缓存; * sync:是否使用异步模式 * @param id * @return * */ @Cacheable(value = {"emp"}/*,keyGenerator = "myKeyGenerator",condition = "#a0>1",unless = "#a0==2"*/) public Employee getEmp(Integer id){ System.out.println("查询"+id+"号员工"); Employee emp = employeeMapper.getEmpById(id); return emp; } /** * @CachePut:既调用方法,又更新缓存数据;同步更新缓存 * 修改了数据库的某个数据,同时更新缓存; * 运行时机: * 一、先调用目标方法 * 二、将目标方法的结果缓存起来 * * 测试步骤: * 一、查询1号员工;查到的结果会放在缓存中; * key:1 value:lastName:张三 * 二、之后查询仍是以前的结果 * 三、更新1号员工;【lastName:zhangsan;gender:0】 * 将方法的返回值也放进缓存了; * key:传入的employee对象 值:返回的employee对象; * 四、查询1号员工? * 应该是更新后的员工; * key = "#employee.id":使用传入的参数的员工id; * key = "#result.id":使用返回后的id * @Cacheable的key是不能用#result * 为何是没更新前的?【1号员工没有在缓存中更新】 * */ @CachePut(/*value = "emp",*/key = "#result.id") public Employee updateEmp(Employee employee){ System.out.println("updateEmp:"+employee); employeeMapper.updateEmp(employee); return employee; } /** * @CacheEvict:缓存清除 * key:指定要清除的数据 * allEntries = true:指定清除这个缓存中全部的数据 * beforeInvocation = false:缓存的清除是否在方法以前执行 * 默认表明缓存清除操做是在方法执行以后执行;若是出现异常缓存就不会清除 * * beforeInvocation = true: * 表明清除缓存操做是在方法运行以前执行,不管方法是否出现异常,缓存都清除 * * */ @CacheEvict(value="emp",beforeInvocation = true/*key = "#id",*/) public void deleteEmp(Integer id){ System.out.println("deleteEmp:"+id); //employeeMapper.deleteEmpById(id); int i = 10/0; } // @Caching 定义复杂的缓存规则 @Caching( cacheable = { @Cacheable(/*value="emp",*/key = "#lastName") }, put = { @CachePut(/*value="emp",*/key = "#result.id"), @CachePut(/*value="emp",*/key = "#result.email") } ) public Employee getEmpByLastName(String lastName){ return employeeMapper.getEmpByLastName(lastName); } }
引入spring-boot-starter-data-redis
application.yml配置redis链接地址
使用RestTemplate操做redis
redisTemplate.opsForValue();//操做字符串
redisTemplate.opsForHash();//操做hash
redisTemplate.opsForList();//操做list
redisTemplate.opsForSet();//操做set
redisTemplate.opsForZSet();//操做有序set
配置缓存、CacheManagerCustomizers
测试使用缓存、切换缓存、 CompositeCacheManager
安装镜像:
链接:
引入redis启动器:
官网:
配置redis:
package com.mikey.cache; import com.mikey.cache.bean.Employee; import com.mikey.cache.mapper.EmployeeMapper; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.test.context.junit4.SpringRunner; @RunWith(SpringRunner.class) @SpringBootTest public class Springboot01CacheApplicationTests { @Autowired EmployeeMapper employeeMapper; @Autowired StringRedisTemplate stringRedisTemplate;//操做字符串 @Autowired RedisTemplate redisTemplate;//k-v都是对象 @Test public void contextLoads() { Employee employee=employeeMapper.getEmpById(1); System.out.println("Message="+employee); } @Test public void testRedis(){ // stringRedisTemplate.opsForValue().append("msg","hello"); // String msg = stringRedisTemplate.opsForValue().get("msg"); // System.out.println("Message="+msg); stringRedisTemplate.opsForList().leftPush("mylist","1"); stringRedisTemplate.opsForList().leftPush("mylist","2"); } @Test public void testObjectRedis(){ Employee employee=employeeMapper.getEmpById(1); redisTemplate.opsForValue().set("emp-01",employee); } }
将数据以json储存:
方法1:将数据直接转成json
方法2:配置:
配置类:
package com.mikey.cache.config; import com.mikey.cache.bean.Employee; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; /** * @author Mikey * @Title: * @Description: * @date 2018/10/26 19:45 * @Version 1.0 */ @Configuration public class MyRedisConfig { @Bean public RedisTemplate<Object, Employee> empredisTemplate( RedisConnectionFactory redisConnectionFactory) throws Exception{ RedisTemplate<Object,Employee> template=new RedisTemplate<Object, Employee>(); template.setConnectionFactory(redisConnectionFactory); Jackson2JsonRedisSerializer<Employee> ser=new Jackson2JsonRedisSerializer<Employee>(Employee.class); template.setDefaultSerializer(ser); return template; } }
测试类:
结果:
配置redis的json格式:
package com.mikey.cache.config; import com.mikey.cache.bean.Employee; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.cache.RedisCacheManager; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; /** * @author Mikey * @Title: * @Description: * @date 2018/10/26 19:45 * @Version 1.0 */ @Configuration public class MyRedisConfig { @Bean public RedisTemplate<Object, Employee> empredisTemplate( RedisConnectionFactory redisConnectionFactory) throws Exception{ RedisTemplate<Object,Employee> template=new RedisTemplate<Object, Employee>(); template.setConnectionFactory(redisConnectionFactory); Jackson2JsonRedisSerializer<Employee> ser=new Jackson2JsonRedisSerializer<Employee>(Employee.class); template.setDefaultSerializer(ser); return template; } @Bean public RedisCacheManager empoyeeCacheManager(RedisTemplate<Object,Employee> employeeRedisTemplate){ RedisCacheManager redisCacheManager=new RedisCacheManager(employeeRedisTemplate); redisCacheManager.setUsePrefix(true); return redisCacheManager; } }
序列号及反序列化:
package com.mikey.cache.config; import com.mikey.cache.bean.Department; import com.mikey.cache.bean.Employee; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import org.springframework.data.redis.cache.RedisCacheManager; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; /** * @author Mikey * @Title: * @Description: * @date 2018/10/26 19:45 * @Version 1.0 */ @Configuration public class MyRedisConfig { @Bean public RedisTemplate<Object, Employee> empredisTemplate( RedisConnectionFactory redisConnectionFactory) throws Exception{ RedisTemplate<Object,Employee> template=new RedisTemplate<Object, Employee>(); template.setConnectionFactory(redisConnectionFactory); Jackson2JsonRedisSerializer<Employee> ser=new Jackson2JsonRedisSerializer<Employee>(Employee.class); template.setDefaultSerializer(ser); return template; } @Bean public RedisTemplate<Object, Department> deptredisTemplate( RedisConnectionFactory redisConnectionFactory) throws Exception{ RedisTemplate<Object,Department> template=new RedisTemplate<Object, Department>(); template.setConnectionFactory(redisConnectionFactory); Jackson2JsonRedisSerializer<Department> ser=new Jackson2JsonRedisSerializer<Department>(Department.class); template.setDefaultSerializer(ser); return template; } @Primary//必须设置一个默认的 @Bean public RedisCacheManager empoyeeCacheManager(RedisTemplate<Object,Employee> employeeRedisTemplate){ RedisCacheManager redisCacheManager=new RedisCacheManager(employeeRedisTemplate); redisCacheManager.setUsePrefix(true); return redisCacheManager; } @Bean public RedisCacheManager deptCacheManager(RedisTemplate<Object,Department> deptloyeeRedisTemplate){ RedisCacheManager redisCacheManager=new RedisCacheManager(deptloyeeRedisTemplate); redisCacheManager.setUsePrefix(true); return redisCacheManager; } }
/** * @author Mikey * @Title: * @Description: * @date 2018/10/26 20:53 * @Version 1.0 */ @RestController public class DeptController { @Autowired @Qualifier("deptCacheManager") private RedisCacheManager deptCacheManager; @Autowired private DeptService deptService; @GetMapping("/dept/{id}") public Department getDeptById(@PathVariable("id") Integer id){ return deptService.getDeptById(id); } @GetMapping("/depts/{id}") public Department getDeptByIds(@PathVariable("id") Integer id){ System.out.println("查询部门"); Department department=deptService.getDeptById(1); Cache dept = deptCacheManager.getCache("dept"); dept.put("dept:1",department); return department; } }
大多应用中,可经过消息服务中间件来提高系统异步通讯、扩展解耦能力
消息服务中两个重要概念:
消息代理(message broker)和目的地(destination)
当消息发送者发送消息之后,将由消息代理接管,消息代理保证消息传递到指定目的地。
消息队列主要有两种形式的目的地
队列(queue):点对点消息通讯(point-to-point)
主题(topic):发布(publish)/订阅(subscribe)消息通讯
消息发送者发送消息,消息代理将其放入一个队列中,消息接收者从队列中获取消息内容,消息读取后被移出队列
消息只有惟一的发送者和接受者,但并非说只能有一个接收者
发送者(发布者)发送消息到主题,多个接收者(订阅者)监听(订阅)这个主题,那么就会在消息到达时同时收到消息
基于JVM消息代理的规范。ActiveMQ、HornetMQ是JMS实现
高级消息队列协议,也是一个消息代理的规范,兼容JMS
RabbitMQ是AMQP的实现
spring-jms提供了对JMS的支持
spring-rabbit提供了对AMQP的支持
须要ConnectionFactory的实现来链接消息代理
提供JmsTemplate、RabbitTemplate来发送消息
@JmsListener(JMS)、@RabbitListener(AMQP)注解在方法上监听消息代理发布的消息
@EnableJms、@EnableRabbit开启支持
JmsAutoConfiguration
RabbitAutoConfiguration
RabbitMQ简介:
RabbitMQ是一个由erlang开发的AMQP(Advanved Message Queue Protocol)的开源实现。
核心概念
Message
消息,消息是不具名的,它由消息头和消息体组成。消息体是不透明的,而消息头则由一系列的可选属性组成,这些属性包括routing-key(路由键)、
priority(相对于其余消息的优先权)、delivery-mode(指出该消息可能须要持久性存储)等。
Publisher
消息的生产者,也是一个向交换器发布消息的客户端应用程序。
Exchange
交换器,用来接收生产者发送的消息并将这些消息路由给服务器中的队列。
Exchange有4种类型:direct(默认),fanout, topic, 和headers,不一样类型的Exchange转发消息的策略有所区别
Queue
消息队列,用来保存消息直到发送给消费者。它是消息的容器,也是消息的终点。一个消息可投入一个或多个队列。
消息一直在队列里面,等待消费者链接到这个队列将其取走。
Binding
绑定,用于消息队列和交换器之间的关联。一个绑定就是基于路由键将交换器和消息队列链接起来的路由规则,
因此能够将交换器理解成一个由绑定构成的路由表。
Exchange 和Queue的绑定能够是多对多的关系。
Connection
网络链接,好比一个TCP链接。
Channel
信道,多路复用链接中的一条独立的双向数据流通道。信道是创建在真实的TCP链接内的虚拟链接,AMQP 命令都是经过信道发出去的,
无论是发布消息、订阅队列仍是接收消息,这些动做都是经过信道完成。由于对于操做系统来讲创建和销毁 TCP 都是很是昂贵的开销,
因此引入了信道的概念,以复用一条 TCP 链接。
Consumer
消息的消费者,表示一个从消息队列中取得消息的客户端应用程序。
Virtual Host
虚拟主机,表示一批交换器、消息队列和相关对象。虚拟主机是共享相同的身份认证和加密环境的独立服务器域。
每一个 vhost 本质上就是一个 mini 版的 RabbitMQ 服务器,拥有本身的队列、交换器、绑定和权限机制。
vhost 是 AMQP 概念的基础,必须在链接时指定,RabbitMQ 默认的 vhost 是 / 。
Broker
表示消息队列服务器实体
AMQP 中的消息路由
AMQP 中消息的路由过程和 Java 开发者熟悉的 JMS 存在一些差异,AMQP 中增长了 Exchange 和 Binding 的角色。生产者把消息发布到 Exchange 上,
消息最终到达队列并被消费者接收,而 Binding 决定交换器的消息应该发送到那个队列。
Exchange分发消息时根据类型的不一样分发策略有区别,目前共四种类型:
direct、fanout、topic、headers 。headers 匹配 AMQP 消息的 header 而不是路由键,
headers 交换器和 direct 交换器彻底一致,但性能差不少,目前几乎用不到了,因此直接看另外三种类型:
每一个发到 fanout 类型交换器的消息都会分到全部绑定的队列上去。fanout 交换器不处理路由键,
只是简单的将队列绑定到交换器上,每一个发送到交换器的消息都会被转发到与该交换器绑定的全部队列上。
很像子网广播,每台子网内的主机都得到了一份复制的消息。fanout 类型转发消息是最快的。
topic 交换器经过模式匹配分配消息的路由键属性,将路由键和某个模式进行匹配,
此时队列须要绑定到一个模式上。它将路由键和绑定键的字符串切分红单词,这些单词之间用点隔开。
它一样也会识别两个通配符:符号“#”和符号“*”。#匹配0个或多个单词,*匹配一个单词。
引入 spring-boot-starter-amqp
application.yml配置
测试RabbitMQ
AmqpAdmin:管理组件
RabbitTemplate:消息发送处理组件
没法访问管理页面?
自动配置
一、RabbitAutoConfiguration
二、有自动配置了链接工厂ConnectionFactory;
三、RabbitProperties 封装了 RabbitMQ的配置
四、 RabbitTemplate :给RabbitMQ发送和接受消息;
五、 AmqpAdmin : RabbitMQ系统管理功能组件;
AmqpAdmin:建立和删除 Queue,Exchange,Binding
六、@EnableRabbit + @RabbitListener 监听消息队列的内容
1.利用idea的spring初始化器建立应用选中RabbitMq模块
2.配置文件:
spring.rabbitmq.addresses=47.106.210.183 spring.rabbitmq.username=guest spring.rabbitmq.password=guest #spring.rabbitmq.port=5672//默认5672 #spring.rabbitmq.virtual-host=
3.测试:
package com.mikey.springbootamqp; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import java.util.Arrays; import java.util.HashMap; import java.util.Map; @RunWith(SpringRunner.class) @SpringBootTest public class SpringbootAmqpApplicationTests { @Autowired private RabbitTemplate rabbitTemplate; @Test public void contextLoads() { // rabbitTemplate.send(exchange,routeKey,message); Map<String,Object> map=new HashMap<>(); map.put("msg","这是第一个消息"); map.put("data", Arrays.asList("helloworld",123,true)); rabbitTemplate.convertAndSend("exchange.direct","atguigu.news",map); } @Test public void receive(){ Object o = rabbitTemplate.receiveAndConvert("atguigu.news"); System.out.println("数据类型="+o.getClass()); System.out.println("数据="+o); } }
自定义messageconveter(json格式)
package com.mikey.springbootamqp.config; import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter; import org.springframework.amqp.support.converter.MessageConverter; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * @author Mikey * @Title: * @Description: * @date 2018/10/27 10:26 * @Version 1.0 */ @Configuration public class MyAMQPConfig { @Bean public MessageConverter messageConverter(){ return new Jackson2JsonMessageConverter(); } }
结果:
测试:
package com.mikey.springbootamqp; import com.mikey.springbootamqp.bean.Book; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import java.util.Arrays; import java.util.HashMap; import java.util.Map; @RunWith(SpringRunner.class) @SpringBootTest public class SpringbootAmqpApplicationTests { @Autowired private RabbitTemplate rabbitTemplate; @Test public void contextLoads() { // rabbitTemplate.send(exchange,routeKey,message); Map<String,Object> map=new HashMap<>(); map.put("msg","这是第一个消息"); map.put("data", Arrays.asList("helloworld",123,true)); rabbitTemplate.convertAndSend("exchange.direct","atguigu.news",map); } @Test public void receive(){ Object o = rabbitTemplate.receiveAndConvert("atguigu.news"); System.out.println("数据类型="+o.getClass()); System.out.println("数据="+o); } /** * 发送javaBean */ @Test public void testBeanSend(){ Book book = new Book("阿姆斯特朗", "回旋喷气式加速炮"); System.out.println("Book="+book); rabbitTemplate.convertAndSend("exchange.direct","atguigu.news",book); } /** * 接收对象 */ @Test public void getBeanSend(){ Book book = (Book) rabbitTemplate.receiveAndConvert("atguigu.news"); System.out.println("messsage="+book); } /** * 广播发送 */ @Test public void sendAll(){ rabbitTemplate.convertAndSend("exchange.fanout","",new Book("麦奇","麦奇")); } }
消息监听器:
启动类添加注解:
2.编写监听器:
package com.mikey.springbootamqp; import com.mikey.springbootamqp.bean.Book; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.amqp.core.AmqpAdmin; import org.springframework.amqp.core.Binding; import org.springframework.amqp.core.DirectExchange; import org.springframework.amqp.core.Queue; import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import java.util.Arrays; import java.util.HashMap; import java.util.Map; @RunWith(SpringRunner.class) @SpringBootTest public class SpringbootAmqpApplicationTests { @Autowired private RabbitTemplate rabbitTemplate; @Autowired private AmqpAdmin amqpAdmin;//操做 /** * 添加Exchange */ @Test public void createExchange(){ amqpAdmin.declareExchange(new DirectExchange("amqpadmin.exchange")); System.out.println("建立完成"); } /** * 添加队列 */ @Test public void createQueue(){ amqpAdmin.declareQueue(new Queue("amqpadmin.queue")); System.out.println("建立队列成功"); } /** * 添加绑定 */ @Test public void createBinding(){ amqpAdmin.declareBinding(new Binding("amqpadmin.queue",Binding.DestinationType.QUEUE,"amqpadmin.exchange","ampq.haha",null)); } }
咱们的应用常常须要添加检索功能,开源的 ElasticSearch 是目前全文搜索引擎的首选。他能够快速的存储、搜索和分析海量数据。
Spring Boot经过整合Spring Data ElasticSearch为咱们提供了很是便捷的检索功能支持;
Elasticsearch是一个分布式搜索服务,提供Restful API,底层基于Lucene,采用多shard(分片)的方式保证数据安全,
而且提供自动resharding的功能,github等大型的站点也是采用了ElasticSearch做为其搜索服务,
docker安装:elasticSearch
docker运行命令:
docker run -e ES_JAVA_OPTS="-Xms256m -Xmx256m" -d -p 9200:9200 -p 9300:9300 --name ES01 5acf0e8da90b
限制堆空间内存,elasticSearch默认占用2G
启动成功:
学习文档:https://www.elastic.co/guide/cn/elasticsearch/guide/current/query-dsl-intro.html
概念:
以 员工文档 的形式存储为例:
一个文档表明一个员工数据。存储数据到 ElasticSearch 的行为叫作 索引 ,
但在索引一个文档以前,须要肯定将文档存储在哪里。
一个 ElasticSearch 集群能够 包含多个 索引 ,相应的每一个索引能够包含多个 类型 。
这些不一样的类型存储着多个 文档 ,每一个文档又有 多个 属性 。
相似关系:
索引-数据库
类型-表
文档-表中的记录
属性-列
3、整合ElasticSearch测试
引入spring-boot-starter-data-elasticsearch
安装Spring Data 对应版本的ElasticSearch
application.yml配置
Spring Boot自动配置的
ElasticsearchRepository、ElasticsearchTemplate、Jest
测试ElasticSearch
/** * SpringBoot默认支持两种技术来和ES交互; * 一、Jest(默认不生效) * 须要导入jest的工具包(io.searchbox.client.JestClient) * 二、SpringData ElasticSearch【ES版本有可能不合适】 * 版本适配说明:https://github.com/spring-projects/spring-data-elasticsearch * 若是版本不适配:2.4.6 * 1)、升级SpringBoot版本 * 2)、安装对应版本的ES * * 1)、Client 节点信息clusterNodes;clusterName * 2)、ElasticsearchTemplate 操做es * 3)、编写一个 ElasticsearchRepository 的子接口来操做ES; * 两种用法:https://github.com/spring-projects/spring-data-elasticsearch * 1)、编写一个 ElasticsearchRepository */
第一种:
配置文件:
先使用jest:
测试类:
package com.mikey.springbootelasticsearch; import com.mikey.springbootelasticsearch.bean.Article; import io.searchbox.client.JestClient; import io.searchbox.core.Index; import io.searchbox.core.Search; import io.searchbox.core.SearchResult; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import java.io.IOException; @RunWith(SpringRunner.class) @SpringBootTest public class SpringbootelasticsearchApplicationTests { @Autowired JestClient jestClient; @Test public void contextLoads() throws IOException { Article article = new Article(); article.setId(1); article.setTitle("ElasticSearch"); article.setAuthor("阿姆斯特朗炮"); article.setContent("Hello world"); Index build = new Index.Builder(article).index("atguigu").type("news").build();//构建一个索引功能 jestClient.execute(build); } /** * 测试搜索 */ @Test public void search() throws IOException { String json="{\n"+ " \"query\" :{\n"+ " \"match\" :{\n"+ " \"content\" : \"hello\"\n"+ " }\n"+ " }\n"+ "}"; Search build = new Search.Builder(json).addIndex("atguigu").addType("news").build(); SearchResult execute = jestClient.execute(build); System.out.println("Message="+execute.getJsonString()); } }
参考文档:https://github.com/searchbox-io/Jest/tree/master/jest
第二种:使用spring-boot-starter-data-elasticsearch
引入:在pom文件中spring-boot-starter-data-elasticsearch
配置文件:
编写bean:
package com.mikey.springbootelasticsearch.bean; import org.springframework.data.elasticsearch.annotations.Document; /** * @author Mikey * @Title: * @Description: * @date 2018/10/27 16:00 * @Version 1.0 */ @Document(indexName = "atguigu",type = "book") public class Book { private Integer id; private String bookName; private String author; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getBookName() { return bookName; } public void setBookName(String bookName) { this.bookName = bookName; } public String getAuthor() { return author; } public void setAuthor(String author) { this.author = author; } @Override public String toString() { return "Book{" + "id=" + id + ", bookName='" + bookName + '\'' + ", author='" + author + '\'' + '}'; } }
编写接口:
package com.atguigu.elastic.repository; import com.atguigu.elastic.bean.Book; import org.springframework.data.elasticsearch.repository.ElasticsearchRepository; import java.util.List; public interface BookRepository extends ElasticsearchRepository<Book,Integer> { //参照 // https://docs.spring.io/spring-data/elasticsearch/docs/3.0.6.RELEASE/reference/html/ public List<Book> findByBookNameLike(String bookName); }
测试类:
@RunWith(SpringRunner.class) @SpringBootTest public class Springboot03ElasticApplicationTests { @Autowired JestClient jestClient; @Autowired BookRepository bookRepository; @Test public void test02(){ // Book book = new Book(); // book.setId(1); // book.setBookName("西游记"); // book.setAuthor("吴承恩"); // bookRepository.index(book); for (Book book : bookRepository.findByBookNameLike("游")) { System.out.println(book); } ; } }
注意:要选择对应的版本否则会报链接超时异常:
参考文档:https://docs.spring.io/spring-data/elasticsearch/docs/3.0.6.RELEASE/reference/html/
在Java应用中,绝大多数状况下都是经过同步的方式来实现交互处理的;可是在处理与第三方系统交互的时候,
容易形成响应迟缓的状况,以前大部分都是使用多线程来完成此类任务,其实,在Spring 3.x以后,
就已经内置了@Async来完美解决这个问题。
两个注解:
@EnableAysnc、@Aysnc
启动类添加:
方法上:
项目开发中常常须要执行一些定时任务,好比须要在天天凌晨时候,分析一次前一天的日志信息。
Spring为咱们提供了异步执行任务调度的方式,提供TaskExecutor 、TaskScheduler 接口。
两个注解:@EnableScheduling、@Scheduled
cron表达式:
代码实现:
启动类加入@EnableScheduling注解
import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; @Service public class ScheduledService { /** * second(秒), minute(分), hour(时), day of month(日), month(月), day of week(周几). * 0 * * * * MON-FRI * 【0 0/5 14,18 * * ?】 天天14点整,和18点整,每隔5分钟执行一次 * 【0 15 10 ? * 1-6】 每月的周一至周六10:15分执行一次 * 【0 0 2 ? * 6L】每月的最后一个周六凌晨2点执行一次 * 【0 0 2 LW * ?】每月的最后一个工做日凌晨2点执行一次 * 【0 0 2-4 ? * 1#1】每月的第一个周一凌晨2点到4点期间,每一个整点都执行一次; */ // @Scheduled(cron = "0 * * * * MON-SAT") //@Scheduled(cron = "0,1,2,3,4 * * * * MON-SAT") // @Scheduled(cron = "0-4 * * * * MON-SAT") @Scheduled(cron = "0/4 * * * * MON-SAT") //每4秒执行一次 public void hello(){ System.out.println("hello ... "); } }
邮件发送须要引入spring-boot-starter-mail
Spring Boot 自动配置MailSenderAutoConfiguration
定义MailProperties内容,配置在application.yml中
自动装配JavaMailSender
测试邮件发送
代码操做:
1.映入相关的启动器依赖:
org.springframework.boot
配置文件 :
测试类:
package com.mikey.boottesk; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.mail.SimpleMailMessage; import org.springframework.mail.javamail.JavaMailSender; import org.springframework.test.context.junit4.SpringRunner; @RunWith(SpringRunner.class) @SpringBootTest public class SpringbootTeskApplicationTests { @Autowired JavaMailSender javaMailSender; @Test public void contextLoads() { SimpleMailMessage simpleMailMessage = new SimpleMailMessage(); simpleMailMessage.setSubject("今晚行动"); simpleMailMessage.setText("hello world"); simpleMailMessage.setTo("18276297824@163.com"); simpleMailMessage.setFrom("1625017540@qq.com"); javaMailSender.send(simpleMailMessage); } }
成功发送:
报错问题:
若是报不安全链接须要ssl则在配置文件中配置
package com.atguigu.task; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.mail.SimpleMailMessage; import org.springframework.mail.javamail.JavaMailSenderImpl; import org.springframework.mail.javamail.MimeMessageHelper; import org.springframework.test.context.junit4.SpringRunner; import javax.mail.internet.MimeMessage; import java.io.File; @RunWith(SpringRunner.class) @SpringBootTest public class Springboot04TaskApplicationTests { @Autowired JavaMailSenderImpl mailSender; @Test public void contextLoads() { SimpleMailMessage message = new SimpleMailMessage(); //邮件设置 message.setSubject("通知-今晚开会"); message.setText("今晚7:30开会"); message.setTo("17512080612@163.com"); message.setFrom("534096094@qq.com"); mailSender.send(message); } @Test public void test02() throws Exception{ //一、建立一个复杂的消息邮件 MimeMessage mimeMessage = mailSender.createMimeMessage(); MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true); //邮件设置 helper.setSubject("通知-今晚开会"); helper.setText("<b style='color:red'>今天 7:30 开会</b>",true); helper.setTo("17512080612@163.com"); helper.setFrom("534096094@qq.com"); //上传文件 helper.addAttachment("1.jpg",new File("C:\\Users\\lfy\\Pictures\\Saved Pictures\\1.jpg")); helper.addAttachment("2.jpg",new File("C:\\Users\\lfy\\Pictures\\Saved Pictures\\2.jpg")); mailSender.send(mimeMessage); } }
两大安全框架:shiro,SpringSecutity
安全
SpringSecutity:
Spring Security是针对Spring项目的安全框架,也是Spring Boot底层安全模块默认的技术选型。他能够实现强大的web安全控制。对于安全控制,咱们仅需引入spring-boot-starter-security模块,进行少许的配置,便可实现强大的安全管理。几个类:
WebSecurityConfigurerAdapter:自定义Security策略
AuthenticationManagerBuilder:自定义认证策略
@EnableWebSecurity:开启WebSecurity模式
应用程序的两个主要区域是“认证”和“受权”(或者访问控制)。这两个主要区域是Spring Security 的两个目标。
“认证”(Authentication),是创建一个他声明的主体的过程(一个“主体”通常是指用户,设备或一些能够在你的应用程序中执行动做的其余系统)。
“受权”(Authorization)指肯定一个主体是否容许在你的应用程序执行一个动做的过程。为了抵达须要受权的店,主体的身份已经有认证过程创建。
这个概念是通用的而不仅在Spring Security中。
2、Web&安全
登录/注销
HttpSecurity配置登录、注销功能
Thymeleaf提供的SpringSecurity标签支持
须要引入thymeleaf-extras-springsecurity4
sec:authentication=“name”得到当前用户的用户名
sec:authorize=“hasRole(‘ADMIN’)”当前用户必须拥有ADMIN权限时才会显示标签内容
remember me
表单添加remember-me的checkbox
配置启用remember-me功能
CSRF(Cross-site request forgery)跨站请求伪造
HttpSecurity启用csrf功能,会为表单添加_csrf的值,提交携带来预防CSRF;
初始化向导建立项目
引入web,thymelef模块
导入依赖:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.mikey</groupId> <artifactId>springboot-security</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>springboot-security</name> <description>Demo project for Spring Boot</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.17.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> <thymeleaf.version>3.0.9.RELEASE</thymeleaf.version> <thymeleaf-layout-dialect.version>2.3.0</thymeleaf-layout-dialect.version> <thymeleaf-extras-springsecurity4.version>3.0.2.RELEASE</thymeleaf-extras-springsecurity4.version> </properties> <dependencies> <dependency> <groupId>org.thymeleaf.extras</groupId> <artifactId>thymeleaf-extras-springsecurity4</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
编写配置类:
参考:spring官网Security模块
1、引入SpringSecurity; 2、编写SpringSecurity的配置类; @EnableWebSecurity extends WebSecurityConfigurerAdapter 3、控制请求的访问权限: configure(HttpSecurity http) { http.authorizeRequests().antMatchers("/").permitAll() .antMatchers("/level1/**").hasRole("VIP1") } 4、定义认证规则: configure(AuthenticationManagerBuilder auth){ auth.inMemoryAuthentication() .withUser("zhangsan").password("123456").roles("VIP1","VIP2") } 5、开启自动配置的登录功能: configure(HttpSecurity http){ http.formLogin(); } 6、注销:http.logout(); 七、记住我:Remeberme();
配置类:
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; @EnableWebSecurity public class MySecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { //super.configure(http); //定制请求的受权规则 http.authorizeRequests().antMatchers("/").permitAll() .antMatchers("/level1/**").hasRole("VIP1") .antMatchers("/level2/**").hasRole("VIP2") .antMatchers("/level3/**").hasRole("VIP3"); //开启自动配置的登录功能,效果,若是没有登录,没有权限就会来到登录页面 http.formLogin().usernameParameter("user").passwordParameter("pwd") .loginPage("/userlogin"); //一、/login来到登录页 //二、重定向到/login?error表示登录失败 //三、更多详细规定 //四、默认post形式的 /login表明处理登录 //五、一但定制loginPage;那么 loginPage的post请求就是登录 //开启自动配置的注销功能。 http.logout().logoutSuccessUrl("/");//注销成功之后来到首页 //一、访问 /logout 表示用户注销,清空session //二、注销成功会返回 /login?logout 页面; //开启记住我功能 http.rememberMe().rememberMeParameter("remeber"); //登录成功之后,将cookie发给浏览器保存,之后访问页面带上这个cookie,只要经过检查就能够免登陆 //点击注销会删除cookie } //定义认证规则 @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { //super.configure(auth); auth.inMemoryAuthentication() .withUser("zhangsan").password("123456").roles("VIP1","VIP2") .and() .withUser("lisi").password("123456").roles("VIP2","VIP3") .and() .withUser("wangwu").password("123456").roles("VIP1","VIP3"); } }
视图:
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4"> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Insert title here</title> </head> <body> <h1 align="center">欢迎光临武林秘籍管理系统</h1> <div sec:authorize="!isAuthenticated()"> <h2 align="center">游客您好,若是想查看武林秘籍 <a th:href="@{/userlogin}">请登陆</a></h2> </div> <div sec:authorize="isAuthenticated()"> <h2><span sec:authentication="name"></span>,您好,您的角色有: <span sec:authentication="principal.authorities"></span></h2> <form th:action="@{/logout}" method="post"> <input type="submit" value="注销"/> </form> </div> <hr> <div sec:authorize="hasRole('VIP1')"> <h3>普通武功秘籍</h3> <ul> <li><a th:href="@{/level1/1}">罗汉拳</a></li> <li><a th:href="@{/level1/2}">武当长拳</a></li> <li><a th:href="@{/level1/3}">全真剑法</a></li> </ul> </div> <div sec:authorize="hasRole('VIP2')"> <h3>高级武功秘籍</h3> <ul> <li><a th:href="@{/level2/1}">太极拳</a></li> <li><a th:href="@{/level2/2}">七伤拳</a></li> <li><a th:href="@{/level2/3}">梯云纵</a></li> </ul> </div> <div sec:authorize="hasRole('VIP3')"> <h3>绝世武功秘籍</h3> <ul> <li><a th:href="@{/level3/1}">葵花宝典</a></li> <li><a th:href="@{/level3/2}">龟派气功</a></li> <li><a th:href="@{/level3/3}">独孤九剑</a></li> </ul> </div> </body> </html>
记住我功能:
出现报错:
缘由:模板引擎版本太低
解决方法:更换新版本的thymeleaf
在分布式系统中,国内经常使用zookeeper+dubbo组合,而Spring Boot推荐使用全栈的Spring,Spring Boot+Spring Cloud。
分布式系统:
单一应用架构
当网站流量很小时,只需一个应用,将全部功能都部署在一块儿,以减小部署节点和成本。此时,用于简化增删改查工做量的数据访问框架(ORM)是关键。
垂直应用架构
当访问量逐渐增大,单一应用增长机器带来的加速度愈来愈小,将应用拆成互不相干的几个应用,以提高效率。此时,用于加速前端页面开发的Web框架(MVC)是关键。
分布式服务架构
当垂直应用愈来愈多,应用之间交互不可避免,将核心业务抽取出来,做为独立的服务,逐渐造成稳定的服务中心,使前端应用能更快速的响应多变的市场需求。此时,用于提升业务复用及整合的分布式服务框架(RPC)是关键。
流动计算架构
当服务愈来愈多,容量的评估,小服务资源的浪费等问题逐渐显现,此时需增长一个调度中心基于访问压力实时管理集群容量,提升集群利用率。此时,用于提升机器利用率的资源调度和治理中心(SOA)是关键
ZooKeeper注册中心
ZooKeeper 是一个分布式的,开放源码的分布式应用程序协调服务。它是一个为分布式应用提供一致性服务的软件,提供的功能包括:配置维护、域名服务、分布式同步、组服务等。
Dubbo分布式服务调用框架
Dubbo是Alibaba开源的分布式服务框架,它最大的特色是按照分层的方式来架构,使用这种方式可使各个层之间解耦合(或者最大限度地松耦合)。从服务模型的角度来看,Dubbo采用的是一种很是简单的模型,要么是提供方提供服务,要么是消费方消费服务,因此基于这一点能够抽象出服务提供方(Provider)和服务消费方(Consumer)两个角色。
一、安装zookeeper做为注册中心
二、编写服务提供者
三、编写服务消费者
四、整合dubbo
消费:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.atguigu</groupId> <artifactId>consumer-user</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>consumer-user</name> <description>Demo project for Spring Boot</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.12.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>com.alibaba.boot</groupId> <artifactId>dubbo-spring-boot-starter</artifactId> <version>0.1.0</version> </dependency> <!--引入zookeeper的客户端工具--> <!-- https://mvnrepository.com/artifact/com.github.sgroschupf/zkclient --> <dependency> <groupId>com.github.sgroschupf</groupId> <artifactId>zkclient</artifactId> <version>0.1</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
dubbo.application.name=consumer-user
dubbo.registry.address=zookeeper://118.24.44.169:2181
package com.atguigu.user; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; /** * 一、引入依赖‘ * 二、配置dubbo的注册中心地址 * 三、引用服务 */ @SpringBootApplication public class ConsumerUserApplication { public static void main(String[] args) { SpringApplication.run(ConsumerUserApplication.class, args); } }
package com.atguigu.user.service; import com.alibaba.dubbo.config.annotation.Reference; import com.atguigu.ticket.service.TicketService; import org.springframework.stereotype.Service; @Service//Spring的service public class UserService{ @Reference//注意两个工程的全类名相同 TicketService ticketService; public void hello(){ String ticket = ticketService.getTicket(); System.out.println("买到票了:"+ticket); } }
package com.atguigu.ticket.service; public interface TicketService { public String getTicket(); }
package com.atguigu.user; import com.atguigu.user.service.UserService; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; @RunWith(SpringRunner.class) @SpringBootTest public class ConsumerUserApplicationTests { @Autowired UserService userService; @Test public void contextLoads() { userService.hello(); } }
服务:
pom文件同上
dubbo.application.name=provider-ticket dubbo.registry.address=zookeeper://118.24.44.169:2181 dubbo.scan.base-packages=com.atguigu.ticket.service
package com.atguigu.ticket; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; /** * 一、将服务提供者注册到注册中心 * 一、引入dubbo和zkclient相关依赖 * 二、配置dubbo的扫描包和注册中心地址 * 三、使用@Service发布服务 */ @SpringBootApplication public class ProviderTicketApplication { public static void main(String[] args) { SpringApplication.run(ProviderTicketApplication.class, args); } }
package com.atguigu.ticket.service; import com.alibaba.dubbo.config.annotation.Service; import org.springframework.stereotype.Component; @Component @Service //将服务发布出去,是dubbo的service public class TicketServiceImpl implements TicketService { @Override public String getTicket() { return "《厉害了,个人国》"; } }
package com.atguigu.ticket.service; public interface TicketService { public String getTicket(); }
Spring Cloud
Spring Cloud是一个分布式的总体解决方案。Spring Cloud 为开发者提供了在分布式系统(配置管理,服务发现,熔断,路由,微代理,控制总线,一次性token,全局琐,leader选举,分布式session,集群状态)中快速构建的工具,使用Spring Cloud的开发者能够快速的启动服务或构建应用、同时可以快速和云平台资源进行对接。
SpringCloud分布式开发五大经常使用组件
服务发现——Netflix Eureka
客服端负载均衡——Netflix Ribbon
断路器——Netflix Hystrix
服务网关——Netflix Zuul
分布式配置——Spring Cloud Config
Spring Cloud 入门
一、建立provider
二、建立consumer
三、引入Spring Cloud
四、引入Eureka注册中心
五、引入Ribbon进行客户端负载均衡
工程结构:
1.新建空工程:
建立model下载Spring初始化向导
1.建立服务中心:eureka-server 选择服务模块
spring:
application:
name: consumer-user
server:
port: 8200
eureka:
instance:
prefer-ip-address: true # 注册服务的时候使用服务的ip地址
client:
service-url:
defaultZone: http://localhost:8761/eureka/
启动类:注意要加注解:
package com.atguigu.consumeruser; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.context.annotation.Bean; import org.springframework.web.client.RestTemplate; @EnableDiscoveryClient //开启发现服务功能 @SpringBootApplication public class ConsumerUserApplication { public static void main(String[] args) { SpringApplication.run(ConsumerUserApplication.class, args); } @LoadBalanced //使用负载均衡机制 @Bean public RestTemplate restTemplate(){ return new RestTemplate(); } }
控制层:
package com.atguigu.consumeruser.controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; @RestController public class UserController { @Autowired RestTemplate restTemplate; @GetMapping("/buy") public String buyTicket(String name){ String s = restTemplate.getForObject("http://PROVIDER-TICKET/ticket", String.class); return name+"购买了"+s; } }
启动服务:以下即成功
2.新建provider-ticket 的model
server: port: 8002 spring: application: name: provider-ticket eureka: instance: prefer-ip-address: true # 注册服务的时候使用服务的ip地址 client: service-url: defaultZone: http://localhost:8761/eureka/
启动类:
package com.atguigu.providerticket; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class ProviderTicketApplication { public static void main(String[] args) { SpringApplication.run(ProviderTicketApplication.class, args); } }
服务层:
package com.atguigu.providerticket.service; import org.springframework.stereotype.Service; @Service public class TicketService { public String getTicket(){ System.out.println("8002"); return "《厉害了,个人国》"; } }
控制层:
package com.atguigu.providerticket.controller; import com.atguigu.providerticket.service.TicketService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class TicketController { @Autowired TicketService ticketService; @GetMapping("/ticket") public String getTicket(){ return ticketService.getTicket(); } }
3.新建model consumer-user
spring: application: name: consumer-user server: port: 8200 eureka: instance: prefer-ip-address: true # 注册服务的时候使用服务的ip地址 client: service-url: defaultZone: http://localhost:8761/eureka/
启动类:
package com.atguigu.consumeruser; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.context.annotation.Bean; import org.springframework.web.client.RestTemplate; @EnableDiscoveryClient //开启发现服务功能 @SpringBootApplication public class ConsumerUserApplication { public static void main(String[] args) { SpringApplication.run(ConsumerUserApplication.class, args); } @LoadBalanced //使用负载均衡机制 @Bean public RestTemplate restTemplate(){ return new RestTemplate(); } }
控制层:
package com.atguigu.consumeruser.controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; @RestController public class UserController { @Autowired RestTemplate restTemplate; @GetMapping("/buy") public String buyTicket(String name){ String s = restTemplate.getForObject("http://PROVIDER-TICKET/ticket", String.class); return name+"购买了"+s; } }
浏览器测试访问:
成功:
1、监控管理
经过引入spring-boot-starter-actuator,可使用Spring Boot为咱们提供的准生产环境下的应用监控和管理功能。咱们能够经过HTTP,JMX,SSH协议来进行操做,自动获得审计、健康及指标信息等
步骤:
引入spring-boot-starter-actuator
经过http方式访问监控端点
可进行shutdown(POST 提交,此端点默认关闭)
关闭便可在浏览器访问查看:
监控和管理端点:
定制端点通常经过endpoints+端点名+属性名来设置。
修改端点id(endpoints.beans.id=mybeans)
开启远程应用关闭功能(endpoints.shutdown.enabled=true)
关闭端点(endpoints.beans.enabled=false)
开启所需端点
endpoints.enabled=false
endpoints.beans.enabled=true
定制端点访问根路径
management.context-path=/manage
关闭http端点
management.port=-1
三自定义健康指示器:
package com.atguigu.springboot08actuator; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; /** * 自定义健康状态指示器 * 一、编写一个指示器 实现 HealthIndicator 接口 * 二、指示器的名字 xxxxHealthIndicator * 三、加入容器中 */ @SpringBootApplication public class Springboot08ActuatorApplication { public static void main(String[] args) { SpringApplication.run(Springboot08ActuatorApplication.class, args); } }
package com.atguigu.springboot08actuator.health; import org.springframework.boot.actuate.health.Health; import org.springframework.boot.actuate.health.HealthIndicator; import org.springframework.stereotype.Component; @Component public class MyAppHealthIndicator implements HealthIndicator { @Override public Health health() { //自定义的检查方法 //Health.up().build()表明健康 return Health.down().withDetail("msg","服务异常").build(); } }
热部署:
在开发中咱们修改一个Java文件后想看到效果不得不重启应用,这致使大量时间花费,咱们但愿不重启应用的状况下,程序能够自动部署(热部署)。有如下四种状况,如何能实现热部署。
一、模板引擎
在Spring Boot中开发状况下禁用模板引擎的cache
页面模板改变ctrl+F9能够从新编译当前页面并生效
二、Spring Loaded
Spring官方提供的热部署程序,实现修改类文件的热部署
下载Spring Loaded(项目地址https://github.com/spring-projects/spring-loaded)
添加运行时参数;
-javaagent:C:/springloaded-1.2.5.RELEASE.jar –noverify
三、JRebel
收费的一个热部署软件
安装插件使用便可
四、Spring Boot Devtools(推荐)
引入依赖
IDEA使用ctrl+F9或作一些小调整 Intellij IEDA和Eclipse不一样,Eclipse设置了自动编译以后,修改类它会自动编译,而IDEA在非RUN或DEBUG状况下才会自动编译(前提是你已经设置了Auto-Compile)。设置自动编译(settings-compiler-make project automatically)ctrl+shift+alt+/(maintenance)勾选compiler.automake.allow.when.app.running