SpringBoot
SpringCloud
Spring是如何简化Java开发的!
为了降低Java开发的复杂性,Spring采用了以下4种关键策略:
1、基于POJO的轻量级和最小侵入性编程;
2、通过IOC,依赖注入(DI)和面向接口实现松耦合;
3、基于切面(AOP)和惯例进行声明式编程;
4、通过切面和模版减少样式代码;
SpringBoot功能
1)自动配置
2)起步依赖
3)辅助功能
SpringBoot原理初探
自动配置:
pom.xml
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
// 程序的主入口
// 本身就是spring的一个组件
// @SpringBootApplication:标注这个类是一个springboot的应用
@SpringBootApplication
public class Springboot04Web2Application {
public static void main(String[] args) {
// SpringApplication 将 springboot应用启动的(通过反射加载类的对象!)
SpringApplication.run(Springboot04Web2Application.class, args);
}
}
结论
SpringApplication这个类主要做了以下四件事情:
@SpringBootConfiguration
作用:SpringBoot的配置类 ,标注在某个类上 , 表示这是一个SpringBoot的配置类;
@Configuration说明这是一个配置类 ,配置类就是对应Spring的xml 配置文件;
@ComponentScan
指定扫描哪些Spring注解
作用:自动扫描并加载符合条件的组件或者bean , 将这个bean定义加载到IOC容器中
@EnableAutoConfiguration
@EnableAutoConfiguration:开启自动配置功能
以前我们需要自己配置的东西,而现在SpringBoot可以自动帮我们配置 ;
@EnableAutoConfiguration告诉SpringBoot开启自动配置功能,这样自动配置才能生效;
面视:会问–谈谈你对springboot的理解 – 1. 自动装配 2. Run()方法
@controller 控制器(注入服务)
用于标注控制层,相当于struts中的action层
@service 服务(注入dao)
用于标注服务层,主要用来进行业务的逻辑处理
@repository (实现dao访问)
用于标注数据访问层,也可以说用于标注数据访问组件,即DAO组件.
@component (把普通pojo实例化到spring容器中,相当于配置文件中的)
泛指各种组件,就是说当我们的类不属于各种归类的时候〈不属于@Controller、@Services等的时候),我们就可以使用@Component来标注这个类。
YAML是 “YAML Ain’t a Markup Language” (YAML不是一种标记语言)的递归缩写。在开发的这种语言时,YAML 的意思其实
是:“Yet Another Markup Language”(仍是一种标记语言)
这种语言以数据作为中心,而不是以标记语言为重点!
SpringBoot使用一个全局的配置文件,配置文件名称是固定的
application.properties
application.yml
配置文件的作用︰修改SpringBoot自动配置的默认值,因为SpringBoot在底层都给我们自动配置好了;
传统xml配置:
<server>
<port>8081<port>
server>
yaml配置:
server:
prot: 8080
yaml基础语法
说明:语法要求严格!
yaml文件更强大的地方在于,他可以给我们的实体类直接注入匹配值!
1、我们在编写一个复杂一点的实体类:Person 类
@Component //注册bean到容器中
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;
//有参无参构造、get、set方法、toString()方法
}
2、我们来使用yaml配置的方式进行注入,大家写的时候注意区别和优势,我们编写一个yaml配置!
# key: value
# 普通的 key-value
server:
port: 8080
name: qingjiang
# yaml可以存一个对象
# 对空格的要求及其严格
# 注入到我们的配置类中!
student:
name: qingjiang
age: 3
# 行内写法
student1: {name: qingjiang,age: 3}
# 数组
pet:
- cat
- dog
- pig
pets: [cat,dog,pig]
# 可以直接给实体类赋值!!!
person:
name: 张三${random.int}
# Yaml中可以使用一些特殊的语法:随机符号 ${random.int}
age: 3
happy: false
birth: 2019/11/02
maps: {k1: v1,k2: v2}
lists:
- code
- music
- girl
dog:
name: ${person.hello:hello}旺财
age: 3
# properties 只能保存键值对
name=qingjiang
3、我们刚才已经把person这个对象的所有值都写好了,我们现在来注入到我们的类中!
/*
@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;
}
推荐:通过yaml可以直接给实体类赋值
@ConfigurationProperties(prefix = “person”)
(之前是使用new实例化一个对象的方式,也可以通过@Value()注解的方式赋值),现在可以通过yaml文件直接为实体类赋值。
@value注解只支持SpEL(Spring 表达式语言)表达式! 所以不建议使用!
参数校验是我们程序开发中必不可少的过程。用户在前端页面上填写表单时,前端js程序会校验参数的合法性,当数据到了后端,为了防止恶意操作,保持程序的健壮性,后端同样需要对数据进行校验。后端参数校验最简单的做法是直接在业务方法里面进行判断,当判断成功之后再继续往下执行。但这样带给我们的是代码的耦合,冗余。当我们多个地方需要校验时,我们就需要在每一个地方调用校验程序,导致代码很冗余,且不美观。
1、导入依赖
创建SpringBoot项目时 会自动导入依赖在 spring-context-5.3.19.jar下面。
手动配置:
org.springframework.boot
spring-boot-starter-validation
2、创建配置类,并在类上加@Validated注解,然后在属性上加上JSR-303定义的校验注解。
例如:@pattern 正则表达式
格式 :@Pattern(regexp = “/[0-9]+/”) !!!
多环境配置 及 配置文件位置:
Yaml配置文件默认在resources下的(classpath:/),是最低优先级的配置!
如果我们需要多个不同的配置环境,可以配置多个优先级不一样的,也可以在一个yaml文件中进行选择!
Spring.profiles.active是springboot的多环境配置:可以选择激活哪一个!!
多环境下如何去切换? 使用分割线 - - - 可以用一个文件写多个文件的配置。
配置文件到底能写什么,都会有一个固有的规律!
XXXAutoConfiguration:默认值 XXXProperties和配置文件绑定,我们就可以使用自定义的配置了!
Application.yaml 和 spring.factories 的联系!
@Configuration //表示这是一个配置类,和以前编写的配置文件一样,也可以给容器中添加组件;
//启动指定类的ConfigurationProperties功能;
//进入这个HttpProperties查看,将配置文件中对应的值和HttpProperties绑定起来;
//并把HttpProperties加入到ioc容器中
@EnableConfigurationProperties({HttpProperties.class})
//Spring底层@Conditional注解
//根据不同的条件判断,如果满足指定的条件,整个配置类里面的配置就会生效;
//这里的意思就是判断当前应用是否是web应用,如果是,当前配置类生效
@ConditionalOnWebApplication(type = Type.SERVLET)
//判断当前项目有没有这个类CharacterEncodingFilter;SpringMVC中进行乱码解决的过滤器;
@ConditionalOnClass({CharacterEncodingFilter.class})
//判断配置文件中是否存在某个配置:spring.http.encoding.enabled;
//如果不存在,判断也是成立的
//即使我们配置文件中不配置spring.http.encoding.enabled=true,也是默认生效的;
@ConditionalOnProperty(
prefix = "spring.http.encoding",
value = {"enabled"},
matchIfMissing = true
)
public class HttpEncodingAutoConfiguration {
//他已经和SpringBoot的配置文件映射了
private final Encoding properties;
//只有一个有参构造器的情况下,参数的值就会从容器中拿
public HttpEncodingAutoConfiguration(HttpProperties properties) {
this.properties = properties.getEncoding();
}
//给容器中添加一个组件,这个组件的某些值需要从properties中获取
@Bean
@ConditionalOnMissingBean //判断容器没有这个组件?
public CharacterEncodingFilter characterEncodingFilter() {
CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
filter.setEncoding(this.properties.getCharset().name());
filter.setForceRequestEncoding(this.properties.shouldForce(org.springframework.boot.autoconfigure.http.HttpProperties.Encoding.Type.REQUEST));
filter.setForceResponseEncoding(this.properties.shouldForce(org.springframework.boot.autoconfigure.http.HttpProperties.Encoding.Type.RESPONSE));
return filter;
}
}
每一个XXXautoConfiguration都有@Configuration注解,表示这是一个配置类,都会被spring接管配置。
我们修改yaml配置文件,然后属性值会自动注入到绑定的XXXXProperties
怎么去修改这些属性呢:说白了就是SpringBoot配置 ----> .yaml、.properties这些文件
那么多的自动配置类,必须在一定的条件下才能生效;也就是说,我们加载了这么多的配置类,但不是所有的都生效了。我们怎么知道哪些自动配置类生效?我们可以通过启用 debug=true属性;来让控制台打印自动配置报告,这样我们就可以很方便的知道哪些自动配置类生效;
# 开启springboot的调试类
debug=true
# Positive matches:(自动配置类启用的:正匹配)
# Negative matches:(没有启动,没有匹配成功的自动配置类:负匹配)
# Unconditional classes: (没有条件的类)
自动配置
Springboot到底帮我们配置了什么?我们能不能进行修改?能修改哪些东西?能不能扩展?
xxxxAutoConfiguration 向容器中自动配置组件!
xxxxProperties: 自动配置类,装配配置文件中自定义的一些内容!
要解决的问题
导入静态资源…
首页
Jsp,模板引擎Thymeleaf,或者其他模板
装配和扩展springMVC
增删改查
拦截器
国际化
在这四个配置的静态资源可以直接访问(例如:输入/XXX.html相应地址即可访问)!
总结:
1、在springboot中,我们可以使用以下方式处理静态资源
①、添加Webjars依赖 通过访问:localhost:8080/webjars/
②、Public 、static(默认)、 /**、 resurces
通过访问;localhost:8080/
2、优先级:classpath : /resources/ > classpath: / static/ > classpath: / public/
可以用一个类来全面扩展spring mvc
//alt + insert 可以看 可以被重写的方法。
// 以前配置拦截器需要实现拦截器的接口,现在只需要重写拦截器的方法!
// 实现了视图解析器ViewResolvers的类,也就是视图解析器!
// 如果想定制一些定制化的功能,只需要写这个组件,然后将它交给springBoot,springboot就会帮我们自动装配!!
例如:格式化消息转换器
配置文件可以绑定到properties文件下,我们可以在properties中配置一些格式化的东西。
# 自定义配置日期格式化 ! 默认是dd/MM/yyyy
pring.mvc.format.date=yyyy-MM-dd
// 如果我们想要扩展Spring mvc ,官方建议我们这样做!既保SpringBoot留所有的自动配置,也能用我们扩展的配置!
// 我们要做的就是编写一个@Configuration注解类,并且类型要为WebMvcConfigurer,还不能标注@EnableWebMvc注解;我们去自己写一个;我们新建一个包叫config,写一个类MyMvcConfig
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
// 视图跳转
@Override
public void addViewControllers(ViewControllerRegistry registry) {
// 添加一个视图控制,添加一个重定向
registry.addViewController("/").setViewName("index");
registry.addViewController("/index.html").setViewName("index");
registry.addViewController("/main.html").setViewName("dashboard");
}
// 配置拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginHandlerInterceptor())
.addPathPatterns("/**")
.excludePathPatterns("/index.html","/","/user/login","/static/**");
// addPathPatterns为拦截的请求,excludePathPatterns为不拦截的请求
}
}
addViewController的使用
在spring中,可能会有仅仅需要页面跳转而没有具体的业务逻辑的代码,如下所示。
我们点击前端某处的时候跳转到登录或者注册的HTML页面,但是controller方法都是空的,仅仅return了一个页面。
@GetMapping("/login.html")
public string loginPage(){
return "login";
}
GetMapping("/reg.html")
public string regPage(){
return "reg";
}
长此以往我们的controller中会有很多这样的空方法,为了避免,我们使用SpringMVC中的addViewController来实现与上相同的逻辑。
新建一个配置类让他去实现WebMvcConfigurer ,并重写addViewControllers方法,
参数传入就是controller中的URL,setViewName中传入的参数就是原return的页面。
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
// 视图跳转
@Override
public void addViewControllers(ViewControllerRegistry registry) {
// 添加一个视图控制,添加一个重定向
registry.addViewController("/login.html").setViewName("login");
registry.addViewController("/index.html").setViewName("index");
}
}
注意:
1、路径映射默认都是GET方式访问的,如果是POST方法,就不能使用,会出现HttpRequestMethodNotSupportedException: Request method “POST” not supported异常,
2、如果是POST方式,提交表单,需要页面的跳转,可以使用重定向方式,即redirect。
3、在springboot 中有很多的XXX Configureration 配置类 ,这是帮助我们进行扩展配置的,只要看到了这个,我们就要注意了!
因为他改变了spring中原有的一些配置或者扩展了,在原始的配置中看不到了。
@ResponseBody 注解
@RestController注解相当于@ResponseBody + @Controller合在一起的作用!
如果只是使用@RestController注解的Controller,则Controller中的方法无法返回jsp页面,配置的视图解析器InternalResourceViewResolver不起作用,返回的内容就是Return 里的内容。
例如:本来应该到success.jsp页面的,则其显示success.
返回到指定页面,则需要用@Controller配合视图解析器InternalResourceViewResolver才行。
如果需要返回JSON,XML或自定义mediaType内容到页面,则需要在对应的方法上加上@ResponseBody注解。
使用@PathVariable可以快速的访问,URL中的部分内容!
在@RequestMapping的value中使用URI template({变量名}),然后在@RequestMapping注解方法的需要绑定的参数前,使用
@PathVariable指定变量名(如果变量名和参数名一致也可以不指定),从而将URL中的值绑定到参数上。
@RequestMapping("/user/{id}")
public String testPathVariable(@PathVariable("id") Integer uid) {
System.out.println(uid);
return "success";
}
Spring boot项目中,在templates下的error文件夹下设置404.html页面,如果访问出现404自动跳转。
1.前端搞定:页面长什么样子 数据
2.设计数据库!!!
3.前端能让他自动运行,独立化工程
4.数据接口如何对接:json,对象all in one
5.前后端联调测试!
得有一套自己熟悉的后台模板!!!
SpringData简介
整合JDBC
1、添加依赖
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-jdbcartifactId>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<scope>runtimescope>
dependency>
2、编写yaml配置文件连接数据库
spring:
datasource:
username: root
password: 123
url: jdbc:mysql://localhost:3306/user?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
driver-class-name: com.mysql.cj.jdbc.Driver
3、配置完这一些东西后,我们就可以直接去使用了,因为SpringBoot已经默认帮我们进行了自动配置;去测试类测试一下!
测试类需要运行测试类才能生效,下次运行记得切换运行主项目。
package com.yang;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
@SpringBootTest
class Springboot05DataApplicationTests {
@Autowired
DataSource dataSource;
@Test
void contextLoads() throws SQLException {
// 查看默认的数据源
System.out.println(dataSource.getClass());
//输出class com.zaxxer.hikari.HikariDataSource
//获取数据库连接
Connection connection = dataSource.getConnection();
System.out.println(connection);
//输出HikariProxyConnection@1835043230 wrapping com.mysql.cj.jdbc.ConnectionImpl@65eb76cd
//关闭
connection.close();
}
}
JDBCTemplate
JdbcTemplate主要提供以下几类方法:
**测试:**编写一个Controller,注入 jdbcTemplate,编写测试方法进行访问测试;
@RestController
@RequestMapping("/jdbc")
public class JdbcController {
/**
* Spring Boot 默认提供了数据源,默认提供了 org.springframework.jdbc.core.JdbcTemplate
* JdbcTemplate 中会自己注入数据源,用于简化 JDBC操作
* 还能避免一些常见的错误,使用起来也不用再自己来关闭数据库连接
*/
@Autowired
JdbcTemplate jdbcTemplate;
//查询employee表中所有数据
//List 中的1个 Map 对应数据库的 1行数据
//Map 中的 key 对应数据库的字段名,value 对应数据库的字段值
@GetMapping("/list")
public List<Map<String, Object>> userList(){
String sql = "select * from employee";
List<Map<String, Object>> maps = jdbcTemplate.queryForList(sql);
return maps;
}
//新增一个用户
@GetMapping("/add")
public String addUser(){
//插入语句,注意时间问题
String sql = "insert into employee(last_name, email,gender,department,birth)" +
" values ('狂神说','[email protected]',1,101,'"+ new Date().toLocaleString() +"')";
jdbcTemplate.update(sql);
//查询
return "addOk";
}
//修改用户信息
@GetMapping("/update/{id}")
public String updateUser(@PathVariable("id") int id){
//插入语句
String sql = "update employee set last_name=?,email=? where id="+id;
//数据
Object[] objects = new Object[2];
objects[0] = "秦疆";
objects[1] = "[email protected]";
jdbcTemplate.update(sql,objects);
//查询
return "updateOk";
}
//删除用户
@GetMapping("/delete/{id}")
public String delUser(@PathVariable("id") int id){
//插入语句
String sql = "delete from employee where id=?";
jdbcTemplate.update(sql,id);
//查询
return "deleteOk";
}
}
@PathVariable 映射 URL 绑定的占位符。
实例:
SpringMVCTest.java
//@PathVariable可以用来映射URL中的占位符到目标方法的参数中
@RequestMapping("/testPathVariable/{id}")
public String testPathVariable(@PathVariable("id") Integer id)
{
System.out.println("testPathVariable:"+id);
return SUCCESS;
}
Druid简介
Java程序很大一部分要操作数据库,为了提高性能操作数据库的时候,又不得不使用数据库连接池。
Druid 是阿里巴巴开源平台上一个数据库连接池实现,结合了 C3P0、DBCP 等 DB 池的优点,同时加入了日志监控。
Druid 可以很好的监控 DB 池连接和 SQL 的执行情况,天生就是针对监控而生的 DB 连接池。
Spring Boot 2.0 以上默认使用 Hikari 数据源,可以说 Hikari 与 Driud 都是当前 Java Web 上最优秀的数据源,我们来重点介绍 Spring Boot 如何集成 Druid 数据源,如何实现数据库监控。
com.alibaba.druid.pool.DruidDataSource 基本配置参数如下:
配置数据源
1、添加上 Druid 数据源依赖
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druidartifactId>
<version>1.1.22version>
dependency>
2、切换数据源;之前已经说过 Spring Boot 2.0 以上默认使用 com.zaxxer.hikari.HikariDataSource 数据源,但可以通过 spring.datasource.type 指定数据源。
spring:
datasource:
username: root
password: 123
url: jdbc:mysql://localhost:3306/user?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
3、数据源切换之后,在测试类中注入 DataSource,然后获取到它,输出一看便知是否成功切换;
4、切换成功!既然切换成功,就可以设置数据源连接初始化大小、最大连接数、等待时间、最小连接数 等设置项;可以查看源码
#Spring Boot 默认是不注入这些属性值的,需要自己绑定
#druid 数据源专有配置
initialSize: 5
minIdle: 5
maxActive: 20
maxWait: 60000
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true
#配置监控统计拦截的filters,stat:监控统计、log4j:日志记录、wall:防御sql注入
#如果允许时报错 java.lang.ClassNotFoundException: org.apache.log4j.Priority
#则导入 log4j 依赖即可,Maven 地址:https://mvnrepository.com/artifact/log4j/log4j
filters: stat,wall,log4j
maxPoolPreparedStatementPerConnectionSize: 20
useGlobalDataSourceStat: true
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
5、看到需要用到log4j,所以需要在pom中导入log4j的依赖
<dependency>
<groupId>log4jgroupId>
<artifactId>log4jartifactId>
<version>1.2.17version>
dependency>
6、现在需要程序员自己为 DruidDataSource 绑定全局配置文件中的参数,再添加到容器中,而不再使用 Spring Boot 的自动生成了;我们需要 自己添加 DruidDataSource 组件到容器中,并绑定属性;
@Configuration
public class DruidConfig {
/*
将自定义的 Druid数据源添加到容器中,不再让 Spring Boot 自动创建
绑定全局配置文件中的 druid 数据源属性到 com.alibaba.druid.pool.DruidDataSource从而让它们生效
@ConfigurationProperties(prefix = "spring.datasource"):作用就是将 全局配置文件中
前缀为 spring.datasource的属性值注入到 com.alibaba.druid.pool.DruidDataSource 的同名参数中
*/
@ConfigurationProperties(prefix = "spring.datasource")
@Bean
public DataSource druidDataSource() {
return new DruidDataSource();
}
}
7、去测试类中测试一下;看是否成功!
@SpringBootTest
class SpringbootDataJdbcApplicationTests {
//DI注入数据源
@Autowired
DataSource dataSource;
@Test
public void contextLoads() throws SQLException {
//看一下默认数据源
System.out.println(dataSource.getClass());
//获得连接
Connection connection = dataSource.getConnection();
System.out.println(connection);
DruidDataSource druidDataSource = (DruidDataSource) dataSource;
System.out.println("druidDataSource 数据源最大连接数:" + druidDataSource.getMaxActive());
System.out.println("druidDataSource 数据源初始化连接数:" + druidDataSource.getInitialSize());
//关闭连接
connection.close();
}
}
配置Druid数据源监控
//配置 Druid 监控管理后台的Servlet;
//内置 Servlet 容器时没有web.xml文件,所以使用 Spring Boot 的注册 Servlet 方式
@Bean
public ServletRegistrationBean statViewServlet() {
ServletRegistrationBean bean = new ServletRegistrationBean(new StatViewServlet(), "/druid/*");
// 这些参数可以在 com.alibaba.druid.support.http.StatViewServlet
// 的父类 com.alibaba.druid.support.http.ResourceServlet 中找到
Map<String, String> initParams = new HashMap<>();
initParams.put("loginUsername", "admin"); //后台管理界面的登录账号
initParams.put("loginPassword", "123456"); //后台管理界面的登录密码
//后台允许谁可以访问
//initParams.put("allow", "localhost"):表示只有本机可以访问
//initParams.put("allow", ""):为空或者为null时,表示允许所有访问
initParams.put("allow", "");
//deny:Druid 后台拒绝谁访问
//initParams.put("kuangshen", "192.168.1.20");表示禁止此ip访问
//设置初始化参数
bean.setInitParameters(initParams);
return bean;
}
配置完毕后,我们可以选择访问 :http://localhost:8080/druid/login.html
配置 Druid web 监控 filter 过滤器
//配置 Druid 监控 之 web 监控的 filter
//WebStatFilter:用于配置Web和Druid数据源之间的管理关联监控统计
@Bean
public FilterRegistrationBean webStatFilter() {
FilterRegistrationBean bean = new FilterRegistrationBean();
bean.setFilter(new WebStatFilter());
//exclusions:设置哪些请求进行过滤排除掉,从而不进行统计
Map<String, String> initParams = new HashMap<>();
initParams.put("exclusions", "*.js,*.css,/druid/*,/jdbc/*");
bean.setInitParameters(initParams);
//"/*" 表示过滤所有请求
bean.setUrlPatterns(Arrays.asList("/*"));
return bean;
}
1、导入起步依赖
<dependency>
<groupId>org.mybatis.spring.bootgroupId>
<artifactId>mybatis-spring-boot-starterartifactId>
<version>2.1.3version>
dependency>
注解本质上就是一个类,开发中我们可以使用注解取代xml配置文件。
@component (把普通pojo实例化到spring容器中,相当于配置文件中的)
泛指各种组件,就是说当我们的类不属于各种归类的时候〈不属于@Controller、@Services等的时候),我们就可以使用@Component来标注这个类。
@component是spring中的一个注解,它的作用就是实现bean的注入。在Java的web开发中,提供3个@Component注解衍生注解(功能与@component一样)分别是:
@controller 控制器(注入服务) 用于标注控制层,相当于struts中的action层
@service 服务(注入Dao) 用于标注服务层,主要用来进行业务的逻辑处理
@repository (实现Dao访问 / mapper) 用于标注数据访问层,也可以说用于标注数据访问组件,即DAO组件
命名空间的作用有两个,一个是利用更长的全限名来将不同的语句隔离开来,同时也实现了你上面见到的接口绑定。就算你觉得暂时用不到接口绑定,你也应该遵循这里的规定,以防哪天你改变了主意。 长远来看,只要将命名空间置于合适的 Java 包命名空间之中,你的代码会变得更加整洁,也有利于你更方便地使用 MyBatis。
命名解析:为了减少输入量,MyBatis 对所有具有名称的配置元素(包括语句,结果映射,缓存等)使用了如下的命名解析规则。
在web开发中,安全第一位!拦截器、过滤器。
安全性属于非功能性需求!
做网站:安全性应该在设计之初就考虑!
安全框架:SpringSecurity、shiro
主要做认证,授权!
权限:功能权限、访问权限、菜单权限…
实现采用的是AOP(横向编程)的思想!
Spring Security 是针对Spring项目的安全框架,也是Spring Boot底层安全模块默认的技术选型,他可以实现强大的Web安全控制,对于安全控制,我们仅需要引入 spring-boot-starter-security 模块,进行少量的配置,即可实现强大的安全管理!记住几个类:
Spring Security的两个主要目标是 “认证” 和 “授权”(访问控制)。
举例:你要登机,你需要出示你的 passport 和源 ticket,passport 是为了证明你张三确实是你张三,这就是 认证;而机票是为了证明你张三确实买了票可以上飞机,这就是 授权。
“认证”(Authentication)
身份验证是关于验证您的凭据,如用户名/用户ID和密码,以验证您的身份。
身份验证通常通过用户名和密码完成,有时与身份验证因素结合使用。
“授权” (Authorization)
实现:
1、导入需要的静态资源:
链接:https://pan.baidu.com/s/1oB9-VKrN4tzh61MtdW5Dow
提取码:clsq
2、引入 SpringSecurity 依赖
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-securityartifactId>
dependency>
授权:
3、新建一个配置文件,并且继承 WebSecurityConfigurerAdapter,并且要添加@EnableWebSecurity注解
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
// 授权
@Override
protected void configure(HttpSecurity http) throws Exception {
// 首页所有人可以访问,功能页只有对应有权限的人可以访问
// 请求授权的规则
http.authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers("/level1/**").hasAnyRole("vip1")
.antMatchers("/level2/**").hasAnyRole("vip2")
.antMatchers("/level3/**").hasAnyRole("vip3");
// 开启自动配置的登录功能
// /login 请求来到登录页
// http.formLogin();
// 测试发现没有权限的时候,会跳转到登录的页面。
}
}
4、测试一下:发现除了首页都进不去了!因为我们目前没有登录的角色,因为请求需要登录的角色拥有对应的权限才可以!
5、在configure()方法中加入以下配置,开启自动配置的登录功能!
// 开启自动配置的登录功能
// /login 请求来到登录页
http.formLogin();
// 测试发现没有权限的时候,会跳转到登录的页面。
认证:
6、定义认证规则,重写configure(AuthenticationManagerBuilder auth)方法
// 定义认证规则
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 这些数据正常应该从数据库中读
auth.inMemoryAuthentication()
.withUser("kuangshen").password("123456").roles("vip2","vip3")
.and()
.withUser("root").password("123456").roles("vip1","vip2","vip3")
.and()
.withUser("guest").password("123456").roles("vip1");
}
7、测试,我们可以使用这些账号登录进行测试!发现会报错! 原因是密码没有加密!
8、原因,我们要将前端传过来的密码进行某种方式加密,否则就无法登录,修改代码
//定义认证规则
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//Spring security 5.0中新增了多种加密方式,也改变了密码的格式。
//要想我们的项目还能够正常登陆,需要修改一下configure中的代码。我们要将前端传过来的密码进行某种方式加密
//spring security 官方推荐的是使用bcrypt加密方式。
auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
.withUser("admin").password(new BCryptPasswordEncoder().encode("123456")).roles("vip2","vip3")
.and()
.withUser("root").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2","vip3")
.and()
.withUser("guest").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2");
}
1、什么是shiro
shiro是apache的一个开源框架,是一个权限管理的框架,实现 用户认证、用户授权。
spring中有spring security ,是一个权限框架,它和spring依赖过于紧密,没有shiro使用简单。
shiro不依赖于spring,shiro不仅可以实现 web应用的权限管理,还可以实现c/s系统。
分布式系统权限管理,shiro属于轻量框架,越来越多企业项目开始使用shiro。
Subject:主体(用户),代表了当前“用户”,这个用户不一定是一个具体的人,与当前应用交互的任何东西都是Subject,如网络爬虫,机器人等;即一个抽象概念;所有Subject 都绑定到SecurityManager,与Subject的所有交互都会委托给SecurityManager;可以把Subject认为是一个门面;SecurityManager才是实际的执行者;
SecurityManager:安全管理器(管理所有用户);即所有与安全有关的操作都会与SecurityManager 交互;且它管理着所有Subject;可以看出它是Shiro 的核心,它负责与后边介绍的其他组件进行交互,如果学习过SpringMVC,你可以把它看成DispatcherServlet前端控制器;
Realm:域(连接数据),Shiro从Realm获取安全数据(如用户、角色、权限),就是说SecurityManager要验证用户身份,那么它需要从Realm获取相应的用户进行比较以确定用户身份是否合法;也需要从Realm得到用户相应的角色/权限进行验证用户是否能进行操作;可以把Realm看成DataSource,即安全数据源。
3、快速入门
1、导入依赖
<dependencies>
<dependency>
<groupId>org.apache.shirogroupId>
<artifactId>shiro-coreartifactId>
<version>1.4.0version>
dependency>
<dependency>
<groupId>org.slf4jgroupId>
<artifactId>slf4j-log4j12artifactId>
<version>1.7.25version>
dependency>
<dependency>
<groupId>log4jgroupId>
<artifactId>log4jartifactId>
<version>1.2.17version>
dependency>
2、配置文件
shiro.ini 和 log4j.propertites
3、Quickstart
import org.apache.commons.collections.Factory;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.realm.text.IniRealm;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Simple Quickstart application showing how to use Shiro's API.
*
* @since 0.9 RC2
*/
public class Quickstart {
private static final transient Logger log = LoggerFactory.getLogger(Quickstart.class);
public static void main(String[] args) {
DefaultSecurityManager securityManager = new DefaultSecurityManager();
IniRealm iniRealm = new IniRealm("classpath:shiro.ini");
securityManager.setRealm(iniRealm);
SecurityUtils.setSecurityManager(securityManager);
// Now that a simple Shiro environment is set up, let's see what you can do:
// 获取当前的用户对象
Subject currentUser = SecurityUtils.getSubject();
// 通过当前用户拿到session,并拿session存值取值
Session session = currentUser.getSession();
session.setAttribute("someKey", "aValue");
String value = (String) session.getAttribute("someKey");
if (value.equals("aValue")) {
log.info("Retrieved the correct value! [" + value + "]");
}
// 判断当前的用户是否被认证
if (!currentUser.isAuthenticated()) {
// Token:令牌 没有获取,随机
UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa");
// 设置记住我
token.setRememberMe(true);
try {
// 执行了登录操作
currentUser.login(token);
} catch (UnknownAccountException uae) {
// 用户名不存在
log.info("There is no user with username of " + token.getPrincipal());
} catch (IncorrectCredentialsException ice) {
// 密码不对
log.info("Password for account " + token.getPrincipal() + " was incorrect!");
} catch (LockedAccountException lae) {
// 用户被锁定了(比如5次密码都不对的情况)
log.info("The account for username " + token.getPrincipal() + " is locked. " +
"Please contact your administrator to unlock it.");
}
// ... catch more exceptions here (maybe custom ones specific to your application?
catch (AuthenticationException ae) {
//unexpected condition? error?
}
}
//say who they are:
//print their identifying principal (in this case, a username):
log.info("User [" + currentUser.getPrincipal() + "] logged in successfully.");
//test a role:
if (currentUser.hasRole("schwartz")) {
log.info("May the Schwartz be with you!");
} else {
log.info("Hello, mere mortal.");
}
// 粗粒度
//test a typed permission (not instance-level)
if (currentUser.isPermitted("lightsaber:wield")) {
log.info("You may use a lightsaber ring. Use it wisely.");
} else {
log.info("Sorry, lightsaber rings are for schwartz masters only.");
}
// 细粒度
//a (very powerful) Instance Level permission:
if (currentUser.isPermitted("winnebago:drive:eagle5")) {
log.info("You are permitted to 'drive' the winnebago with license plate (id) 'eagle5'. " +
"Here are the keys - have fun!");
} else {
log.info("Sorry, you aren't allowed to drive the 'eagle5' winnebago!");
}
// 注销
//all done - log out!
currentUser.logout();
// 结束系统
System.exit(0);
}
}
Springboot中集成Shiro
package com.yang.config;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.ShiroFilter;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.LinkedHashMap;
import java.util.Map;
@Configuration
public class ShiroConfig {
// ShiroFilterFactoryBean -- 第三步
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("SecurityManager") DefaultWebSecurityManager defaultWebSecurityManager){
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
// 设置安全管理器
bean.setSecurityManager(defaultWebSecurityManager);
// 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();
}
}
package com.yang.config;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;
// 自定义userRealm
public class UserRealm extends AuthorizingRealm {
// 授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
// 认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
return null;
}
}
登录拦截
package com.yang.config;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.ShiroFilter;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.LinkedHashMap;
import java.util.Map;
@Configuration
public class ShiroConfig {
// ShiroFilterFactoryBean -- 第三步
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("SecurityManager") DefaultWebSecurityManager defaultWebSecurityManager){
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
// 设置安全管理器
bean.setSecurityManager(defaultWebSecurityManager);
//添加shiro的内置过滤器
/*
* anon: 无需认证就可以访问
* authc: 必须认证了才能访问
* user: 必须拥有 记住我 功能才能使用
* perms: 拥有对某个资源的权限才能访问
* role: 拥有某个角色权限才能访问
*/
// 登录拦截
Map<String,String> filterMap = new LinkedHashMap<>();
// filterMap.put("/user/add","authc");
// filterMap.put("/user/update","authc");
filterMap.put("/user/*","authc");
bean.setFilterChainDefinitionMap(filterMap);
// 设置登录的请求
bean.setLoginUrl("/toLogin");
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();
}
}
实现登录认证
package com.yang.controller;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class MyController {
@RequestMapping({"/","/index"})
public String toIndex(Model model){
model.addAttribute("msg","hello shiro!");
return "index";
}
@RequestMapping("/user/add")
public String add(){
return "user/add";
}
@RequestMapping("/user/update")
public String update(){
return "user/update";
}
@RequestMapping("/toLogin")
public String toLogin(){
return "login";
}
@RequestMapping("login")
public String login(String username, String password, Model model){
//获取当前用户
Subject subject = SecurityUtils.getSubject();
//封装用户的登录数据--设置成令牌加密
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
try {
subject.login(token); // 执行登录的方法,如果没有异常就说明ok了
return "index";
}catch (UnknownAccountException e){ // 用户名不存在
model.addAttribute("msg","用户名错误");
return "login";
}catch (IncorrectCredentialsException e){ // 用户名不存在
model.addAttribute("msg","密码错误");
return "login";
}
}
}
package com.yang.config;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;
// 自定义userRealm
public class UserRealm extends AuthorizingRealm {
// 授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
// 认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("执行了认证,doGetAuthenticationInfo");
// 用户名,密码 数据中取
String username = "root";
String password = "123456";
UsernamePasswordToken userToken = (UsernamePasswordToken) token;
if(!userToken.getUsername().equals(username)){
return null; // 抛出异常UnkonwnAcccountException
}
// 密码认证 , shiro做
return new SimpleAuthenticationInfo("",password,"");
}
}
shiro整合mybatis
shiro请求授权实现
Shiro整合Thymeleaf
全后端分离 例如:Vue + springboot
后端:后端控制层、服务层、数据访问层
前端:前端控制层、视图层 – 伪造数据,json。不需要后端数据已经存在了,前端工程依旧能跑起来。
前后端如何交互: API
前后端相对独立、松耦合, 前后端甚至可以部署在不同地服务器上。
产生问题:
解决方案:
Swagger
一、Springboot集成Swagger
1、创建一个springboot项目 - web项目
2、导入依赖
<dependency>
<groupId>io.springfoxgroupId>
<artifactId>springfox-swagger2artifactId>
<version>2.9.2version>
dependency>
<dependency>
<groupId>io.springfoxgroupId>
<artifactId>springfox-swagger-uiartifactId>
<version>2.9.2version>
dependency>
3、编写一个hello工程:编写HelloController,测试确保运行成功!
4、要使用Swagger,我们需要编写一个配置类-SwaggerConfig来配置 Swagger
@Configuration //配置类
@EnableSwagger2 //开启Swagger2
public class SwaggerConfig {
}
注意:
当Spring Boot 2.6.x 和Swagger 3.0.0 整合的时候,可能会报错如下所示:Failed to start bean ‘documentationPluginsBootstrapper’; nested exception is java.lang.NullPointerException。
原因:因为Springfox 使用的路径匹配是基于AntPathMatcher的,而Spring Boot 2.6.X使用的是PathPatternMatcher。
解决:springboot2.5.6 + swagger2.9.2兼容。
5、访问测试 :http://localhost:8080/swagger-ui.html ,可以看到swagger的界面;
二、配置swagger
1、Swagger实例Bean是Docket,所以通过配置Docket实例来配置Swagger,通过Docket对象接管了原来默认的配置
@Bean //配置docket以配置Swagger具体参数
public Docket docket() {
return new Docket(DocumentationType.SWAGGER_2);
}
2、可以通过apiInfo()属性配置文档信息
// 配置文档信息
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<>()// 扩展
);
}
3、Docket 实例关联上 apiInfo()
// 配置了swagger的docket的bean实例
@Bean //配置docket以配置Swagger具体参数
public Docket docket() {
return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo());
}
三、配置swagger扫描接口!
// 配置了swagger的docket的bean实例
@Bean //配置docket以配置Swagger具体参数
public Docket docket() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
// .select()---.build()是一套 得放一块
.select()
// RequestHandlerSelectors配置想要扫描接口的方式
// basePackage:指定要扫描的包
// any(): 扫描全部
// none(): 不扫描
// withClassAnnotation: 扫描类上的注解,参数是一个注解的反射现象
// withMethodAnnotation: 扫描方法上的注解
.apis(RequestHandlerSelectors.basePackage("com.yang.controller"))
// paths() : 过滤什么路径
.paths(PathSelectors.ant("/yang/**"))
.build();
}
四、配置是否启动swagger
// 配置了swagger的docket的bean实例
@Bean //配置docket以配置Swagger具体参数
public Docket docket() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
// enable() 是是否启动swagger,如果是False,则swagger不能在浏览器中使用
.enable(false)
.select()
.apis(RequestHandlerSelectors.basePackage("com.yang.controller"))
// .paths(PathSelectors.ant("/yang/**"))
.build();
}
如果我只希望我的Swagger在生产环境中使用,在发布的时候不使用?
@Bean //配置docket以配置Swagger具体参数
public Docket docket(Environment environment) {
// 设置要显示的swagger环境
Profiles profiles = Profiles.of("dev","test");
// 获取项目的环境(就是properties中的参数)
// 通过environment.acceptsProfiles 判断是否处在自己设定的环境当中
boolean flag = environment.acceptsProfiles(profiles);
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
// enable()是是否启动swagger,如果是False,则swagger不能在浏览器中使用
.enable(flag)
// // .select()---.build()是一套 得放一块
.select()
.apis(RequestHandlerSelectors.basePackage("com.yang.controller"))
// .paths(PathSelectors.ant("/yang/**"))
.build();
}
五、配置API文档的分组
.groupName("Yst")
如何配置多个分组?多个Docket实例即可
每一个Docket相当于一个用户
@Bean
public Docket docket1() {
return new Docket(DocumentationType.SWAGGER_2).groupName("A");
}
@Bean
public Docket docket2() {
return new Docket(DocumentationType.SWAGGER_2).groupName("B");
}
@Bean
public Docket docket3() {
return new Docket(DocumentationType.SWAGGER_2).groupName("C");
}
// 配置了swagger的docket的bean实例
@Bean
public Docket docket(Environment environment) {.......}
六、实体类配置
// 就是给生成的文档中的实体添加一个注释
@ApiModel("用户实体类")
public class User {
@ApiModelProperty("用户名")
public String username;
@ApiModelProperty("密码")
public String password;
}
package com.yang.controller;
import com.yang.pojo.User;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloController {
@ApiOperation("Hello方法")
@GetMapping(value = "/hello")
public String hello(){
return "hello";
}
// 只要我们的接口中,返回值中存在实体类,他就会自动被扫描到swagger中
@PostMapping(value = "/user")
public User user(){
return new User();
}
}
swagger常用注解:
总结:
我们可以通过Swagger给一些比较难理解的属性或者接口,增加注释信息
接口文档实时更新
可以在线测试
注意:在正式发布的时候,关闭Swagger!!!出于安全考虑,而且节省运行的内存。
任务:常用的有 异步任务!定时任务!邮件发送!
异步任务
1、Service
@Service
public class AsyncService{
// 告诉Spring这是一个异步的方法
@Async
public void hello(){
try{
Thread.sleep(3000);
} catch (InterruptedException e){
e.printStackTrace();
}
System.out.println("数据正在处理...");
}
}
2、Controller
@RestController
public class AsyncController{
@Autowired
AsyncService asyncService;
@RequestMapping("/hello")
public String hello(){
asyncService.hello(); // 停止三秒,转圈!
return "OK";
}
}
3、启动类
@EnableAsync // 开启异步注解功能
1、导入依赖
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-mailartifactId>
dependency>
2、编写配置
[email protected]
#授权码 -- 代替我们的密码
spring.mail.password=adsfisonlgmcbddj
#主机 host
spring.mail.host=smtp.qq.com
#开启加密验证
#spring.mail.properties.mail.smtp.ssl.enable=true
3、写测试类
package com.yang;
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.JavaMailSenderImpl;
import org.springframework.mail.javamail.MimeMessageHelper;
import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage;
import java.io.File;
@SpringBootTest
class Springboot10TestApplicationTests {
@Autowired
JavaMailSenderImpl mailSender;
@Test
void contextLoads() {
// 一个简单的邮件!
SimpleMailMessage mailMessage = new SimpleMailMessage();
// 邮件主题 正文 从哪来 发哪去
mailMessage.setSubject("你好啊");
mailMessage.setText("这是一个自动发送的邮件(来自YST)!!!");
mailMessage.setTo("[email protected]");
mailMessage.setFrom("[email protected]");
mailSender.send(mailMessage);
}
@Test
void contextLoads2() throws MessagingException {
// 一个复杂的邮件!
MimeMessage mimeMessage = mailSender.createMimeMessage();
// 组装~
MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true);
// 正文
helper.setSubject("你好啊");
helper.setText("这是一个邮件的内容!
", true);
// 附件
// helper.addAttachment("1.jpg", new File("F:\\文件\\图片\\1.jpg"));
helper.setTo("[email protected]");
helper.setFrom("[email protected]");
mailSender.send(mimeMessage);
}
}
4、抽取为工具类util
TaskScheduler 任务调度
TaskExecutor 任务执行者
@EnableScheduling // 开启定时功能的注解 !!! 在启动类上添加
@Scheduled // 什么时候执行
Cron表达式 可以百度搜!
package com.yang.service;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
@Service
public class ScheduledService {
// 在一个特定的事件执行这个方法!
// cron表达式
// 秒 分 时 日 月 周
// cron = "50 20 20 * * 0-7" 每天的20:20:50秒时都执行这个操作!
// cron = "50 0/5 10,18 * * ?" 每天10点和18点,每隔5分钟执行一次
@Scheduled(cron = "50 20 20 * * 0-7")
public void hello(){
System.out.println("hello,你被执行了!");
}
}
什么是分布式系统?
1、单一应用架构
当网站流量很小时,只需一个应用,将所有功能都部署在一起,以减少部署节点和成本。此时,用于简化增删改查工作量的数据访问框架(ORM)是关键。
适用于小型网站,小型管理系统,将所有功能都部署到一个功能里,简单易用。
缺点:
性能扩展比较难
协同开发问题
不利于升级维护
2、垂直应用架构
当访问量逐渐增大,单一应用增加机器带来的加速度越来越小,将应用拆成互不相干的几个应用,以提升效率。此时,用于加速前端页面开发的Web框架(MVC)是关键。
通过切分业务来实现各个模块独立部署,降低了维护和部署的难度,团队各司其职更易管理,性能扩展也更方便,更有针对性。
缺点:公用模块无法重复利用,开发性的浪费
3、分布式服务架构
4、流动计算架构
当服务越来越多,容量的评估,小服务资源的浪费等问题逐渐显现,此时需增加一个调度中心基于访问压力实时管理集群容量,提高集群利用率。此时,用于提高机器利用率的资源调度和治理中心(SOA)[ Service Oriented Architecture]是关键。
什么是RPC?
RPC【Remote Procedure Call】是指远程过程调用,是一种进程间通信方式,他是一种技术的思想,而不是规范。它允许程序调用另一个地址空间(通常是共享网络的另一台机器上)的过程或函数,而不用程序员显式编码这个远程调用的细节。即程序员无论是调用本地的还是远程的函数,本质上编写的调用代码基本相同。
也就是说两台服务器A,B,一个应用部署在A服务器上,想要调用B服务器上应用提供的函数/方法,由于不在一个内存空间,不能直接调用,需要通过网络来表达调用的语义和传达调用的数据。为什么要用RPC呢?就是无法在一个进程内,甚至一个计算机内通过本地调用的方式完成的需求,比如不同的系统间的通讯,甚至不同的组织间的通讯,由于计算能力需要横向扩展,需要在多台机器组成的集群上部署应用。RPC就是要像调用本地的函数一样去调远程函数;
推荐阅读文章:https://www.jianshu.com/p/2accc2840a1b
说白了就是不同于调用本地的而是调用远程资源和方法!
RPC原理:
RPC两个核心模块:通讯,序列化。
通讯:为了传输,用HTTP也可以传输。
序列化:为了方便数据的传输,数据传输需要转换。
1、Dubbo是什么
Dubbo是一个分布式服务框架,致力于提供高性能和透明化的RPC远程服务调用方案,以及SOA服务治理方案。简单的说,dubbo就是个服务框架,如果没有分布式的需求,其实是不需要用的,只有在分布式的时候,才有dubbo这样的分布式服务框架的需求,并且本质上是个服务调用的东西,说白了就是个远程服务调用的分布式框架。
其核心部分包含:
远程通讯: 提供对多种基于长连接。的NIO框架抽象封装,包括多种线程模型,序列化,以及“请求-响应”模式的信息交换方式。
集群容错: 提供基于接口方法的透明远程过程调用,包括多协议支持,以及软负载均衡,失败容错,地址路由,动态配置等集群支持。
自动发现: 基于注册中心目录服务,使服务消费方能动态的查找服务提供方,使地址透明,使服务提供方可以平滑增加或减少机器。
服务提供者(Provider):暴露服务的服务提供方,服务提供者在启动时,向注册中心注册自己提供的服务。
服务消费者(Consumer):调用远程服务的服务消费方,服务消费者在启动时,向注册中心订阅自己所需的服务,服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
注册中心(Registry):注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者
监控中心(Monitor):服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心
2、Dubbo能做什么
3、Dubbo和Nacos比较
Nacos 主要功能集中在 动态服务发现、服务配置、服务元数据及流量管理。你可以把他简单的理解为是一个注册中心和配置中心。
Dubbo 是一款高性能、轻量级的开源 Java 服务框架。 主要功能点在于 RPC 框架。
Apache Dubbo 提供了六大核心能力:面向接口代理的高性能RPC调用,智能容错和负载均衡,服务自动注册和发现,高度可扩展能力,运行期流量调度,可视化的服务治理与运维。
Nacos 致力于帮助您发现、配置和管理微服务。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据及流量管理。
4、dubbo-admin
dubbo是个jar包。
dubbo-admin是一个监控管理后台,查看我们注册了哪些服务,哪些服务被消费了。
5、zookeeper是什么
dubbo有很多服务的提供者和消费者,这么多的提供者和消费者需要一个管理中心来管理,这个时候用zookeeper来管理即可。
zookeeper:注册中心
6、总结:
1、Zookeeper的作用
zookeeper用来 注册服务 和进行 负载均衡,哪一个服务由哪一个机器来提供必需让调用者知道,简单来说就是ip地址和服务名称的对应关系。当然也可以通过硬编码的方式把这种对应关系在调用方业务代码中实现,但是如果提供服务的机器挂掉调用者无法知晓,如果不更改代码会继续请求挂掉的机器提供服务。zookeeper通过心跳机制可以检测挂掉的机器并将挂掉机器的ip和服务对应关系从列表中删除。至于支持高并发,简单来说就是横向扩展,在不更改代码的情况通过添加机器来提高运算能力。通过添加新的机器向zookeeper注册服务,服务的提供者多了能服务的客户就多了。
2、dubbo
是管理中间层的工具,在业务层到数据仓库间有非常多服务的接入和服务提供者需要调度,dubbo提供一个框架解决这个问题。
注意这里的dubbo只是一个框架,至于你架子上放什么是完全取决于你的,就像一个汽车骨架,你需要配你的轮子引擎。这个框架中要完成调度必须要有一个分布式的注册中心,储存所有服务的元数据,你可以用zk,也可以用别的,只是大家都用zk。
3、zookeeper和dubbo的关系
Dubbo的将注册中心进行抽象,是得它可以外接不同的存储媒介给注册中心提供服务,有ZooKeeper,Memcached,Redis等。
引入了ZooKeeper作为存储媒介,也就把ZooKeeper的特性引进来。首先是负载均衡,单注册中心的承载能力是有限的,在流量达到一定程度的时候就需要分流,负载均衡就是为了分流而存在的,一个ZooKeeper群配合相应的Web应用就可以很容易达到负载均衡;资源同步,单单有负载均衡还不够,节点之间的数据和资源需要同步,ZooKeeper集群就天然具备有这样的功能;命名服务,将树状结构用于维护全局的服务地址列表,服务提供者在启动的时候,向ZK上的指定节点/dubbo/${serviceName}/providers目录下写入自己的URL地址,这个操作就完成了服务的发布。其他特性还有Mast选举,分布式锁等。
微服务架构问题?
分布式架构会遇到的四个核心问题?
1.这么多服务,客户端该如何去访问?
2.这么多服务,服务之间如何进行通信?
3.这么多服务,如何治理呢?
4.服务挂了,怎么办?
解决方案:
Springcloud 是一套生态,是用来解决以上分布式架构的问题。
一、Spring Cloud NetFix 出了一套解决方案
1、Api网关,统一服务治理 —> zuul组件
2、Feign 基于 HttpClient 基于HTTP的通信方式(同步并阻塞)
3、治理:服务注册与发现 Eureka
4、熔断机制 Hystrix
二、Apache Dubbo zookeeper 第二套解决系统
1、API网关:没有,需要找第三方组件,要么自己实现
2、Dubbo 进行通信,是基于Java实现的 RPC通讯框架!
3、服务注册与发现,zookeeper:管理(Hadoop、Hive)
3、没有熔断机制,借助了 Hystrix
三、Spring Cloud Alibaba 解决方案
服务网格(service mesh):下一代微服务标准
代表解决方案:istio
万变不离其宗,一通百通!
1.API网关,服务路由
2.HTTP,RPC框架,异步调用
3.服务注册与发现,高可用
4.熔断机制,服务降级