一、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: SpringBoot_06
mail:
host: smtp.qq.com
username: xxxxxx@qq.com
password: xxxxxxxxxx
properties:
mail:
smtp:
auth: true
starttls:
enable: true
required: true
password不是QQ密码,是邮箱的授权码
进入邮箱点击设置——》账户——》开启第二行——》生成授权码
3、测试类中进行测试
package com.zxy.code;
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;
import javax.sql.DataSource;
@SpringBootTest
class SpringBoot06ApplicationTests {
@Autowired
JavaMailSender mailSender;
@Test
void contextLoads() {
SimpleMailMessage message = new SimpleMailMessage();
message.setFrom("2781494496@qq.com");
message.setTo("zxyqyf@foxmail.com");
message.setSubject("主题:简单邮件");
message.setText("测试邮件内容");
mailSender.send(message);
}
}
三、自定义启动器
为了更加方便,我们将验证码功能变成启动器,用时直接导入就可以了
由于启动器本身是一个spring项目,这时我们新建一个模块,不需要web
1.新建项目( yzm-spring-boot-starter)
2.导入邮件的pom依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
3.建立类EmailProperties,属性为yml文件的参数
package com.yzm.yzm;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* 邮件属性配置
*/
@AllArgsConstructor
@NoArgsConstructor
@Data
@ConfigurationProperties(prefix = "email")
public class EmailProperties {
/**
* SMTP的主机协议
*/
private String host;
/*
发送方用户名
*/
private String username;
/**
* 发送方授权码
*/
private String password;
/**
* 是否启用邮箱功能
*/
private boolean enable;
}
报错原因是没有被spring所管理,spring没有填充属性
4.yml文件
email:
host: smtp.qq.com
username: xxxxxx@qq.com
password: 邮箱授权码
5.创建接口
发送行为的方法,参数是接收方的邮箱
package com.yzm.yzm;
/**
* 邮箱发送者
*/
public interface EmailSender {
// 发送邮件的方法
String sendText(String receiver);
}
6.建立实现类
@EnableConfigurationProperties(value = EmailProperties.class) 表示开启加载配置的属性
相当于在 EmailProperties上加上compoent
@Configuration 将实现类变成一个组件,立马被加载
package com.yzm.yzm;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSenderImpl;
import java.util.Properties;
/**
* 邮件发送实现类
*/
@Configuration
@EnableConfigurationProperties(value = EmailProperties.class) //开始配置的属性读取
@ConditionalOnProperty(prefix = "email",name = "enable",havingValue = "true")
public class EmailSendImpl implements EmailSender{
private EmailProperties emailProperties;
private JavaMailSenderImpl mailSender;
// 1. 获得注入的信息
@Autowired
public EmailSendImpl(EmailProperties emailProperties){
// 初始化一个邮件发送的对象
this.emailProperties=emailProperties;
mailSender =new JavaMailSenderImpl();
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("aaaaaaaa");
mailSender.send(message);
return "aabb";
}
}
@Configuration:将此类声明为组件,并且交给spring进行管理
@Slf4j:日志输出
@EnableConfigurationProperties(value = EmailProperties.class)完成对应元素组件化
@ConditionalOnProperty(prefix = "email", value = "enable", havingValue = "true")控制邮件功能是否可用
测试类放入一个接收邮箱
package com.yzm.yzm;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class YzmSpringBootStarterApplicationTests {
@Autowired
private EmailSender sender;
@Test
void contextLoads() {
// String yzm = sender.sendText("zxyqyf@foxmail.com");
// System.out.println(yzm);
sender.sendText("2781494496@qq.com");
}
}
运行效果:
为了控制是否启用,可以在yml文件中增加一个属性 enable
email:
enable: true
host: smtp.qq.com
username: xxxxxxxx@qq.com
password: 邮箱授权码
EmailProperties中增加一个属性
在实现类上添加注解
@EnableConfigurationProperties(value = EmailProperties.class) //开始配置的属性读取
表示只有enable属性为true才被spring管理,如果为false会直接报错
四、邮箱的使用
提高邮箱使用灵活性
由于每个人的邮箱都不一样,不可能将发送和收件人的邮箱以及是否启用邮箱都固定,所以要将yml中的内容进行更改,提高邮箱使用灵活性。
步骤:
1、在yzm-spring-boot-starter导入yml文件注入依赖
<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>
2、在resource文件下添加文件 (如下图)
META-INF > spring.factories
文件内容:
#当前springboot的自动配置类 org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.yzm.yzm.EmailSendImpl 此段代码的说明:当整个项目运行时,对应类就会随着运行
2、将整个项目打包成jar包
最后到本地仓库中查看有没有yzm这个文件夹(这个名字就是自己命名的)可以在pom文件中查看
<modelVersion>4.0.0</modelVersion>
<groupId>com.yzm</groupId>
<artifactId>yzm-spring-boot-starter</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>yzm-spring-boot-starter</name>
<description>yzm-spring-boot-starter</description>
打包成功。
3、到之前的项目中导入邮箱依赖(必须使用同一个本地仓库)
<dependency>
<groupId>com.yzm</groupId>
<artifactId>yzm-spring-boot-starter</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
4、在测试类中加入注解
// 使用自定义的邮箱启动器 @Autowired private EmailSender sender;
导入此注解时会进行报错,原因:没有配置资源文件
email:
enable: true
host: smtp.qq.com
username: xxxxxx@qq.com
password: 邮箱授权吗
加了基本配置文件后,还是报错(原因:没有开启邮箱,有提示说明导入依赖成功)
导入成功
五、结合redis实现操作
由于现在数据繁多,不可能每次都会进入到数据库拿取数据,这时候就会运用缓存来提高程序运行速度。
1、步骤
1、导入操作redis的依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2、在applicaion.yml资源文件中加入redis资源
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 #连接超时时间
3、将帮助类写入到项目中
conf--->CrossConfiguration
package com.zxy.code.conf;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.util.ClassUtils;
import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.time.Duration;
@Configuration
@EnableCaching //开启缓存
public class RedisConfiguration extends CachingConfigurerSupport {
//@Bean 相当于 @Component,只不过Bean可以写在方法上面,内在跟Component是一样的
@Bean
@Primary
//cachemManager缓存管理器
CacheManager cacheManager(RedisConnectionFactory factory) {
RedisCacheConfiguration cacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
.computePrefixWith(cacheName -> cacheName + ":-cache-:")
/*设置缓存过期时间*/
.entryTtl(Duration.ofHours(1))
/*禁用缓存空值,不缓存null校验*/
.disableCachingNullValues()
/*设置CacheManager的值序列化方式为json序列化,可使用加入@Class属性*/
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(
new GenericJackson2JsonRedisSerializer()
));
/*使用RedisCacheConfiguration创建RedisCacheManager*/
RedisCacheManager manager = RedisCacheManager.builder(factory)
.cacheDefaults(cacheConfiguration)
.build();
return manager;
}
@Bean
//cachemManager缓存管理器
CacheManager yzmManager(RedisConnectionFactory factory) {
RedisCacheConfiguration cacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
.computePrefixWith(cacheName -> cacheName + ":-yzm-:")
/*设置缓存过期时间*/
.entryTtl(Duration.ofSeconds(60))
/*禁用缓存空值,不缓存null校验*/
.disableCachingNullValues()
/*设置CacheManager的值序列化方式为json序列化,可使用加入@Class属性*/
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(
new GenericJackson2JsonRedisSerializer()
));
/*使用RedisCacheConfiguration创建RedisCacheManager*/
RedisCacheManager manager = RedisCacheManager.builder(factory)
.cacheDefaults(cacheConfiguration)
.build();
return manager;
}
@Bean
@Primary
//redisTemplate直接用它操作redis里面的内容
public RedisTemplate redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<String, Object>();
redisTemplate.setConnectionFactory(factory);
RedisSerializer stringSerializer = new StringRedisSerializer();
/* key序列化 */
redisTemplate.setKeySerializer(stringSerializer);
/* value序列化 */
redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
/* Hash key序列化 */
redisTemplate.setHashKeySerializer(stringSerializer);
/* Hash value序列化 */
redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
@Bean
@Primary
@Override
//keyGenerator主键生成器
public KeyGenerator keyGenerator() {
return (Object target, Method method, Object... params) -> {
final int NO_PARAM_KEY = 0;
final int NULL_PARAM_KEY = 53;
StringBuilder key = new StringBuilder();
/* Class.Method: */
key.append(target.getClass().getSimpleName())
.append(".")
.append(method.getName())
.append(":");
if (params.length == 0) {
return key.append(NO_PARAM_KEY).toString();
}
int count = 0;
for (Object param : params) {
/* 参数之间用,进行分隔 */
if (0 != count) {
key.append(',');
}
if (param == null) {
key.append(NULL_PARAM_KEY);
} else if (ClassUtils.isPrimitiveArray(param.getClass())) {
int length = Array.getLength(param);
for (int i = 0; i < length; i++) {
key.append(Array.get(param, i));
key.append(',');
}
} else if (ClassUtils.isPrimitiveOrWrapper(param.getClass()) || param instanceof String) {
key.append(param);
} else {
/*JavaBean一定要重写hashCode和equals*/
key.append(param.hashCode());
}
count++;
}
return key.toString();
};
}
}
conf--->RedisConfiguration
package com.zxy.code.conf;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.util.ClassUtils;
import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.time.Duration;
@Configuration
@EnableCaching
public class RedisConfiguration extends CachingConfigurerSupport {
@Bean
@Primary
CacheManager cacheManager(RedisConnectionFactory factory) {
RedisCacheConfiguration cacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
.computePrefixWith(cacheName -> cacheName + ":-cache-:")
/*设置缓存过期时间*/
.entryTtl(Duration.ofHours(1))
/*禁用缓存空值,不缓存null校验*/
.disableCachingNullValues()
/*设置CacheManager的值序列化方式为json序列化,可使用加入@Class属性*/
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(
new GenericJackson2JsonRedisSerializer()
));
/*使用RedisCacheConfiguration创建RedisCacheManager*/
RedisCacheManager manager = RedisCacheManager.builder(factory)
.cacheDefaults(cacheConfiguration)
.build();
return manager;
}
@Bean
@Primary
public RedisTemplate redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<String, Object>();
redisTemplate.setConnectionFactory(factory);
RedisSerializer stringSerializer = new StringRedisSerializer();
/* key序列化 */
redisTemplate.setKeySerializer(stringSerializer);
/* value序列化 */
redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
/* Hash key序列化 */
redisTemplate.setHashKeySerializer(stringSerializer);
/* Hash value序列化 */
redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
@Bean
@Primary
@Override
public KeyGenerator keyGenerator() {
return (Object target, Method method, Object... params) -> {
final int NO_PARAM_KEY = 0;
final int NULL_PARAM_KEY = 53;
StringBuilder key = new StringBuilder();
/* Class.Method: */
key.append(target.getClass().getSimpleName())
.append(".")
.append(method.getName())
.append(":");
if (params.length == 0) {
return key.append(NO_PARAM_KEY).toString();
}
int count = 0;
for (Object param : params) {
/* 参数之间用,进行分隔 */
if (0 != count) {
key.append(',');
}
if (param == null) {
key.append(NULL_PARAM_KEY);
} else if (ClassUtils.isPrimitiveArray(param.getClass())) {
int length = Array.getLength(param);
for (int i = 0; i < length; i++) {
key.append(Array.get(param, i));
key.append(',');
}
} else if (ClassUtils.isPrimitiveOrWrapper(param.getClass()) || param instanceof String) {
key.append(param);
} else {
/*JavaBean一定要重写hashCode和equals*/
key.append(param.hashCode());
}
count++;
}
return key.toString();
};
}
}
4、新建一个实体类和控制类
pojo:
package com.zxy.code.pojo;
import lombok.Data;
import org.springframework.stereotype.Component;
@Data
@Component
public class User {
private String account;
}
UserController:
package com.zxy.code.controller;
import com.yzm.yzm.EmailSender;
import com.zxy.code.pojo.User;
import com.zxy.code.util.RedisUtil;
import com.zxy.code.util.Task;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.concurrent.TimeUnit;
@RestController
@RequestMapping("/user")
public class UserController {
// @Autowired
// private EmailSender sender;
@Autowired
private Task task ;
@GetMapping("/register")
public String register(User user) {
if (user.getAccount().length() < 5) {
return "不符合规则";
}
// 普安段邮箱验证码无效
String yzm = RedisUtil.StringOps.get(user.getAccount());
// 消息队列
// 异步操作
if(yzm==null){
// yzm = sender.sendText(user.getAccount());
// RedisUtil.StringOps.setEx(user.getAccount(),yzm,1, TimeUnit.SECONDS);
task.sendyzm(user.getAccount());
return "验证码已经发送";
}
return "yzm目前还是有效的";
}
@GetMapping("/verify")
public String verify(User user, String yzm) {
String code = RedisUtil.StringOps.get(user.getAccount());
if(code==null){
return "有毛病";
}
return code.equals(yzm)?"yes":"no";
}
}
运行之后出现一个错误:
原因是没有打上自动注入注解
2、邮箱的使用
前言:在我们注册一个邮箱时,往往会出现注册缓慢,理想状态应该为先出现注册成功,在之后出现邮箱发送成功
解决方法:
1、消息队列
2、异步操作
通常采取第二种方法,使用异步操作
步骤:
1、创建一个task类,专用来发送验证码
package com.zxy.code.util;
import com.yzm.yzm.EmailSender;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
/**
* 存放所有的異步操作
*/
@Component
public class Task {
@Autowired
private EmailSender emailSender;
@Async
public void sendyzm(String account){
String yzm = emailSender.sendText(account);
RedisUtil.StringOps.setEx(account,yzm,1, TimeUnit.MINUTES);
}
}
注意点:
一定要注入注解,不然会报错
其中
@Component(将此类声明为bean对象交给spring进行管理)
@Autowired(将其他类注入进来)
@Async(异步注解)这三个注解尤为重要
2、在启动类中注入开启异步注解
@EnableAsync
3、在controller类中注入task注解,
@Autowired private Task task;
package com.zxy.code.controller;
import com.yzm.yzm.EmailSender;
import com.zxy.code.pojo.User;
import com.zxy.code.util.RedisUtil;
import com.zxy.code.util.Task;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.concurrent.TimeUnit;
@RestController
@RequestMapping("/user")
public class UserController {
// @Autowired
// private EmailSender sender;
@Autowired
private Task task ;
@GetMapping("/register")
public String register(User user) {
if (user.getAccount().length() < 5) {
return "不符合规则";
}
// 普安段邮箱验证码无效
String yzm = RedisUtil.StringOps.get(user.getAccount());
// 消息队列
// 异步操作
if(yzm==null){
// yzm = sender.sendText(user.getAccount());
// RedisUtil.StringOps.setEx(user.getAccount(),yzm,1, TimeUnit.SECONDS);
task.sendyzm(user.getAccount());
return "验证码已经发送";
}
return "yzm目前还是有效的";
}
@GetMapping("/verify")
public String verify(User user, String yzm) {
String code = RedisUtil.StringOps.get(user.getAccount());
if(code==null){
return "有毛病";
}
return code.equals(yzm)?"yes":"no";
}
}
4、进行测试