在idea中双击shift键盘查询全部的类什么的
Spring是为了解决企业级应用开发的复杂性而创建的,简化开发。
4种关键策略:
1、基于POJO的轻量级的最小侵入性编程;
2、通过IOC、依赖注入(DI)和面向接口实现松耦合;
3、基于切面(AOP)和惯例进行声明式编程;
4、通过切面和模板减小样式代码。
web应用:Servlet结合Tomcat
框架Struts:https://xxx.do
SpringMVC
SpringBoot:就是一个javaweb的开发框架。简化开发,约定大于配置。
衍生出一些一站式解决方案。核心思想约定大于配置。
约定大于配置出现在:maven、spring、springmvc、……docker、k8s
集成大量常用第三方库配置:Redis、MongoDB、Jpa、RabbitMQ、Quartz等。
新服务架构:服务网格!
为所有Spring开发者更快的入门
开箱即用,提供各种默认配置来简化项目配置
内嵌式容器简化Web项目
没有荣誉代码生产和XML配置的要求
微服务是一种架构风格。将应用构建成一系列小服务的组合;可以通过http访问(补充一条:rpc)
含义:将一个应用中的所有应用服务都封装在一个应用中。
好处:易于开发和测试;方便部署;需要扩展时,只需将war复制多份,然后放到多个服务器上,再做个负载均衡就可以了。
缺点:修改一个非常小的地方,都需停掉整个服务,重新打包,部署这个应用war包。对于一个大型应用,不可能把所有内容放在一个应用里面,我们如何维护、如何分工合作都是问题。
all in one的架构方式,就是把功能单元放在一个应用里面,然后我们把整个应用部署到服务器上。
微服务架构,就是打破之前all in one的架构方式,把每个元素独立出来。把独立出来的功能元素动态组合,需要的功能元素才去拿来组合,需要多一些时间可以整合多个功能元素。
好处:
节省了调用资源。
每个功能元素的服务都是一个可替换的、可独立升级的软件代码。
原文地址:
翻译地址:
构建一个个功能独立的微服务应用单元,可以使用spring boot,可以帮我们快速创建一个应用;大型分布式网络服务的调用,这部分由spring cloud来完成,实现分布式;在分布式中间,进行流式数据计算、批处理,我们由spring cloud data flow。spring为我们想清楚了整个从开始构建应用到大型分布式应用全流程方案。
到底多么简单:1、jdk1.82、maven 3.6.13、spring boot:最新版4、IDEA
官方:提供了一个快捷生成的网站!IDEA集成了这个网站!
可以在官网直接下载后,导入idea开发(官网在哪)直接使用idea创建一个spring boot项目(一般开发直接在IDEA中创建)
parent:继承spring-boot-starter-parent的依赖管理,控制版本与打包等内容。dependencies:项目具体依赖,包含了spring-boot-starter-web(该依赖包含了spring mvc),官方描述:使用spring mvc构建web(包括RESTful)应用程序的入门者,使用Tomcat作为默认嵌入式容器。spring-boot-starter-test用于编写单元测试的依赖包。还有更多功能build:构建配置部分。默认使用了spring-boot-maven-plugin,配合spring-boot-starter-parent就可以把spring boot应用打包成jar来直接运行。
在主程序的同级目录下,新建一个controller包【一定要在同级目录下,否则识别不到】
在包中新建一个Controller类
@RestControllerpublic class HelloController { //接口:http://localhost:8080/hello @RequestMapping("/hello") public String hello(){ //调用业务,接受前端的参数! return "hello,World"; }}
编写完毕后,从主程序启动项目。浏览器发起请求,看页面返回。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-btlxFzJ4-1623924293477)(SpringBoot.assets/image-20210530192755092.png)]
将项目打成jar包
在jar包所在位置运行cmd,输入命令:java -jar ks-helloword-0.0.1-SNAPSHOT.jar
server.port=8081
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WNOvoiMt-1623924293479)(SpringBoot.assets/image-20210530203308699.png)]
spring boot banner在线生成网址:
spring-boot-dependencies:核心依赖都在父工程中
我们在写或者引入一些spring boot依赖的时候,不需要指定版本,就因为有这些版本仓库
spring boot所有依赖网址:
<dependency> <groupId>org.springframework.bootgroupId> <artifactId>spring-boot-starterartifactId>dependency>
启动器:说白了就是spring boot的启动场景;
比如:spring-boot-starter-web,他就会帮我们自动导入web环境所有的依赖!
spring boot会将所有的功能场景,都变成一个个的启动器
我们要使用什么功能,就只需要找到对应的启动器就可以了 starter
spring boot所有启动器网址:
//本身就是spring的一个组件//程序的主入口//@SpringBootApplication:标注这个类是一个springboot的应用@SpringBootApplicationpublic class KsHellowordApplication { public static void main(String[] args) { //将springboot应用启动 SpringApplication.run(KsHellowordApplication.class, args); }}
注解
@SpringBootConfiguration:spring boot的配置 @Configuration:spring配置类 @Component:说明这也是一个spring的组件@EnableAutoConfiguration:自动配置 @AutoConfigurationPackage:自动配置包 @Import(AutoConfigurationPackages.Registrar.class):自动配置 `包注册` @Import(AutoConfigurationImportSelector.class)://获取所有的配置List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
获取候选的配置
~~~xml protected List getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) { List 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:自动配置的核心文件
Properties properties = PropertiesLoaderUtils.loadProperties(resource);所有资源加载到配置类中!
spring boot所有自动配置都是在启动的时候扫描并加载:`spring.factories`所有的自动配置类都在这里面,但是不一定生效,要判断条件是否成立,只要导入对应的start,就有对应的启动器了,有了启动器,我们自动装配就会生效,然后就配置成功!
spring boot在启动的时候,从类路径下/META-INF/spring.factories获取指定的值;将这些自动配置的类导入容器,自动配置就会生效,帮我们进行自动配置!以前我们需要自动配置的东西,现在spring boot帮我们做了!整合JavaEE,解决方案和自动配置的东西都在spring-boot-autoconfigure-2.5.0.jar这个包下它把所用需要导入的组件,以类名的方式返回,这些组件就会被添加到容器;容器中也会存在非常多的xxxAutoConfiguration的文件(@Bean),就是这些类给容器中导入了这个场景需要的所有组件;并自动配置,@Configuration,JavaConfig!有了自动配置类,免去了我们手动编写配置文件的工作!
spring boot自动配置原理思维导图看外边:spring boot自动配置原理.pdf
我最以为就是运行了一个main方法,没想到却开启了一个服务;
@SpringBootApplicationpublic class KsHellowordApplication { public static void main(String[] args) { //该方法返回一个ConfigurableApplicationContext对象 //参数一:应用入口的类 参数类:命令行参数 SpringApplication.run(KsHellowordApplication.class, args); }}
SpringApplication.run分析
分析该方法主要分两部分,一部分是SpringApplication的实例化;二是run方法的执行。
这个类主要做了以下四件事情1、推断应用的类型是普通的项目还是Web项目2、查找并加载所有可用初始化器,设置到initializers属性中3、找出所有的应用程序监听器,设置到listeners属性中4、推断并设置main方法的定义类,找到运行的主类
run方法流程分析图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YjOOudz5-1623924293480)(SpringBoot.assets/run方法流程分析图)]
javaConfig @Configuration @Bean
Docker:进程 ???
关于spring boot,谈谈你的理解:
Spring cloud 全面接管springmvc的配置!实操! ???
所有yaml官方文档网址:(在88.4下面)
spring boot使用一个全局的配置文件,配置文件名称是固定的。
配置文件的作用:修改spring boot自动配置的默认值,因为spring boot在底层都给我们自动配置好了。
YAML是"YAML Ain't a Markup Language"(YAML不是一种标记语言)的递归缩写。在开发的这种语言时,YAML 的意思其实是:"Yet Another Markup Language"(仍是一种标记语言),但为了强调这种语言以数据做为中心,而不是以标记语言为重点,而用反向缩略语重命名。
标记语言
以前的配置文件,大多数都是使用xml来配置;比如一个简单的端口配置,我们来对比下yaml和xml
yaml配置:
server: port: 8080
xml配置:
<server> <port>8081port>server>
k:(空格) v
注意:属性和值的大小写都是十分敏感的。
# spring boot这个配置文件中到底可以配置哪些东西呢?#官方的配置太多了#了解原理:一通百通#k=v#对空格的要求十分高#普通的key-value#注入到我们的配置类中name: qinjiang#对象student: name: qinjiang age: 30#行内写法 student: {name: qinjiang,age: 30}#数组pets: - cat - dog - pig pets: [cat,dog,pig]
@ConfigurationProperties(prefix = "person")把yaml文件绑定起来了=============================Person======================@Component@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; public person() { } public person(String name, Integer age, Boolean happy, Date birth, Map<String, Object> maps, List<Object> lists, Dog dog) { this.name = name; this.age = age; this.happy = happy; this.birth = birth; this.maps = maps; this.lists = lists; this.dog = dog; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public Boolean getHappy() { return happy; } public void setHappy(Boolean happy) { this.happy = happy; } public Date getBirth() { return birth; } public void setBirth(Date birth) { this.birth = birth; } public Map<String, Object> getMaps() { return maps; } public void setMaps(Map<String, Object> maps) { this.maps = maps; } public List<Object> getLists() { return lists; } public void setLists(List<Object> lists) { this.lists = lists; } public Dog getDog() { return dog; } public void setDog(Dog dog) { this.dog = dog; } @Override public String toString() { return "person{" + "name='" + name + '\'' + ", age=" + age + ", happy=" + happy + ", birth=" + birth + ", maps=" + maps + ", lists=" + lists + ", dog=" + dog + '}'; }}
=============================yaml========================person: name: fengjiu age: 16 happy: false birth: 2021/5/31 maps: {k1: vl,k2: v2} lists: - code - music - girl dog: name: xiaokeai age: 5
====================test测试=============================@SpringBootTestclass KsBoot02ConfigApplicationTests { @Autowired private Person person; @Test void contextLoads() { System.out.println(person); }}===========================结果==========================person{name='fengjiu', age=16, happy=false, birth=Mon May 31 00:00:00 CST 2021, maps={k1=vl, k2=v2}, lists=[code, music, girl], dog=Dog{name='xiaokeai', age=5}}
==================解决pojo爆红原因========================<dependency> <groupId>org.springframework.bootgroupId> <artifactId>spring-boot-configuration-processorartifactId> <optional>trueoptional>dependency>
@component : 把普通pojo实例化到spring容器中,相当于配置文件中的 <bean id="" class=""/> 注册bean@ConfigurationProperties作用: 将配置文件中配置的每一个属性的值,映射到这个组件中; 告诉spring boot将本类中的所有属性和配置文件中相关的配置进行绑定; 参数(prefix = "person"):将配置文件中的person下面的所有属性一一对应 只有这个组件是容器中的组件,才能使用容器提供的@ConfigurationProperties功能
SPEL表达式???
@PropertySource(value = “classpath:application.properties”):加载指定的配置文件
javaConfig绑定我们配置文件的值,可以采取这些方式!
person: name: fengjiu${random.uuid} age: ${random.int} happy: false birth: 2021/5/31 maps: {k1: vl,k2: v2} hello: happy lists: - code - music - girl dog: name: ${person.hello:hello}_小可爱 age: 5=====================结果======================person{name='fengjiud7acc6a1-ff4c-4b4f-8728-759e98fbe3d6', age=1252694831, happy=false, birth=Mon May 31 00:00:00 CST 2021, maps={k1=vl, k2=v2}, lists=[code, music, girl], dog=Dog{name='happy_小可爱', age=5}}=====================解释======================${random.uuid}:随机生成一个uuid${random.int}:随机数 ${person.hello:hello}_小可爱:如果person.hello存在就是person.hello里的值_小可爱,不存在就是hello_小可爱
松散绑定:比如yaml中写的last-name,这个和lastName是一样的,-后面跟着的字母默认是大写的。这就是松散绑定。JSR303数据校验:就是我们可以在字段添加一层过滤器验证,可以保证数据的合法性复杂类型封装:yaml中可以封装对象。使用@value不支持
配置yaml和配置properties都可以获取到值,强烈推荐yml(注:yaml和yml是一个东西)如果我们在某个业务中,只需获取配置文件的某个值,可以使用一下@Value如果说,我们专门编写了一个JavaBean来和配置文件进行映射,就直接使用@ConfigurationProperties,不要犹豫!
使用数据校验,可以保证数据的正确性;
前提:pom.xml
<dependency> <groupId>org.springframework.bootgroupId> <artifactId>spring-boot-starter-validationartifactId>dependency>
应用:
@Validated //数据校验public class Person { @Email(message = "邮箱格式错误") private String name;}
常见参数
@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 对象是否符合正则表达式的规则:正则表达式.......等等除此以外,我们还可以自定义一些数据校验规则
#spring boot的多环境配置,可以选择激活哪一个配置文件spring.profiles.active=test
#spring: # profiles: dev 被弃用#推荐使用 #spring: # config: # activate: # on-profile: devspring: profiles: active: test---server: port: 8081spring: profiles: test---server: port: 8082spring: profiles: dev
yaml配置文件到底写什么—联系—spring.factories
在我们这配置文件中能配置的东西,都存在一个固有的规律
xxxAutoConfiguration:默认值 xxxProperties 和 配置文件绑定,我们就可以会用自定义的配置了!
//表示这是一个配置类@Configuration(proxyBeanMethods = false)//自动配置属性:ServerProperties@EnableConfigurationProperties(ServerProperties.class)//spring的底层注解:根据不同的条件,来判断当前配置或者类是否生效!@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
这就是自动装配的原理!
1、spring boot启动会加载大量的自动配置类;
2、我们看我们需要的功能有没有在spring boot默认写好的自动配置类当中;
3、我们再来看这个自动配置类中到底配置了哪些组件;(只要我们要用的组件存放在其中,我们就不需要在手动配置了)
4、给容器中自动配置类添加组件的时候,会从properties类中获取某些属性。我们只需要在配置文件中指定这些属性的值即可;
xxxAutoConfiguration:自动配置类;给容器中添加组件
xxxProperties:封装配置文件中相关属性;
#可以通过 debug=true 来查看,哪些自动配置类生效,哪些没有生效!debug: true
jar:webapp!
自动装配
public void addResourceHandlers(ResourceHandlerRegistry registry) { if (!this.resourceProperties.isAddMappings()) { logger.debug("Default resource handling disabled"); return; } addResourceHandler(registry, "/webjars/**", "classpath:/META-INF/resources/webjars/"); addResourceHandler(registry, this.mvcProperties.getStaticPathPattern(), (registration) -> { registration.addResourceLocations(this.resourceProperties.getStaticLocations()); if (this.servletContext != null) { ServletContextResource resource = new ServletContextResource(this.servletContext, SERVLET_LOCATION); registration.addResourceLocations(resource); } });}
从WebJars 官网找到所需静态资源对应的jar包版本,通过maven引入对应的文件引入,启动spring boot,访问对应的http://localhost:8080/webjars/xxx/xxx/xxx,可以通过左边jar包结构写对应xxx。例如:http://localhost:8080/webjars/jquery/3.4.1/jquery.js
什么是webjars?
WebJars 官网
引入jquery后启动访问网址
在resources文件夹下创建public/resources/static目录,在目录下放入所需jar包启动,访问http://localhost:8080/xxx,例:http://localhost:8080/jquery.js
优先级:resources最大,static,public最小
一般:public放些公共资源,static放些图片,resources放些上传文件。
创建包,放入js,访问网址
#自定义访问静态资源路径,一般不这么干,会使默认静态资源四种方式失效#spring.mvc.static-path-pattern=/hello/,classpath:/kuang/
1、在spring boot,我们可以使用以下方式处理静态资源 webjars localhost:8080/webjars/… public,static,/**,resources localhost:8080/…2、优先级:resources>static(默认)>public
把图片改为favicon.ico,然后放在public/resources/static目录下,启动,清理浏览器缓存。
1、Thymeleaf官网:
2、Thymeleaf在Github的主页:
3、Spring官方文档:
==============thymeleaf的pom.xml===================================<dependency> <groupId>org.thymeleafgroupId> <artifactId>thymeleaf-spring5artifactId>dependency><dependency> <groupId>org.thymeleaf.extrasgroupId> <artifactId>thymeleaf-extras-java8timeartifactId>dependency>
=====================controller==============================//在templates目录下的所有页面,只能通过controller来跳转!//这个需要模板引擎的支持!thymeleaf@Controllerpublic class IndexController { @RequestMapping("/index") public String index(Model model){ model.addAttribute("msg","你好!"); return "index"; }}
==================templates目录下的html文件======================DOCTYPE html><html lang="en" xmlns:th="http://www.thymeleaf.org"><head> <meta charset="UTF-8"> <title>Titletitle>head><body> <h1 th:text="${msg}">首页h1>body>html>
结论:只要需要使用Thymeleaf,只需要导入对应的依赖就可以了!我们将html放在我们的templates目录下即可!
public static final String DEFAULT_PREFIX = "classpath:/templates/";public static final String DEFAULT_SUFFIX = ".html";
头部引入
<html lang="en" xmlns:th="http://www.thymeleaf.org">
${...}
(变量)*{...}
(选择表达式)#{...}
(消息)@{...}
(url)~{...}
(片段表达式)'one text'
, 'Another one!'
,…0
, 34
, 3.0
, 12.3
,…true
, false
null
one
, sometext
, main
,…+
|The name is ${name}|
+
, -
, *
, /
, %
-
and
, or
!
, not
>
, <
, >=
, <=
(gt
, lt
, ge
, le
)==
, !=
(eq
, ne
)(if) ? (then)
(if) ? (then) : (else)
(value) ?: (defaultvalue)
_
0、头部引入 xmlns:th="http://www.thymeleaf.org"例:<html lang="en" xmlns:th="http://www.thymeleaf.org">1、日期转换:${#dates.format(需要转换的值/变量,'yyyy-MM-dd HH:mm:ss')}例:<td th:text="${#dates.format(emp.getBirth(),'yyyy-MM-dd HH:mm:ss')}">td>2、三元运算${条件?'true':'false'}例:<td th:text="${emp.getGender()==0?'女':'男'}">td>3、each循环 th:each="赋为的值:${传过来的变量}"例:<tr th:each="emp:${emps}">tr>4、抽取公共部分 th:fragment="起的名" 和 引用 th:replace="~{抽取公共部分所放的位置::抽取公共部分起的名}"例:<div th:fragment="sidebar"> <div th:replace="~{commons/commons::sidebar}">5、传参(active='参数')例:如果要传递参数,可以直接使用()传参,接受判断即可! <div th:replace="~{commons/commons::sidebar(active='main.html')}">div> 6、if判断th:if="${判断条件}"例: <p style="color:red;" th:text="${msg}" th:if="${not #strings.isEmpty(msg)}">p> 7、取变量的两种方式 ${}或[[${}]]例:<button th:text="${session.loginUser}">xxxbutton>推荐使用 <button>[[${session.loginUser}]]button>不推荐使用 8、路径 @{}或@{~/}例:<link th:href="@{css/style.css}" rel="stylesheet"> //有时这样写不对,路径有问题,需写出下面的方式 <link th:href="@{~/css/style.css}" rel="stylesheet">9、消息 #{} 例:<h1 class="sign-title" th:text="#{login.tip}">请登录h1>//i18n国际化例子
====================controller===================================//在templates目录下的所有页面,只能通过controller来跳转!//这个需要模板引擎的支持!thymeleaf@Controllerpublic class IndexController { @RequestMapping("/index") public String index(Model model){ model.addAttribute("msg","你好,Spring Boot!
"); return "index"; }}
==============html========================DOCTYPE html><html lang="en" xmlns:th="http://www.thymeleaf.org"><head> <meta charset="UTF-8"> <title>Titletitle>head><body> <div th:text="${msg}">首页div>// <div th:utext="${msg}">首页div>body>html>
====================controller===================================//在templates目录下的所有页面,只能通过controller来跳转!//这个需要模板引擎的支持!thymeleaf@Controllerpublic class IndexController { @RequestMapping("/index") public String index(Model model){ model.addAttribute("users", Arrays.asList("qinjiang","kuangshen")); return "index"; }}
==============html========================DOCTYPE html><html lang="en" xmlns:th="http://www.thymeleaf.org"><head> <meta charset="UTF-8"> <title>Titletitle>head><body><h3 th:each="user:${users}" th:text="${user}">h3>body>html>
官网
ctrl+o打开类重写的方法
//如果你想diy一些定制化的功能,只要写这个组件,然后将它交给spring boot,spring boot就会帮我们自动装配!//扩展springmvc dispatchservlet@Configurationpublic class MyMvcConfig implements WebMvcConfigurer { //public interface ViewResolver 实现了视图解析器接口的类,我们就可以把它看做视图解析器 @Bean public ViewResolver myViewResolver(){ return new MyViewResolver(); } //自定义了一个自己的视图解析器MyViewResolver public static class MyViewResolver implements ViewResolver{ @Override public View resolveViewName(String viewName, Locale locale) throws Exception { return null; } }}
#自定义的配置日期格式化!#已过时#spring.mvc.date-format=#spring.mvc.format.date=
//如果我们要扩展springmvc,官方建议我们这样去做!@Configurationpublic class MyMvcConfig implements WebMvcConfigurer { //视图跳转 @Override public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController("/kuang").setViewName("index"); }}
//@EnableWebMvc//这玩意就是导入了一个类:DelegatingWebMvcConfiguration:从容器中获取所有的WebMvcConfig:注意:千万不要加入@EnableWebMvc,否则所有东西都失效
在spring boot中,有非常多的xxxConfiguration帮助我们进行扩展配置,只要看见了这个东西,我们就要注意了!
<dependency> <groupId>org.projectlombokgroupId> <artifactId>lombokartifactId>dependency>
=================================部门表========================//部门表@Data@AllArgsConstructor@NoArgsConstructorpublic class Department { private Integer id; private String departmentName;}=================================员工表========================@Data@NoArgsConstructorpublic class Employee { private Integer id; private String lastName; private String email; private Integer gender;//0:女 1:男 private Department department; private Date birth; public Employee(Integer id, String lastName, String email, Integer gender, Department department) { this.id = id; this.lastName = lastName; this.email = email; this.gender = gender; this.department = department; //默认的创建日期! this.birth = new Date(); }}
3.模拟数据库数据
==============================部门dao======================================//部门dao@Repositorypublic class DepartmentDao { //模拟数据库中的数据 private static Map departments=null; static { departments=new HashMap();//创建一个部门表 departments.put(101,new Department(101,"教学部")); departments.put(102,new Department(102,"市场部")); departments.put(103,new Department(103,"教研部")); departments.put(104,new Department(104,"运营部")); departments.put(105,new Department(105,"后勤部")); } //获得所有部门信息 public Collection getDepartments(){ return departments.values(); } //通过id得到部门 public Department getDepartmentById(Integer id){ return departments.get(id); }}==============================员工Dao======================================//员工Dao@Repositorypublic class EmployeeDao { //模拟数据库中的数据 public static Map employees=null; //员工有所属的部门 @Autowired private DepartmentDao departmentDao; static { employees=new HashMap(); employees.put(1001,new Employee(1001,"AA","[email protected]",0,new Department(101,"教学部"))); employees.put(1002,new Employee(1002,"BB","[email protected]",1,new Department(102,"市场部"))); employees.put(1003,new Employee(1003,"CC","[email protected]",0,new Department(103,"教研部"))); employees.put(1004,new Employee(1004,"DD","[email protected]",1,new Department(104,"运营部"))); employees.put(1005,new Employee(1005,"EE","[email protected]",0,new Department(105,"后勤部"))); } //主键自增! private static Integer initId=1006; //增加一个员工 public void save(Employee employee){ if (employee.getId()==null){ employee.setId(initId++); } employee.setDepartment(departmentDao.getDepartmentById(employee.getDepartment().getId())); employees.put(employee.getId(),employee); } //查询全部员工信息 public Collection getAll(){ return employees.values(); } //通过id查询员工 public Employee getEmployeeById(Integer id){ return employees.get(id); } //删除员工通过id public void delete(Integer id){ employees.remove(id); }}
====================推荐:================================@Configurationpublic class MyMvcConfig implements WebMvcConfigurer { @Override public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController("/").setViewName("login"); registry.addViewController("/login.html").setViewName("login"); }}=====================等同于上面==================================== @Controllerpublic class IndexController { @RequestMapping({"/","/index.html"}) public String index(){ return "index"; }}
# 关闭模板引擎的缓存spring.thymeleaf.cache=false#注:高版本好像不需要这步
3.页面配置:
注意点,所有的静态资源都需要使用Thymeleaf接管;
<html lang="en" xmlns:th="http://www.thymeleaf.org"> <link th:href="@{~/css/style.css}" rel="stylesheet"> <link th:href="@{~/css/style-responsive.css}" rel="stylesheet">注: 有时@{}路径不对,需要写为@{~}
url:@{}
页面国际化:(实现中英切换)
我们需要配置i18n文件
==============login.properties======================login.btn=登录login.forget=忘记密码login.notip=还没有账号?login.password=密码login.register=去注册login.rempass=记住密码login.tip=请登录login.username=用户名==============login_zh_CN.properties======================login.btn=登录login.forget=忘记密码login.notip=还没有账号?login.password=密码login.register=去注册login.rempass=记住密码login.tip=请登录login.username=用户名==============login_en_US.properties======================login.btn=Sign inlogin.forget=Forget passwordlogin.notip=No account yet?login.password=Passwordlogin.register=To registerlogin.rempass=Remember passwordlogin.tip=Please sign inlogin.username=Username
注意添加:
#我们的配置文件的真实位置spring.messages.basename=i18n.login
我们如果需要在项目中进行按钮自动切换,我们需要自定义一个组件LocaleResolver
public class MyLocaleResolver implements LocaleResolver { //解析请求 @Override public Locale resolveLocale(HttpServletRequest request) { //获取请求中的语言参数 String language=request.getParameter("l"); Locale locale=Locale.getDefault();//如果没有就使用默认的; //如果请求的链接携带了国际化的参数 if (!ObjectUtils.isEmpty(language)){ //zh_CN String[] split=language.split("_"); //国家,地区 locale=new Locale(split[0],split[1]); } return locale; } @Override public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) { }}
记得将自己写的组件配置到spring容器@Bean
@Configurationpublic class MyMvcConfig implements WebMvcConfigurer { //自定义的国际化组件就生效了 @Bean public LocaleResolver localeResolver(){ return new MyLocaleResolver(); }}
#{}
<div class="language"> <a class="btn btn-sm btn-zh" th:href="@{/login.html(l='zh_CN')}">中文a> <a class="btn btn-sm btn-en" th:href="@{/login.html(l='en_US')}">Englisha>div>
=============================登录页面主要部分============================================ <form class="form-signin" th:action="@{/user/login}"> <div class="form-signin-heading text-center"> <h1 class="sign-title" th:text="#{login.tip}">请登录h1> <p style="color:red;" th:text="${msg}" th:if="${not #strings.isEmpty(msg)}">p> <img th:src="@{~/images/login-logo.png}" alt=""/> div> <div class="login-wrap"> <input type="text" name="username" th:placeholder="#{login.username}" class="form-control" autofocus> <input type="password" name="password" th:placeholder="#{login.password}" class="form-control"> <button class="btn btn-lg btn-login btn-block" type="submit" th:text="#{login.btn}"> button> <div class="registration"> [[#{login.notip}]] <a class="" href="registration.html" th:text="#{login.register}"> 去注册 a> div> <label class="checkbox"> <input type="checkbox" value="remember-me" th:text="#{login.rempass}"> <span class="pull-right"> <a data-toggle="modal" href="#myModal" th:text="#{login.forget}">a> span> label> div>form>
2.登录Controller层实现
=======================登录Controller层================================@Controllerpublic class LoginController { @RequestMapping({"/user/login"}) public String login(@RequestParam("username")String username, @RequestParam("password")String password, Model model, HttpSession session){ //具体的业务: if (!ObjectUtils.isEmpty(username)&&password.equals("123")){ session.setAttribute("loginUser",username); return "redirect:/main.html"; }else { //告诉用户,你登录失败了 model.addAttribute("msg","用户名或者密码错误"); return "login"; } }}
3.虚拟路径跳转位置:
@Configurationpublic class MyMvcConfig implements WebMvcConfigurer { @Override public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController("/").setViewName("login"); registry.addViewController("/login.html").setViewName("login"); registry.addViewController("/main.html").setViewName("index");//虚拟路径跳转到主页 }}
4.注意:
以上有个问题,直接访问http://localhost:8080/main.html,就会直接进入主页。
解决方案:配置登录拦截器
//拦截器public class LoginHandlerInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { //登录成功之后,应该有用户的session Object loginUser =request.getSession().getAttribute("loginUser"); if (loginUser==null){//没有登录 request.setAttribute("msg","没有权限,请先登录"); request.getRequestDispatcher("/login.html").forward(request,response); return false; }else { return true; } }}
2.拦截器生效:
@Configurationpublic class MyMvcConfig implements WebMvcConfigurer { //配置拦截器 //.addPathPatterns("/**")拦截全部 //excludePathPatterns("/login.html","/","user/login","/css/**","/fonts/**","/images/**","/js/**")不拦截登录和静态资源 @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new LoginHandlerInterceptor()) .addPathPatterns("/**").excludePathPatterns("/login.html","/","/user/login", "/css/**","/fonts/**","/images/**","/js/**"); }}
3.错误页面提示:
====================login.html页面===============================<p style="color:red;" th:text="${msg}" th:if="${not #strings.isEmpty(msg)}">p>
4.成功页面取出session: [[${session.loginUser}]]
====================index.html页面===============================<a href="#" class="btn btn-default dropdown-toggle" data-toggle="dropdown"> <img src="images/photos/user-avatar.png" alt="" /> [[${session.loginUser}]] <span class="caret">span>a>
注:(前提:需要1、准备工作的pojo和模拟数据)
提取公共页面
1、<html lang="en" xmlns:th="http://www.thymeleaf.org">2、<div th:fragment="sidebar">3、<div th:replace="~{commons/commons::sidebar}">4、如果要传递参数,可以直接使用()传参,接受判断即可!<div th:replace="~{commons/commons::sidebar(active='main.html')}">div> <li th:class="${active=='main.html'?'active':''}">
列表循环展示
所用:
循环遍历th:each,取值th:text="${变量}",三元运算th:text="${条件?:}",日期转换th:text="${#dates.format(转换的值或变量,'yyyy-MM-dd HH:mm:ss')}"
具体实现:
controller层
=====================controller层=================================@Controllerpublic class EmployeeController { @Autowired EmployeeDao employeeDao; @RequestMapping("/emps") public String list(Model model){ Collection<Employee> employees=employeeDao.getAll(); model.addAttribute("emps",employees); return "emp/list"; }}
list.html页面
<table class="display table table-bordered table-striped" id="dynamic-table"> <thead> <tr> <th>idth> <th>lastNameth> <th>emailth> <th>genderth> <th>departmentth> <th>birthth> <th>操作th> tr> thead> <tbody> <tr th:each="emp:${emps}" > <td th:text="${emp.getId()}">td> <td th:text="${emp.getLastName()}">td> <td th:text="${emp.getEmail()}">td> <td th:text="${emp.getGender()==0?'女':'男'}">td> <td th:text="${emp.getDepartment.getDepartmentName()}">td> <td th:text="${#dates.format(emp.getBirth(),'yyyy-MM-dd HH:mm:ss')}">td> <td> <button class="btn btn-sm btn-primary">编辑button> <button class="btn btn-sm btn-danger">删除button> td> tr> tbody>table>
按钮提交
===============================list.html==============================<h2> <a th:href="@{/emp}" class="btn btn-sm btn-success">添加员工a>h2>
跳转到添加页面
controller
@Controllerpublic class EmployeeController { @Autowired EmployeeDao employeeDao; @Autowired DepartmentDao departmentDao; @GetMapping("/emp") public String toAddPage(Model model){ //查出所有部门的信息 Collection departments = departmentDao.getDepartments(); model.addAttribute("departments",departments); return "emp/add"; }}
add.html
<form th:action="@{/emp}" method="post"> <div class="form-group"> <label>lastNamelabel> <input type="text" name="lastName" class="form-control" placeholder="xiaokeai"> div> <div class="form-group"> <label>emaillabel> <input type="email" name="email" class="form-control" placeholder="[email protected]"> div> <div class="form-group"> <label>genderlabel><br/> <div class="radio"> <label> <input type="radio" name="gender" value="1" checked> 男 label> div> <div class="radio"> <label> <input type="radio" name="gender" value="0"> 女 label> div> div> <div class="form-group"> <label>departmentlabel> <select class="form-control" name="department.id"> <option th:each="dept:${departments}" th:text="${dept.getDepartmentName()}" th:value="${dept.getId()}">option> select> div> <div class="form-group"> <label>birthlabel> <input type="text" name="birth" class="form-control" placeholder="keai"> div> <button type="submit" class="btn btn-primary">添加button>form>
添加员工成功,返回首页
controller
@Controllerpublic class EmployeeController { @Autowired EmployeeDao employeeDao; @Autowired DepartmentDao departmentDao; @PostMapping("/emp") public String addEmp(Employee employee){ System.out.println("employee"); employeeDao.save(employee);//调用底层业务方法保存员工信息 System.out.println(employee); return "redirect:/emps"; }}
按钮提交
===============================list.html==============================<td> <a class="btn btn-sm btn-primary" th:href="@{'/emp/'+${emp.getId()}}">编辑a> <button class="btn btn-sm btn-danger">删除button>td>
跳转到修改页面
controller
@Controllerpublic class EmployeeController { @Autowired EmployeeDao employeeDao; @Autowired DepartmentDao departmentDao; //去员工的修改页面 @GetMapping("/emp/{id}") public String toUpdate(@PathVariable("id")Integer id,Model model){ //查出员工的数据 Employee employee = employeeDao.getEmployeeById(id); model.addAttribute("emp",employee); //查出所有部门的信息 Collection departments = departmentDao.getDepartments(); model.addAttribute("departments",departments); return "emp/update"; }}
update.html
<form th:action="@{/updateEmp}" method="post"> <input type="hidden" name="id" th:value="${emp.getId()}"> <div class="form-group"> <label>lastNamelabel> <input th:value="${emp.getLastName()}" type="text" name="lastName" class="form-control" placeholder="xiaokeai"> div> <div class="form-group"> <label>emaillabel> <input th:value="${emp.getEmail()}"type="email" name="email" class="form-control" placeholder="[email protected]"> div> <div class="form-group"> <label>genderlabel><br/> <div class="radio"> <label> <input type="radio" name="gender" value="1" checked th:checked="${emp.getGender()==1}"> 男 label> div> <div class="radio"> <label> <input type="radio" name="gender" value="0" th:checked="${emp.getGender()==0}"> 女 label> div> div> <div class="form-group"> <label>departmentlabel> <select class="form-control" name="department.id"> <option th:selected="${dept.getId()==emp.getDepartment().getId()}" th:each="dept:${departments}" th:text="${dept.getDepartmentName()}" th:value="${dept.getId()}">option> select> div> <div class="form-group"> <label>birthlabel> <input th:value="${#dates.format(emp.getBirth(),'yyyy-MM-dd HH:mm:ss')}" type="text" name="birth" class="form-control" placeholder="keai"> div> <button type="submit" class="btn btn-primary">修改button>form>
修改成功,回到首页
controller
@Controllerpublic class EmployeeController { @Autowired EmployeeDao employeeDao; @Autowired DepartmentDao departmentDao; @PostMapping("/updateEmp") public String updateEmp(Employee employee){ employeeDao.save(employee); return "redirect:/emps"; }}
按钮提交
===============================list.html==============================<td> <a class="btn btn-sm btn-primary" th:href="@{'/emp/'+${emp.getId()}}">编辑a> <a class="btn btn-sm btn-danger" th:href="@{'/deleteEmp/'+${emp.getId()}}">删除a>td>
删除成功,返回页面
controller
@Controllerpublic class EmployeeController { @Autowired EmployeeDao employeeDao; @GetMapping("/deleteEmp/{id}") public String delete(@PathVariable("id")Integer id){ employeeDao.delete(id); return "redirect:/emps"; }}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hieWgl9B-1623924293483)(SpringBoot.assets/image-20210606175902841.png)]
方式一:(不推荐使用)因为有session,直接访问http://localhost:8080/main.html,就会进入主页。
<li><a th:href="@{/login.html}"><i class="fa fa-sign-out">i> 登出a>li>
方式二:(推荐)因为清掉了session,直接访问http://localhost:8080/main.html,不会进入主页。
html
<li><a th:href="@{/user/logout}"><i class="fa fa-sign-out">i> 登出a>li>
controller
@Controllerpublic class LoginController { @RequestMapping("/user/logout") public String logout(HttpSession session){ session.invalidate();//移除session return "redirect:/login.html"; }}
前端:
# 1、前端搞定: 页面长什么样子: 数据# 2、设计数据库(数据库设计难点!)# 3、前端让他能够自动运行,独立化工程# 4、数据接口如何对接:json,对象 all in one!# 5、前后端联调测试!1.有一套自己熟悉的后台模板:工作必要!x-admin2.前端界面:至少自己能够通过前端框架,组合出来一个网站页面 -index -about -blog -post -user3.让这个网站能够独立运行一个月!
这周:
对于数据访问层,无论是SQL(关系型数据库)还是NOSQL(非关系型数据库),Spring Boot底层都是采用Spring Data的方式进行统一处理。
Spring Boot底层都是采用Spring Data的方式进行统一处理各种数据库,Spring Data也是Spring中与Spring Boot、Spring Cloud等齐名的知名项目。
Spring Data官网: https://spring.io/projects/spring-data
数据库相关的启动器:可以参考官方文档: https://docs.spring.io/spring-boot/docs/2.1.7.RELEASE/reference/htmlsingle/#using-boot-starter
pom.xml
<dependencies> <dependency> <groupId>org.springframework.bootgroupId> <artifactId>spring-boot-starter-webartifactId> dependency> <dependency> <groupId>org.springframework.bootgroupId> <artifactId>spring-boot-starter-jdbcartifactId> dependency> <dependency> <groupId>mysqlgroupId> <artifactId>mysql-connector-javaartifactId> <scope>runtimescope> dependency> <dependency> <groupId>org.springframework.bootgroupId> <artifactId>spring-boot-starter-testartifactId> <scope>testscope> dependency>dependencies>
test
@SpringBootTestclass KsBoot04DataApplicationTests { @Autowired DataSource dataSource; @Test void contextLoads() throws SQLException { //查看一下默认的数据源:class com.zaxxer.hikari.HikariDataSource System.out.println(dataSource.getClass()); //获得数据库连接 Connection connection = dataSource.getConnection(); System.out.println(connection); //xxxx Template : SpringBoot已经配置好模板bean,拿来即用 CRUD //关闭 connection.close(); }}
application.yaml
spring: datasource: username: root password: root #假如时区报错了,就增加一个时区的配置就ok了 serverTimezone=UTC url: jdbc:mysql://localhost:3306/ceshi?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8 driver-class-name: com.mysql.cj.jdbc.Driver
controller
@RestControllerpublic class JDBCController { @Autowired JdbcTemplate jdbcTemplate; //查询数据库的所有信息 //没有实体类,数据库中的东西,怎么获取?Map @GetMapping("/userList") public List
运行
引入Druid官网: https://mvnrepository.com/artifact/com.alibaba/druid
Hikari和Druid都是当前Java Web上最优秀的数据源。HikariDataSource号称Java Web当前速度最快的数据源,相比于传统的C3P0、DBCP、Tomcat jdbc等连接池更加优秀。Druid可以更好的监控DB池连接和SQL的执行情况。
<dependency> <groupId>com.alibabagroupId> <artifactId>druidartifactId> <version>1.2.3version>dependency>
spring: datasource: username: root password: root #假如时区报错了,就增加一个时区的配置就ok了 serverTimezone=UTC url: jdbc:mysql://localhost:3306/ceshi?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8 driver-class-name: com.mysql.cj.jdbc.Driver type: com.alibaba.druid.pool.DruidDataSource
#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
@Configurationpublic class DruidConfig { @ConfigurationProperties(prefix = "spring.datasource") @Bean public DataSource druidDataSource(){ return new DruidDataSource(); } //后台监控 : web.xml,ServletRegistrationBean //因为Spring Boot内置了servlet容器,所以没有web.xml,替代方法:ServletRegistrationBean @Bean public ServletRegistrationBean statViewServlet(){ ServletRegistrationBean bean=new ServletRegistrationBean<>(new StatViewServlet(),"/druid/*"); //后台需要有人登录,账号密码配置 HashMap initParameters=new HashMap<>(); //增加配置 initParameters.put("loginUsername","admin");//登录key 是固定的 loginUsername loginPassword initParameters.put("loginPassword","admin"); //允许谁可以访问 initParameters.put("allow",""); //禁止谁能访问 initParameters.put("xiaokeai","192.168.13.14"); bean.setInitParameters(initParameters);//设置初始化参数 return bean; } //filter @Bean public FilterRegistrationBean webStatFilter(){ FilterRegistrationBean bean = new FilterRegistrationBean<>(); bean.setFilter(new WebStatFilter()); //可与过滤哪些请求呢? HashMap initParameters = new HashMap<>(); //这些东西不进行统计 initParameters.put("exclusions","*.js,*.css,/druid/*"); bean.setInitParameters(initParameters); return bean; }}
登录成功后:
整合包
mybatis-spring-boot-starter
mybatis-spring-boot-starter官方文档
M:数据和业务
C:交接
V:HTML
<dependencies> <dependency> <groupId>org.mybatis.spring.bootgroupId> <artifactId>mybatis-spring-boot-starterartifactId> <version>2.2.0version> dependency> <dependency> <groupId>org.projectlombokgroupId> <artifactId>lombokartifactId> dependency> <dependency> <groupId>org.springframework.bootgroupId> <artifactId>spring-boot-starter-jdbcartifactId> dependency> <dependency> <groupId>org.springframework.bootgroupId> <artifactId>spring-boot-starter-webartifactId> dependency> <dependency> <groupId>mysqlgroupId> <artifactId>mysql-connector-javaartifactId> <scope>runtimescope> dependency> <dependency> <groupId>org.springframework.bootgroupId> <artifactId>spring-boot-starter-testartifactId> <scope>testscope> dependency>dependencies>
spring.datasource.username=rootspring.datasource.password=rootspring.datasource.url=jdbc:mysql://localhost:3306/ceshi?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
@SpringBootTestclass KsBoot05MybatisApplicationTests { @Autowired DataSource dataSource; @Test void contextLoads() throws SQLException { System.out.println(dataSource.getClass()); System.out.println(dataSource.getConnection()); }}
@Data@AllArgsConstructor@NoArgsConstructorpublic class User { private int id; private String username; private String password;}
# 整合mybatismybatis.type-aliases-package=com.kuang.pojomybatis.mapper-locations=classpath:mybatis/mapper/*.xml
mapper接口:
//这个注解表示了这是一个 mybatis 的 mapper类;@Mapper//方式一 //方式二 在主程序上加这个@MapperScan("com.kuang.mapper")@Repositorypublic interface UserMapper { List queryUserList(); User queryUserById(Integer id); int addUser(User user); int updateUser(User user); int deleteUser(Integer id);}
方式二所写位置:
@SpringBootApplication//@MapperScan("com.kuang.mapper")方式二public class KsBoot05MybatisApplication { public static void main(String[] args) { SpringApplication.run(KsBoot05MybatisApplication.class, args); }}
mapper.xml:
DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.kuang.mapper.UserMapper"> <select id="queryUserList" resultType="User"> select * from user select> <select id="queryUserById" resultType="User"> select * from user where id=#{id} select> <insert id="addUser" parameterType="User"> insert into user(username,password) values (#{username},#{password}) insert> <update id="updateUser" parameterType="User"> update user set username=#{username},password=#{password} where id=#{id} update> <delete id="deleteUser" parameterType="int"> delete from user where id=#{id} delete>mapper>
@RestControllerpublic class UserController { @Autowired private UserMapper userMapper; @GetMapping("/queryUserList") public List<User> queryUserList(){ List<User> users = userMapper.queryUserList(); return users; } @GetMapping("/queryUserById/{id}") public User queryUserById(@PathVariable("id") int id){ User user = userMapper.queryUserById(id); return user; } @GetMapping("/addUser") public String addUser(){ userMapper.addUser(new User(3,"quest","quest")); return "ok"; } @GetMapping("/updateUser") public String updateUser(){ userMapper.updateUser(new User(3,"测试","quest")); return "ok"; } @GetMapping("/deleteUser") public String deleteUser(){ userMapper.deleteUser(3); return "ok"; }}
在web开发中,安全第一位!过滤器,拦截器~
功能性需求:否
做网站:安全应该在什么时候考虑?设计之初!
shiro、SpringSecurity:很像~除了类不一样,名字不一样;
认证,授权(vip1,vip2,vip3)
AOP:横切~ 配置类~
Spring Security是针对Spring项目的安全框架,也是Spring Boot底层安全模块默认的技术选型,他可以实现强大的Web安全控制,对于安全控制,我们仅需要引入 spring-boot-starter-security 模块,进行少量的配置,即可实现强大的安全管理!
记住几个类:
Spring Security的两个主要目标是“认证”和“授权”(访问控制)。
“认证”(Authentication)
“授权”(Authorization)
这个概念是通用的,而不是只在Spring Security中存在。
Spring Security官网: https://spring.io/projects/spring-security
Spring Security对应的帮助文档: https://docs.spring.io/spring-security/site/docs/5.2.0.RELEASE/reference/htmlsingle/
<dependencies> <dependency> <groupId>org.springframework.bootgroupId> <artifactId>spring-boot-starter-securityartifactId> dependency> <dependency> <groupId>org.springframework.bootgroupId> <artifactId>spring-boot-starter-webartifactId> dependency> <dependency> <groupId>org.thymeleafgroupId> <artifactId>thymeleaf-spring5artifactId> dependency> <dependency> <groupId>org.thymeleaf.extrasgroupId> <artifactId>thymeleaf-extras-java8timeartifactId> dependency> <dependency> <groupId>org.springframework.bootgroupId> <artifactId>spring-boot-starter-testartifactId> <scope>testscope> dependency>dependencies>
# 关闭模板缓存spring.thymeleaf.cache=false
@Controllerpublic class RouterController { @RequestMapping({"/","/index"}) public String index(){ return "index"; } @RequestMapping("toLogin") public String toLogin(){ return "views/login"; } @RequestMapping("/level1/{id}") public String level1(@PathVariable("id") int id){ return "views/level1/"+id; } @RequestMapping("/level2/{id}") public String level2(@PathVariable("id") int id){ return "views/level2/"+id; } @RequestMapping("/level3/{id}") public String level3(@PathVariable("id") int id){ return "views/level3/"+id; }}
@EnableWebSecuritypublic 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"); //没有权限默认会到登录页面,需要开启登录的页面 //Login http.formLogin(); } //认证 spring boot 2.1.X 可以直接使用 //密码编码:PasswordEncoder //在Spring Security 5.0+ 新增了很多的加密方式~ //解决方式: // .passwordEncoder(new BCryptPasswordEncoder()) //.password(new BCryptPasswordEncoder().encode("root")) @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { //这些数据正常应该从数据库中读 auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder()) .withUser("admin").password(new BCryptPasswordEncoder().encode("admin")).roles("vip1","vip2","vip3") .and() .withUser("root").password(new BCryptPasswordEncoder().encode("root")).roles("vip1","vip2") .and() .withUser("quest").password(new BCryptPasswordEncoder().encode("quest")).roles("vip1"); }}
congfig中写功能(http.logout().logoutSuccessUrl("/");)
@EnableWebSecuritypublic 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"); //没有权限默认会到登录页面,需要开启登录的页面 //Login http.formLogin(); //注销,开启了注销功能 http.logout().logoutSuccessUrl("/"); }}
所在位置:
html
<a class="item" th:href="@{/logout}"> <i class="sign-out icon">i> 注销a>
由于thymeleaf-extras-springsecurity4只支持spring boot2.0.9版本和spring boot2.0.7版本,这两个版本是支持里面最好使的两版本。不满足spring boot2.5.1版本。可能不满足spring boot2.0.9版本以上。
我用的spring boot2.5.1版本,比上面两个版本样式好看多了,thymeleaf-extras-springsecurity5满足支持spring boot2.5.1版本。【推荐使用】
<dependency> <groupId>org.thymeleaf.extrasgroupId> <artifactId>thymeleaf-extras-springsecurity5artifactId> <version>3.0.4.RELEASEversion>dependency>
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
如果输入sec:authorize="!isAuthenticated()"不提示,可以ctrl+F9。
sec:authorize="!isAuthenticated()"判断登录状态(登录或未登录)
sec:authentication="name"获取用户名
sec:authentication="principal.authorities"获取用户包含的角色
<div class="right menu"> <div sec:authorize="!isAuthenticated()"> <a class="item" th:href="@{/toLogin}"> <i class="address card icon">i> 登录 a> div> <div sec:authorize="isAuthenticated()"> <a class="item"> 用户名:<span sec:authentication="name">span> 角色: <span sec:authentication="principal.authorities">span> a> div> <div sec:authorize="isAuthenticated()"> <a class="item" th:href="@{/logout}"> <i class="sign-out icon">i> 注销 a> div>div>
问题:注销注销不了,报错
解决方案:加这个 http.csrf().disable();//关闭csrf功能。 登录失败可能存在的原因~
@EnableWebSecuritypublic 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"); //没有权限默认会到登录页面,需要开启登录的页面 //Login http.formLogin(); //注销,开启了注销功能 //防止网站攻击 get,post http.csrf().disable();//关闭csrf功能。 注销失败可能存在的原因~ http.logout().logoutSuccessUrl("/"); }}
sec:authorize=“hasRole(‘vip1’)” 根据判断用户所具备的角色判断所展示对应的页面
<div class="ui three column stackable grid"> <div class="column" sec:authorize="hasRole('vip1')"> <div class="ui raised segment"> <div class="ui"> <div class="content"> <h5 class="content">Level 1h5> <hr> <div><a th:href="@{/level1/1}"><i class="bullhorn icon">i> Level-1-1a>div> <div><a th:href="@{/level1/2}"><i class="bullhorn icon">i> Level-1-2a>div> <div><a th:href="@{/level1/3}"><i class="bullhorn icon">i> Level-1-3a>div> div> div> div> div> <div class="column" sec:authorize="hasRole('vip2')"> <div class="ui raised segment"> <div class="ui"> <div class="content"> <h5 class="content">Level 2h5> <hr> <div><a th:href="@{/level2/1}"><i class="bullhorn icon">i> Level-2-1a>div> <div><a th:href="@{/level2/2}"><i class="bullhorn icon">i> Level-2-2a>div> <div><a th:href="@{/level2/3}"><i class="bullhorn icon">i> Level-2-3a>div> div> div> div> div> <div class="column" sec:authorize="hasRole('vip3')"> <div class="ui raised segment"> <div class="ui"> <div class="content"> <h5 class="content">Level 3h5> <hr> <div><a th:href="@{/level3/1}"><i class="bullhorn icon">i> Level-3-1a>div> <div><a th:href="@{/level3/2}"><i class="bullhorn icon">i> Level-3-2a>div> <div><a th:href="@{/level3/3}"><i class="bullhorn icon">i> Level-3-3a>div> div> div> div> div>div>
@EnableWebSecuritypublic 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"); //没有权限默认会到登录页面,需要开启登录的页面 //Login http.formLogin(); //注销,开启了注销功能 //防止网站攻击 get,post http.csrf().disable();//关闭csrf功能。 注销失败可能存在的原因~ http.logout().logoutSuccessUrl("/"); //开启记住我功能 本质上是cookie,默认保存两周 http.rememberMe(); }}
config:【 http.rememberMe().rememberMeParameter(“remember”);】
@EnableWebSecuritypublic 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"); //没有权限默认会到登录页面,需要开启登录的页面 //Login //默认是login页面,定制登录页.loginPage("/toLogin"),toLogin我们写的页面 //如果login登录页面用户名的name值是username,密码password的name值是就不用写.usernameParameter("xxx").passwordParameter("xxx") //如果login登录页面用户名自定义name值是user,密码自定义name值是pwd就需要写.usernameParameter("user").passwordParameter("pwd") //登录成功from跳转路径如果与.loginPage("/toLogin")里的值对应就不用写.loginProcessingUrl("/xxx"),不对应则写对应的from表单提交的地址.loginProcessingUrl("/login")。 http.formLogin().loginPage("/toLogin").usernameParameter("user").passwordParameter("pwd").loginProcessingUrl("/login"); //注销,开启了注销功能 //防止网站攻击 get,post http.csrf().disable();//关闭csrf功能。 注销失败可能存在的原因~ http.logout().logoutSuccessUrl("/"); //开启记住我功能 本质上是cookie,默认保存两周。自定义接受前端的参数.rememberMeParameter("remember") http.rememberMe().rememberMeParameter("remember"); }}
html:【记住我】
<div class="field"> <input type="checkbox" name="remember">记住我div>
config: 【http.formLogin().loginPage("/toLogin");】
@EnableWebSecuritypublic 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"); //没有权限默认会到登录页面,需要开启登录的页面 //Login //默认是login页面,定制登录页.loginPage("/toLogin"),toLogin我们写的页面 http.formLogin().loginPage("/toLogin"); //注销,开启了注销功能 //防止网站攻击 get,post http.csrf().disable();//关闭csrf功能。 注销失败可能存在的原因~ http.logout().logoutSuccessUrl("/"); //开启记住我功能 本质上是cookie,默认保存两周 http.rememberMe(); }}
html:【】
<form th:action="@{/toLogin}" method="post"> <div class="field"> <label>Usernamelabel> <div class="ui left icon input"> <input type="text" placeholder="Username" name="username"> <i class="user icon">i> div> div> <div class="field"> <label>Passwordlabel> <div class="ui left icon input"> <input type="password" name="password"> <i class="lock icon">i> div> div> <input type="submit" class="ui blue submit button"/>form>
如果login登录页面用户名的name值是username,密码password的name值是就不用写.usernameParameter("xxx").passwordParameter("xxx")如果login登录页面用户名自定义name值是user,密码自定义name值是pwd就需要写.usernameParameter("user").passwordParameter("pwd")登录成功from跳转路径如果与.loginPage("/toLogin")里的值对应就不用写.loginProcessingUrl("/xxx"),不对应则写对应的from表单提交的地址.loginProcessingUrl("/login")。
config: 【 http.formLogin().loginPage("/toLogin").usernameParameter(“user”).passwordParameter(“pwd”).loginProcessingUrl("/login");】
@EnableWebSecuritypublic 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"); //没有权限默认会到登录页面,需要开启登录的页面 //Login //默认是login页面,定制登录页.loginPage("/toLogin"),toLogin我们写的页面 //如果login登录页面用户名的name值是username,密码password的name值是就不用写.usernameParameter("xxx").passwordParameter("xxx") //如果login登录页面用户名自定义name值是user,密码自定义name值是pwd就需要写.usernameParameter("user").passwordParameter("pwd") //登录成功from跳转路径如果与.loginPage("/toLogin")里的值对应就不用写.loginProcessingUrl("/xxx"),不对应则写对应的from表单提交的地址.loginProcessingUrl("/login")。 http.formLogin().loginPage("/toLogin").usernameParameter("user").passwordParameter("pwd").loginProcessingUrl("/login"); //注销,开启了注销功能 //防止网站攻击 get,post http.csrf().disable();//关闭csrf功能。 注销失败可能存在的原因~ http.logout().logoutSuccessUrl("/"); //开启记住我功能 本质上是cookie,默认保存两周 http.rememberMe(); }}
html【、、】
<form th:action="@{/login}" method="post"> <div class="field"> <label>Usernamelabel> <div class="ui left icon input"> <input type="text" placeholder="Username" name="user"> <i class="user icon">i> div> div> <div class="field"> <label>Passwordlabel> <div class="ui left icon input"> <input type="password" name="pwd"> <i class="lock icon">i> div> div> <input type="submit" class="ui blue submit button"/>form>
shiro依赖包网址: https://github.com/apache/shiro/blob/main/samples/quickstart/pom.xml
<dependencies> <dependency> <groupId>org.apache.shirogroupId> <artifactId>shiro-coreartifactId> <version>1.7.1version> dependency> <dependency> <groupId>org.slf4jgroupId> <artifactId>jcl-over-slf4jartifactId> <version>1.7.21version> dependency> <dependency> <groupId>org.slf4jgroupId> <artifactId>slf4j-log4j12artifactId> <version>1.7.21version> dependency> <dependency> <groupId>log4jgroupId> <artifactId>log4jartifactId> <version>1.2.17version> dependency>dependencies>
log4j.rootLogger=INFO, stdoutlog4j.appender.stdout=org.apache.log4j.ConsoleAppenderlog4j.appender.stdout.layout=org.apache.log4j.PatternLayoutlog4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m %n# General Apache librarieslog4j.logger.org.apache=WARN# Springlog4j.logger.org.springframework=WARN# Default Shiro logginglog4j.logger.org.apache.shiro=INFO# Disable verbose logginglog4j.logger.org.apache.shiro.util.ThreadContext=WARNlog4j.logger.org.apache.shiro.cache.ehcache.EhCache=WARN
首先安装ini插件:
shiro.ini:
[users]# user 'root' with password 'secret' and the 'admin' roleroot = secret, admin# user 'guest' with the password 'guest' and the 'guest' roleguest = guest, guest# user 'presidentskroob' with password '12345' ("That's the same combination on# my luggage!!!" ;)), and role 'president'presidentskroob = 12345, president# user 'darkhelmet' with password 'ludicrousspeed' and roles 'darklord' and 'schwartz'darkhelmet = ludicrousspeed, darklord, schwartz# user 'lonestarr' with password 'vespa' and roles 'goodguy' and 'schwartz'lonestarr = vespa, goodguy, schwartz# -----------------------------------------------------------------------------# Roles with assigned permissions## Each line conforms to the format defined in the# org.apache.shiro.realm.text.TextConfigurationRealm#setRoleDefinitions JavaDoc# -----------------------------------------------------------------------------[roles]# 'admin' role has all permissions, indicated by the wildcard '*'admin = *# The 'schwartz' role can do anything (*) with any lightsaber:schwartz = lightsaber:*# The 'goodguy' role is allowed to 'drive' (action) the winnebago (type) with# license plate 'eagle5' (instance specific id)goodguy = winnebago:drive:eagle5
所在位置:
log4j和shiro.ini查找网址:
网址找的报错,把爆红灰的import删了,重新导入即可
import org.apache.shiro.SecurityUtils;import org.apache.shiro.authc.*;import org.apache.shiro.config.IniSecurityManagerFactory;import org.apache.shiro.mgt.SecurityManager;import org.apache.shiro.session.Session;import org.apache.shiro.subject.Subject;import org.apache.shiro.util.Factory;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) { // The easiest way to create a Shiro SecurityManager with configured // realms, users, roles and permissions is to use the simple INI config. // We'll do that by using a factory that can ingest a .ini file and // return a SecurityManager instance: // Use the shiro.ini file at the root of the classpath // (file: and url: prefixes load from files and urls respectively): Factory factory = new IniSecurityManagerFactory("classpath:shiro.ini"); SecurityManager securityManager = factory.getInstance(); // for this simple example quickstart, make the SecurityManager // accessible as a JVM singleton. Most applications wouldn't do this // and instead rely on their container configuration or web.xml for // webapps. That is outside the scope of this simple quickstart, so // we'll just do the bare minimum so you can continue to get a feel // for things. SecurityUtils.setSecurityManager(securityManager); // Now that a simple Shiro environment is set up, let's see what you can do: // get the currently executing user: Subject currentUser = SecurityUtils.getSubject(); // Do some stuff with a Session (no need for a web or EJB container!!!) 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 + "]"); } // let's login the current user so we can check against roles and permissions: if (!currentUser.isAuthenticated()) { 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) { 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); }}
所在位置:
Quickstart类查找网址:
Subject currentUser = SecurityUtils.getSubject();//获得SubjectSession session = currentUser.getSession();//拿到sessioncurrentUser.isAuthenticated()//是否被认证currentUser.getPrincipal()//获取当前用户的认证currentUser.hasRole("schwartz")//这个用户是否拥有什么角色currentUser.isPermitted("lightsaber:wield")//根据当前用户获取一些权限currentUser.logout();//注销
Quickstart类分析:
import org.apache.shiro.SecurityUtils;import org.apache.shiro.authc.*;import org.apache.shiro.config.IniSecurityManagerFactory;import org.apache.shiro.mgt.SecurityManager;import org.apache.shiro.session.Session;import org.apache.shiro.subject.Subject;import org.apache.shiro.util.Factory;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) { Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini"); SecurityManager securityManager = factory.getInstance(); SecurityUtils.setSecurityManager(securityManager); // get the currently executing user: //获取当前的用户对象Subject Subject currentUser = SecurityUtils.getSubject(); //通过当前用户拿到Session Session session = currentUser.getSession(); session.setAttribute("someKey", "aValue"); String value = (String) session.getAttribute("someKey"); if (value.equals("aValue")) { log.info("Subject=>session[" + 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) { 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); }}
<dependency> <groupId>org.apache.shirogroupId> <artifactId>shiro-springartifactId> <version>1.7.1version>dependency><dependency> <groupId>org.springframework.bootgroupId> <artifactId>spring-boot-starter-webartifactId>dependency><dependency> <groupId>org.thymeleafgroupId> <artifactId>thymeleaf-spring5artifactId>dependency><dependency> <groupId>org.thymeleaf.extrasgroupId> <artifactId>thymeleaf-extras-java8timeartifactId>dependency>
ShiroConfig:
@Configurationpublic class ShiroConfig { //ShiroFilterFactoryBean:3 @Bean public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager){ ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean(); //设置安全管理器 bean.setSecurityManager(defaultWebSecurityManager); return bean; } //DefaultWebSecurityManager:2 @Bean(name = "securityManager") public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm){ DefaultWebSecurityManager securityManager=new DefaultWebSecurityManager(); //关联UserRealm securityManager.setRealm(userRealm); return securityManager; } //创建realm对象,需要自定义类:1 @Bean public UserRealm userRealm(){ return new UserRealm(); }}
UserRealm:
//自定义的 UserRealm extends AuthorizingRealmpublic class UserRealm extends AuthorizingRealm { //授权 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { System.out.println("执行了=>授权doGetAuthorizationInfo"); return null; } //认证 @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { System.out.println("执行了=>认证doGetAuthorizationInfo"); return null; }}
index.html:
DOCTYPE html><html lang="en" xmlns:th="http://www.thymeleaf.org"><head> <meta charset="UTF-8"> <title>Titletitle>head><body> <h1>首页h1> <p th:text="${msg}">p> <hr/><a th:href="@{/user/add}">adda> | <a th:href="@{/user/update}">updatea>body>html>
user/add.html
DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>Titletitle>head><body><h1>addh1>body>html>
user/update.html
DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>Titletitle>head><body><h1>updateh1>body>html>
@Controllerpublic 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"; }}
DOCTYPE html><html lang="en" xmlns:th="http://www.thymeleaf.org"><head> <meta charset="UTF-8"> <title>Titletitle>head><body><h1>登录h1><form action=""> <p>用户名:<input type="text" name="username">p> <p>密码:<input type="password" name="username">p> <p><input type="submit">p>form>body>html>
@Controllerpublic class MyController { ……………………(省略前面与本主题无关的代码,前面写的代码) @RequestMapping("/toLogin") public String toLogin(){ return "login"; }}
下面核心代码提取:
//登录拦截Map filterMap = new LinkedHashMap<>();filterMap.put("/user/*","authc");bean.setFilterChainDefinitionMap(filterMap);//设置登录的请求bean.setLoginUrl("/toLogin");
全:
@Configurationpublic class ShiroConfig { //ShiroFilterFactoryBean:3 @Bean public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager){ ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean(); //设置安全管理器 bean.setSecurityManager(defaultWebSecurityManager); //添加shiro的内置过滤器 /* anon:无需认证就可以访问 authc:必须认证了才能访问 user:必须拥有 记住我 功能才能用 perms:拥有对某个资源的权限才能访问 role:拥有某个角色权限才能访问// filterMap.put("/user/add","authc");// filterMap.put("/user/update","authc"); */ //登录拦截 Map filterMap = new LinkedHashMap<>(); filterMap.put("/user/*","authc"); bean.setFilterChainDefinitionMap(filterMap); //设置登录的请求 bean.setLoginUrl("/toLogin"); return bean; } ……………………(省略后面与本主题无关的代码,前面写的代码) }
@Controllerpublic class MyController { ……………………(省略前面与本主题无关的代码,前面写的代码) @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"; } }}
<!DOCTYPE html><html lang="en" xmlns:th="http://www.thymeleaf.org"><head> <meta charset="UTF-8"> <title>Title</title></head><body><h1>登录</h1><hr/><p th:text="${msg}" style="color: red;"></p><form th:action="@{/login}"> <p>用户名:<input type="text" name="username"></p> <p>密码:<input type="text" name="password"></p> <p><input type="submit"></p></form></body></html>
//自定义的 UserRealm extends AuthorizingRealmpublic class UserRealm extends AuthorizingRealm { //授权 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { System.out.println("执行了=>授权doGetAuthorizationInfo"); return null; } //认证 @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { System.out.println("执行了=>认证doGetAuthorizationInfo"); //用户名,密码~ 数据中取 String name="root"; String password="123"; UsernamePasswordToken userToken = (UsernamePasswordToken) token; if (!userToken.getUsername().equals(name)){ return null;//抛出异常 UnknownAccountException } //密码认证,shiro做~ return new SimpleAuthenticationInfo("",password,""); }}
<dependencies> <dependency> <groupId>mysqlgroupId> <artifactId>mysql-connector-javaartifactId> dependency> <dependency> <groupId>log4jgroupId> <artifactId>log4jartifactId> <version>1.2.17version> dependency> <dependency> <groupId>com.alibabagroupId> <artifactId>druidartifactId> <version>1.2.3version> dependency> <dependency> <groupId>org.mybatis.spring.bootgroupId> <artifactId>mybatis-spring-boot-starterartifactId> <version>2.2.0version> dependency> <dependency> <groupId>org.projectlombokgroupId> <artifactId>lombokartifactId> <version>1.18.20version> dependency> <dependency> <groupId>org.apache.shirogroupId> <artifactId>shiro-springartifactId> <version>1.7.1version> dependency> <dependency> <groupId>org.springframework.bootgroupId> <artifactId>spring-boot-starter-webartifactId> dependency> <dependency> <groupId>org.thymeleafgroupId> <artifactId>thymeleaf-spring5artifactId> dependency> <dependency> <groupId>org.thymeleaf.extrasgroupId> <artifactId>thymeleaf-extras-java8timeartifactId> dependency> <dependency> <groupId>org.springframework.bootgroupId> <artifactId>spring-boot-starter-testartifactId> <scope>testscope> dependency>dependencies>
spring: datasource: username: root password: root #假如时区报错了,就增加一个时区的配置就ok了 serverTimezone=UTC url: jdbc:mysql://localhost:3306/ceshi?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8 driver-class-name: com.mysql.cj.jdbc.Driver type: com.alibaba.druid.pool.DruidDataSource #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
mybatis.type-aliases-package=com.kuang.pojomybatis.mapper-locations=classpath:mapper/*.xml
@Data@AllArgsConstructor@NoArgsConstructorpublic class User { private int id; private String username; private String password;}
UserMapper接口:
@Repository@Mapperpublic interface UserMapper { public User queryUserByName(String username);}
UserMapper.xml:
DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.kuang.mapper.UserMapper"> <select id="queryUserByName" parameterType="String" resultType="User"> select * from ceshi.user where username=#{username} select>mapper>
UserService接口:
public interface UserService { public User queryUserByName(String username);}
UserServiceImpl实现类:
@Servicepublic class UserServiceImpl implements UserService { @Autowired UserMapper userMapper; @Override public User queryUserByName(String username) { return userMapper.queryUserByName(username); }}
@SpringBootTestclass ShiroSpringbootApplicationTests { @Autowired UserServiceImpl userService; @Test void contextLoads() { System.out.println(userService.queryUserByName("root")); }}
config/UserRealm类
//自定义的 UserRealm extends AuthorizingRealmpublic class UserRealm extends AuthorizingRealm { @Autowired//自定义的 UserRealm extends AuthorizingRealmpublic class UserRealm extends AuthorizingRealm { @Autowired UserService userService; //授权 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { System.out.println("执行了=>授权doGetAuthorizationInfo"); return null; } //认证 @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {// System.out.println("执行了=>认证doGetAuthorizationInfo"); //用户名,密码~ 数据中取// String name="root";// String password="123"; UsernamePasswordToken userToken = (UsernamePasswordToken) token; if (!userToken.getUsername().equals(name)){// return null;//抛出异常 UnknownAccountException// } //密码认证,shiro做~// return new SimpleAuthenticationInfo("",password,""); UsernamePasswordToken userToken = (UsernamePasswordToken) token; //连接真实的数据库 User user=userService.queryUserByName(userToken.getUsername()); if (user==null){//没有这个人 return null; //UnknownAccountException } //可以加密: MD5 MD5盐值加密 //密码认证,shiro做~ return new SimpleAuthenticationInfo("",user.getPassword(),""); }}
@Configurationpublic class ShiroConfig { //ShiroFilterFactoryBean:3 @Bean public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager){ ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean(); //设置安全管理器 bean.setSecurityManager(defaultWebSecurityManager); //添加shiro的内置过滤器 /* anon:无需认证就可以访问 authc:必须认证了才能访问 user:必须拥有 记住我 功能才能用 perms:拥有对某个资源的权限才能访问 role:拥有某个角色权限才能访问// filterMap.put("/user/add","authc");// filterMap.put("/user/update","authc"); */ //登录拦截 Map filterMap = new LinkedHashMap<>(); //授权,正常的情况下,没有授权会跳转到未授权页面 filterMap.put("/user/add","perms[user:add]"); filterMap.put("/user/update","perms[user:update]"); filterMap.put("/user/*","authc"); bean.setFilterChainDefinitionMap(filterMap); //设置登录的请求 bean.setLoginUrl("/toLogin"); //未授权页面 bean.setUnauthorizedUrl("/noauth"); return bean; } //DefaultWebSecurityManager:2 @Bean(name = "securityManager") public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm){ DefaultWebSecurityManager securityManager=new DefaultWebSecurityManager(); //关联UserRealm securityManager.setRealm(userRealm); return securityManager; } //创建realm对象,需要自定义类:1 @Bean public UserRealm userRealm(){ return new UserRealm(); }}
//自定义的 UserRealm extends AuthorizingRealmpublic class UserRealm extends AuthorizingRealm { @Autowired UserService userService; //授权 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { System.out.println("执行了=>授权doGetAuthorizationInfo"); //SimpleAuthorizationInfo SimpleAuthorizationInfo info=new SimpleAuthorizationInfo(); //添加权限 info.addStringPermission("user:add"); //拿到当前登录的这个对象 Subject subject = SecurityUtils.getSubject(); User currentUser = (User) subject.getPrincipal();//拿到User对象 //设置当前用户的权限 info.addStringPermission(currentUser.getPerms()); //return info; return info; } //认证 @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {// System.out.println("执行了=>认证doGetAuthorizationInfo"); //用户名,密码~ 数据中取// String name="root";// String password="123"; UsernamePasswordToken userToken = (UsernamePasswordToken) token; if (!userToken.getUsername().equals(name)){// return null;//抛出异常 UnknownAccountException// } //密码认证,shiro做~// return new SimpleAuthenticationInfo("",password,""); UsernamePasswordToken userToken = (UsernamePasswordToken) token; //连接真实的数据库 User user=userService.queryUserByName(userToken.getUsername()); if (user==null){//没有这个人 return null; //UnknownAccountException } //可以加密: MD5 MD5盐值加密 //密码认证,shiro做~ return new SimpleAuthenticationInfo(user,user.getPassword(),""); }}
@Data@AllArgsConstructor@NoArgsConstructorpublic class User { private int id; private String username; private String password; private String perms;}
config:新增注销访问和未授权访问
@Controllerpublic 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"; } } @RequestMapping("/noauth") @ResponseBody public String unauthorized(){ return "未经授权无法访问此页面"; } @RequestMapping("/logout") public String logout(){ //获取当前用的用户 Subject subject = SecurityUtils.getSubject(); //注销 subject.logout(); return "index"; }}
DOCTYPE html><html lang="en" xmlns:th="http://www.thymeleaf.org"><head> <meta charset="UTF-8"> <title>Titletitle>head><body> <h1>首页h1> <p th:text="${msg}">p> <hr/><a th:href="@{/user/add}">adda> | <a th:href="@{/user/update}">updatea><a class="item" th:href="@{/logout}"> <i class="sign-out icon">i> 注销a>body>html>
在上面7里pom.xml基础上,新增一个依赖包。
<dependency> <groupId>com.github.theborakompanionigroupId> <artifactId>thymeleaf-extras-shiroartifactId> <version>2.0.0version>dependency>
@Configurationpublic class ShiroConfig { ……………………(省略后面与本主题无关的代码,前面写的代码) //整合ShiroDialect:用老整合 shiro Thymeleaf @Bean public ShiroDialect getShiroDialect(){ return new ShiroDialect(); }}
重点: Subject currentSubject=SecurityUtils.getSubject();
Session session = currentSubject.getSession();
session.setAttribute(“loginUser”,user);
//自定义的 UserRealm extends AuthorizingRealmpublic class UserRealm extends AuthorizingRealm { @Autowired UserService userService; ……………………授权代码(省略后面与本主题无关的代码,前面写的代码) //认证 @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { UsernamePasswordToken userToken = (UsernamePasswordToken) token; //连接真实的数据库 User user=userService.queryUserByName(userToken.getUsername()); if (user==null){//没有这个人 return null; //UnknownAccountException } Subject currentSubject=SecurityUtils.getSubject(); Session session = currentSubject.getSession(); session.setAttribute("loginUser",user); //可以加密: MD5 MD5盐值加密 //密码认证,shiro做~ return new SimpleAuthenticationInfo(user,user.getPassword(),""); }}
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
DOCTYPE html><html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro"><head> <meta charset="UTF-8"> <title>Titletitle>head><body> <h1>首页h1> <div shiro:notAuthenticated="session.loginUser"> <a th:href="@{/toLogin}">登录a> div> <div shiro:user="session.loginUser"> <a th:href="@{/logout}">注销a> div> <p th:text="${msg}">p> <hr/> <div shiro:hasPermission="user:add"> <a th:href="@{/user/add}">adda> div> <div shiro:hasPermission="user:update"> <a th:href="@{/user/update}">updatea> div>body>html>
开源项目: https://github.com/WinterChenS/my-site?tdsourcetag=s_pcqq_aiomsg
前后端分离
Vue+SpringBoot
后端时代:前端只用管理静态页面;html==>后端。模板引擎 JSP==>后端是主力
前后端分离式时代:
产生一个问题:
解决方案:
swagger官网: https://swagger.io/
在项目使用Swagger需要springbox;
<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>
问题:swagger2和swagger-ui依赖包版本为3.0.0可能变动了,http://localhost:8080/swagger-ui.html访问不了
解决图:
@RestControllerpublic class HelloController { @RequestMapping("/hello") public String hello(){ return "hello"; }}
@Configuration@EnableSwagger2 //开启Swagger2public class SwaggerConfig { }
访问网址: http://localhost:8080/swagger-ui.html
@Configuration@EnableSwagger2 //开启Swagger2public class SwaggerConfig { //配置了Swagger的Docket的bean实例 @Bean public Docket docket(){ return new Docket(DocumentationType.SWAGGER_2) .apiInfo(apiInfo()); } //配置swagger信息=apiInfo private ApiInfo apiInfo(){ Contact contact = new Contact("灵语日记", "http://1.15.235.248:8080/h", "[email protected]"); return new ApiInfo("风久的SwaggerAPI文档", "相信明天会更好", "v1.0", "http://1.15.235.248:8080/h", contact, "Apache 2.0", "http://www.apache.org/licenses/LICENSE-2.0", new ArrayList() ); }}
Docket.select()
@Configuration@EnableSwagger2 //开启Swagger2public class SwaggerConfig { //配置了Swagger的Docket的bean实例 @Bean public Docket docket(){ return new Docket(DocumentationType.SWAGGER_2) .apiInfo(apiInfo()) .select() //RequestHandlerSelectors:配置要扫描接口的方式 //basePackage():指定要扫描的包 //any():扫描全部 //none():不扫描 //withClassAnnotation:扫描类上的注解,参数是一个注解的反射对象 //withMethodAnnotation:扫描方法上的注解 .apis(RequestHandlerSelectors.basePackage("com.kuang.controller")) //paths():过滤什么路径 .paths(PathSelectors.ant("/kuang/**")) .build(); } ……………………(省略后面与本主题无关的代码,此为前面写的代码) }
.enable(false)
@Configuration@EnableSwagger2 //开启Swagger2public class SwaggerConfig { //配置了Swagger的Docket的bean实例 @Bean public Docket docket(){ return new Docket(DocumentationType.SWAGGER_2) .apiInfo(apiInfo()) //enable是否启动Swagger,如果为false,则Swagger不能再浏览器中访问 .enable(false) .select() //RequestHandlerSelectors:配置要扫描接口的方式 //basePackage():指定要扫描的包 //any():扫描全部 //none():不扫描 //withClassAnnotation:扫描类上的注解,参数是一个注解的反射对象 //withMethodAnnotation:扫描方法上的注解 .apis(RequestHandlerSelectors.basePackage("com.kuang.controller")) //paths():过滤什么路径 //.paths(PathSelectors.ant("/kuang/**")) .build(); } ……………………(省略后面与本主题无关的代码,此为前面写的代码) }
判断是不是生产环境 flag=false
//设置要显示的Swagger环境Profiles profiles=Profiles.of("dev","test");//通过environment.acceptsProfiles判断是否处在自己设定的环境当中boolean flag = environment.acceptsProfiles(profiles);
注入enable(false)
.enable(flag)
@Configuration@EnableSwagger2 //开启Swagger2public class SwaggerConfig { //配置了Swagger的Docket的bean实例 @Bean public Docket docket(Environment environment){ //设置要显示的Swagger环境 Profiles profiles=Profiles.of("dev","test"); //通过environment.acceptsProfiles判断是否处在自己设定的环境当中 boolean flag = environment.acceptsProfiles(profiles); return new Docket(DocumentationType.SWAGGER_2) .apiInfo(apiInfo()) //enable是否启动Swagger,如果为false,则Swagger不能再浏览器中访问 .enable(flag) .select() //RequestHandlerSelectors:配置要扫描接口的方式 //basePackage():指定要扫描的包 //any():扫描全部 //none():不扫描 //withClassAnnotation:扫描类上的注解,参数是一个注解的反射对象 //withMethodAnnotation:扫描方法上的注解 .apis(RequestHandlerSelectors.basePackage("com.kuang.controller")) //paths():过滤什么路径 //.paths(PathSelectors.ant("/kuang/**")) .build(); } ……………………(省略后面与本主题无关的代码,此为前面写的代码) }
application.properties
spring.profiles.active=dev
application-dev.properties
server.port=8081
application-pro.properties
server.port=8082
当生产环境时 dev:访问网址: http://localhost:8081/swagger-ui.html
application.properties
spring.profiles.active=pro
当发布的时候 pro:访问网址: http://localhost:8082/swagger-ui.html
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-L79kbQby-1623924293485)(SpringBoot.assets/image-20210614190602330.png)]
.groupName(“风久”)
@Configuration@EnableSwagger2 //开启Swagger2public class SwaggerConfig { //配置了Swagger的Docket的bean实例 @Bean public Docket docket(Environment environment){ //设置要显示的Swagger环境 Profiles profiles=Profiles.of("dev","test"); //通过environment.acceptsProfiles判断是否处在自己设定的环境当中 boolean flag = environment.acceptsProfiles(profiles); return new Docket(DocumentationType.SWAGGER_2) .apiInfo(apiInfo()) .groupName("风久") //enable是否启动Swagger,如果为false,则Swagger不能再浏览器中访问 .enable(flag) .select() //RequestHandlerSelectors:配置要扫描接口的方式 //basePackage():指定要扫描的包 //any():扫描全部 //none():不扫描 //withClassAnnotation:扫描类上的注解,参数是一个注解的反射对象 //withMethodAnnotation:扫描方法上的注解 .apis(RequestHandlerSelectors.basePackage("com.kuang.controller")) //paths():过滤什么路径 //.paths(PathSelectors.ant("/kuang/**")) .build(); } ……………………(省略后面与本主题无关的代码,此为前面写的代码) }
效果:
@Configuration@EnableSwagger2 //开启Swagger2public class SwaggerConfig { @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"); } ……………………(省略后面与本主题无关的代码,此为前面写的代码) }
效果:
//@Api(注释)@ApiModel("用户实体类")public class User { @ApiModelProperty("用户名") public String username; @ApiModelProperty("密码") public String password;}
controller
@RestControllerpublic class HelloController { @GetMapping("/hello") public String hello(){ return "hello"; } //只要我们的接口中,返回值中存在实体类,他就会被扫描到Swagger中 @PostMapping("/user") public User user(){ return new User(); } //Operation接口 @ApiOperation("Hello控制类") @GetMapping("/hello2") public String hello2(@ApiParam("用户名") String username){ return "hello"+username; } @ApiOperation("Post测试类") @PostMapping("/postt") public User postt(@ApiParam("用户名") User user){ int i=5/0; return user; }}
Swagger是一个优秀的工具,几乎所有大公司都有使用它
【注意点】在正式发布的时候,关闭Swagger!!!出于安全考虑。而且节省运行的内存。
@Servicepublic class AsyncService { @Async public void hello(){ try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("数据正在处理……"); }}
@RestControllerpublic class AsyncController { @Autowired AsyncService asyncService; @RequestMapping("/hello") public String hello(){ asyncService.hello();//停止三秒,转圈~ return "OK"; }}
@EnableAsync//开启异步注解功能@SpringBootApplicationpublic class KsBoot09TestApplication { public static void main(String[] args) { SpringApplication.run(KsBoot09TestApplication.class, args); }}
TaskScheduler 任务调度程序TaskExecutor 任务执行者@EnableScheduling//开启定时功能的注解@Scheduled//什么时候执行Cron表达式
@EnableAsync//开启异步注解功能@EnableScheduling//开启定时功能的注解@SpringBootApplicationpublic class KsBoot09TestApplication { public static void main(String[] args) { SpringApplication.run(KsBoot09TestApplication.class, args); }}
@Servicepublic class ScheduledService { //在一个特定的时间执行这个方法~ Timer //cron 表达式~ //秒 分 时 日 月 周几~ /* 15 52 15 * * ? 每天15点52分10秒 执行一次 15 0/1 15,16 * * ? 每天15点和16点,以15分为起点,每隔一分钟执行一次。 0 58 15 ? * 1-6 每个月的周一到周六15点58分执行一次 0/2 * * * * ? 每天每隔2秒钟执行一次 */ @Scheduled(cron = "0/2 * * * * ?") public void hello(){ System.out.println("hello,你被执行了~"); }}
<dependencies> <dependency> <groupId>org.springframework.bootgroupId> <artifactId>spring-boot-starter-webartifactId> dependency> <dependency> <groupId>org.springframework.bootgroupId> <artifactId>spring-boot-starter-mailartifactId> dependency> <dependency> <groupId>org.springframework.bootgroupId> <artifactId>spring-boot-starter-testartifactId> <scope>testscope> dependency>dependencies>
[email protected]=ijtmgjxxhxxcebjgspring.mail.host=smtp.qq.com#开启加密验证spring.mail.properties.mail.smtp.ss.enable=true
@SpringBootTestclass KsBoot09TestApplicationTests { @Autowired JavaMailSenderImpl mailSender; @Test void contextLoads() { //一个简单的邮件~ SimpleMailMessage mailMessage = new SimpleMailMessage(); mailMessage.setSubject("收件人你好呀~"); mailMessage.setText("时常的忙碌,不代表遗忘;夏日的来到,愿你心情痛快,曾落下的问候,这一刻补偿,所有的关心,凝结在这条短信,祝端午节快乐!"); mailMessage.setTo("[email protected]");//金// mailMessage.setTo("[email protected]");//乔 mailMessage.setFrom("[email protected]"); mailSender.send(mailMessage); } @Test void contextLoads2() throws MessagingException { //一个复杂的邮件~ MimeMessage mailMessage =mailSender.createMimeMessage(); //组装~ MimeMessageHelper helper=new MimeMessageHelper(mailMessage,true); //正文 helper.setSubject("收件人你好呀~plus"); helper.setText("时常的忙碌,不代表遗忘;夏日的来到,愿你心情痛快,曾落下的问候,这一刻补偿,所有的关心,凝结在这条短信,祝端午节快乐!
",true); //附件 helper.addAttachment("happy.jpeg",new File("C:\\Users\\admin\\Desktop\\wu.jpeg")); helper.addAttachment("Ankang.jpeg",new File("C:\\Users\\admin\\Desktop\\duan.jpeg")); helper.setTo("[email protected]");//金// helper.setTo("[email protected]");//乔 helper.setFrom("[email protected]"); mailSender.send(mailMessage); }}
/** * * @param html:true * @param subject:主体 * @param text:内容 * @throws MessagingException *///封装为一个方法public void sendMail(Boolean html,String subject,String text) throws MessagingException { //一个复杂的邮件~ MimeMessage mailMessage =mailSender.createMimeMessage(); //组装~ MimeMessageHelper helper=new MimeMessageHelper(mailMessage,html); //正文 helper.setSubject(subject); helper.setText(text,html); //附件 helper.addAttachment("happy.jpeg",new File("C:\\Users\\admin\\Desktop\\wu.jpeg")); helper.addAttachment("Ankang.jpeg",new File("C:\\Users\\admin\\Desktop\\duan.jpeg")); // helper.setTo("[email protected]"); helper.setTo("[email protected]"); helper.setFrom("[email protected]"); mailSender.send(mailMessage);}
Spring Boot 操作数据:spring-data jpa jdbc mongodb redis!
Spring Data 也是和 Spring Boot齐名的项目!
说明:在Spring Boot2.x之后,原来使用的 jedis 被替换为了 lettuce?
jedis :采用的直连,多个线程操作的话,是不安全的,如果想要避免不安全的,使用jedis pool连接池!更像 BIO 模式
lettuce:采用netty,实例可以在多个线程中进行共享,不存在线程不安全的情况!可以减少线程数据了,更像 NIO 模式
@Bean@ConditionalOnMissingBean(name = "redisTemplate")//我们可以自己定义一个redisTemplate来替换这个默认的!@ConditionalOnSingleCandidate(RedisConnectionFactory.class)public RedisTemplate
1、Redis下载地址: https://github.com/microsoftarchive/redis/releases/tag/win-3.2.100
2、运行程序,启动服务
<dependencies> <dependency> <groupId>org.springframework.bootgroupId> <artifactId>spring-boot-starter-data-redisartifactId> dependency> <dependency> <groupId>org.springframework.bootgroupId> <artifactId>spring-boot-starter-webartifactId> dependency> <dependency> <groupId>org.springframework.bootgroupId> <artifactId>spring-boot-devtoolsartifactId> <scope>runtimescope> <optional>trueoptional> dependency> <dependency> <groupId>org.springframework.bootgroupId> <artifactId>spring-boot-configuration-processorartifactId> <optional>trueoptional> dependency> <dependency> <groupId>org.projectlombokgroupId> <artifactId>lombokartifactId> <optional>trueoptional> dependency> <dependency> <groupId>org.springframework.bootgroupId> <artifactId>spring-boot-starter-testartifactId> <scope>testscope> dependency>dependencies>
# Spring Boot 所有的配置类,都有一个自动配置类 RedisAutoConfiguration# 自动配置类都会绑定一个 properties 配置文件 RedisProperties# 配置redisspring.redis.host=127.0.0.1spring.redis.port=6379
@SpringBootTestclass KsBoot10RedisApplicationTests { @Autowired private RedisTemplate redisTemplate; @Test void contextLoads() { // redisTemplate 操作不同的数据类型,api和我们的指令是一样的 // opsForValue() 操作字符串 类似String // opsForList() 操作List 类似List // opsForSet() // opsForHash() // opsForZSet() // opsForGeo() // opsForHyperLogLog() //除了基本的操作,我们常用的方法都可以直接通过redisTemplate操作,比如事务,和基本的CRUD //获取redis的连接对象// RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();// connection.flushDb();// connection.flushAll(); redisTemplate.opsForValue().set("mykey","关闭狂神说公众号"); System.out.println(redisTemplate.opsForValue().get("mykey")); }}
@Component@AllArgsConstructor@NoArgsConstructor@Data//在企业中,我们的所有pojo都会序列化!public class User implements Serializable { private String name; private int age;}
@SpringBootTestclass KsBoot10RedisApplicationTests { @Autowired private RedisTemplate redisTemplate; @Test public void test() throws JsonProcessingException { //真是的开发一般都使用json来传递对象 User user = new User("小可爱", 3);// 方式一:// String jsonUser = new ObjectMapper().writeValueAsString(user);// redisTemplate.opsForValue().set("user",jsonUser);// 方式二:前提pojo实体类需要序列化 implements Serializable redisTemplate.opsForValue().set("user",user); System.out.println(redisTemplate.opsForValue().get("user")); }}
@Configurationpublic class RedisConfig { /** * 编写自定义的 redisTemplate * 这是一个比较固定的模板,拿去就可以直接使用 */ @Bean @SuppressWarnings("all") public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException { // 为了开发方便,直接使用 RedisTemplate template = new RedisTemplate(); template.setConnectionFactory(redisConnectionFactory); // Json 配置序列化 // 使用 jackson 解析任意的对象 Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class); // 使用 objectMapper 进行转义 ObjectMapper objectMapper = new ObjectMapper(); objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL); jackson2JsonRedisSerializer.setObjectMapper(objectMapper); // 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.setHashValueSerializer(jackson2JsonRedisSerializer); // 把所有的配置 set 进 template template.afterPropertiesSet(); return template; }}
所有的redis操作,其实对于java开发人员来说,十分的简单,更重要是要去理解redis的思想和每一种数据结构的用处和作用场景!
//在我们真实的开发中,或者你们在公司,一般都可以看到一个公司自己封装的RedidUtil@Componentpublic final class RedisUtil { @Autowired private RedisTemplate redisTemplate; // =============================common============================ /** * 指定缓存失效时间 * @param key 键 * @param time 时间(秒) */ public boolean expire(String key, long time) { try { if (time > 0) { redisTemplate.expire(key, time, TimeUnit.SECONDS); } return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 根据key 获取过期时间 * @param key 键 不能为null * @return 时间(秒) 返回0代表为永久有效 */ public long getExpire(String key) { return redisTemplate.getExpire(key, TimeUnit.SECONDS); } /** * 判断key是否存在 * @param key 键 * @return true 存在 false不存在 */ public boolean hasKey(String key) { try { return redisTemplate.hasKey(key); } catch (Exception e) { e.printStackTrace(); return false; } } /** * 删除缓存 * @param key 可以传一个值 或多个 */ @SuppressWarnings("unchecked") public void del(String... key) { if (key != null && key.length > 0) { if (key.length == 1) { redisTemplate.delete(key[0]); } else { redisTemplate.delete(String.valueOf(CollectionUtils.arrayToList(key))); } } } // ============================String============================= /** * 普通缓存获取 * @param key 键 * @return 值 */ public Object get(String key) { return key == null ? null : redisTemplate.opsForValue().get(key); } /** * 普通缓存放入 * @param key 键 * @param value 值 * @return true成功 false失败 */ public boolean set(String key, Object value) { try { redisTemplate.opsForValue().set(key, value); return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 普通缓存放入并设置时间 * @param key 键 * @param value 值 * @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期 * @return true成功 false 失败 */ public boolean set(String key, Object value, long time) { try { if (time > 0) { redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS); } else { set(key, value); } return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 递增 * @param key 键 * @param delta 要增加几(大于0) */ public long incr(String key, long delta) { if (delta < 0) { throw new RuntimeException("递增因子必须大于0"); } return redisTemplate.opsForValue().increment(key, delta); } /** * 递减 * @param key 键 * @param delta 要减少几(小于0) */ public long decr(String key, long delta) { if (delta < 0) { throw new RuntimeException("递减因子必须大于0"); } return redisTemplate.opsForValue().increment(key, -delta); } // ================================Map================================= /** * HashGet * @param key 键 不能为null * @param item 项 不能为null */ public Object hget(String key, String item) { return redisTemplate.opsForHash().get(key, item); } /** * 获取hashKey对应的所有键值 * @param key 键 * @return 对应的多个键值 */ public Map hmget(String key) { return redisTemplate.opsForHash().entries(key); } /** * HashSet * @param key 键 * @param map 对应多个键值 */ public boolean hmset(String key, Map map) { try { redisTemplate.opsForHash().putAll(key, map); return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * HashSet 并设置时间 * @param key 键 * @param map 对应多个键值 * @param time 时间(秒) * @return true成功 false失败 */ public boolean hmset(String key, Map map, long time) { try { redisTemplate.opsForHash().putAll(key, map); if (time > 0) { expire(key, time); } return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 向一张hash表中放入数据,如果不存在将创建 * * @param key 键 * @param item 项 * @param value 值 * @return true 成功 false失败 */ public boolean hset(String key, String item, Object value) { try { redisTemplate.opsForHash().put(key, item, value); return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 向一张hash表中放入数据,如果不存在将创建 * * @param key 键 * @param item 项 * @param value 值 * @param time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间 * @return true 成功 false失败 */ public boolean hset(String key, String item, Object value, long time) { try { redisTemplate.opsForHash().put(key, item, value); if (time > 0) { expire(key, time); } return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 删除hash表中的值 * * @param key 键 不能为null * @param item 项 可以使多个 不能为null */ public void hdel(String key, Object... item) { redisTemplate.opsForHash().delete(key, item); } /** * 判断hash表中是否有该项的值 * * @param key 键 不能为null * @param item 项 不能为null * @return true 存在 false不存在 */ public boolean hHasKey(String key, String item) { return redisTemplate.opsForHash().hasKey(key, item); } /** * hash递增 如果不存在,就会创建一个 并把新增后的值返回 * * @param key 键 * @param item 项 * @param by 要增加几(大于0) */ public double hincr(String key, String item, double by) { return redisTemplate.opsForHash().increment(key, item, by); } /** * hash递减 * * @param key 键 * @param item 项 * @param by 要减少记(小于0) */ public double hdecr(String key, String item, double by) { return redisTemplate.opsForHash().increment(key, item, -by); } // ============================set============================= /** * 根据key获取Set中的所有值 * @param key 键 */ public Set sGet(String key) { try { return redisTemplate.opsForSet().members(key); } catch (Exception e) { e.printStackTrace(); return null; } } /** * 根据value从一个set中查询,是否存在 * * @param key 键 * @param value 值 * @return true 存在 false不存在 */ public boolean sHasKey(String key, Object value) { try { return redisTemplate.opsForSet().isMember(key, value); } catch (Exception e) { e.printStackTrace(); return false; } } /** * 将数据放入set缓存 * * @param key 键 * @param values 值 可以是多个 * @return 成功个数 */ public long sSet(String key, Object... values) { try { return redisTemplate.opsForSet().add(key, values); } catch (Exception e) { e.printStackTrace(); return 0; } } /** * 将set数据放入缓存 * * @param key 键 * @param time 时间(秒) * @param values 值 可以是多个 * @return 成功个数 */ public long sSetAndTime(String key, long time, Object... values) { try { Long count = redisTemplate.opsForSet().add(key, values); if (time > 0) expire(key, time); return count; } catch (Exception e) { e.printStackTrace(); return 0; } } /** * 获取set缓存的长度 * * @param key 键 */ public long sGetSetSize(String key) { try { return redisTemplate.opsForSet().size(key); } catch (Exception e) { e.printStackTrace(); return 0; } } /** * 移除值为value的 * * @param key 键 * @param values 值 可以是多个 * @return 移除的个数 */ public long setRemove(String key, Object... values) { try { Long count = redisTemplate.opsForSet().remove(key, values); return count; } catch (Exception e) { e.printStackTrace(); return 0; } } // ===============================list================================= /** * 获取list缓存的内容 * * @param key 键 * @param start 开始 * @param end 结束 0 到 -1代表所有值 */ public List lGet(String key, long start, long end) { try { return redisTemplate.opsForList().range(key, start, end); } catch (Exception e) { e.printStackTrace(); return null; } } /** * 获取list缓存的长度 * * @param key 键 */ public long lGetListSize(String key) { try { return redisTemplate.opsForList().size(key); } catch (Exception e) { e.printStackTrace(); return 0; } } /** * 通过索引 获取list中的值 * * @param key 键 * @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推 */ public Object lGetIndex(String key, long index) { try { return redisTemplate.opsForList().index(key, index); } catch (Exception e) { e.printStackTrace(); return null; } } /** * 将list放入缓存 * * @param key 键 * @param value 值 */ public boolean lSet(String key, Object value) { try { redisTemplate.opsForList().rightPush(key, value); return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 将list放入缓存 * @param key 键 * @param value 值 * @param time 时间(秒) */ public boolean lSet(String key, Object value, long time) { try { redisTemplate.opsForList().rightPush(key, value); if (time > 0) expire(key, time); return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 将list放入缓存 * * @param key 键 * @param value 值 * @return */ public boolean lSet(String key, List value) { try { redisTemplate.opsForList().rightPushAll(key, value); return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 将list放入缓存 * * @param key 键 * @param value 值 * @param time 时间(秒) * @return */ public boolean lSet(String key, List value, long time) { try { redisTemplate.opsForList().rightPushAll(key, value); if (time > 0) expire(key, time); return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 根据索引修改list中的某条数据 * * @param key 键 * @param index 索引 * @param value 值 * @return */ public boolean lUpdateIndex(String key, long index, Object value) { try { redisTemplate.opsForList().set(key, index, value); return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 移除N个值为value * * @param key 键 * @param count 移除多少个 * @param value 值 * @return 移除的个数 */ public long lRemove(String key, long count, Object value) { try { Long remove = redisTemplate.opsForList().remove(key, count, value); return remove; } catch (Exception e) { e.printStackTrace(); return 0; } }}
test测试应用
@SpringBootTestclass KsBoot10RedisApplicationTests { @Autowired private RedisUtil redisUtil; @Test public void test1(){ redisUtil.set("name","乔帅"); System.out.println(redisUtil.get("name")); }}
定义:“分布式系统是若干独立计算机的集合,这些计算机对于用户来说就像单个相关系统”。
其目的是利用更多的机器,处理更多的数据。
分布式系统(distributed system)是建立在网络之上的软件系统。
首先需要明确的是,只有当单个节点的处理能力无法满足日益增长的计算、存储任务的时候,且硬件的提升(加内存、加磁盘、使用更好的CPU)高昂到得不偿失的时候,应用程序也不能进一步优化的时候,我们才需要考虑分布式系统。因为,分布式系统要解决的问题本身就是和单机系统一样的,而由于分布式系统多节点、通过网络通信的拓扑结构,会引入很多单机系统没有的问题,为了解决这些问题又会引入更多的机制、协议,带来更多的问题。。。
随着互联网的发展,网站应用的规模不断扩大,常规的垂直应用架构已无法应对,分布式服务架构以及流动计算架构势在必行,急需一个治理系统确保架构有条不紊的演进。
在Dubbo的官网文档有这样一张图
当网站流量很小时,只需一个应用,将所有功能都部署在一起,以减少部署节点和成本。此时,用于简化增删改查工作量的数据访问框架(ORM)是关键。
适用于小型网站,小型管理系统,将所有功能都部署到一个功能里,简单易用。
缺点:
1、性能扩展比较难
2、协同开发问题
3、不利于升级维护
当访问量逐渐增大,单一应用增加机器带来的加速度越来越小,将应用拆成互不相干的几个应用,以提升效率。此时,用于加速前端页面开发的Web框架(MVC)是关键。
通过切分业务来实现各个模块独立部署,降低了维护和部署的难度,团队各司其职更易管理,性能扩展也更方便,更有针对性。
缺点:公用模块无法重复利用,开发性的浪费
当垂直应用越来越多,应用之间交互不可避免,将核心业务抽取出来,作为独立的服务,逐渐形成稳定的服务中心,使前端应用能更快速的响应多变的市场需求。此时,用于提高业务复用及整合的**分布式服务框架(RPC)**是关键。
当服务越来越多,容量的评估,小服务资源的浪费等问题逐渐显现,此时需增加一个调度中心基于访问压力实时管理集群容量,提高集群利用率。此时,用于提高机器利用率的资源调度和治理中心(SOA)[ Service Oriented Architecture]是关键。
RPC【Remote Procedure Call】是指远程过程调用,是一种进程间通信方式,他是一种技术的思想,而不是规范。它允许程序调用另一个地址空间(通常是共享网络的另一台机器上)的过程或函数,而不用程序员显式编码这个远程调用的细节。即程序员无论是调用本地的还是远程的函数,本质上编写的调用代码基本相同。
也就是说两台服务器A,B,一个应用部署在A服务器上,想要调用B服务器上应用提供的函数/方法,由于不在一个内存空间,不能直接调用,需要通过网络来表达调用的语义和传达调用的数据。为什么要用RPC呢?就是无法在一个进程内,甚至一个计算机内通过本地调用的方式完成的需求,比如不同的系统间的通讯,甚至不同的组织间的通讯,由于计算能力需要横向扩展,需要在多台机器组成的集群上部署应用。RPC就是要像调用本地的函数一样去调远程函数;
推荐阅读文章:https://www.jianshu.com/p/2accc2840a1b
RPC
HTTP SpringCloud(生态)
序列化:数据传输需要转换。
Netty
Dubbo~ 18年重启! Dubbo 3.x RPC Error Exception~
专业的事,交给专业的人来做~ 不靠谱!
Apache Dubbo |ˈdʌbəʊ| 是一款高性能、轻量级的开源Java RPC框架,它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现。
dubbo官网:https://dubbo.apache.org/zh/docs/
dubbo基本概念
服务提供者(Provider):暴露服务的服务提供方,服务提供者在启动时,向注册中心注册自己提供的服务。
服务消费者(Consumer):调用远程服务的服务消费方,服务消费者在启动时,向注册中心订阅自己所需的服务,服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
注册中心(Registry):注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者
监控中心(Monitor):服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心
调用关系说明
l 服务容器负责启动,加载,运行服务提供者。
l 服务提供者在启动时,向注册中心注册自己提供的服务。
l 服务消费者在启动时,向注册中心订阅自己所需的服务。
l 注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。
l 服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
l 服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。
点进dubbo官方文档,推荐我们使用Zookeeper Zookeeper 注册中心: https://dubbo.apache.org/zh/docs/v2.7/user/references/registry/zookeeper/
什么是zookeeper呢?可以查看 ZooKeeper官方文档: https://zookeeper.apache.org/
1、下载zookeeper :https://downloads.apache.org/zookeeper/, 我们下载3.5.9 !解压zookeeper
2、运行/bin/zkServer.cmd ,初次运行会报错,没有zoo.cfg配置文件;
可能遇到问题:闪退 !
解决方案:编辑zkServer.cmd文件末尾添加pause 。这样运行出错就不会退出,会提示错误信息,方便找到原因。
3、修改zoo.cfg配置文件
将conf文件夹下面的zoo_sample.cfg复制一份改名为zoo.cfg即可。
注意几个重要位置:
dataDir=./ 临时数据存储的目录(可写相对路径)
clientPort=2181 zookeeper的端口号
修改完成后再次启动zookeeper
4、使用zkCli.cmd测试
ls /:列出zookeeper根下保存的所有节点
[zk: localhost:2181(CONNECTED) 0] ls /[zookeeper]
create –e /kuangshen 123:创建一个kuangshen节点,值为123
[zk: localhost:2181(CONNECTED) 1] create -e /kuangshen 123Created /kuangshen
get /kuangshen:获取/kuangshen节点的值
[zk: localhost:2181(CONNECTED) 3] get /kuangshen123
我们再来查看一下节点
[zk: localhost:2181(CONNECTED) 2] ls /[kuangshen, zookeeper]
dubbo本身并不是一个服务软件。它其实就是一个jar包,能够帮你的java程序连接到zookeeper,并利用zookeeper消费、提供服务。
但是为了让用户更好的管理监控众多的dubbo服务,官方提供了一个可视化的监控程序dubbo-admin,不过这个监控即使不装也不影响使用。
我们这里来安装一下:
1、下载dubbo-admin
地址 :https://github.com/apache/dubbo-admin/tree/master
2、解压进入目录
修改 dubbo-admin\src\main\resources \application.properties 指定zookeeper地址
server.port=7001spring.velocity.cache=falsespring.velocity.charset=UTF-8spring.velocity.layout-url=/templates/default.vmspring.messages.fallback-to-system-locale=falsespring.messages.basename=i18n/messagespring.root.password=rootspring.guest.password=guest# 注册中心的地址dubbo.registry.address=zookeeper://127.0.0.1:2181
3、在项目目录下打包dubbo-admin
前提:配好maven环境变量
mvn clean package -Dmaven.test.skip=true
第一次打包的过程有点慢,需要耐心等待!直到成功!
4、执行 dubbo-admin\target 下的dubbo-admin-0.0.1-SNAPSHOT.jar
java -jar dubbo-admin-0.0.1-SNAPSHOT.jar
【注意:zookeeper的服务一定要打开!】
执行完毕,我们去访问一下 http://localhost:7001/ , 这时候我们需要输入登录账户和密码,我们都是默认的root-root;
登录成功后,查看界面
zookeeper:注册中心
dubbo-admin:是一个监控管理后台查看我们注册了哪些服务,哪些服务被消费了
Dubbo:jar包~
provider-server
模块,选择web依赖public interface TicketService { public String getTicket();}
serviceImpl
public class TicketServiceImpl implements TicketService { @Override public String getTicket() { return "《小可爱》"; }}
consumer-server
模块,选择web依赖public class UserService { //想拿到provider-server提供的票}
provider-server
<dependencies> <dependency> <groupId>org.apache.dubbogroupId> <artifactId>dubbo-spring-boot-starterartifactId> <version>2.7.3version> dependency> <dependency> <groupId>com.github.sgroschupfgroupId> <artifactId>zkclientartifactId> <version>0.1version> dependency> <dependency> <groupId>org.apache.curatorgroupId> <artifactId>curator-frameworkartifactId> <version>2.12.0version> dependency> <dependency> <groupId>org.apache.curatorgroupId> <artifactId>curator-recipesartifactId> <version>2.12.0version> dependency> <dependency> <groupId>org.apache.zookeepergroupId> <artifactId>zookeeperartifactId> <version>3.4.14version> <exclusions> <exclusion> <groupId>org.slf4jgroupId> <artifactId>slf4j-log4j12artifactId> exclusion> exclusions> dependency> <dependency> <groupId>org.springframework.bootgroupId> <artifactId>spring-boot-starter-webartifactId> dependency> <dependency> <groupId>org.springframework.bootgroupId> <artifactId>spring-boot-starter-testartifactId> <scope>testscope> dependency>dependencies>
server.port=8081# 服务应用名字dubbo.application.name=provider-server# 注册中心地址dubbo.registry.address=zookeeper://127.0.0.1:2181# 哪些服务要被注册dubbo.scan.base-packages=com.kuang.service
service
public interface TicketService { public String getTicket();}
serviceImpl
import org.apache.dubbo.config.annotation.Service;import org.springframework.stereotype.Component;@Service //可以被扫描到,在项目一启动就自动注册到注册中心@Component //使用了Dubbo后尽量不要用Service注解public class TicketServiceImpl implements TicketService { @Override public String getTicket() { return "《小可爱》"; }}
consumer-server
<dependencies> <dependency> <groupId>org.apache.dubbogroupId> <artifactId>dubbo-spring-boot-starterartifactId> <version>2.7.3version> dependency> <dependency> <groupId>com.github.sgroschupfgroupId> <artifactId>zkclientartifactId> <version>0.1version> dependency> <dependency> <groupId>org.apache.curatorgroupId> <artifactId>curator-frameworkartifactId> <version>2.12.0version> dependency> <dependency> <groupId>org.apache.curatorgroupId> <artifactId>curator-recipesartifactId> <version>2.12.0version> dependency> <dependency> <groupId>org.apache.zookeepergroupId> <artifactId>zookeeperartifactId> <version>3.4.14version> <exclusions> <exclusion> <groupId>org.slf4jgroupId> <artifactId>slf4j-log4j12artifactId> exclusion> exclusions> dependency> <dependency> <groupId>org.springframework.bootgroupId> <artifactId>spring-boot-starter-webartifactId> dependency> <dependency> <groupId>org.springframework.bootgroupId> <artifactId>spring-boot-starter-testartifactId> <scope>testscope> dependency>dependencies>
server.port=8082# 消费者去哪里拿到服务需要暴露自己的名字dubbo.application.name=consumer-server# 注册中心的地址,可以在任何电脑上!dubbo.registry.address=zookeeper://127.0.0.1:2181
本来正常步骤是需要将服务提供者的接口打包,然后用pom文件导入,我们这里使用简单的方式,直接将服务的接口拿过来,路径必须保证正确,即和服务提供者相同;
UserService
import com.alibaba.dubbo.config.annotation.Reference;import org.springframework.stereotype.Service;@Service //放到容器中~public class UserService { //想拿到provider-server提供的票,要去注册中心拿到服务 @Reference //引用, Pom坐标,可以定义路径相同的接口名 TicketService ticketService; public void buyTicket(){ String ticket=ticketService.getTicket(); System.out.println("在注册中心拿到=>"+ticket); }}
@SpringBootTestclass ConsumerServerApplicationTests { @Autowired UserService userService; @Test void contextLoads() { userService.buyTicket(); }}
启动zookeeper
打开dubbo-admin实现监控【可以不用做】
开启服务者
消费者消费测试
结果:
前提:zookeeper服务已开启!
三层架构 :MVC 架构 --本质--> 解耦 开发框架 Spring IOC AOP IOC:控制反转 约会: 吃饭,购物……,约会 附近的人,打招呼。加微信,聊天,天天聊,---> 约会 相亲(容器):餐厅,商场,约会 直接去相亲,就有人和你一起了! 原来我们都是自己一步一步操作,现在交给容器了!我们需要什么就去拿就可以了 AOP:切面(本质,动态代理) 为了解决什么?不影响业务本来的情况下,实现动态增加功能,大量应用在日志,事务……等等方面 Spring是一个轻量级的Java开源框架,容器 目的:解决企业开发的复杂性问题 Spring是春天,觉得他是春天,也十分复杂,配置文件! Spring Boot Spring Boot并不是新东西,就是Spring的升级版! 新一代JavaEE的开发标准,开箱即用!->拿过来就可以用! 它自动帮我们配置了非常多的东西,我们拿来即用! 特性:约定大于配置随着公司体系越来越大,用户越来越多! 模块化,功能化! 用户,支付,签到,娱乐,……; 人多余多;一台服务器解决不了;在增加服务器! 横向 假设A服务器占用98%资源,B服务器值占用了10% 。--负载均衡; 将原来的整体项目,分成模块化,用户就是一个单独的项目,签到也是一个单独的项目,项目和项目之前需要通信,如何通信? 用户非常多,而签到十分少! 给用户多一点服务器,给签到少一点服务器! 微服务架构问题? 分布式架构会遇到的四个核心问题? 1.这么多服务,客户端该如何去访问? 2.这么多服务,服务之间如何进行通信? 3.这么多服务,如何治理呢? 4.服务挂了,怎么办? 解决方案: SpringCloud,是一套生态,就是来解决以上分布式架构的4个问题 想使用SpringCloud,必须掌握Spring Boot,因为SpringCloud是基于Spring Boot; 1.Spring Cloud NetFlix,出来了一套解决方案! 一站式解决方案。我们都可以直接去这里拿? Api网关,zuul组件 Feign-->HttpClient-->HTTP的通信方式,同步并阻塞 服务注册与发现,Eureka 熔断机制,Hystrix 2018年年底,NetFlix宣布无限期停止维护。生态不再维护,就会脱节。 2.Apache Dubbo zookeeper,第二套解决方案 API:没有!要么找第三方组件,要么自己实现 Dubbo是一个高性能的基于Java实现的 RPC通信框架! 2.6.x 服务注册与发现,zookeeper:动物园管理员(Hadoop,Hive) 没有:借助了 Hystrix 不完善,Dubbo 3.0.x 3.SpringCloud Alibaba 一站式解决方案! 目前,又提出了一种方案: 服务网格:下一代微服务标准,Server Mesh 代表解决方案: istio(你们未来可能需要掌握!) 万变不离其宗;一通百通! 1.API网关,服务器由 2.HTTP,RPC框架,异步调用 3.服务注册与发现,高可用 4.熔断机制,服务降级 如果,你们基于这四个问题,开发一套解决方案,也叫Spring Cloud!为什么要解决这个问题?本质:网络是不可靠的!程序猿,不要停下学习的脚步!