自动配置原理
pom.xml:
启动器:
org.springframework.boot
spring-boot-starter-web
启动类:
两个核心注解:
@SpringBootConfiguration:SpringBoot的配置
@Configuration:Spring配置类
@Component:说明这也是一个Spring的组件
@EnableAutoConfiguration:自动配置
@AutoConfigurationPackage:自动导入包
@Import(AutoConfigurationPackages.Registrar.class):导入选择器
metadata:源数据
@Import(EnableAutoConfigurationImportSelector.class):自动配置导入选择
AutoConfigurationImportSelector:
List configurations = getCandidateConfigurations(annotationMetadata,attributes);//获取所有的配置
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
AnnotationAttributes attributes) {
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
Assert.notEmpty(configurations,
"No auto configuration classes found in META-INF/spring.factories. If you "
+ "are using a custom packaging, make sure that file is correct.");
return configurations;
}
META-INF/spring.factories:自动配置的核心
@ConfigurationProperties
/*
@ConfigurationProperties作用:
将配置文件中配置的每一个属性的值,映射到这个组件中;
告诉SpringBoot将本类中的所有属性和配置文件中相关的配置进行绑定
参数 prefix = “person” : 将配置文件中的person下面的所有属性一一对应
*/
@Component //注册bean 必须要添加这个注解
@ConfigurationProperties(prefix = "person")
public class Person {
private String name;
private Integer age;
private Boolean happy;
private Date birth;
private Map<String,Object> maps;
private List<Object> lists;
private Dog dog;
}
JSR303数据校验
--示例
@Validated //数据校验
public class Person {
@Email(message="邮箱格式错误") //email必须是邮箱格式,message是报错信息
private String email;
}
--常见参数
@NotNull(message="名字不能为空")
private String userName;
@Max(value=120,message="年龄最大不能查过120")
private int age;
@Email(message="邮箱格式错误")
private String email;
空检查
@Null 验证对象是否为null
@NotNull 验证对象是否不为null, 无法查检长度为0的字符串
@NotBlank 检查约束字符串是不是Null还有被Trim的长度是否大于0,只对字符串,且会去掉前后空格.
@NotEmpty 检查约束元素是否为NULL或者是EMPTY.
Booelan检查
@AssertTrue 验证 Boolean 对象是否为 true
@AssertFalse 验证 Boolean 对象是否为 false
长度检查
@Size(min=, max=) 验证对象(Array,Collection,Map,String)长度是否在给定的范围之内
@Length(min=, max=) string is between min and max included.
日期检查
@Past 验证 Date 和 Calendar 对象是否在当前时间之前
@Future 验证 Date 和 Calendar 对象是否在当前时间之后
@Pattern 验证 String 对象是否符合正则表达式的规则
邮件任务
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
在邮箱中开启POP3/SMTP服务
将授权码作为邮箱密码
配置邮箱
mail:
username: 1558647481@qq.com
password: hnbikvdksulfjeih #可以是授权码 也可以是密码
host: smtp.qq.com
#开启加密授权验证
properties:
mail.smtp.auth: true
@Autowired
JavaMailSenderImpl mailSender;
@Test
void contextLoads() {
//一个简单的邮件
SimpleMailMessage mailMessage = new SimpleMailMessage();
mailMessage.setFrom("[email protected]");
mailMessage.setTo("[email protected]");
mailMessage.setSubject("主题");
mailMessage.setText("正文");
mailSender.send(mailMessage);
}
@Test
void test() throws MessagingException {
//一个复杂的邮件
MimeMessage message = mailSender.createMimeMessage();
//多文件
MimeMessageHelper helper = new MimeMessageHelper(message, true);
helper.setSubject("主题");
helper.setText("网页
", true); //支持网页
File file1 = new File("/Users/reggie/ttc/C-销售/个人汇总.xlsx");
File file2 = new File("/Users/reggie/ttc/C-销售/明细.xlsx");
//附件
helper.addAttachment("汇总.xlsx", file1);
helper.addAttachment("明细.xlsx", file2);
mailSender.send(message);
}
定时任务
TaskScheduler 任务调度者
TaskExecutor 任务执行者
@EnableScheduling //开启定时功能的注解(加在启动类上)
@Sheduled //什么时候执行
@Service
public class TaskService {
//秒 分 时 日 月 周几
@Scheduled(cron = "0 48 9 * * ?")
public void hello(){
System.out.println("hello");
}
}
异步任务
使用异步任务的好处:
我们如果想让用户快速得到反馈,就在后台使用多线程的方式进行处理即可,但是每次都需要自己手动去编写多线程的实现的话,太麻烦了,我们只需要用一个简单的办法,在我们的方法上加一个简单的注解即可,如下:
@EnableAsync //开启异步功能 加在启动类上
@Async //加在方法上 异步方法
@Service
public class AsyncService {
//告诉Spring这是一个异步方法
@Async
public void hello(){
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("执行完毕");
}
}
SpringBoot就会自己开一个线程池,进行调用!
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
@Configuration
@EnableSwagger2
public class Swagger2Config {
@Bean
public Docket docket() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()// 通过.select()方法,去配置扫描接口,RequestHandlerSelectors配置如何扫描接口
.apis(RequestHandlerSelectors.basePackage("com.kuang.swagger.controller"))
.build();;
}
//配置文档信息
private ApiInfo apiInfo() {
Contact contact = new Contact("联系人名字", "http://xxx.xxx.com/联系人访问链接", "联系人邮箱");
return new ApiInfo(
"Swagger学习", // 标题
"学习演示如何配置Swagger", // 描述
"v1.0", // 版本
"http://terms.service.url/组织链接", // 组织链接
contact, // 联系人信息
"Apach 2.0 许可", // 许可
"许可链接", // 许可连接
new ArrayList<>()// 扩展
);
}
}
问题:如何让swagger在生产环境失效?
利用enable字段
@Bean
public Docket docket(Environment environment) {
// 设置要显示swagger的环境
Profiles of = Profiles.of("dev", "test");
// 判断当前是否处于该环境
// 通过 enable() 接收此参数判断是否要显示
boolean b = environment.acceptsProfiles(of);
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.enable(b) //配置是否启用Swagger,如果是false,在浏览器将无法访问
.select()// 通过.select()方法,去配置扫描接口,RequestHandlerSelectors配置如何扫描接口
.apis(RequestHandlerSelectors.basePackage("com.kuang.swagger.controller"))
// 配置如何通过path过滤,即这里只扫描请求以/kuang开头的接口
.paths(PathSelectors.ant("/kuang/**"))
.build();
}
作用:身份验证和权限控制
简介
Spring Security 是Spring项目的安全框架,可以实现强大的Web安全控制。
记住几个类:
Spring Security的两个主要目标:
认证(Authentication)
授权(Authorization)
配置类:
@EnableWebSecurity
public class SecurityConfig 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");
//没有权限回到登录
http.formLogin();
// http.formLogin().loginPage("/toLogin").usernameParameter("user").passwordParameter("pwd").loginProcessingUrl("login"); 定制登录页
http.csrf().disable();//关闭csrf功能 登录失败可能的原因
//注销 注销后 跳转到首页
http.logout().logoutSuccessUrl("/");
//记住我功能 cookie 默认保存两周
http.rememberMe();
// http.rememberMe().rememberMeParameter("remember"); 自定义接收前端的参数
}
/**
*密码编码:BCryptPasswordEncoder
* 认证
* Spring Security 5.0+新增了很多加密方法
* @param auth
* @throws Exception
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
.withUser("ys").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1")
.and()
.withUser("root").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1", "vip2", "vip3");
}
}
简介
基础命令
Subject currentUser = SecurityUtils.getSubject();//获取当前用户
Session session = currentUser.getSession();//通过当前用户拿到Session
currentUser.isAuthenticated();//判断当前用户是否被认证
currentUser.getPrincipal();//当前用户的一个认证
currentUser.hasRole("test");//当前用户是否拥有某个角色
currentUser.isPermitted("test");//当前用户是否拥有某个权限
currentUser.logout();//注销
SpringBoot整合Shiro
1:导入依赖
org.apache.shiro
shiro-spring
1.4.1
2:配置文件
配置类:
@Configuration
public class ShiroConfig {
//Shiro
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager securityManager){
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
//关联securityManager
bean.setSecurityManager(securityManager);
//添加Shiro的内置过滤器
/*
anon:无需认证就可以访问
authc:必须认证才能访问
user:必须拥有记住我功能才能使用
perms:拥有对某个资源的权限才能使用
role:拥有某个角色权限才能使用
*/
//登录拦截
Map<String, String> filterMap = new LinkedHashMap<>();
filterMap.put("user/*", "authc");//该接口只有认证后才能访问 如果没有认证,会自动跳转到登录页面
filterMap.put("user/add", "perms[user:add]");//该接口只有授权后才能访问 如果没有授权,会自动跳转到未授权页面
bean.setFilterChainDefinitionMap(filterMap);
//设置登录的请求
bean.setLoginUrl("/toLogin");
//未授权页面
bean.setUnauthorizedUrl("/noauth");
return bean;
}
//DefaultWebSecurityManager
@Bean(name = "securityManager")
public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//关联userRealm
securityManager.setRealm(userRealm);
return securityManager;
}
//创建Realm对象 需要自定义一个类
@Bean
public UserRealm userRealm(){
return new UserRealm();
}
}
UserRealm类:
//自定义的UserRealm
public class UserRealm extends AuthorizingRealm {
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("执行了授权操作");
UsernamePasswordToken userToken = (UsernamePasswordToken) token;
User user = userService.findUserByName(userToken.getUsername());
if(user == null){ //没有这个用户
return null;//抛出异常 UnknownAccountException(未知用户)
}
//可以加密 MD5盐值加密
//密码认证 shiro做
return new SimpleAuthenticationInfo(user, user.getPassword(), "");
}
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
System.out.println("执行了认证操作");
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
// info.addStringPermission("user:add");
//获取当前对象
Subject subject = SecurityUtils.getSubject();
User currentUser = (User)subject.getPrincipal();//拿到User对象
info.addStringPermission(currentUser.getPerms());
return info;
}
}
Controller层的登录接口
@PostMapping("/login")
public ResponseEntity login(String username, String password){
//获取当前的用户
Subject subject = SecurityUtils.getSubject();
//封装用户的登录数据
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
//执行登录方法 如果没有异常 则说明登录成功
try {
subject.login(token);
}catch (UnknownAccountException e){
log.info("用户名不存在");
}catch (IncorrectCredentialsException e){
log.error("密码错误");
}catch (LockedAccountException e){
log.info("账户已锁定");
} catch (AuthenticationException e) {
e.printStackTrace();
}
return ResponseEntity.ok("");
}
3:快速开始
本地过程调用:本地的A方法调用本地的B方法
RPC:远程过程调用,本地的A方法调用服务器的B方法,两个核心:通讯、序列化(序列化是为了更好的传输数据)
HTTP:网络传输协议,基于TCP,也可用来远程服务调用。
什么是dubbo?
Apache Dubbo是一款高性能、轻量级的开源Java RPC框架,提供了三大核心能力:面向接口的远程方法调用,只能容错和负载均衡,以及服务自动注册和发现。
dubbo-admin:是一个监控管理后台,查看我们注册了哪些服务,哪些服务被消费了。
zookeeper:注册中心(服务注册与发现)。是一个分布式的,开放源码的分布式应用程序协调服务,相当于一个注册中心。是Google的Chubby一个开源的实现,是Hadoop和Hbase的重要组件。它是一个为分布式应用提供一致性服务的软件,提供的功能包括:配置维护、域名服务、分布式同步、组服务等 。
服务器注册实战
前提:zookeeper服务已启动
1:提供者提供服务
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>2.7.3</version>
</dependency>
<!--zookeeper客户端-->
<dependency>
<groupId>com.github.sgroschupf</groupId>
<artifactId>zkclient</artifactId>
<version>0.1</version>
</dependency>
<!-- 引入zookeeper -->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>2.12.0</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>2.12.0</version>
</dependency>
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.14</version>
<!--排除这个slf4j-log4j12 否则会日志冲突-->
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
</exclusions>
</dependency>
dubbo:
application:
name: provider-server
registry:
address: zookeeper://127.0.0.1:2181
scan:
base-packages: com.ys.providerserver.service
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
@Service //可以被扫描到,在项目中一启动就自动注册到注册中心
@Component //使用Dubbo后尽量不要用service注解
public class TicketServiceImpl implements TicketService {
@Override
public String getTicket() {
return "狂神说JAVA";
}
}
2:消费者消费服务
dubbo:
application:
name: provider-server
registry:
address: zookeeper://127.0.0.1:2181
import com.kuang.provider.service.TicketService;
import org.apache.dubbo.config.annotation.Reference;
import org.springframework.stereotype.Service;
@Service //注入到容器中
public class UserService {
@Reference //远程引用指定的服务,Pom坐标或定义路径相同的接口名
TicketService ticketService;
public void bugTicket(){
String ticket = ticketService.getTicket();
System.out.println("在注册中心买到"+ticket);
}
}
SpringBoot操作数据:spring-data jpa jdbc mongodb redis
SpringData也是和SpringBoot齐名的项目!
说明:在SpringBoot2.x之后,原来使用的jedis被替换为了lettuce
jedis:采用的直连,多个线程操作的话,是不安全的。如果要避免不安全,要使用jedis pool连接池,更像BIO。
lettuce:采用Netty,实例可以在多个线程中共享,不存在线程不安全的情况。可以减少线程数据,更像NIO。
源码分析
@Bean
@ConditionalOnMissingBean(
name = {"redisTemplate"}
) //我们可以自己定义一个redisTemplate来替换这个默认的
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
//默认的redisTemplate没有过多的设置 redis对象是需要序列化的
//两个泛型都是
RedisTemplate<Object, Object> template = new RedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
@Bean
@ConditionalOnMissingBean //由于String是redis中最经常使用的一个类型,所以单独提出来了一个bean
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
简单使用:
org.springframework.boot
spring-boot-starter-data-redis
spring:
redis:
host: 127.0.0.1
port: 6379
@Autowired
private RedisTemplate redisTemplate;
@Test
void contextLoads() {
//redisTemplate 操作不同的数据类型
//opsForValue 操作字符串 类似String
//opsForList 操作List
//opsForSet
//opsForHash
//opsForGeo 操作地理位置
//获取redis的连接对象
// RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
// connection.flushDb();
// connection.flushAll();
redisTemplate.opsForValue().set("key", "this is a key");
System.out.println(redisTemplate.opsForValue().get("key"));
}
自定义RedisTemplate
public class RedisConfig {
//编写我们自己的redisTemplate
@Bean
@SuppressWarnings("all")
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
//为了开发方便 一般使用
RedisTemplate<String, Object> template = new RedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
//Json序列化配置
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);
//String的序列化
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
//key采用String的序列化方式
template.setKeySerializer(stringRedisSerializer);
//hash的key也采用String的序列化方式
template.setHashKeySerializer(stringRedisSerializer);
//value的序列化方式采用jackson
template.setValueSerializer(jackson2JsonRedisSerializer);
//hash的value序列化方式采用jackson
template.setHashKeySerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
}
RedisUtils工具类编写
/**
* Redis工具类,使用之前请确保RedisTemplate成功注入
*
* @author ye17186
* @version 2019/2/22 10:48
*/
public class RedisUtils {
private RedisUtils() {
}
@SuppressWarnings("unchecked")
private static RedisTemplate<String, Object> redisTemplate = SpringUtils
.getBean("redisTemplate", RedisTemplate.class);
/**
* 设置有效时间
*
* @param key Redis键
* @param timeout 超时时间
* @return true=设置成功;false=设置失败
*/
public static boolean expire(final String key, final long timeout) {
return expire(key, timeout, TimeUnit.SECONDS);
}
/**
* 设置有效时间
*
* @param key Redis键
* @param timeout 超时时间
* @param unit 时间单位
* @return true=设置成功;false=设置失败
*/
public static boolean expire(final String key, final long timeout, final TimeUnit unit) {
Boolean ret = redisTemplate.expire(key, timeout, unit);
return ret != null && ret;
}
/**
* 删除单个key
*
* @param key 键
* @return true=删除成功;false=删除失败
*/
public static boolean del(final String key) {
Boolean ret = redisTemplate.delete(key);
return ret != null && ret;
}
/**
* 删除多个key
*
* @param keys 键集合
* @return 成功删除的个数
*/
public static long del(final Collection<String> keys) {
Long ret = redisTemplate.delete(keys);
return ret == null ? 0 : ret;
}
/**
* 存入普通对象
*
* @param key Redis键
* @param value 值
*/
public static void set(final String key, final Object value) {
redisTemplate.opsForValue().set(key, value, 1, TimeUnit.MINUTES);
}
// 存储普通对象操作
/**
* 存入普通对象
*
* @param key 键
* @param value 值
* @param timeout 有效期,单位秒
*/
public static void set(final String key, final Object value, final long timeout) {
redisTemplate.opsForValue().set(key, value, timeout, TimeUnit.SECONDS);
}
/**
* 获取普通对象
*
* @param key 键
* @return 对象
*/
public static Object get(final String key) {
return redisTemplate.opsForValue().get(key);
}
// 存储Hash操作
/**
* 往Hash中存入数据
*
* @param key Redis键
* @param hKey Hash键
* @param value 值
*/
public static void hPut(final String key, final String hKey, final Object value) {
redisTemplate.opsForHash().put(key, hKey, value);
}
/**
* 往Hash中存入多个数据
*
* @param key Redis键
* @param values Hash键值对
*/
public static void hPutAll(final String key, final Map<String, Object> values) {
redisTemplate.opsForHash().putAll(key, values);
}
/**
* 获取Hash中的数据
*
* @param key Redis键
* @param hKey Hash键
* @return Hash中的对象
*/
public static Object hGet(final String key, final String hKey) {
return redisTemplate.opsForHash().get(key, hKey);
}
/**
* 获取多个Hash中的数据
*
* @param key Redis键
* @param hKeys Hash键集合
* @return Hash对象集合
*/
public static List<Object> hMultiGet(final String key, final Collection<Object> hKeys) {
return redisTemplate.opsForHash().multiGet(key, hKeys);
}
// 存储Set相关操作
/**
* 往Set中存入数据
*
* @param key Redis键
* @param values 值
* @return 存入的个数
*/
public static long sSet(final String key, final Object... values) {
Long count = redisTemplate.opsForSet().add(key, values);
return count == null ? 0 : count;
}
/**
* 删除Set中的数据
*
* @param key Redis键
* @param values 值
* @return 移除的个数
*/
public static long sDel(final String key, final Object... values) {
Long count = redisTemplate.opsForSet().remove(key, values);
return count == null ? 0 : count;
}
// 存储List相关操作
/**
* 往List中存入数据
*
* @param key Redis键
* @param value 数据
* @return 存入的个数
*/
public static long lPush(final String key, final Object value) {
Long count = redisTemplate.opsForList().rightPush(key, value);
return count == null ? 0 : count;
}
/**
* 往List中存入多个数据
*
* @param key Redis键
* @param values 多个数据
* @return 存入的个数
*/
public static long lPushAll(final String key, final Collection<Object> values) {
Long count = redisTemplate.opsForList().rightPushAll(key, values);
return count == null ? 0 : count;
}
/**
* 往List中存入多个数据
*
* @param key Redis键
* @param values 多个数据
* @return 存入的个数
*/
public static long lPushAll(final String key, final Object... values) {
Long count = redisTemplate.opsForList().rightPushAll(key, values);
return count == null ? 0 : count;
}
/**
* 从List中获取begin到end之间的元素
*
* @param key Redis键
* @param start 开始位置
* @param end 结束位置(start=0,end=-1表示获取全部元素)
* @return List对象
*/
public static List<Object> lGet(final String key, final int start, final int end) {
return redisTemplate.opsForList().range(key, start, end);
}