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