可以使用三種注解來引入DAO層的接口到spring容器中。
1.@Mapper,寫在每一個DAO層接口上,如下:
2.@MapperScan和@ComponentScan兩者之一。前者的意義是將指定包中的所有接口都標注為DAO層接口,相當於在每一個接口上寫@Mapper。后者則是代替所有
//指定這是一個操作數據庫的mapper
@Mapper
public interface DepartmentMapper { @Select("select * from department where id=#{id}") public Department getDeptById(Integer id); @Delete("delete from department where id=#{id}") public int deleteDeptById(Integer id); @Options(useGeneratedKeys = true,keyProperty = "id") @Insert("insert into department(departmentName) values(#{departmentName})") public int insertDept(Department department); @Update("update department set departmentName=#{departmentName} where id=#{id}") public int updateDept(Department department); }
使用MapperScan批量掃描所有的Mapper接口; @MapperScan(value = "com.atguigu.springboot.mapper") @SpringBootApplication public class SpringBoot06DataMybatisApplication { public static void main(String[] args) { SpringApplication.run(SpringBoot06DataMybatisApplication.class, args); } }
第二種
配置文件版
mybatis: config‐location: classpath:mybatis/mybatis‐config.xml 指定全局配置文件的位置 mapper‐locations: classpath:mybatis/mapper/*.xml 指定sql映射文件的位置
自定義MyBatis的配置規則;給容器中添加一個ConfigurationCustomizer;
給下列文件設置駝峰命名法
@org.springframework.context.annotation.Configuration public class MyBatisConfig { @Bean public ConfigurationCustomizer configurationCustomizer(){ return new ConfigurationCustomizer(){ @Override public void customize(Configuration configuration) { configuration.setMapUnderscoreToCamelCase(true); } }; } }
整合SpringData JPA
SpringData簡介
JPA:ORM(Object Relational Mapping);
1)、編寫一個實體類(bean)和數據表進行映射,並且配置好映射關系;
//使用JPA注解配置映射關系 @Entity //告訴JPA這是一個實體類(和數據表映射的類) @Table(name = "tbl_user") //@Table來指定和哪個數據表對應;如果省略默認表名就是user; public class User { @Id //這是一個主鍵 @GeneratedValue(strategy = GenerationType.IDENTITY)//自增主鍵 private Integer id; @Column(name = "last_name",length = 50) //這是和數據表對應的一個列 private String lastName; @Column //省略默認列名就是屬性名 private String email;
編寫一個Dao接口來操作實體類對應的數據表(Repository)
//繼承JpaRepository來完成對數據庫的操作 public interface UserRepository extends JpaRepository<User,Integer> { }
pom 文件的基本配置
spring: jpa: hibernate: # 更新或者創建數據表結構 ddl‐auto: update # 控制台顯示SQL show‐sql: true
=================================================
springboot與緩存
Java Caching定義了5個核心接口,分別是
CachingProvider,
定義了創建、配置、獲取、管理和控制多個CacheManager。一個應用可以在運行期訪問多個CachingProvider。
CacheManager,
定義了創建、配置、獲取、管理和控制多個唯一命名的Cache,這些Cache存在於CacheManager的上下文中。一個CacheManager僅被一個CachingProvider所擁有。
Cache,
是一個類似Map的數據結構並臨時存儲以Key為索引的值。一個Cache僅被一個CacheManager所擁有。
Entry
是一個存儲在Cache中的key-value對。
Expiry。
每一個存儲在Cache中的條目有一個定義的有效期。一旦超過這個時間,條目為過期的狀態。一旦過期,條目將不可訪問、更新和刪除。緩存有效期可以通過ExpiryPolicy設置。
spring緩存抽象
Spring從3.1開始定義了org.springframework.cache.Cache 和org.springframework.cache.CacheManager接口來統一不同的緩存技術; 並支持使用JCache(JSR-107)注解簡化我們開發;
Cache接口為緩存的組件規范定義,包含緩存的各種操作集合; Cache接口下Spring提供了各種xxxCache的實現;如RedisCache,EhCacheCache , ConcurrentMapCache等;
簡單點說就是,Cache接口中是所有的緩存功能的集合
每次調用需要緩存功能的方法時,Spring會檢查檢查指定參數的指定的目標方法是否已經被調用過;如果有就直接從緩存中獲取方法調用后的結果,如果沒有就調用方法並緩存結果后返回給用戶。下次調用直接從緩存中獲取。 使用Spring緩存抽象時我們需要關注以下兩點; 1、確定方法需要被緩存以及他們的緩存策略 2、從緩存中讀取之前緩存存儲的數據
我們從這四個方面來介紹緩存
1.引入 spring-boot stater-cache
整合redis 實現緩存
如果redis忘記了基本內容可以看我學習到的入門知識(https://www.cnblogs.com/zhulina-917/p/10660930.html)
先寫一個簡單例子
新建一個SpringBoot+web+mysql+mybatis+cache+jdbc
遇到的問題1;
如果你想使用jdbc的驅動已經導入了jdbc的場景啟動器,可以在寫配置文件的時候還是不能找到jdbc的路徑這個時候你需要加入一個這時候就可以了
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.11</version>
</dependency>
使用兩個實體類

spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.url=jdbc:mysql://localhost:3306/spring_cache spring.datasource.username=root spring.datasource.password=root # 開啟駝峰命名匹配 mybatis.configuration.map-underscore-to-camel-case=true # 打印sql logging.level.com.cuzz.cache.mapper=debug # 可以打印配置報告 debug=true

package myproject.redisproject.entry; import lombok.Data; import lombok.NoArgsConstructor; import java.io.Serializable; /** * Description: redis-project * Created by lenovo on 2019/5/2 17:30 */ @Data @NoArgsConstructor public class Department implements Serializable { private Integer id; private String deptName; }

package myproject.redisproject.entry; import lombok.Data; import lombok.NoArgsConstructor; import java.io.Serializable; /** * Description: redis-project * Created by lenovo on 2019/5/2 17:31 */ @Data @NoArgsConstructor public class Employee implements Serializable { private Integer id; private String lastName; private String gender; private String email; private Integer dId; }

package myproject.redisproject.mapper; import myproject.redisproject.entry.Employee; import org.apache.ibatis.annotations.Delete; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Select; import org.apache.ibatis.annotations.Update; /** * Description: redis-project * Created by lenovo on 2019/5/2 17:32 */ @Mapper public interface EmployeeMapper { @Select("SELECT * FROM employee WHERE id = #{id}") Employee getEmployeeById(Integer id); @Update("UPDATE employee SET last_name=#{lastName},email=#{email},gender=#{gender},d_id=#{dId} WHERE id=#{id}") void updateEmp(Employee employee); @Delete("DELETE FROM employee WHERE employee.id=#{id}") void deleteEmp(Integer id); @Select("SELECT * FROM employee WHERE last_name=#{lastName}") Employee getEmpByLastName(String lastName); }
如果你不想每一個接口都是用@Mapper這個注解那么就需要在主啟動類中加上MapperScan("包名 ")

package myproject.redisproject; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cache.annotation.EnableCaching; @SpringBootApplication @MapperScan("myproject.redisproject.mapper.EmployeeMapper") @EnableCaching //開啟緩存 public class RedisprojectApplication { public static void main(String[] args) { SpringApplication.run(RedisprojectApplication.class, args); } }
接下來就要寫具體的Service 方法將我們對數據庫擦搜做的結果進行緩存,以后再由相同的數據直接從緩存中進行獲取而不需要再次調用方法
CacheManager中管理多個Cache組件,對緩存的真正CRUD操作在Cache組件中,每個緩存組件都有自己的唯一名字;
屬性:
- CacheName/value:指定存儲緩存組件的名字
- key:緩存數據使用的key,可以使用它來指定。默認是使用方法參數的值,1-方法的返回值
- 編寫Spel表達式:#id 參數id的值, #a0/#p0 #root.args[0]
- keyGenerator:key的生成器,自己可以指定key的生成器的組件id
- key/keyGendertor二選一使用
- cacheManager指定Cache管理器,或者cacheReslover指定獲取解析器
- condition:指定符合條件的情況下,才緩存;
- unless:否定緩存,unless指定的條件為true,方法的返回值就不會被緩存,可以獲取到結果進行判斷
- sync:是否使用異步模式,unless不支持
首先呢,如果你不小心加上了redis 的啟動器那么你就要設置遠程連接的主機和端口號,否則就會報錯
這時你可以將redis的場景啟動器去掉。
還有一點需要注意
logging.level.myproject.redisproject.mapper=debug
這個是你自定一個的包的名不需要加上類。
這個是結果
當沒有查詢過數據就會執行sql語句當已經使用過的數據就會從緩存中查找。
緩存原理
CacheAutoConfiguration
@Configuration @ConditionalOnClass(CacheManager.class) @ConditionalOnBean(CacheAspectSupport.class) @ConditionalOnMissingBean(value = CacheManager.class, name = "cacheResolver") @EnableConfigurationProperties(CacheProperties.class) @AutoConfigureAfter({ CouchbaseAutoConfiguration.class, HazelcastAutoConfiguration.class, HibernateJpaAutoConfiguration.class, RedisAutoConfiguration.class }) @Import(CacheConfigurationImportSelector.class) public class CacheAutoConfiguration {
@Import(CacheConfigurationImportSelector.class)點開發現

現在找那個緩存組件生效
SimpleCacheConfiguration生效
@Configuration @ConditionalOnMissingBean(CacheManager.class) @Conditional(CacheCondition.class) class SimpleCacheConfiguration { private final CacheProperties cacheProperties; private final CacheManagerCustomizers customizerInvoker;
SimpleCacheConfiguration(CacheProperties cacheProperties, CacheManagerCustomizers customizerInvoker) { this.cacheProperties = cacheProperties; this.customizerInvoker = customizerInvoker; } @Bean public ConcurrentMapCacheManager cacheManager() { ConcurrentMapCacheManager cacheManager = new ConcurrentMapCacheManager(); List<String> cacheNames = this.cacheProperties.getCacheNames(); if (!cacheNames.isEmpty()) { cacheManager.setCacheNames(cacheNames); } return this.customizerInvoker.customize(cacheManager); } }
給容器注冊一個CacheManager:ConcurrentMapCacheManager
可以獲取和創建ConcurrentMapCache,作用是將數據保存在ConcurrentMap中
如果緩存組件的名字是空的,那么就會給一個默認值
運行流程
方法運行之前,先查Cache(緩存組件),按照cacheName的指定名字獲取;
cacheManager 會先獲取緩存組件,如果沒有緩存組件就創建一個
然后從Cache中查找相應的key 默認key就是方法的參數名
key是按照某種策略生成的,默認是使用keyGenerator生成的,默認使用SimpleKeyGenerator生成key
沒有參數 key=new SimpleKey()
如果有一個參數 key=參數值
如果多個參數 key=new SimpleKey(params);
public class SimpleKeyGenerator implements KeyGenerator { @Override public Object generate(Object target, Method method, Object... params) { return generateKey(params); } /** * Generate a key based on the specified parameters. */ public static Object generateKey(Object... params) { if (params.length == 0) { return SimpleKey.EMPTY; } if (params.length == 1) { Object param = params[0]; if (param != null && !param.getClass().isArray()) { return param; } } return new SimpleKey(params); }
沒有查到緩存就調用目標方法
將目標方法的返回結果放在緩存中
方法執行之前,@Cacheable先來檢查緩存中是否有數據,按照參數的值作為key去查詢緩存,如果沒有,就運行方法,存入緩存,如果有數據,就取出map的值。
然后我們就可以自定義keyGenerator
注意一點,如果你以前曾自定義過keyGenerator 要注意當你的工程還存在時時,要注意keyGenerator 的名字是不是重復

package myproject.redisproject.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; /** * Description: redis-project * Created by lenovo on 2019/5/2 19:27 */ @Configuration public class MyKeyGenerator { @Bean("myKeyGenerator1") public KeyGenerator keyGenerator(){ return new KeyGenerator() { @Override public Object generate(Object target, Method method, Object... params) { return method.getName() + "[" + Arrays.asList(params) + "]"; } }; } }

package myproject.redisproject.service; import myproject.redisproject.entry.Employee; import myproject.redisproject.mapper.EmployeeMapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; /** * Description: redis-project * Created by lenovo on 2019/5/2 18:07 */ @Service public class EmployeeService { @Autowired EmployeeMapper employeeMapper; @Cacheable(cacheNames = "emp",keyGenerator = "myKeyGenerator1") public Employee getEmployee(Integer id) { System.out.println("----> 查詢" + id + "號員工"); return employeeMapper.getEmployeeById(id); } }
@Cacheable(cacheNames = "emp",keyGenerator = "myKeyGenerator1")
cacheName是緩存組件的名字 keyGenerator 是自定義key生成器的類
講解Cache 的那些注解
1.cacheput 即調用方法也對結果進行緩存
修改數據庫的某個數據,同時更新緩存
運行時機
先運行方法,再將目標結果緩存起來
service
@CachePut(value = {"emp"}) public Employee updateEmployee(Employee employee) { System.out.println("---->updateEmployee"+employee); employeeMapper.updateEmp(employee); return employee; }
controller
@GetMapping("/emp") public Employee update(Employee employee) { return employeeService.updateEmployee(employee); }
要注意一點就是
cacheable的key是不能使用result的參數的 所以要使@CachePut使用@Cacheable使用相同的key @CachePut(value = {"emp"}, key = "#result.id") public Employee updateEmployee(Employee employee) { System.out.println("---->updateEmployee"+employee); employeeMapper.updateEmp(employee); return employee; }
CacheEvict
清除緩存
service @CacheEvict(value = "emp",key = "#id") public void deleteEmployee(Integer id){ System.out.println("---->刪除的employee的id是: "+id); }
controller @GetMapping("/delete") public String delete(Integer id) { employeeService.deleteEmployee(id); return "success"; }
allEntries = true,代表不論清除那個key,都重新刷新緩存
beforeInvocation=true 方法執行前,清空緩存,默認是false,如果程序異常,就不會清除緩存
Caching定義組合復雜注解
CacheConfig抽取緩存的公共配置
組合
- Cacheable
- CachePut
- CacheEvict
CacheConfig抽取緩存的公共配置
@Caching( cacheable = { @Cacheable(value = "emp",key = "#lastName") }, put = { @CachePut(value = "emp",key = "#result.id"), @CachePut(value = "emp",key = "#result.gender") } ) public Employee getEmployeeByLastName(String lastName) { return employeeMapper.getEmpByLastName(lastName); }
如果查完lastName,再查的id是剛才的值,就會直接從緩存中獲取數據
4、CacheConfig抽取緩存的公共配置 @CacheConfig(cacheNames = "emp") // 這樣餓哦們就不需要在每一個方法上都聲明 cacheName @Service public class EmployeeService {
2、Redis的Template
Redis的常用五大數據類型
String【字符串】、List【列表】、Set【集合】、Hash【散列】、ZSet【有序集合】
分為兩種一種是StringRedisTemplate,另一種是RedisTemplate
根據不同的數據類型,大致的操作也分為這5種,以StringRedisTemplate為例
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> <version>2.1.3.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> <version>2.1.3.RELEASE</version> </dependency>
spring: redis: host: 192.168.111.130 port: 6379 password: xxx
RestTemplate 查看源碼可以分析點東西出來
// @param <K> the Redis key type against which the template works (usually a String)
// @param <V> the Redis value type against which the template works
public class RedisTemplate<K, V> extends RedisAccessor implements RedisOperations<K, V>, BeanClassLoaderAware {
private boolean enableTransactionSupport = false;
private boolean exposeConnection = false;
private boolean initialized = false;
private boolean enableDefaultSerializer = true;
private @Nullable RedisSerializer<?> defaultSerializer;
private @Nullable ClassLoader classLoader;
@SuppressWarnings("rawtypes") private @Nullable RedisSerializer keySerializer = null;
@SuppressWarnings("rawtypes") private @Nullable RedisSerializer valueSerializer = null;
@SuppressWarnings("rawtypes") private @Nullable RedisSerializer hashKeySerializer = null;
@SuppressWarnings("rawtypes") private @Nullable RedisSerializer hashValueSerializer = null;
private RedisSerializer<String> stringSerializer = RedisSerializer.string();
private @Nullable ScriptExecutor<K> scriptExecutor;
// cache singleton objects (where possible)
private @Nullable ValueOperations<K, V> valueOps;
private @Nullable ListOperations<K, V> listOps;
private @Nullable SetOperations<K, V> setOps;
private @Nullable ZSetOperations<K, V> zSetOps;
private @Nullable GeoOperations<K, V> geoOps;
private @Nullable HyperLogLogOperations<K, V> hllOps;
模板中的Redis key的類型(通常為String)如:RedisTemplate<String, Object>
注意:如果沒特殊情況,切勿定義成RedisTemplate<Object, Object>,否則根據里氏替換原則,使用的時候會造成類型錯誤 。
RedisTemplate中定義了對5種數據結構操作
redisTemplate.opsForValue();//操作字符串 redisTemplate.opsForHash();//操作hash redisTemplate.opsForList();//操作list redisTemplate.opsForSet();//操作set redisTemplate.opsForZSet();//操作有序set
先說一下序列化規則;
StringRedisTemplate 和RedisTemplate的序列化規則不一樣
-
兩者的關系是StringRedisTemplate繼承RedisTemplate。
-
兩者的數據是不共通的;也就是說StringRedisTemplate只能管理StringRedisTemplate里面的數據,RedisTemplate只能管理RedisTemplate中的數據。
-
SDR默認采用的序列化策略有兩種,一種是String的序列化策略,一種是JDK的序列化策略。
StringRedisTemplate默認采用的是String的序列化策略,保存的key和value都是采用此策略序列化保存的。
RedisTemplate默認采用的是JDK的序列化策略,保存的key和value都是采用此策略序列化保存的。
redisTemplate 的序列化配置
==================================================
鏡像下載不下來 我就不使用鏡像
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
spring.redis.host=192.168.111.130 spring.redis.password=xxx
package myproject.redisproject; import myproject.redisproject.entry.Employee; import myproject.redisproject.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 RedisprojectApplicationTests { /*那兩種對象都不需要聲明了,可以直接進行調用*/ @Autowired StringRedisTemplate stringRedisTemplate; @Autowired RedisTemplate redisTemplate; @Autowired EmployeeMapper employeeMapper; @Test public void contextLoads() { stringRedisTemplate.opsForValue().set("k1","v1"); System.out.println(stringRedisTemplate.opsForValue().get("k1")); } @Test public void tedt02(){ Employee emp = employeeMapper.getEmployeeById(2); redisTemplate.opsForValue().set("emp-01", emp); System.out.println(); } }
這樣應該就沒有問題了
然后如果你想存入一個對象不加上序列化是不會正常顯示的
所以這個時候需要做兩步
1.你的實體類要實現序列化
2.你要寫上 一個配置文件里面是你定義的序列化 這個是分兩種的, 1,StringRedisTemplate 2,RedisTemplate
我測試的是RedisTemplate
package myproject.redisproject.config; import myproject.redisproject.entry.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; import java.rmi.UnknownHostException; /** * Description: redis-project * Created by lenovo on 2019/5/2 22:00 */ @Configuration public class MyRedisConfig { @Bean public RedisTemplate<Object,Employee> redisTemplate(RedisConnectionFactory redisConnectionFactory){ RedisTemplate<Object, Employee> template = new RedisTemplate<Object, Employee>(); template.setConnectionFactory(redisConnectionFactory); /*定義一種序列化規則*/ Jackson2JsonRedisSerializer<Employee> jsonRedisSerializer = new Jackson2JsonRedisSerializer<Employee>(Employee.class); /*將這中序列化規則應用到我們的redisTemplate上*/ template.setDefaultSerializer(jsonRedisSerializer); return template; } }
你的測試類中 只需要加上泛型就可以了
@Autowired RedisTemplate<Object,Employee> redisTemplate;
@Test
public void tedt02(){
Employee emp = employeeMapper.getEmployeeById(2);
redisTemplate.opsForValue().set("emp-01", emp);
System.out.println();
}
你可以在你的Linux中看到,這個就是json序列化后的結果,這種方式只是將我們的對象名稱正常顯示了出來,但是我們的數據還是不能正常顯示
OK 127.0.0.1:6379> keys * 1) "\"emp-01\"" 127.0.0.1:6379>
需要在配置文件中加上一些配置才能讓我們的數據正常顯示
在springboot2.0以前使用的是這種寫法
@Bean public RedisCacheManager employeeCacheManager(RedisTemplate<Object, Employee> empRedisTemplate) { RedisCacheManager cacheManager = new RedisCacheManager(empRedisTemplate); // 使用前綴,默認將CacheName作為前綴 cacheManager.setUsePrefix(true); return cacheManager; }
但是springboot2.0之后進行了改進去除了下面的構造方法
@SuppressWarnings("rawtypes") public RedisCacheManager(RedisOperations redisOperations) { this(redisOperations, Collections.<String> emptyList()); }
如果在進行配置會報錯
RedisCacheManager rcm = new RedisCacheManager(redisTemplate);
解決辦法
@Bean public CacheManager cacheManager(RedisConnectionFactory factory) { RedisSerializer<String> redisSerializer = new StringRedisSerializer(); Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class); //解決查詢緩存轉換異常的問題 ObjectMapper om = new ObjectMapper(); om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); jackson2JsonRedisSerializer.setObjectMapper(om); // 配置序列化(解決亂碼的問題) RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig() .entryTtl(timeToLive)//設置有效期 .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer)) .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer)) .disableCachingNullValues(); RedisCacheManager cacheManager = RedisCacheManager.builder(factory) .cacheDefaults(config) .build(); return cacheManager; } }
測試結果很成功啊
{ "id": 2, "lastName": "lisi", "gender": null, "email": "lisi@qq.com", "did": 2 }
其實2.0的升級也是很好的,這樣我們就不需要每次創建一個類都要寫一個具體類型的配置簡化了開發
使用緩存時,默認使用的是ConcurrentMapCache,將數據保存在ConcurrentMap中,開發中使用的是緩存中間件,redis、memcached、ehcache等
starter啟動時,有順序,redis優先級比ConcurrentMapCache更高,CacheManager變為RedisCacheManager,所以使用的是redis緩存
傳入的是RedisTemplate<Object, Object>
默認使用的是jdk的序列化保存
SpringBoot的消息中間件
1、大多數應用,可以通過消息服務中間件來提升系統的異步通信、拓展解耦能力
2、消息服務中的兩個重要概念:
消息代理(message broker)和目的地(destination),當消息發送者發送消息以后,將由消息代理接管,消息代理保證消息傳遞到指定的目的地。
3、消息隊列主要的兩種形式的目的地
1)、隊列(queue):點對點消息通信【point-to-point】,取出一個沒一個,一個發布,多個消費
2)、主題(topic):發布(publish)/訂閱(subscribe)消息通信,多人【訂閱者】可以同時接到消息
4、JMS(Java Message Service) Java消息服務:
- 基於JVM消息規范的代理。ActiveMQ/HornetMQ是JMS的實現
5、AMQP(Advanced Message Queuing Protocol)
- 高級消息隊列協議,也是一個消息代理的規范,兼容JMS
- RabbitMQ是AMQP的實現
6、SpringBoot的支持
spring-jms提供了對JMS的支持
spring-rabbit提供了對AMQP的支持
需要創建ConnectionFactory的實現來連接消息代理
提供JmsTemplate,RabbitTemplate來發送消息
@JmsListener(JMS).@RabbitListener(AMQP)注解在方法上的監聽消息代理發布的消息
@EnableJms,@EnableRabbit開啟支持
7、SpringBoot的自動配置
- JmsAutoConfiguration
- RabbitAutoConfiguration
2、RabbitMQ簡介
AMQP的實現
1、核心概念
Message:消息頭和消息體組成,消息體是不透明的,而消息頭上則是由一系列的可選屬性組成,屬性:路由鍵【routing-key】,優先級【priority】,指出消息可能需要持久性存儲【delivery-mode】
Publisher:消息的生產者,也是一個向交換器發布消息的客戶端應用程序
Exchange:交換器,用來接收生產者發送的消息並將這些消息路由給服務器中的隊列
Exchange的4中類型:direct【默認】點對點,fanout,topic和headers, 發布訂閱,不同類型的Exchange轉發消息的策略有所區別
Queue:消息隊列,用來保存消息直到發送給消費者,它是消息的容器,也是消息的終點,一個消息可投入一個或多個隊列,消息一直在隊列里面,等待消費者連接到這個隊列將數據取走。
Binding:綁定,隊列和交換機之間的關聯,多對多關系
Connection:網絡連接,例如TCP連接
Channel:信道,多路復用連接中的一條獨立的雙向數據流通道,信道是建立在真是的TCP鏈接之內的虛擬連接AMQP命令都是通過信道發送出去的。不管是發布消息,訂閱隊列還是接受消息,都是信道,減少TCP的開銷,復用一條TCP連接。
Consumer:消息的消費者,表示一個從消息隊列中取得消息的客戶端的 應用程序
VirtualHost:虛擬主機,表示一批交換器、消息隊列和相關對象。虛擬主機是共享相同的身份認證和加密環境的獨立服務器域。每個 vhost 本質上就是一個 mini 版的 RabbitMQ 服務器,擁有自己的隊列、交換器、綁定和權限機制。vhost 是 AMQP 概念的基礎,必須在連接時指定,RabbitMQ 默認的 vhost 是 / 。
Broker:表示消息隊列 服務實體
2、RabbitMQ的運行機制
Exchange分發消息時根據類型的不同分發策略有區別,目前共四種類型:direct、fanout、topic、headers 。headers 匹配 AMQP 消息的 header 而不是路由鍵, headers 交換器和 direct 交換器完全一致,但性能差很多,目前幾乎用不到了,所以直接看另外三種類型:
direct:根據路由鍵直接匹配,一對一
fanout:不經過路由鍵,直接發送到每一個隊列
topic:類似模糊匹配的根據路由鍵,來分配綁定的隊列
RabbitMQ安裝測試
1、打開虛擬機,在docker中安裝RabbitMQ #1.安裝rabbitmq,使用鏡像加速 docker pull registry.docker-cn.com/library/rabbitmq:3-management [root@node1 ~]# docker images REPOSITORY TAG IMAGE ID CREATED SIZE registry.docker-cn.com/library/rabbitmq 3-management e1a73233e3be 11 days ago 149 MB #2.運行rabbitmq ##### 端口:5672 客戶端和rabbitmq通信 15672:管理界面的web頁面 docker run -d -p 5672:5672 -p 15672:15672 --name myrabbitmq e1a73233e3be #3.查看運行 docker ps
打開網頁客戶端並登陸,賬號【guest】,密碼【guest】,登陸
https://i.cnblogs.com/EditPosts.aspx?postid=10505453
上面也是我做過的筆記,我只需要添加新學習到的知識
Connection就是建立一個TCP連接,生產者和消費者的都是通過TCP的連接到RabbitMQ Server中的,這個后續會再程序中體現出來。
然后將剩下的三個交換機的類型都綁定上隊列
/*: 代表匹配1個單詞
/#:代表匹配0個或者多個單詞
點開后
創建工程整合
1、RabbitAutoConfiguration 2、自動配置了連接工廠 ConnectionFactory 3、RabbitProperties封裝了 RabbitMQ 4、RabbitTemplate:給RabbitMQ發送和接受消息的 5、AmqpAdmin:RabbitMQ的系統管理功能組件
1、新建SpringBoot工程,SpringBoot1.5+Integeration/RabbitMQ+Web
2、RabbitAutoConfiguration文件
3、編寫配置文件application.properties
spring.rabbitmq.host=192.168.124.136 spring.rabbitmq.port=5672 spring.rabbitmq.username=guest spring.rabbitmq.password=guest
@Autowired RabbitTemplate rabbitTemplate; @Test public void contextLoads() { /** *需要構建一個message 包含消息體和消息頭 * Object 默認當成消息體,只需要傳入要發送的對象,自動化序列發送給rabbitmq; * */ HashMap<String, Object> hashMap = new HashMap<>(); hashMap.put("msg","這是第一個消息"); hashMap.put("data", Arrays.asList("helloworld",123,true)); //對象默認被序列化好以后發送出去 rabbitTemplate.convertAndSend("exchange.direct","cuzz.news",hashMap); }
取出隊列中的值, 取出隊列中數據就沒了
@Test public void receiveandConveter(){ Object receiveAndConvert = rabbitTemplate.receiveAndConvert("cuzz.news"); System.out.println(receiveAndConvert.getClass()); System.out.println(receiveAndConvert); }
執行結果
class java.util.HashMap {msg=這是第一個消息, data=[helloworld, 123, true]}
7、使用Json方式傳遞,並傳入對象Book
1)、MyAMQPConfig,自定義一個MessageConverter返回Jackson2JsonMessageConverter
剛才我們的rabbitmq 的官網上顯示的數據我們都看不懂現在把它轉化為json
就需要修改配置文件
@Configuration public class MyAMQPConfig { @Bean public MessageConverter messageConverter(){ return new Jackson2JsonMessageConverter(); } }
可以看到,這里面得到的消息已經轉化為了我們能看的動的消息體。
2)、編寫Book實體類
/** * @Author: cuzz * @Date: 2018/9/27 14:22 * @Description: */ @Data public class Book { private String bookName; private String author; public Book(){ } public Book(String bookName, String author) { this.bookName = bookName; this.author = author; } }
3)、測試類
@Test public void test() { // 對象被默認序列以后發送出去 rabbitTemplate.convertAndSend("exchange.direct","cuzz.news", new Book("Effect java", "Joshua Bloch")); }
取出數據
@Test public void test02(){ Object o = rabbitTemplate.receiveAndConvert("cuzz.emps"); System.out.println(o.getClass()); System.out.println(o); }
結果
class cn.edu.aynu.Entity.Book Book(bookName=Effect java, author=Joshua Bloch)
開啟基於注解的方式
新建一個bootService
@Service public class BookService { @RabbitListener(queues = "cuzz.news") public void receive(Book book){ System.out.println(book); } @RabbitListener(queues = "cuzz") public void receive02(Message message){ System.out.println(message.getBody()); System.out.println(message.getMessageProperties()); } }
主程序開啟RabbitMQ的注解
@EnableRabbit // 開啟基於注解的rabbitmq @SpringBootApplication public class Springboot10AmqpApplication { public static void main(String[] args) { SpringApplication.run(Springboot10AmqpApplication.class, args); } }
AmqpAdmin
創建和刪除 Exchange 、Queue、Bind
創建Exchange
@Autowired
AmqpAdmin amqpAdmin;
@Test public void test03(){ amqpAdmin.declareExchange(new DirectExchange("amqpadmin.direct")); System.out.println("create finish"); }
創建Queue
@Autowired
AmqpAdmin amqpAdmin;
@Test public void test04(){ String declareQueue = amqpAdmin.declareQueue(new Queue("amqpadmin.queue", true)); System.out.println("Creat finish"); }
3)、創建Bind規則
bind 需要指定綁定 隊列,綁定的交換機,綁定的類型,還有需要的路由鍵
@Test public void createBind(){ amqpAdmin.declareBinding(new Binding("amqpadmin.queue",Binding.DestinationType.QUEUE , "amqpadmin.direct", "amqp.haha", null)); }
結果
刪除
@Test public void deleteChange(){
//刪除交換機 amqpAdmin.deleteExchange("amqpadmin.direct");
// 刪除隊列 //amqpAdmin.deleteQueue("amqpadmin.queue"); System.out.println("delete finish"); }
SpringBoot的檢索
ElasticSearch簡介
ElasticSearch是一個基於Lucene的搜索服務器。它提供了一個分布式多用戶能力的全文搜索引擎,基於RESTful web接口。Elasticsearch是用Java開發的,並作為Apache許可條款下的開放源碼發布,是當前流行的企業級搜索引擎。設計用於雲計算中,能夠達到實時搜索,穩定,可靠,快速,安裝使用方便。
2、ElasticSearch的安裝
1、安裝java最新版本
- 下載linux的.tar.gz
- 解壓到指定目錄
- 配置環境變量
2、安裝Docker(非必須這是是在Docker中安裝)
1、查看centos版本 # uname -r 3.10.0-693.el7.x86_64 要求:大於3.10 如果小於的話升級*(選做) # yum update 2、安裝docker # yum install docker 3、啟動docker # systemctl start docker # docker -v 4、開機啟動docker # systemctl enable docker 5、停止docker # systemctl stop docker
3、安裝ElasticSearch的鏡像
docker pull registry.docker-cn.com/library/elasticsearch
4、運行ElasticSearch -e ES_JAVA_OPTS="-Xms256m -Xmx256m" 表示占用的最大內存為256m,默認是2G [root@node1 ~]# docker run -e ES_JAVA_OPTS="-Xms256m -Xmx256m" -d -p 9200:9200 -p 9300:9300 --name ES01 5acf0e8da90b
5、測試是否啟動成功
http://192.168.124.136:9200/ 查看是否返回json數據
{ "name" : "Albion", "cluster_name" : "elasticsearch", "cluster_uuid" : "HUHGrosDQD2R6cubcwHLUQ", "version" : { "number" : "2.4.6", "build_hash" : "5376dca9f70f3abef96a77f4bb22720ace8240fd", "build_timestamp" : "2017-07-18T12:17:44Z", "build_snapshot" : false, "lucene_version" : "5.5.4" }, "tagline" : "You Know, for Search" }
3、Elastic的快速入門
最好的工具就是官方文檔,以下操作都在文檔中進行操作。(https://www.elastic.co/guide/cn/elasticsearch/guide/current/index.html)
1、基礎概念
面向文檔,JSON作為序列化格式,ElasticSearch的基本概念
索引(名詞):
如前所述,一個 索引 類似於傳統關系數據庫中的一個 數據庫 ,是一個存儲關系型文檔的地方。 索引 (index) 的復數詞為 indices 或 indexes 。
索引(動詞):
索引一個文檔 就是存儲一個文檔到一個 索引 (名詞)中以便它可以被檢索和查詢到。這非常類似於 SQL 語句中的 INSERT
關鍵詞,除了文檔已存在時新文檔會替換舊文檔情況之外。
類型:相當於數據庫中的表
文檔:相當於數據庫中的行,即每條數據都叫一個文檔
屬性:相當於數據庫中的列,即文檔的屬性
SpringBoot+ElasticSearch
1、新建項目SpringBoot1.5+Web+Nosql-->ElasticSearch
2、springBoot默認支持兩種技術和ES進行交互
1、Jest【需要導入使用】
利用JestClient和服務器的9200端口進行http通信
2、SpringData ElasticSearch【默認】
1)、客戶端:Client節點信息: clusterNodes: clusterName
2)、ElasticsearchTemplate操作es
3)、編寫ElasticsearchRepository子接口
步驟:
1、Jest
1、注釋SpringDataElasticSearch的依賴,並導入Jest【5.xx】的相關依賴
<!--<dependency>--> <!--<groupId>org.springframework.boot</groupId>--> <!--<artifactId>spring-boot-starter-data-elasticsearch</artifactId>--> <!--</dependency>--> <dependency> <groupId>io.searchbox</groupId> <artifactId>jest</artifactId> <version>5.3.3</version> </dependency>
2、修改配置文件application.properties
spring.elasticsearch.jest.uris= http://192.168.124.136:9200
3.建立一個實體類

package cn.edu.aynu.ENtity; import io.searchbox.annotations.JestId; import lombok.Data; /** * Description: elasticsearch-project * Created by lenovo on 2019/5/4 10:12 */ @Data public class Article { @JestId private Integer id; private String autor; private String title; private String content; }
4.建立一個測試類

package cn.edu.aynu; import cn.edu.aynu.ENtity.Article; import io.searchbox.client.JestClient; import io.searchbox.core.Index; 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 ElasticsearchApplicationTests { @Autowired JestClient jestClient; @Test public void contextLoads() throws IOException { //給elasticsearch中索引 (保存一個文檔) Article article = new Article(); article.setId(1); article.setTitle("Effect Java"); article.setAutor("Joshua Bloch"); article.setContent("Hello World"); //構建一個索引功能 Index index = new Index.Builder(article).index("db").type("article").build(); //執行 jestClient.execute(index); } }
5.執行結果
查詢數據

@Test public void search(){ // 查詢表達式 String json = "{\n" + " \"query\" : {\n" + " \"match\" : {\n" + " \"content\" : \"Hello\"\n" + " }\n" + " }\n" + "}"; // 構建搜索操作 Search search = new Search.Builder(json).addIndex("cuzz").addType("article").build(); // 執行 try { SearchResult result = jestClient.execute(search); System.out.println(result.getJsonString()); } catch (IOException e) { e.printStackTrace(); } }
執行結果
2、SpringData-Elastic
1、下載對應版本的ElasticSearch
如果版本不適配,會報錯,解決方案:升級SpringBoot版本,或者安裝合適的ES
2、在Docker中安裝適合版本的ES【2.4.6】
docker pull elasticsearch:2.4.6 docker run -e ES_JAVA_OPTS="-Xms256m -Xmx256m" -d -p 9201:9200 -p 9301:9300 --name ES02 id
3、編寫配置文件
spring: data: elasticsearch: cluster-name: elasticsearch cluster-nodes: 10.138.223.126:9301
4、修改pom文件,把使用data-elasticsearch,把剛才注釋刪除
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-elasticsearch</artifactId> </dependency>
5、操作ElasticSearch有兩種方式
1)、編寫一個ElasticsearchRepositry
2)、編寫一個ElasticsearchTemplate
6、ElasticsearchRepositry的操作
1)、新建一個bean/Book類,注意:@Document(indexName = "cuzz", type="book")

@Document(indexName = "cuzz",type="book") @Data public class Book { private Integer id; private String bookName; private String auto; public Book() { super(); } public Book(Integer id, String bookName, String auto) { super(); this.id = id; this.bookName = bookName; this.auto = auto; } }
2)、新建一個repositry/BookRepositry
public interface BookRepository extends ElasticsearchRepository<Book, Integer> { //自定義查詢方法 public List<Book> findByBookNameLike(String bookName); }
3)、編寫測試類
@Autowired BookRepositry bookRepositry; @Test public void testSearch(){ for (Book book : bookRepositry.findByBookNameLike("Effect")) { System.out.println(book); } }
SpringBoot的任務
1、異步任務
先開啟異步注解,添加@EnableAsync
@EnableAsync @SpringBootApplication public class Springboot12TaskApplication { public static void main(String[] args) { SpringApplication.run(Springboot12TaskApplication.class, args); } }
service,在方法上添加@Async
@Service public class AsynSerivce { @Async public void hello() { try { Thread.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("處理數據中..."); } }
controller
@RestController public class AsynController { @Autowired AsynSerivce asynSerivce; @GetMapping("/hello") public String hello() { asynSerivce.hello(); return "success"; } }
http://localhost:8080/hello
當我們訪問時,發現我們睡眠的3秒沒有起作用,而是直接就執行了這個方法,不會被阻塞在這里異步處理hello請求
2、定時任務
項目開發中經常需要執行一些定時任務,比如需要在每天凌晨時候,分析一次前一天的日志信息。Spring為我們提供了異步執行任務調度的方式,提供TaskExecutor 、TaskScheduler 接口。
主要有@Scheduled注解,cron()方法
底層源碼
public @interface Scheduled { /** * A cron-like expression, extending the usual UN*X definition to include triggers * on the second as well as minute, hour, day of month, month and day of week. * <p>E.g. {@code "0 * * * * MON-FRI"} means once per minute on weekdays * (at the top of the minute - the 0th second). * @return an expression that can be parsed to a cron schedule * @see org.springframework.scheduling.support.CronSequenceGenerator */ String cron() default ""; }
可以寫上一個測試類
@Service public class ScheduledService { // 表示周一到周六當秒為0時執行一次 @Scheduled(cron = "0 * * * * MON-SAT") public void hello() { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String date = sdf.format(new Date()); System.out.println(date + " hello..."); } }
開啟定時任務注解@EnableScheduling
@EnableScheduling @SpringBootApplication public class Springboot12TaskApplication { public static void main(String[] args) { SpringApplication.run(Springboot12TaskApplication.class, args); } }
很有意思的事情是就是只需要加上三個注解,一個是@Service ,一個是在主啟動類上開啟定時任務,另外一個就是執行你的定時時間
它就自動的進行了執行。
3、郵件任務
1、郵件發送需要引入spring-boot-starter-mail
可以看到spring-boot-starter-mail-xxx.jar對Sun公司的郵件api功能進行了相應的封裝。
MailSenderAutoConfiguration

@Configuration @ConditionalOnClass({ MimeMessage.class, MimeType.class, MailSender.class }) @ConditionalOnMissingBean(MailSender.class) @Conditional(MailSenderCondition.class) @EnableConfigurationProperties(MailProperties.class) @Import({ MailSenderJndiConfiguration.class, MailSenderPropertiesConfiguration.class }) public class MailSenderAutoConfiguration { /** * Condition to trigger the creation of a {@link MailSender}. This kicks in if either * the host or jndi name property is set. */ static class MailSenderCondition extends AnyNestedCondition { MailSenderCondition() { super(ConfigurationPhase.PARSE_CONFIGURATION); } @ConditionalOnProperty(prefix = "spring.mail", name = "host") static class HostProperty { } @ConditionalOnProperty(prefix = "spring.mail", name = "jndi-name") static class JndiNameProperty { } } }
首先,它會通過注入Mail的屬性配置類MailProperties:

@ConfigurationProperties(prefix = "spring.mail") public class MailProperties { private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8; /** * SMTP server host. For instance, `smtp.example.com`. */ private String host; /** * SMTP server port. */ private Integer port; /** * Login user of the SMTP server. */ private String username; /** * Login password of the SMTP server. */ private String password; /** * Protocol used by the SMTP server. */ private String protocol = "smtp"; /** * Default MimeMessage encoding. */ private Charset defaultEncoding = DEFAULT_CHARSET; /** * Additional JavaMail Session properties. */ private Map<String, String> properties = new HashMap<>(); /** * Session JNDI name. When set, takes precedence over other Session settings. */ private String jndiName; public String getHost() { return this.host; } public void setHost(String host) { this.host = host; } public Integer getPort() { return this.port; } public void setPort(Integer port) { this.port = port; } public String getUsername() { return this.username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return this.password; } public void setPassword(String password) { this.password = password; } public String getProtocol() { return this.protocol; } public void setProtocol(String protocol) { this.protocol = protocol; } public Charset getDefaultEncoding() { return this.defaultEncoding; } public void setDefaultEncoding(Charset defaultEncoding) { this.defaultEncoding = defaultEncoding; } public Map<String, String> getProperties() { return this.properties; } public void setJndiName(String jndiName) { this.jndiName = jndiName; } public String getJndiName() { return this.jndiName; } }
在MailSenderAutoConfiguration自動配置類中,創建了一個Bean,其類為JavaMailSenderImpl,它是Spring專門用來發送Mail郵件的服務類,SpringBoot也使用它來發送郵件。它是JavaMailSender接口的實現類,通過它的send()方法來發送不同類型的郵件,主要分為兩類,一類是簡單的文本郵件,不帶任何html格式,不帶附件,不帶圖片等簡單郵件,還有一類則是帶有html格式文本或者鏈接,有附件或者圖片的復雜郵件。
原文:https://blog.csdn.net/caychen/article/details/82887926
通用配置application.properties:
# 設置郵箱主機 spring.mail.host=smtp.qq.com # 設置用戶名 spring.mail.username=xxxxxx@qq.com # 設置密碼,該處的密碼是QQ郵箱開啟SMTP的授權碼而非QQ密碼 spring.mail.password=pwvtabrwxogxidac # 設置是否需要認證,如果為true,那么用戶名和密碼就必須的, # 如果設置false,可以不設置用戶名和密碼,當然也得看你的對接的平台是否支持無密碼進行訪問的。 spring.mail.properties.mail.smtp.auth=true # STARTTLS[1] 是對純文本通信協議的擴展。它提供一種方式將純文本連接升級為加密連接(TLS或SSL),而不是另外使用一個端口作加密通信。 spring.mail.properties.mail.smtp.starttls.enable=true spring.mail.properties.mail.smtp.starttls.required=true mail.from=${spring.mail.username} mail.to=yyyyyy@qq.com
由於使用QQ郵箱的用戶占多數,所以這里選擇QQ郵箱作為測試。還有注意的是spring.mail.password這個值不是QQ郵箱的密碼,而是QQ郵箱給第三方客戶端郵箱生成的授權碼。具體要登錄QQ郵箱,點擊設置,找到SMTP服務:
默認SMTP服務是關閉的,即默認狀態為關閉狀態,如果是第一次操作,點擊開啟后,會通過驗證會獲取到授權碼;而我之前已經開啟過SMTP服務,所以直接點擊生成授權碼后通過驗證獲取到授權碼。
下面是一個測試的例子
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-mail</artifactId> </dependency>
2、Spring Boot 自動配置MailSenderAutoConfiguration
3、定義MailProperties內容,配置在application.properties中
spring.mail.username=3176497244@qq.com
#設置密碼是qq郵箱生成的授權碼
spring.mail.password=yhgwerqdhdjmdche
#設置郵箱主機
spring.mail.host=smtp.qq.com
spring.mail.properties.mail.stmp.ssl.enable=true
4、自動裝配JavaMailSender
@Autowired JavaMailSenderImpl javaMailSender; @Test public void test03(){ SimpleMailMessage simpleMailMessage = new SimpleMailMessage(); /*設置主題*/ simpleMailMessage.setSubject("Hello World"); simpleMailMessage.setText("text"); simpleMailMessage.setTo(""); simpleMailMessage.setFrom("3176497244@qq.com"); javaMailSender.send(simpleMailMessage); }
5、測試郵件發送
這里有一個詳細的連接對於我們了解mail
(https://blog.csdn.net/caychen/article/details/82887926)
十三、SpringBoot的安全
Spring Security是針對Spring項目的安全框架,也是Spring Boot底層安全模塊默認的技術選型。他可以實現強大的web安全控制。對於安全控制,我們僅需引入spring-boot-starter-security模塊,進行少量的配置,即可實現強大的安全管理。
1、幾個類
- WebSecurityConfigurerAdapter:自定義Security策略
- AuthenticationManagerBuilder:自定義認證策略
- @EnableWebSecurity:開啟WebSecurity模式
2、基本概念
- 應用程序的兩個主要區域是“認證”和“授權”(或者訪問控制)。這兩個主要區域是Spring Security 的兩個目標。
- “認證”(Authentication),是建立一個他聲明的主體的過程(一個“主體”一般是指用戶,設備或一些可以在你的應用程序中執行動作的其他系統)。
- “授權”(Authorization)指確定一個主體是否允許在你的應用程序執行一個動作的過程。為了抵達需要授權的店,主體的身份已經有認證過程建立。
- 這個概念是通用的而不只在Spring Security中
官方文檔(https://docs.spring.io/spring-security/site/docs/current/guides/html5/helloworld-boot.html)
練習頁面下載(https://github.com/cuzz1/springboot-learning/tree/master/supporting/SpringSecurity實驗)
你需要自定義角色進行認證,授權
package cn.edu.aynu.security.config;
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;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
@EnableWebSecurity
public class MySecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
/*定制請求的授權規則 只能訪問首頁其他頁面拒絕訪問*/
http.authorizeRequests().antMatchers("/").permitAll()
.antMatchers("/level1/**").hasRole("VIP1")
.antMatchers("/level2/**").hasRole("VIP2")
.antMatchers("/level3/**").hasRole("VIP3");
/*開啟登入功能,如果有權限就來到登錄頁面 自動開啟配置登錄功能 提醒你登錄*/
//表單的name屬性就是user pwd
http.formLogin().usernameParameter("user").passwordParameter("pwd").loginPage("/userlogin");
//login 來到登錄頁
//重定向到/login?error表示登錄失敗
//更多詳細規定
//默認post形式的/login代表處理登錄
//一旦定制loginPage 那么loginPage 的post請求就是登錄
/*開啟自動配置的注銷功能*/
http.logout().logoutSuccessUrl("/");//注銷成功后要去哪的地址
// http.logout();//注銷成功后要去哪的地址
//訪問/logout 注銷用戶清空session
//注銷成功 會返回/login?logout頁面
/*有效期是14天*/
http.rememberMe().rememberMeParameter("remeber");
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// super.configure(auth);
auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder()).withUser("user1").password(new BCryptPasswordEncoder().encode("123456")).roles("VIP1","VIP2");
}
}
先說一下,為什么Thymeleaf 會直接解析login.html進入到他的源碼就會知道了
打開HttpSecurity 這個類
public AnonymousConfigurer<HttpSecurity> anonymous() throws Exception { return getOrApply(new AnonymousConfigurer<>()); } /** * Specifies to support form based authentication. If * {@link FormLoginConfigurer#loginPage(String)} is not specified a default login page * will be generated. * * <h2>Example Configurations</h2> * * The most basic configuration defaults to automatically generating a login page at * the URL "/login", redirecting to "/login?error" for authentication failure. The * details of the login page can be found on * {@link FormLoginConfigurer#loginPage(String)} *
可以參考<https://www.jianshu.com/p/246e4fec1469>
另外一定要注意版本的問題,springboot springSecurity 如果有不能識別的標簽的錯誤,那很可能就是版本的問題了。

<!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()"> <!--未登錄,點擊 <a th:href="@{/login}">登錄</a>--> <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>

<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Insert title here</title> </head> <body> <a th:href="@{/}">返回</a> <h1>羅漢拳</h1> <p>羅漢拳站當央,打起來不要慌</p> </body> </html>

<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Insert title here</title> </head> <body> <a th:href="@{/}">返回</a> <h1>武當長拳</h1> <p>長一點在長一點</p> </body> </html>

<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Insert title here</title> </head> <body> <a th:href="@{/}">返回</a> <h1>梯雲縱</h1> <p>踩自己的腳往上跳</p> </body> </html>

<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Insert title here</title> </head> <body> <a th:href="@{/}">返回</a> <h1>太極拳</h1> <p> 一個西瓜圓又圓 劈它一刀成兩半 你一半來 給你你不要 給他他不收 那就不給 把兩人攆走 他們不走你走 走啦,一揮手,傷自尊 不買西瓜別纏我,緩慢糾纏様 兩人纏我賴皮,手慢動作左右揮動 看我厲害,轉頭緩步拍蒼蠅狀 拍死了,手抱西瓜狀+奧特曼十字手+廣播操准備運動的站立 </p> </body> </html>

<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Insert title here</title> </head> <body> <a th:href="@{/}">返回</a> <h1>七傷拳</h1> <p>練這拳的人全都死了</p> </body> </html>

<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Insert title here</title> </head> <body> <a th:href="@{/}">返回</a> <h1>全真劍法</h1> <p>全都是真的</p> </body> </html>

<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Insert title here</title> </head> <body> <a th:href="@{/}">返回</a> <h1>葵花寶典</h1> <p>欲練神功,揮刀自宮</p> </body> </html>

<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Insert title here</title> </head> <body> <a th:href="@{/}">返回</a> <h1>龜派氣功</h1> <p>龜-派-氣-功-波</p> </body> </html>

<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Insert title here</title> </head> <body> <a th:href="@{/}">返回</a> <h1>獨孤九劍</h1> <p>欲練此劍,必先犯賤</p> </body> </html>

package cn.edu.aynu.security.Controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @Controller public class KungfuController { private final String PREFIX = "pages/"; /** * 歡迎頁 * @return */ @GetMapping("/") public String index() { return "welcome"; } /** * 登陸頁 * @return */ @GetMapping("/userlogin") public String loginPage() { return PREFIX+"login"; } /** * level1頁面映射 * @param path * @return */ @GetMapping("/level1/{path}") public String level1(@PathVariable("path")String path) { return PREFIX+"level1/"+path; } /** * level2頁面映射 * @param path * @return */ @GetMapping("/level2/{path}") public String level2(@PathVariable("path")String path) { return PREFIX+"level2/"+path; } /** * level3頁面映射 * @param path * @return */ @GetMapping("/level3/{path}") public String level3(@PathVariable("path")String path) { return PREFIX+"level3/"+path; } }