springBoot自定义启动器

一、starter的概念

starter 的理念:
starter 会把所有用到的依赖都给包含进来,避免了开发者自己去引入依赖所带来的麻烦。 需要注意的是
不同的 starter 是为了解决不同的依赖,所以它们内部的实现可能会有很大的差异,
例如 jpa 的 starter 和 Redis 的 starter 可能实现就不一样,这是因为 starter 的本质在 synthesize , 这是一层在逻辑层面的抽象,也许这种理念有点类似于Docker ,因为它们都是在做一个 “ 包装 ” 的操作。
starter 的实现:
虽然不同的 starter 实现起来各有差异, 但是他们基本上都会使用到两个相同的内容:
ConfigurationProperties 和 AutoConfiguration 。 因为 SpringBoot坚信 “ 约定大于配置 ” 这一理念,所以我们使用 ConfigurationProperties 来保存我们的配置, 并且这 些配置都可以有一个默认值,即在我们没有主动覆写原始配置的情况下,默认值就会生效,这在很多情
况下是非常有用的。
除此之外, starter 的 ConfigurationProperties 还使得所有的配置属性被聚集到一个文件中 (一般在 resources目录下的 application.properties ),这样我们就告别了 Spring 项目中 XML 地狱。

原理图

二、邮件的使用

1.pom依赖

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

 2.yml文件

可以决定是否加载JavaMailSender

spring:
  application:
    name: springBoot6
  redis:
    database: 0         #数据库索引
    host: 127.0.0.1     #主机位置
    port: 6379          #端口
    password:            #密码
    jedis:
      pool:
        max-active: 8   #最大连接数
        max-wait: -1    #最大阻塞等待时间(负数表示没限制)
        max-idle: 8     #最大空闲
        min-idle: 0     #最小空闲
    timeout: 10000      #连接超时时间
email:
  host: smtp.qq.com
  username: 2103349206@qq.com
  password: vnmjnmokyudgefgh
  enable: true

password不是QQ密码,是邮箱的授权码

 

 测试类中进行测试

package com.sjy.demo2;
 
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
 
@SpringBootTest
class Demo2ApplicationTests {
 
    @Autowired
    JavaMailSender mailSender;
    @Test
    void contextLoads() {
        SimpleMailMessage message = new SimpleMailMessage();
        message.setFrom("2490527049@qq.com");
        message.setTo("sunjiayi0513@qq.com");
        message.setSubject("主题:简单邮件");
        message.setText("测试邮件内容");
        mailSender.send(message);
    }
 
}

 三、自定义启动器 

为了更加方便,我们将验证码功能变成启动器,用时直接导入就可以了

由于启动器本身是一个spring项目,这时我们新建一个模块,不需要web

1.导入邮件的pom依赖

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

2.建立类EmailProperties,属性为yml文件的参数

报错原因是没有被spring所管理,spring没有填充属性 

3.yml文件

email:
  host: smtp.qq.com
  username: 2103349206@qq.com
  password: vnmjnmokyudgefgh
  enable: true

 4.创建接口

发送行为的方法,参数是接收方的邮箱

 5.建立实现类 

@EnableConfigurationProperties(value = EmailProperties.class) 表示开启加载配置的属性
相当于在 EmailProperties上加上compoent

@Configuration 将实现类变成一个组件,立马被加载

package com.yzm.yzm;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.JavaMailSenderImpl;
 
import java.util.Properties;
 
/**
 * @author 小宝的宝
 */
@Configuration
@EnableConfigurationProperties(value = EmailProperties.class)
public class EmailSendImpl implements EmailSend{
    private EmailProperties emailProperties;
    JavaMailSenderImpl mailSender;
    //
    @Autowired
    public EmailSendImpl(EmailProperties emailProperties){
        this.emailProperties=emailProperties;
        mailSender =new JavaMailSenderImpl();
        mailSender.setHost(emailProperties.getHost());
        mailSender.setUsername(emailProperties.getUsername());
        mailSender.setPassword(emailProperties.getPassword());
        mailSender.setDefaultEncoding("Utf-8");
        Properties p = new Properties();
        p.setProperty("mail.smtp.auth", "true");
        p.setProperty("mail.smtp.starttls.enable", "true");
        p.setProperty("mail.smtp.starttls.required", "true");
        mailSender.setJavaMailProperties(p);
    }
 
    @Override
    public String sendtext(String receiver) {
        SimpleMailMessage message = new SimpleMailMessage();
        message.setFrom(emailProperties.getUsername());
        message.setTo(receiver);
        message.setSubject("验证码");
        message.setText("1234");
        mailSender.send(message);
        return "aabb";
    }
}

测试类放入一个接收邮箱

package com.lj.springboot6;

import com.yzm.yzm.EmailSender;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;

@SpringBootTest
class SpringBoot6ApplicationTests {

    @Autowired
    private EmailSender emailSender;


    @Test
    void contextLoads() {
        emailSender.sendText("maliuan@foxmail.com");
    }

}

结果

 为了控制是否启用,可以增加一个属性 enable

 

 

在实现类上添加注解

@ConditionalOnProperty(prefix = "email",name= "enable",havingValue = "true")

表示只有enable属性为true才被spring管理,如果为false会直接报错

5.引入其他依赖

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>

<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.4.1</version>
<configuration>
<classifier>exec</classifier>
</configuration>
</plugin>

 将上面的plugin替换掉之前的pom里面最后一个长的plugin

为了命名规范,在pom文件中进行修改name和artifactId

然后下载相应的插件

 

 

  此时在maven仓库中可看到文件

 自定义启动器就完成了,下面我们来测试以下

在spring项目的pom中引入自定义的依赖

<dependency>
    <groupId>com.yzm</groupId>
    <artifactId>yzm-spring-boot-statrer</artifactId>
    <version>0.0.1-SNAPSHOT</version>
</dependency>

然后再配置yml文件

spring:
  application:
    name: springBoot6
  redis:
    database: 0         #数据库索引
    host: 127.0.0.1     #主机位置
    port: 6379          #端口
    password:            #密码
    jedis:
      pool:
        max-active: 8   #最大连接数
        max-wait: -1    #最大阻塞等待时间(负数表示没限制)
        max-idle: 8     #最大空闲
        min-idle: 0     #最小空闲
    timeout: 10000      #连接超时时间
email:
  host: smtp.qq.com
  username: 2103349206@qq.com
  password: vnmjnmokyudgefgh
  enable: true

 测试

package com.lj.springboot6;

import com.yzm.yzm.EmailSender;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;

@SpringBootTest
class SpringBoot6ApplicationTests {

    @Autowired
    private EmailSender emailSender;


    @Test
    void contextLoads() {
        emailSender.sendText("maliuan@foxmail.com");
    }

}

结果

 四、实用场景

创建用户时发送验证码功能 

1.创建用户实体类

邮箱名字

package com.lj.springboot6.pojo;

import lombok.Data;

@Data
public class User {
    private String account;
}

2.配置yml文件(redis用于将验证码存入缓存并保存60秒)

spring:
  application:
    name: springBoot6
  redis:
    database: 0         #数据库索引
    host: 127.0.0.1     #主机位置
    port: 6379          #端口
    password:            #密码
    jedis:
      pool:
        max-active: 8   #最大连接数
        max-wait: -1    #最大阻塞等待时间(负数表示没限制)
        max-idle: 8     #最大空闲
        min-idle: 0     #最小空闲
    timeout: 10000      #连接超时时间
email:
  host: smtp.qq.com
  username: 2103349206@qq.com
  password: vnmjnmokyudgefgh
  enable: true

用来操作各种redis类型

package com.lj.springboot6.util;

import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.data.redis.RedisSystemException;
import org.springframework.data.redis.connection.DataType;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisStringCommands;
import org.springframework.data.redis.connection.ReturnType;
import org.springframework.data.redis.connection.jedis.JedisConnection;
import org.springframework.data.redis.connection.lettuce.LettuceConnection;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ZSetOperations.TypedTuple;
import org.springframework.data.redis.core.types.Expiration;
import org.springframework.stereotype.Component;

import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.TimeUnit;

/**
 * Redis工具类
 * <p>
 * 声明: 此工具只简单包装了redisTemplate的大部分常用的api,没有包装redisTemplate所有的api
 * 如果对此工具类中的功能不太满意,或对StringRedisTemplate提供的api不太满意,
 * 那么可自行实现相应的{@link StringRedisTemplate}类中的对应execute方法,以达
 * 到自己想要的效果; 至于如何实现,则可参考源码或{@link LockOps}中的方法
 * <p>
 * 注: 此工具类依赖spring-boot-starter-data-redis类库
 * 注: 更多javadoc细节,可详见{@link RedisOperations}
 * <p>
 * 统一说明一: 方法中的key、 value都不能为null
 * 统一说明二: 不能跨数据类型进行操作,否者会操作失败/操作报错
 * 如: 向一个String类型的做Hash操作,会失败/报错......等等
 */
@Slf4j
@Component
@SuppressWarnings("unused")
public class RedisUtil implements ApplicationContextAware {

    /**
     * 使用StringRedisTemplate(,其是RedisTemplate的定制化升级)
     */
    private static StringRedisTemplate redisTemplate;

    private static final ObjectMapper mapper = new ObjectMapper();

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        RedisUtil.redisTemplate = applicationContext.getBean(StringRedisTemplate.class);
    }

    /**
     * key相关操作
     */
    public static class KeyOps {

        /**
         * 根据key,删除redis中的对应key-value
         * <p>
         * 注: 若删除失败,则返回false
         * <p>
         * 若redis中,不存在该key,那么返回的也是false
         * 所以,不能因为返回了false,就认为redis中一定还存
         * 在该key对应的key-value
         *
         * @param key 要删除的key
         * @return 删除是否成功
         */
        public static Boolean delete(String key) {
            log.info("delete(...) => key -> {}", key);
            // 返回值只可能为true/false,不可能为null
            Boolean result = redisTemplate.delete(key);
            log.info("delete(...) => result -> {}", result);
            if (result == null) {
                throw new RedisOpsResultIsNullException();
            }
            return result;
        }

        /**
         * 根据keys,批量删除key-value
         * <p>
         * 注: 若redis中,不存在对应的key,那么计数不会加1,即:
         * redis中存在的key-value里,有名为a1、a2的key,
         * 删除时,传的集合是a1、a2、a3,那么返回结果为2
         *
         * @param keys 要删除的key集合
         * @return 删除了的key-value个数
         */
        public static long delete(Collection<String> keys) {
            log.info("delete(...) => keys -> {}", keys);
            Long count = redisTemplate.delete(keys);
            log.info("delete(...) => count -> {}", count);
            if (count == null) {
                throw new RedisOpsResultIsNullException();
            }
            return count;
        }

        /**
         * 将key对应的value值进行序列化,并返回序列化后的value值
         * <p>
         * 注: 若不存在对应的key,则返回null
         * 注: dump时,并不会删除redis中的对应key-value
         * 注: dump功能与restore相反
         *
         * @param key 要序列化的value的key
         * @return 序列化后的value值
         */
        public static byte[] dump(String key) {
            log.info("dump(...) =>key -> {}", key);
            byte[] result = redisTemplate.dump(key);
            log.info("dump(...) => result -> {}", result);
            return result;
        }

        /**
         * 将给定的value值,反序列化到redis中,形成新的key-value
         *
         * @param key        value对应的key
         * @param value      要反序列的value值
         *                   注: 这个值可以由{@link this#dump(String)}获得
         * @param timeToLive 反序列化后的key-value的存活时长
         * @param unit       timeToLive的单位
         * @throws RedisSystemException 如果redis中已存在同样的key时,抛出此异常
         */
        public static void restore(String key, byte[] value, long timeToLive, TimeUnit unit) {
            restore(key, value, timeToLive, unit, false);
        }

        /**
         * 将给定的value值,反序列化到redis中,形成新的key-value
         *
         * @param key     value对应的key
         * @param value   要反序列的value值
         *                注: 这个值可以由{@link this#dump(String)}获得
         * @param timeout 反序列化后的key-value的存活时长
         * @param unit    timeout的单位
         * @param replace 若redis中已经存在了相同的key,是否替代原来的key-value
         * @throws RedisSystemException 如果redis中已存在同样的key,且replace为false时,抛出此异常
         */
        public static void restore(String key, byte[] value, long timeout, TimeUnit unit, boolean replace) {
            log.info("restore(...) => key -> {},value -> {},timeout -> {},unit -> {},replace -> {}",
                    key, value, timeout, unit, replace);
            redisTemplate.restore(key, value, timeout, unit, replace);
        }

        /**
         * redis中是否存在,指定key的key-value
         *
         * @param key 指定的key
         * @return 是否存在对应的key-value
         */
        public static boolean hasKey(String key) {
            log.info("hasKey(...) => key -> {}", key);
            Boolean result = redisTemplate.hasKey(key);
            log.info("hasKey(...) => result -> {}", result);
            if (result == null) {
                throw new RedisOpsResultIsNullException();
            }
            return result;
        }

        /**
         * 给指定的key对应的key-value设置: 多久过时
         * <p>
         * 注:过时后,redis会自动删除对应的key-value
         * 注:若key不存在,那么也会返回false
         *
         * @param key     指定的key
         * @param timeout 过时时间
         * @param unit    timeout的单位
         * @return 操作是否成功
         */
        public static boolean expire(String key, long timeout, TimeUnit unit) {
            log.info("expire(...) => key -> {},timeout -> {},unit -> {}", key, timeout, unit);
            Boolean result = redisTemplate.expire(key, timeout, unit);
            log.info("expire(...) => result is -> {}", result);
            if (result == null) {
                throw new RedisOpsResultIsNullException();
            }
            return result;
        }

        /**
         * 给指定的key对应的key-value设置: 什么时候过时
         * <p>
         * 注:过时后,redis会自动删除对应的key-value
         * 注:若key不存在,那么也会返回false
         *
         * @param key  指定的key
         * @param date 啥时候过时
         * @return 操作是否成功
         */
        public static boolean expireAt(String key, Date date) {
            log.info("expireAt(...) => key -> {},date -> {}", key, date);
            Boolean result = redisTemplate.expireAt(key, date);
            log.info("expireAt(...) => result is -> {}", result);
            if (result == null) {
                throw new RedisOpsResultIsNullException();
            }
            return result;
        }

        /**
         * 找到所有匹配pattern的key,并返回该key的结合.
         * <p>
         * 提示:若redis中键值对较多,此方法耗时相对较长,慎用!慎用!慎用!
         *
         * @param pattern 匹配模板
         *                注: 常用的通配符有:
         *                ?    有且只有一个;
         *                *     >=0个;
         * @return 匹配pattern的key的集合 可能为null
         */
        public static Set<String> keys(String pattern) {
            log.info("keys(...) => pattern -> {}", pattern);
            Set<String> keys = redisTemplate.keys(pattern);
            log.info("keys(...) => keys -> {}", keys);
            return keys;
        }

        /**
         * 将当前数据库中的key对应的key-value,移动到对应位置的数据库中
         * <p>
         * 注:单机版的redis,默认将存储分为16个db,index为0 到 15
         * 注:同一个db下,key唯一; 但是在不同db中,key可以相同
         * 注:若目标db下,已存在相同的key,那么move会失败,返回false
         *
         * @param key     定位要移动的key-value的key
         * @param dbIndex 要移动到哪个db
         * @return 移动是否成功
         * 注: 若目标db下,已存在相同的key,那么move会失败,返回false
         */
        public static boolean move(String key, int dbIndex) {
            log.info("move(...) => key  -> {},dbIndex -> {}", key, dbIndex);
            Boolean result = redisTemplate.move(key, dbIndex);
            log.info("move(...) =>result -> {}", result);
            if (result == null) {
                throw new RedisOpsResultIsNullException();
            }
            return result;
        }

        /**
         * 移除key对应的key-value的过期时间,使该key-value一直存在
         * <p>
         * 注: 若key对应的key-value,本身就是一直存在(无过期时间的),那么persist方法会返回false;
         * 若没有key对应的key-value存在,本那么persist方法会返回false;
         *
         * @param key 定位key-value的key
         * @return 操作是否成功
         */
        public static boolean persist(String key) {
            log.info("persist(...) => key -> {}", key);
            Boolean result = redisTemplate.persist(key);
            log.info("persist(...) => result -> {}", result);
            if (result == null) {
                throw new RedisOpsResultIsNullException();
            }
            return result;
        }

        /**
         * 获取key对应的key-value的过期时间
         * <p>
         * 注: 若key-value永不过期,那么返回的为-1
         * 注: 若不存在key对应的key-value,那么返回的为-2
         * 注:若存在零碎时间不足1 SECONDS,则(大体上)四舍五入到SECONDS级别
         *
         * @param key 定位key-value的key
         * @return 过期时间(单位s)
         */
        public static long getExpire(String key) {
            return getExpire(key, TimeUnit.SECONDS);
        }

        /**
         * 获取key对应的key-value的过期时间
         * <p>
         * 注: 若key-value永不过期,那么返回的为-1
         * 注: 若不存在key对应的key-value,那么返回的为-2
         * 注:若存在零碎时间不足1 unit,则(大体上)四舍五入到unit别
         *
         * @param key 定位key-value的key
         * @return 过期时间(单位unit)
         */
        public static long getExpire(String key, TimeUnit unit) {
            log.info("getExpire(...) =>key -> {},unit is -> {}", key, unit);
            Long result = redisTemplate.getExpire(key, unit);
            log.info("getExpire(...) => result ->  {}", result);
            if (result == null) {
                throw new RedisOpsResultIsNullException();
            }
            return result;
        }

        /**
         * 从redis的所有key中,随机获取一个key
         * <p>
         * 注: 若redis中不存在任何key-value,那么这里返回null
         *
         * @return 随机获取到的一个key
         */
        public static String randomKey() {
            String result = redisTemplate.randomKey();
            log.info("randomKey(...) => result is -> {}", result);
            return result;
        }

        /**
         * 重命名对应的oldKey为新的newKey
         * <p>
         * 注: 若oldKey不存在,则会抛出异常.
         * 注: 若redis中已存在与newKey一样的key,
         * 那么原key-value会被丢弃,
         * 只留下新的key,以及原来的value
         * 示例说明: 假设redis中已有 (keyAlpha,valueAlpha) 和 (keyBeta,valueBeat),
         * 在使用rename(keyAlpha,keyBeta)替换后,redis中只会剩下(keyBeta,valueAlpha)
         *
         * @param oldKey 旧的key
         * @param newKey 新的key
         * @throws RedisSystemException 若oldKey不存在时,抛出此异常
         */
        public static void rename(String oldKey, String newKey) {
            log.info("rename(...) => oldKey -> {},newKey -> {}", oldKey, newKey);
            redisTemplate.rename(oldKey, newKey);
        }

        /**
         * 当redis中不存在newKey时,重命名对应的oldKey为新的newKey
         * 否者不进行重命名操作
         * <p>
         * 注: 若oldKey不存在,则会抛出异常.
         *
         * @param oldKey 旧的key
         * @param newKey 新的key
         * @throws RedisSystemException 若oldKey不存在时,抛出此异常
         */
        public static boolean renameIfAbsent(String oldKey, String newKey) {
            log.info("renameIfAbsent(...) => oldKey -> {},newKey -> {}", oldKey, newKey);
            Boolean result = redisTemplate.renameIfAbsent(oldKey, newKey);
            log.info("renameIfAbsent(...) => result -> {}", result);
            if (result == null) {
                throw new RedisOpsResultIsNullException();
            }
            return result;
        }

        /**
         * 获取key对应的value的数据类型
         * <p>
         * 注: 若redis中不存在该key对应的key-value,那么这里返回NONE
         *
         * @param key 用于定位的key
         * @return key对应的value的数据类型
         */
        public static DataType type(String key) {
            log.info("type(...) => key -> {}", key);
            DataType result = redisTemplate.type(key);
            log.info("type(...) => result -> {}", result);
            return result;
        }
    }

    /**
     * string相关操作
     */
    public static class StringOps {

        /**
         * 设置key-value
         * <p>
         * 注: 若已存在相同的key,那么原来的key-value会被丢弃
         *
         * @param key   key
         * @param value key对应的value
         */
        public static void set(String key, String value) {
            log.info("set(...) => key -> {},value -> {}", key, value);
            redisTemplate.opsForValue().set(key, value);
        }

        /**
         * 处理redis中key对应的value值,将第offset位的值,设置为1或0
         * <p>
         * 说明: 在redis中,存储的字符串都是以二级制的进行存在的; 如存储的key-value里,值为abc,实际上,
         * 在redis里面存储的是01
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值