springboot 註冊dao層 service 層的三種方式,高級內容詳解

可使用三種註解來引入DAO層的接口到spring容器中。
1.@Mapper,寫在每個DAO層接口上,以下:html

2.@MapperScan和@ComponentScan二者之一。前者的意義是將指定包中的全部接口都標註爲DAO層接口,至關於在每個接口上寫@Mapper。後者則是代替全部html5

//指定這是一個操做數據庫的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);
}
}

 

第二種java

配置文件版node

mybatis:
config‐location: classpath:mybatis/mybatis‐config.xml 指定全局配置文件的位置
mapper‐locations: classpath:mybatis/mapper/*.xml 指定sql映射文件的位置

自定義MyBatis的配置規則;給容器中添加一個ConfigurationCustomizer;mysql

給下列文件設置駝峯命名法
@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 JPAlinux

SpringData簡介git

 

JPA:ORM(Object Relational Mapping);
1)、編寫一個實體類(bean)和數據表進行映射,而且配置好映射關係;github

//使用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)web

 

//繼承JpaRepository來完成對數據庫的操做
public interface UserRepository extends JpaRepository<User,Integer> {
}

pom 文件的基本配置redis

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.引入 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
Pom
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;
}
Department
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;
}
Employee
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);
}
EmployeeMapper

若是你不想每個接口都是用@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) + "]";

            }
        };

    }





}
自定義keyGenerator
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);
    }
}
Service層使用自定義的keyGenerator
  @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 {

 

 

 

 

 

二、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的消息中間件

 

一、大多數應用,能夠經過消息服務中間件來提高系統的異步通訊、拓展解耦能力

二、消息服務中的兩個重要概念:

消息代理(message broker)和目的地(destination),當消息發送者發送消息之後,將由消息代理接管,消息代理保證消息傳遞到指定的目的地。

三、消息隊列主要的兩種形式的目的地

1)、隊列(queue):點對點消息通訊【point-to-point】,取出一個沒一個,一個發佈,多個消費

2)、主題(topic):發佈(publish)/訂閱(subscribe)消息通訊,多人【訂閱者】能夠同時接到消息

四、JMS(Java Message Service) Java消息服務:

  • 基於JVM消息規範的代理。ActiveMQ/HornetMQ是JMS的實現

五、AMQP(Advanced Message Queuing Protocol)

  • 高級消息隊列協議,也是一個消息代理的規範,兼容JMS
  • RabbitMQ是AMQP的實現

六、SpringBoot的支持

spring-jms提供了對JMS的支持

spring-rabbit提供了對AMQP的支持

須要建立ConnectionFactory的實現來鏈接消息代理

提供JmsTemplate,RabbitTemplate來發送消息

@JmsListener(JMS).@RabbitListener(AMQP)註解在方法上的監聽消息代理髮布的消息

@EnableJms,@EnableRabbit開啓支持

七、SpringBoot的自動配置

  • JmsAutoConfiguration
  • RabbitAutoConfiguration

 

二、RabbitMQ簡介

AMQP的實現

一、核心概念

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:表示消息隊列 服務實體

 

 

 

二、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個或者多個單詞

點開後

建立工程整合

 一、RabbitAutoConfiguration 二、自動配置了鏈接工廠 ConnectionFactory 三、RabbitProperties封裝了 RabbitMQ 四、RabbitTemplate:給RabbitMQ發送和接受消息的 五、AmqpAdmin:RabbitMQ的系統管理功能組件

 

一、新建SpringBoot工程,SpringBoot1.5+Integeration/RabbitMQ+Web

二、RabbitAutoConfiguration文件

三、編寫配置文件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]}

七、使用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許可條款下的開放源碼發佈,是當前流行的企業級搜索引擎。設計用於雲計算中,可以達到實時搜索,穩定,可靠,快速,安裝使用方便。

二、ElasticSearch的安裝

一、安裝java最新版本

  • 下載linux的.tar.gz
  • 解壓到指定目錄
  • 配置環境變量

二、安裝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

三、安裝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

五、測試是否啓動成功

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"
}

三、Elastic的快速入門

最好的工具就是官方文檔,如下操做都在文檔中進行操做。(https://www.elastic.co/guide/cn/elasticsearch/guide/current/index.html

一、基礎概念

面向文檔,JSON做爲序列化格式,ElasticSearch的基本概念

索引(名詞):

如前所述,一個 索引 相似於傳統關係數據庫中的一個 數據庫 ,是一個存儲關係型文檔的地方。 索引 (index) 的複數詞爲 indices 或 indexes 。

索引(動詞):

索引一個文檔 就是存儲一個文檔到一個 索引 (名詞)中以便它能夠被檢索和查詢到。這很是相似於 SQL 語句中的 INSERT 關鍵詞,除了文檔已存在時新文檔會替換舊文檔狀況以外。

類型:至關於數據庫中的表

文檔:至關於數據庫中的行,即每條數據都叫一個文檔

屬性:至關於數據庫中的列,即文檔的屬性

SpringBoot+ElasticSearch

一、新建項目SpringBoot1.5+Web+Nosql-->ElasticSearch

二、springBoot默認支持兩種技術和ES進行交互

​ 一、Jest【須要導入使用】

​ 利用JestClient和服務器的9200端口進行http通訊

​ 二、SpringData ElasticSearch【默認】

​ 1)、客戶端:Client節點信息: clusterNodes: clusterName

​ 2)、ElasticsearchTemplate操做es

​ 3)、編寫ElasticsearchRepository子接口

步驟: 

一、Jest

一、註釋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>

二、修改配置文件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;



}
Article

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);

    }

}
Test

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();
    }
}
Test

執行結果

二、SpringData-Elastic

 

一、下載對應版本的ElasticSearch

若是版本不適配,會報錯,解決方案:升級SpringBoot版本,或者安裝合適的ES

二、在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

三、編寫配置文件

spring:
  data:
    elasticsearch:
      cluster-name: elasticsearch
      cluster-nodes: 10.138.223.126:9301

四、修改pom文件,把使用data-elasticsearch,把剛纔註釋刪除

 

       <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
        </dependency>

五、操做ElasticSearch有兩種方式

1)、編寫一個ElasticsearchRepositry
2)、編寫一個ElasticsearchTemplate

六、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;
    }
}
Book

 2)、新建一個repositry/BookRepositry

方法參考官方文檔(https://docs.spring.io/spring-data/elasticsearch/docs/3.1.0.RELEASE/reference/html/#elasticsearch.repositories

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的任務

一、異步任務

先開啓異步註解,添加@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請求

二、定時任務

項目開發中常常須要執行一些定時任務,好比須要在天天凌晨時候,分析一次前一天的日誌信息。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 ,一個是在主啓動類上開啓定時任務,另一個就是執行你的定時時間

它就自動的進行了執行。

三、郵件任務

一、郵件發送須要引入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 {
        }
    }
}
MailSenderAutoConfiguration

 

首先,它會經過注入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;
    }

}
MailProperties

在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>

二、Spring Boot 自動配置MailSenderAutoConfiguration

三、定義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

四、自動裝配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);


  }

五、測試郵件發送

這裏有一個詳細的鏈接對於咱們瞭解mail

 (https://blog.csdn.net/caychen/article/details/82887926)

十3、SpringBoot的安全

Spring Security是針對Spring項目的安全框架,也是Spring Boot底層安全模塊默認的技術選型。他能夠實現強大的web安全控制。對於安全控制,咱們僅需引入spring-boot-starter-security模塊,進行少許的配置,便可實現強大的安全管理。

一、幾個類

  • WebSecurityConfigurerAdapter:自定義Security策略
  • AuthenticationManagerBuilder:自定義認證策略
  • @EnableWebSecurity:開啓WebSecurity模式

二、基本概念

  • 應用程序的兩個主要區域是「認證」和「受權」(或者訪問控制)。這兩個主要區域是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>
welcome.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>
level1 1.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>
level1 2.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>
level1 3.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>
level2 1.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>
level2 2.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>
level2 3.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>
level3 1.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>
level3 2.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>
level3 3.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;
    }


}
controller
相關文章
相關標籤/搜索