首页
壁纸
留言板
友链
更多
统计归档
Search
1
TensorBoard:训练日志及网络结构可视化工具
12,588 阅读
2
主板开机跳线接线图【F_PANEL接线图】
7,052 阅读
3
Linux使用V2Ray 原生客户端
6,161 阅读
4
移动光猫获取超级密码&开启公网ipv6
4,722 阅读
5
NVIDIA 显卡限制功率
3,134 阅读
好物分享
实用教程
linux使用
wincmd
学习笔记
mysql
java学习
nginx
综合面试题
大数据
网络知识
linux
放码过来
python
javascript
java
opencv
蓝桥杯
leetcode
深度学习
开源模型
相关知识
数据集和工具
模型轻量化
语音识别
计算机视觉
杂七杂八
硬件科普
主机安全
嵌入式设备
其它
bug处理
登录
/
注册
Search
标签搜索
好物分享
学习笔记
linux
MySQL
nvidia
typero
内网穿透
webdav
vps
java
cudann
gcc
cuda
树莓派
CNN
图像去雾
ssh安全
nps
暗通道先验
阿里云
jupiter
累计撰写
354
篇文章
累计收到
71
条评论
首页
栏目
好物分享
实用教程
linux使用
wincmd
学习笔记
mysql
java学习
nginx
综合面试题
大数据
网络知识
linux
放码过来
python
javascript
java
opencv
蓝桥杯
leetcode
深度学习
开源模型
相关知识
数据集和工具
模型轻量化
语音识别
计算机视觉
杂七杂八
硬件科普
主机安全
嵌入式设备
其它
bug处理
页面
壁纸
留言板
友链
统计归档
搜索到
354
篇与
的结果
2022-06-07
NVIDIA 显卡限制功率
1.应用背景给实验室的一台服务器Dell T7910加装了两块3090显卡,但是由于供电口不够,所有用了两个6pin转8pin的转接线进行转接,导致显卡的电源输入功率不够,跑实验的时候一跑到满载就会扛不住关机重启,暂时无法更换电源,因此考虑对显卡功率进行限制。2.配置实现2.1 临时设置nvidia-smi -pm 1 # enable persistance mode nvidia-smi -pl 250 # set power limit to 250W3090配置前的最大功率为350W,因供电原因配置限制后的最大功率为250W2.2 永久设置新建nvidia-setpower.service文件sudo vim /etc/systemd/system/nvidia-setpower.service[Unit] Description=Nvidia SetPower Service After=network.target Wants=network.target [Service] Type=simple PIDFile=/run/nvidia-setpower.pid ExecStart=sh /usr/bin/nvidia-setpower.sh Restart=on-failure # Don't restart in the case of configuration error RestartPreventExitStatus=23 [Install] WantedBy=multi-user.target新建nvidia-setpower.shsudo vim /usr/bin/nvidia-setpower.shsudo nvidia-smi -pm 1 sudo nvidia-smi -pl 250sudo nvidia-smi -i 1 -pl 250,-i可以指定第几个显卡。设置服务开机启动:systemctl daemon-reload systemctl start nvidia-setpower.service systemctl enable nvidia-setpower.service参考资料NVIDIA 显卡在 Ubuntu下限制其功率的方法ubuntu 永久 设置 降低N显卡功率 nvidia-smi
2022年06月07日
3,134 阅读
2 评论
0 点赞
2022-06-05
SpringBoot学习笔记
SpringBoot学习笔记1.SpringBoot简介1.1 回顾什么是SpringSpring是一个开源框架,2003 年兴起的一个轻量级的Java 开发框架,作者:Rod Johnson 。Spring是为了解决企业级应用开发的复杂性而创建的,简化开发。1.2 Spring是如何简化Java开发的为了降低Java开发的复杂性,Spring采用了以下4种关键策略:1、基于POJO的轻量级和最小侵入性编程,所有东西都是bean;2、通过IOC,依赖注入(DI)和面向接口实现松耦合;3、基于切面(AOP)和惯例进行声明式编程;4、通过切面和模版减少样式代码,RedisTemplate,xxxTemplate;1.3什么是SpringBoot学过javaweb的同学就知道,开发一个web应用,从最初开始接触Servlet结合Tomcat, 跑出一个Hello Wolrld程序,是要经历特别多的步骤;后来就用了框架Struts,再后来是SpringMVC,到了现在的SpringBoot,过一两年又会有其他web框架出现;你们有经历过框架不断的演进,然后自己开发项目所有的技术也在不断的变化、改造吗?建议都可以去经历一遍;言归正传,什么是SpringBoot呢,就是一个javaweb的开发框架,和SpringMVC类似,对比其他javaweb框架的好处,官方说是简化开发,约定大于配置, you can "just run",能迅速的开发web应用,几行代码开发一个http接口。所有的技术框架的发展似乎都遵循了一条主线规律:从一个复杂应用场景 衍生 一种规范框架,人们只需要进行各种配置而不需要自己去实现它,这时候强大的配置功能成了优点;发展到一定程度之后,人们根据实际生产应用情况,选取其中实用功能和设计精华,重构出一些轻量级的框架;之后为了提高开发效率,嫌弃原先的各类配置过于麻烦,于是开始提倡“约定大于配置”,进而衍生出一些一站式的解决方案。是的这就是Java企业级应用->J2EE->spring->springboot的过程。随着 Spring 不断的发展,涉及的领域越来越多,项目整合开发需要配合各种各样的文件,慢慢变得不那么易用简单,违背了最初的理念,甚至人称配置地狱。Spring Boot 正是在这样的一个背景下被抽象出来的开发框架,目的为了让大家更容易的使用 Spring 、更容易的集成各种常用的中间件、开源软件;Spring Boot 基于 Spring 开发,Spirng Boot 本身并不提供 Spring 框架的核心特性以及扩展功能,只是用于快速、敏捷地开发新一代基于 Spring 框架的应用程序。也就是说,它并不是用来替代 Spring 的解决方案,而是和 Spring 框架紧密结合用于提升 Spring 开发者体验的工具。Spring Boot 以约定大于配置的核心思想,默认帮我们进行了很多设置,多数 Spring Boot 应用只需要很少的 Spring 配置。同时它集成了大量常用的第三方库配置(例如 Redis、MongoDB、Jpa、RabbitMQ、Quartz 等等),Spring Boot 应用中这些第三方库几乎可以零配置的开箱即用。简单来说就是SpringBoot其实不是什么新的框架,它默认配置了很多框架的使用方式,就像maven整合了所有的jar包,spring boot整合了所有的框架 。Spring Boot 出生名门,从一开始就站在一个比较高的起点,又经过这几年的发展,生态足够完善,Spring Boot 已经当之无愧成为 Java 领域最热门的技术。Spring Boot的主要优点:为所有Spring开发者更快的入门开箱即用,提供各种默认配置来简化项目配置内嵌式容器简化Web项目没有冗余代码生成和XML配置的要求真的很爽,我们快速去体验开发个接口的感觉吧!2.HelloSpringBoot2.1 准备工作我们将学习如何快速的创建一个Spring Boot应用,并且实现一个简单的Http请求处理。通过这个例子对Spring Boot有一个初步的了解,并体验其结构简单、开发快速的特性。环境准备:java version "1.8.0_181"Maven-3.6.1SpringBoot 2.x 最新版开发工具:IDEA2.2 创建基础项目说明Spring官方提供了非常方便的工具让我们快速构建应用Spring Initializr:https://start.spring.io/项目创建方式一:使用Spring Initializr 的 Web页面创建项目1、打开 https://start.spring.io/2、填写项目信息3、点击”Generate Project“按钮生成项目;下载此项目4、解压项目包,并用IDEA以Maven项目导入,一路下一步即可,直到项目导入完毕。5、如果是第一次使用,可能速度会比较慢,包比较多、需要耐心等待一切就绪。项目创建方式二:使用 IDEA 直接创建项目1、创建一个新项目2、选择spring initalizr , 可以看到默认就是去官网的快速构建工具那里实现3、填写项目信息4、选择初始化的组件(初学勾选 Web 即可)5、填写项目路径6、等待项目构建成功项目结构分析:通过上面步骤完成了基础项目的创建。就会自动生成以下文件。1、程序的主启动类2、一个 application.properties 配置文件3、一个 测试类4、一个 pom.xml2.3 pom.xml 分析打开pom.xml,看看Spring Boot项目的依赖:<!-- 父依赖 --> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.2.5.RELEASE</version> <relativePath/> </parent> <dependencies> <!-- web场景启动器 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- springboot单元测试 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <!-- 剔除依赖 --> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> </dependencies> <build> <plugins> <!-- 打包插件 --> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>2.4 编写一个http接口1、在主程序的同级目录下,新建一个controller包,一定要在同级目录下,否则识别不到2、在包中新建一个HelloController类@RestController public class HelloController { @RequestMapping("/hello") public String hello() { return "Hello World"; } }3、编写完毕后,从主程序启动项目,浏览器发起请求,看页面返回;控制台输出了 Tomcat 访问的端口号!简单几步,就完成了一个web接口的开发,SpringBoot就是这么简单。所以我们常用它来建立我们的微服务项目!2.5 将项目打成jar包,点击 maven的 package如果遇到以上错误,可以配置打包时 跳过项目运行测试用例<!-- 在工作中,很多情况下我们打包是不想执行测试用例的 可能是测试用例不完事,或是测试用例会影响数据库数据 跳过测试用例执 --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <configuration> <!--跳过项目运行测试用例--> <skipTests>true</skipTests> </configuration> </plugin>如果打包成功,则会在target目录下生成一个 jar 包打成了jar包后,就可以在任何地方运行了!OK3.运行原理探究我们之前写的HelloSpringBoot,到底是怎么运行的呢,Maven项目,我们一般从pom.xml文件探究起;3.1 pom.xml父依赖其中它主要是依赖一个父项目,主要是管理项目的资源过滤及插件!<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.2.5.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent>点进去,发现还有一个父依赖<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>2.2.5.RELEASE</version> <relativePath>../../spring-boot-dependencies</relativePath> </parent>这里才是真正管理SpringBoot应用里面所有依赖版本的地方,SpringBoot的版本控制中心;以后我们导入依赖默认是不需要写版本;但是如果导入的包没有在依赖中管理着就需要手动配置版本了;启动器 spring-boot-starter<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>springboot-boot-starter-xxx:就是spring-boot的场景启动器spring-boot-starter-web:帮我们导入了web模块正常运行所依赖的组件;SpringBoot将所有的功能场景都抽取出来,做成一个个的starter (启动器),只需要在项目中引入这些starter即可,所有相关的依赖都会导入进来 , 我们要用什么功能就导入什么样的场景启动器即可 ;我们未来也可以自己自定义 starter;3.2 主启动类即相关注解默认的主启动类//@SpringBootApplication 来标注一个主程序类 //说明这是一个Spring Boot应用 @SpringBootApplication public class SpringbootApplication { public static void main(String[] args) { //以为是启动了一个方法,没想到启动了一个服务 SpringApplication.run(SpringbootApplication.class, args); } }但是一个简单的启动类并不简单!我们来分析一下这些注解都干了什么@SpringBootApplication作用:标注在某个类上说明这个类是SpringBoot的主配置类 , SpringBoot就应该运行这个类的main方法来启动SpringBoot应用;进入这个注解:可以看到上面还有很多其他注解!//@SpringBootApplication 来标注一个主程序类 //说明这是一个Spring Boot应用 @SpringBootApplication public class SpringbootApplication { public static void main(String[] args) { //以为是启动了一个方法,没想到启动了一个服务 SpringApplication.run(SpringbootApplication.class, args); } }@ComponentScan这个注解在Spring中很重要 ,它对应XML配置中的元素。作用:自动扫描并加载符合条件的组件或者bean , 将这个bean定义加载到IOC容器中@SpringBootConfiguration作用:SpringBoot的配置类 ,标注在某个类上 , 表示这是一个SpringBoot的配置类;我们继续进去这个注解查看// 点进去得到下面的 @Component @Configuration public @interface SpringBootConfiguration {} @Component public @interface Configuration {}这里的 @Configuration,说明这是一个配置类 ,配置类就是对应Spring的xml 配置文件;里面的 @Component 这就说明,启动类本身也是Spring中的一个组件而已,负责启动应用!我们回到 SpringBootApplication 注解中继续看。@EnableAutoConfiguration@EnableAutoConfiguration :开启自动配置功能以前我们需要自己配置的东西,而现在SpringBoot可以自动帮我们配置 ;@EnableAutoConfiguration告诉SpringBoot开启自动配置功能,这样自动配置才能生效;点进注解接续查看:@AutoConfigurationPackage :自动配置包@Import({Registrar.class})public @interface AutoConfigurationPackage {}@import :Spring底层注解@import , 给容器中导入一个组件Registrar.class 作用:将主启动类的所在包及包下面所有子包里面的所有组件扫描到Spring容器 ;这个分析完了,退到上一步,继续看@Import({AutoConfigurationImportSelector.class}) :给容器导入组件 ;AutoConfigurationImportSelector :自动配置导入选择器,那么它会导入哪些组件的选择器呢?我们点击去这个类看源码:1、这个类中有一个这样的方法// 获得候选的配置 protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) { //这里的getSpringFactoriesLoaderFactoryClass()方法 //返回的就是我们最开始看的启动自动导入配置文件的注解类;EnableAutoConfiguration List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.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; }2、这个方法又调用了 SpringFactoriesLoader 类的静态方法!我们进入SpringFactoriesLoader类loadFactoryNames() 方法public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) { String factoryClassName = factoryClass.getName(); //这里它又调用了 loadSpringFactories 方法 return (List)loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList()); }3、我们继续点击查看 loadSpringFactories 方法private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) { //获得classLoader , 我们返回可以看到这里得到的就是EnableAutoConfiguration标注的类本身 MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader); if (result != null) { return result; } else { try { //去获取一个资源 "META-INF/spring.factories" Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories"); LinkedMultiValueMap result = new LinkedMultiValueMap(); //将读取到的资源遍历,封装成为一个Properties while(urls.hasMoreElements()) { URL url = (URL)urls.nextElement(); UrlResource resource = new UrlResource(url); Properties properties = PropertiesLoaderUtils.loadProperties(resource); Iterator var6 = properties.entrySet().iterator(); while(var6.hasNext()) { Entry<?, ?> entry = (Entry)var6.next(); String factoryClassName = ((String)entry.getKey()).trim(); String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue()); int var10 = var9.length; for(int var11 = 0; var11 < var10; ++var11) { String factoryName = var9[var11]; result.add(factoryClassName, factoryName.trim()); } } } cache.put(classLoader, result); return result; } catch (IOException var13) { throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13); } } }4、发现一个多次出现的文件:spring.factories,全局搜索它spring.factories我们根据源头打开spring.factories , 看到了很多自动配置的文件;这就是自动配置根源所在!WebMvcAutoConfiguration我们在上面的自动配置类随便找一个打开看看,比如 :WebMvcAutoConfiguration可以看到这些一个个的都是JavaConfig配置类,而且都注入了一些Bean,可以找一些自己认识的类,看着熟悉一下!所以,自动配置真正实现是从classpath中搜寻所有的META-INF/spring.factories配置文件 ,并将其中对应的 org.springframework.boot.autoconfigure. 包下的配置项,通过反射实例化为对应标注了 @Configuration的JavaConfig形式的IOC容器配置类 , 然后将这些都汇总成为一个实例并加载到IOC容器中。结论:SpringBoot在启动的时候从类路径下的META-INF/spring.factories中获取EnableAutoConfiguration指定的值将这些值作为自动配置类导入容器 , 自动配置类就生效 , 帮我们进行自动配置工作;整个J2EE的整体解决方案和自动配置都在springboot-autoconfigure的jar包中;它会给容器中导入非常多的自动配置类 (xxxAutoConfiguration), 就是给容器中导入这个场景需要的所有组件 , 并配置好这些组件 ;有了自动配置类 , 免去了我们手动编写配置注入功能组件等的工作;现在大家应该大概的了解了下,SpringBoot的运行原理,后面我们还会深化一次!3.3 SpringApplication不简单的方法我最初以为就是运行了一个main方法,没想到却开启了一个服务;@SpringBootApplication public class SpringbootApplication { public static void main(String[] args) { SpringApplication.run(SpringbootApplication.class, args); } }SpringApplication.run分析分析该方法主要分两部分,一部分是SpringApplication的实例化,二是run方法的执行;SpringApplication这个类主要做了以下四件事情:1、推断应用的类型是普通的项目还是Web项目2、查找并加载所有可用初始化器 , 设置到initializers属性中3、找出所有的应用程序监听器,设置到listeners属性中4、推断并设置main方法的定义类,找到运行的主类查看构造器:public SpringApplication(ResourceLoader resourceLoader, Class... primarySources) { // ...... this.webApplicationType = WebApplicationType.deduceFromClasspath(); this.setInitializers(this.getSpringFactoriesInstances(); this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class)); this.mainApplicationClass = this.deduceMainApplicationClass(); }3.4 run方法流程分析4. yaml语法学习4.1 配置文件SpringBoot使用一个全局的配置文件 , 配置文件名称是固定的application.properties语法结构 :key=valueapplication.yml语法结构 :key:空格 value配置文件的作用 :修改SpringBoot自动配置的默认值,因为SpringBoot在底层都给我们自动配置好了;比如我们可以在配置文件中修改Tomcat 默认启动的端口号!测试一下!server.port=80814.2 yaml概述YAML是 "YAML Ain't a Markup Language" (YAML不是一种标记语言)的递归缩写。在开发的这种语言时,YAML 的意思其实是:"Yet Another Markup Language"(仍是一种标记语言)这种语言以数据作为中心,而不是以标记语言为重点!以前的配置文件,大多数都是使用xml来配置;比如一个简单的端口配置,我们来对比下yaml和xml传统xml配置:<server> <port>8081<port> </server>yaml配置:server: prot: 80804.3 yaml基础语法说明:语法要求严格!1、空格不能省略2、以缩进来控制层级关系,只要是左边对齐的一列数据都是同一个层级的。3、属性和值的大小写都是十分敏感的。字面量:普通的值 [ 数字,布尔值,字符串 ]字面量直接写在后面就可以 , 字符串默认不用加上双引号或者单引号;k: v注意:“ ” 双引号,不会转义字符串里面的特殊字符 , 特殊字符会作为本身想表示的意思;比如 :name: "kuang \n shen" 输出 :kuang 换行 shen'' 单引号,会转义特殊字符 , 特殊字符最终会变成和普通字符一样输出比如 :name: ‘kuang \n shen’ 输出 :kuang \n shen对象、Map(键值对)#对象、Map格式 k: v1: v2:在下一行来写对象的属性和值得关系,注意缩进;比如:student: name: qinjiang age: 3行内写法student: {name: qinjiang,age: 3}数组( List、set )用 - 值表示数组中的一个元素,比如:pets: - cat - dog - pig行内写法pets: [cat,dog,pig]修改SpringBoot的默认端口号配置文件中添加,端口号的参数,就可以切换端口;server: port: 80825.注入配置文件yaml文件更强大的地方在于,他可以给我们的实体类直接注入匹配值!5.1 yaml注入配置文件1、在springboot项目中的resources目录下新建一个文件 application.yml2、编写一个实体类 Dog;package com.kuang.springboot.pojo; @Component //注册bean到容器中 public class Dog { private String name; private Integer age; //有参无参构造、get、set方法、toString()方法 }3、思考,我们原来是如何给bean注入属性值的!@Value,给狗狗类测试一下:@Component //注册bean public class Dog { @Value("阿黄") private String name; @Value("18") private Integer age; }4、在SpringBoot的测试类下注入狗狗输出一下;@SpringBootTest class DemoApplicationTests { @Autowired //将狗狗自动注入进来 Dog dog; @Test public void contextLoads() { System.out.println(dog); //打印看下狗狗对象 } }结果成功输出,@Value注入成功,这是我们原来的办法对吧。5、我们在编写一个复杂一点的实体类:Person 类@Component //注册bean到容器中 public class Person { private String name; private Integer age; private Boolean happy; private Date birth; private Map<String,Object> maps; private List<Object> lists; private Dog dog; //有参无参构造、get、set方法、toString()方法 }6、我们来使用yaml配置的方式进行注入,大家写的时候注意区别和优势,我们编写一个yaml配置!person: name: qinjiang age: 3 happy: false birth: 2000/01/01 maps: {k1: v1,k2: v2} lists: - code - girl - music dog: name: 旺财 age: 17、我们刚才已经把person这个对象的所有值都写好了,我们现在来注入到我们的类中!/* @ConfigurationProperties作用: 将配置文件中配置的每一个属性的值,映射到这个组件中; 告诉SpringBoot将本类中的所有属性和配置文件中相关的配置进行绑定 参数 prefix = “person” : 将配置文件中的person下面的所有属性一一对应 */ @Component //注册bean @ConfigurationProperties(prefix = "person") public class Person { private String name; private Integer age; private Boolean happy; private Date birth; private Map<String,Object> maps; private List<Object> lists; private Dog dog; }8、IDEA 提示,springboot配置注解处理器没有找到,让我们看文档,我们可以查看文档,找到一个依赖!<!-- 导入配置文件处理器,配置文件进行绑定就会有提示,需要重启 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> </dependency>9、确认以上配置都OK之后,我们去测试类中测试一下:@SpringBootTest class DemoApplicationTests { @Autowired Person person; //将person自动注入进来 @Test public void contextLoads() { System.out.println(person); //打印person信息 } }结果:所有值全部注入成功!yaml配置注入到实体类完全OK!课堂测试:1、将配置文件的key 值 和 属性的值设置为不一样,则结果输出为null,注入失败2、在配置一个person2,然后将 @ConfigurationProperties(prefix = "person2") 指向我们的person2;5.2 加载指定的配置文件@PropertySource :加载指定的配置文件;@configurationProperties:默认从全局配置文件中获取值;1、我们去在resources目录下新建一个person.properties文件name=kuangshen2、然后在我们的代码中指定加载person.properties文件@PropertySource(value = "classpath:person.properties") @Component //注册bean public class Person { @Value("${name}") private String name; ...... }3、再次输出测试一下:指定配置文件绑定成功!5.3 配置文件占位符配置文件还可以编写占位符生成随机数person: name: qinjiang${random.uuid} # 随机uuid age: ${random.int} # 随机int happy: false birth: 2000/01/01 maps: {k1: v1,k2: v2} lists: - code - girl - music dog: name: ${person.hello:other}_旺财 age: 15.4 回顾properties配置我们上面采用的yaml方法都是最简单的方式,开发中最常用的;也是springboot所推荐的!那我们来唠唠其他的实现方式,道理都是相同的;写还是那样写;配置文件除了yml还有我们之前常用的properties , 我们没有讲,我们来唠唠!【注意】properties配置文件在写中文的时候,会有乱码 , 我们需要去IDEA中设置编码格式为UTF-8;settings-->FileEncodings 中配置;测试步骤:1、新建一个实体类User@Component //注册bean public class User { private String name; private int age; private String sex; }2、编辑配置文件 user.propertiesuser1.name=kuangshen user1.age=18 user1.sex=男3、我们在User类上使用@Value来进行注入!@Component //注册bean @PropertySource(value = "classpath:user.properties") public class User { //直接使用@value @Value("${user.name}") //从配置文件中取值 private String name; @Value("#{9*2}") // #{SPEL} Spring表达式 private int age; @Value("男") // 字面量 private String sex; }4、Springboot测试@SpringBootTest class DemoApplicationTests { @Autowired User user; @Test public void contextLoads() { System.out.println(user); } }结果正常输出:5.5 对比小结@Value这个使用起来并不友好!我们需要为每个属性单独注解赋值,比较麻烦;我们来看个功能对比图1、@ConfigurationProperties只需要写一次即可 , @Value则需要每个字段都添加2、松散绑定:这个什么意思呢? 比如我的yml中写的last-name,这个和lastName是一样的, - 后面跟着的字母默认是大写的。这就是松散绑定。可以测试一下3、JSR303数据校验 , 这个就是我们可以在字段是增加一层过滤器验证 , 可以保证数据的合法性4、复杂类型封装,yml中可以封装对象 , 使用value就不支持结论:配置yml和配置properties都可以获取到值 , 强烈推荐 yml;如果我们在某个业务中,只需要获取配置文件中的某个值,可以使用一下 @value;如果说,我们专门编写了一个JavaBean来和配置文件进行一一映射,就直接@configurationProperties,不要犹豫!6.JSR303数据校验6.1 先看看如何使用Springboot中可以用@validated来校验数据,如果数据异常则会统一抛出异常,方便异常中心统一处理。我们这里来写个注解让我们的name只能支持Email格式;@Component //注册bean @ConfigurationProperties(prefix = "person") @Validated //数据校验 public class Person { @Email(message="邮箱格式错误") //name必须是邮箱格式 private String name; }运行结果 :default message [不是一个合法的电子邮件地址];使用数据校验,可以保证数据的正确性;6.2 常见参数@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 对象是否符合正则表达式的规则 .......等等 除此以外,我们还可以自定义一些数据校验规则7.多环境切换profile是Spring对不同环境提供不同配置功能的支持,可以通过激活不同的环境版本,实现快速切换环境;7.1 多配置文件我们在主配置文件编写的时候,文件名可以是 application-{profile}.properties/yml , 用来指定多个环境版本;例如:application-test.properties 代表测试环境配置application-dev.properties 代表开发环境配置但是Springboot并不会直接启动这些配置文件,它默认使用application.properties主配置文件;我们需要通过一个配置来选择需要激活的环境:#比如在配置文件中指定使用dev环境,我们可以通过设置不同的端口号进行测试; #我们启动SpringBoot,就可以看到已经切换到dev下的配置了; spring.profiles.active=dev7.2 yaml的多文档块和properties配置文件中一样,但是使用yml去实现不需要创建多个配置文件,更加方便了 !server: port: 8081 #选择要激活那个环境块 spring: profiles: active: prod --- server: port: 8083 spring: profiles: dev #配置环境的名称 --- server: port: 8084 spring: profiles: prod #配置环境的名称注意:如果yml和properties同时都配置了端口,并且没有激活其他环境 , 默认会使用properties配置文件的!7.3 配置文件加载位置外部加载配置文件的方式十分多,我们选择最常用的即可,在开发的资源文件中进行配置!官方外部配置文件说明参考文档springboot 启动会扫描以下位置的application.properties或者application.yml文件作为Spring boot的默认配置文件:优先级1:项目路径下的config文件夹配置文件 优先级2:项目路径下配置文件 优先级3:资源路径下的config文件夹配置文件 优先级4:资源路径下配置文件优先级由高到底,高优先级的配置会覆盖低优先级的配置;SpringBoot会从这四个位置全部加载主配置文件;互补配置;我们在最低级的配置文件中设置一个项目访问路径的配置来测试互补问题;#配置项目的访问路径 server.servlet.context-path=/kuang7.4 拓展,运维小技巧指定位置加载配置文件我们还可以通过spring.config.location来改变默认的配置文件位置项目打包好以后,我们可以使用命令行参数的形式,启动项目的时候来指定配置文件的新位置;这种情况,一般是后期运维做的多,相同配置,外部指定的配置文件优先级最高java -jar spring-boot-config.jar --spring.config.location=F:/application.properties8.自动配置原理配置文件到底能写什么?怎么写?SpringBoot官方文档中有大量的配置,我们无法全部记住8.1 分析自动配置原理我们以HttpEncodingAutoConfiguration(Http编码自动配置)为例解释自动配置原理;//表示这是一个配置类,和以前编写的配置文件一样,也可以给容器中添加组件; @Configuration //启动指定类的ConfigurationProperties功能; //进入这个HttpProperties查看,将配置文件中对应的值和HttpProperties绑定起来; //并把HttpProperties加入到ioc容器中 @EnableConfigurationProperties({HttpProperties.class}) //Spring底层@Conditional注解 //根据不同的条件判断,如果满足指定的条件,整个配置类里面的配置就会生效; //这里的意思就是判断当前应用是否是web应用,如果是,当前配置类生效 @ConditionalOnWebApplication( type = Type.SERVLET ) //判断当前项目有没有这个类CharacterEncodingFilter;SpringMVC中进行乱码解决的过滤器; @ConditionalOnClass({CharacterEncodingFilter.class}) //判断配置文件中是否存在某个配置:spring.http.encoding.enabled; //如果不存在,判断也是成立的 //即使我们配置文件中不配置pring.http.encoding.enabled=true,也是默认生效的; @ConditionalOnProperty( prefix = "spring.http.encoding", value = {"enabled"}, matchIfMissing = true ) public class HttpEncodingAutoConfiguration { //他已经和SpringBoot的配置文件映射了 private final Encoding properties; //只有一个有参构造器的情况下,参数的值就会从容器中拿 public HttpEncodingAutoConfiguration(HttpProperties properties) { this.properties = properties.getEncoding(); } //给容器中添加一个组件,这个组件的某些值需要从properties中获取 @Bean @ConditionalOnMissingBean //判断容器没有这个组件? public CharacterEncodingFilter characterEncodingFilter() { CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter(); filter.setEncoding(this.properties.getCharset().name()); filter.setForceRequestEncoding(this.properties.shouldForce(org.springframework.boot.autoconfigure.http.HttpProperties.Encoding.Type.REQUEST)); filter.setForceResponseEncoding(this.properties.shouldForce(org.springframework.boot.autoconfigure.http.HttpProperties.Encoding.Type.RESPONSE)); return filter; } //。。。。。。。 }一句话总结 :根据当前不同的条件判断,决定这个配置类是否生效!一但这个配置类生效;这个配置类就会给容器中添加各种组件;这些组件的属性是从对应的properties类中获取的,这些类里面的每一个属性又是和配置文件绑定的;所有在配置文件中能配置的属性都是在xxxxProperties类中封装着;配置文件能配置什么就可以参照某个功能对应的这个属性类//从配置文件中获取指定的值和bean的属性进行绑定 @ConfigurationProperties(prefix = "spring.http") public class HttpProperties { // ..... }我们去配置文件里面试试前缀,看提示!这就是自动装配的原理!8.2 精髓1、SpringBoot启动会加载大量的自动配置类2、我们看我们需要的功能有没有在SpringBoot默认写好的自动配置类当中;3、我们再来看这个自动配置类中到底配置了哪些组件;(只要我们要用的组件存在在其中,我们就不需要再手动配置了)4、给容器中自动配置类添加组件的时候,会从properties类中获取某些属性。我们只需要在配置文件中指定这些属性的值即可;xxxxAutoConfigurartion:自动配置类;给容器中添加组件xxxxProperties:封装配置文件中相关属性;8.3 了解:@Conditional了解完自动装配的原理后,我们来关注一个细节问题,自动配置类必须在一定的条件下才能生效;@Conditional派生注解(Spring注解版原生的@Conditional作用)作用:必须是@Conditional指定的条件成立,才给容器中添加组件,配置配里面的所有内容才生效;那么多的自动配置类,必须在一定的条件下才能生效;也就是说,我们加载了这么多的配置类,但不是所有的都生效了。我们怎么知道哪些自动配置类生效?我们可以通过启用 debug=true属性;来让控制台打印自动配置报告,这样我们就可以很方便的知道哪些自动配置类生效;#开启springboot的调试类 debug=truePositive matches:(自动配置类启用的:正匹配)Negative matches:(没有启动,没有匹配成功的自动配置类:负匹配)Unconditional classes: (没有条件的类)【演示:查看输出的日志】9.整合JDBC9.1 SpringData简介对于数据访问层,无论是 SQL(关系型数据库) 还是 NOSQL(非关系型数据库),Spring Boot 底层都是采用 Spring Data 的方式进行统一处理。Spring Boot 底层都是采用 Spring Data 的方式进行统一处理各种数据库,Spring Data 也是 Spring 中与 Spring Boot、Spring Cloud 等齐名的知名项目。Sping Data 官网:https://spring.io/projects/spring-data数据库相关的启动器 :可以参考官方文档:https://docs.spring.io/spring-boot/docs/2.2.5.RELEASE/reference/htmlsingle/#using-boot-starter9.2 创建测试项目测试数据源1、我去新建一个项目测试:springboot-data-jdbc ; 引入相应的模块!基础模块2、项目建好之后,发现自动帮我们导入了如下的启动器:<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency>3、编写yaml配置文件连接数据库;spring: datasource: username: root password: 123456 #?serverTimezone=UTC解决时区的报错 url: jdbc:mysql://localhost:3306/springboot?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8 driver-class-name: com.mysql.cj.jdbc.Driver4、配置完这一些东西后,我们就可以直接去使用了,因为SpringBoot已经默认帮我们进行了自动配置;去测试类测试一下@SpringBootTest class SpringbootDataJdbcApplicationTests { //DI注入数据源 @Autowired DataSource dataSource; @Test public void contextLoads() throws SQLException { //看一下默认数据源 System.out.println(dataSource.getClass()); //获得连接 Connection connection = dataSource.getConnection(); System.out.println(connection); //关闭连接 connection.close(); } }结果:我们可以看到他默认给我们配置的数据源为 : class com.zaxxer.hikari.HikariDataSource , 我们并没有手动配置我们来全局搜索一下,找到数据源的所有自动配置都在 :DataSourceAutoConfiguration文件:@Import( {Hikari.class, Tomcat.class, Dbcp2.class, Generic.class, DataSourceJmxConfiguration.class} ) protected static class PooledDataSourceConfiguration { protected PooledDataSourceConfiguration() { } }这里导入的类都在 DataSourceConfiguration 配置类下,可以看出 Spring Boot 2.2.5 默认使用HikariDataSource 数据源,而以前版本,如 Spring Boot 1.5 默认使用 org.apache.tomcat.jdbc.pool.DataSource 作为数据源;HikariDataSource 号称 Java WEB 当前速度最快的数据源,相比于传统的 C3P0 、DBCP、Tomcat jdbc 等连接池更加优秀;可以使用 spring.datasource.type 指定自定义的数据源类型,值为 要使用的连接池实现的完全限定名。9.3 JDBCTemplate1、有了数据源(com.zaxxer.hikari.HikariDataSource),然后可以拿到数据库连接(java.sql.Connection),有了连接,就可以使用原生的 JDBC 语句来操作数据库;2、即使不使用第三方第数据库操作框架,如 MyBatis等,Spring 本身也对原生的JDBC 做了轻量级的封装,即JdbcTemplate。3、数据库操作的所有 CRUD 方法都在 JdbcTemplate 中。4、Spring Boot 不仅提供了默认的数据源,同时默认已经配置好了 JdbcTemplate 放在了容器中,程序员只需自己注入即可使用5、JdbcTemplate 的自动配置是依赖 org.springframework.boot.autoconfigure.jdbc 包下的 JdbcTemplateConfiguration 类JdbcTemplate主要提供以下几类方法:execute方法:可以用于执行任何SQL语句,一般用于执行DDL语句;update方法及batchUpdate方法:update方法用于执行新增、修改、删除等语句;batchUpdate方法用于执行批处理相关语句;query方法及queryForXXX方法:用于执行查询相关语句;call方法:用于执行存储过程、函数相关语句。9.4 测试编写一个Controller,注入 jdbcTemplate,编写测试方法进行访问测试;package com.kuang.controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.Date; import java.util.List; import java.util.Map; @RestController @RequestMapping("/jdbc") public class JdbcController { /** * Spring Boot 默认提供了数据源,默认提供了 org.springframework.jdbc.core.JdbcTemplate * JdbcTemplate 中会自己注入数据源,用于简化 JDBC操作 * 还能避免一些常见的错误,使用起来也不用再自己来关闭数据库连接 */ @Autowired JdbcTemplate jdbcTemplate; //查询employee表中所有数据 //List 中的1个 Map 对应数据库的 1行数据 //Map 中的 key 对应数据库的字段名,value 对应数据库的字段值 @GetMapping("/list") public List<Map<String, Object>> userList(){ String sql = "select * from employee"; List<Map<String, Object>> maps = jdbcTemplate.queryForList(sql); return maps; } //新增一个用户 @GetMapping("/add") public String addUser(){ //插入语句,注意时间问题 String sql = "insert into employee(last_name, email,gender,department,birth)" + " values ('狂神说','24736743@qq.com',1,101,'"+ new Date().toLocaleString() +"')"; jdbcTemplate.update(sql); //查询 return "addOk"; } //修改用户信息 @GetMapping("/update/{id}") public String updateUser(@PathVariable("id") int id){ //插入语句 String sql = "update employee set last_name=?,email=? where id="+id; //数据 Object[] objects = new Object[2]; objects[0] = "秦疆"; objects[1] = "24736743@sina.com"; jdbcTemplate.update(sql,objects); //查询 return "updateOk"; } //删除用户 @GetMapping("/delete/{id}") public String delUser(@PathVariable("id") int id){ //插入语句 String sql = "delete from employee where id=?"; jdbcTemplate.update(sql,id); //查询 return "deleteOk"; } }测试请求,结果正常;10.整合Druid10.1 Druid简介Java程序很大一部分要操作数据库,为了提高性能操作数据库的时候,又不得不使用数据库连接池。Druid 是阿里巴巴开源平台上一个数据库连接池实现,结合了 C3P0、DBCP 等 DB 池的优点,同时加入了日志监控。Druid 可以很好的监控 DB 池连接和 SQL 的执行情况,天生就是针对监控而生的 DB 连接池。Druid已经在阿里巴巴部署了超过600个应用,经过一年多生产环境大规模部署的严苛考验。Spring Boot 2.0 以上默认使用 Hikari 数据源,可以说 Hikari 与 Driud 都是当前 Java Web 上最优秀的数据源,我们来重点介绍 Spring Boot 如何集成 Druid 数据源,如何实现数据库监控。Github地址:https://github.com/alibaba/druid/com.alibaba.druid.pool.DruidDataSource 基本配置参数如下:10.2 配置数据源1、添加上 Druid 数据源依赖。<!-- https://mvnrepository.com/artifact/com.alibaba/druid --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.21</version> </dependency>2、切换数据源;之前已经说过 Spring Boot 2.0 以上默认使用 com.zaxxer.hikari.HikariDataSource 数据源,但可以 通过 spring.datasource.type 指定数据源。spring: datasource: username: root password: 123456 url: jdbc:mysql://localhost:3306/springboot?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8 driver-class-name: com.mysql.cj.jdbc.Driver type: com.alibaba.druid.pool.DruidDataSource # 自定义数据源3、数据源切换之后,在测试类中注入 DataSource,然后获取到它,输出一看便知是否成功切换;4、切换成功!既然切换成功,就可以设置数据源连接初始化大小、最大连接数、等待时间、最小连接数 等设置项;可以查看源码 spring: datasource: username: root password: 123456 #?serverTimezone=UTC解决时区的报错 url: jdbc:mysql://localhost:3306/springboot?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=5005、导入Log4j 的依赖<!-- https://mvnrepository.com/artifact/log4j/log4j --> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency>6、现在需要程序员自己为 DruidDataSource 绑定全局配置文件中的参数,再添加到容器中,而不再使用 Spring Boot 的自动生成了;我们需要 自己添加 DruidDataSource 组件到容器中,并绑定属性;package com.kuang.config; import com.alibaba.druid.pool.DruidDataSource; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import javax.sql.DataSource; @Configuration public class DruidConfig { /* 将自定义的 Druid数据源添加到容器中,不再让 Spring Boot 自动创建 绑定全局配置文件中的 druid 数据源属性到 com.alibaba.druid.pool.DruidDataSource从而让它们生效 @ConfigurationProperties(prefix = "spring.datasource"):作用就是将 全局配置文件中 前缀为 spring.datasource的属性值注入到 com.alibaba.druid.pool.DruidDataSource 的同名参数中 */ @ConfigurationProperties(prefix = "spring.datasource") @Bean public DataSource druidDataSource() { return new DruidDataSource(); } }7、去测试类中测试一下;看是否成功!@SpringBootTest class SpringbootDataJdbcApplicationTests { //DI注入数据源 @Autowired DataSource dataSource; @Test public void contextLoads() throws SQLException { //看一下默认数据源 System.out.println(dataSource.getClass()); //获得连接 Connection connection = dataSource.getConnection(); System.out.println(connection); DruidDataSource druidDataSource = (DruidDataSource) dataSource; System.out.println("druidDataSource 数据源最大连接数:" + druidDataSource.getMaxActive()); System.out.println("druidDataSource 数据源初始化连接数:" + druidDataSource.getInitialSize()); //关闭连接 connection.close(); } }输出结果 :可见配置参数已经生效!10.3 配置Druid数据源监控Druid 数据源具有监控的功能,并提供了一个 web 界面方便用户查看,类似安装 路由器 时,人家也提供了一个默认的 web 页面。所以第一步需要设置 Druid 的后台管理页面,比如 登录账号、密码 等;配置后台管理; //配置 Druid 监控管理后台的Servlet; //内置 Servlet 容器时没有web.xml文件,所以使用 Spring Boot 的注册 Servlet 方式 @Bean public ServletRegistrationBean statViewServlet() { ServletRegistrationBean bean = new ServletRegistrationBean(new StatViewServlet(), "/druid/*"); // 这些参数可以在 com.alibaba.druid.support.http.StatViewServlet // 的父类 com.alibaba.druid.support.http.ResourceServlet 中找到 Map<String, String> initParams = new HashMap<>(); initParams.put("loginUsername", "admin"); //后台管理界面的登录账号 initParams.put("loginPassword", "123456"); //后台管理界面的登录密码 //后台允许谁可以访问 //initParams.put("allow", "localhost"):表示只有本机可以访问 //initParams.put("allow", ""):为空或者为null时,表示允许所有访问 initParams.put("allow", ""); //deny:Druid 后台拒绝谁访问 //initParams.put("kuangshen", "192.168.1.20");表示禁止此ip访问 //设置初始化参数 bean.setInitParameters(initParams); return bean; }配置完毕后,我们可以选择访问 :http://localhost:8080/druid/login.html进入之后配置 Druid web 监控 filter 过滤器//配置 Druid 监控 之 web 监控的 filter //WebStatFilter:用于配置Web和Druid数据源之间的管理关联监控统计 @Bean public FilterRegistrationBean webStatFilter() { FilterRegistrationBean bean = new FilterRegistrationBean(); bean.setFilter(new WebStatFilter()); //exclusions:设置哪些请求进行过滤排除掉,从而不进行统计 Map<String, String> initParams = new HashMap<>(); initParams.put("exclusions", "*.js,*.css,/druid/*,/jdbc/*"); bean.setInitParameters(initParams); //"/*" 表示过滤所有请求 bean.setUrlPatterns(Arrays.asList("/*")); return bean; }平时在工作中,按需求进行配置即可,主要用作监控!11.整合MyBatis11.1 整合测试1、导入 MyBatis 所需要的依赖<dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.1.1</version> </dependency>2、配置数据库连接信息(不变)spring: datasource: username: root password: 123456 #?serverTimezone=UTC解决时区的报错 url: jdbc:mysql://localhost:3306/springboot?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=5003、测试数据库是否连接成功!4、创建实体类,导入 Lombok!Department.javapackage com.kuang.pojo; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @Data @NoArgsConstructor @AllArgsConstructor public class Department { private Integer id; private String departmentName; }5、创建mapper目录以及对应的 Mapper 接口DepartmentMapper.java //@Mapper : 表示本类是一个 MyBatis 的 Mapper @Mapper @Repository public interface DepartmentMapper { // 获取所有部门信息 List<Department> getDepartments(); // 通过id获得部门 Department getDepartment(Integer id); }6、对应的Mapper映射文件DepartmentMapper.xml <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.kuang.mapper.DepartmentMapper"> <select id="getDepartments" resultType="Department"> select * from department; </select> <select id="getDepartment" resultType="Department" parameterType="int"> select * from department where id = #{id}; </select> </mapper>7、maven配置资源过滤问题<resources> <resource> <directory>src/main/java</directory> <includes> <include>**/*.xml</include> </includes> <filtering>true</filtering> </resource> </resources>8、编写部门的 DepartmentController 进行测试!@RestController public class DepartmentController { @Autowired DepartmentMapper departmentMapper; // 查询全部部门 @GetMapping("/getDepartments") public List<Department> getDepartments(){ return departmentMapper.getDepartments(); } // 查询全部部门 @GetMapping("/getDepartment/{id}") public Department getDepartment(@PathVariable("id") Integer id){ return departmentMapper.getDepartment(id); } }启动项目访问进行测试!11.2 增加一个员工类再测试下,为之后做准备1、新建一个pojo类 Employee ;@Data @AllArgsConstructor @NoArgsConstructor public class Employee { private Integer id; private String lastName; private String email; //1 male, 0 female private Integer gender; private Integer department; private Date birth; private Department eDepartment; // 冗余设计 }2、新建一个 EmployeeMapper 接口//@Mapper : 表示本类是一个 MyBatis 的 Mapper @Mapper @Repository public interface EmployeeMapper { // 获取所有员工信息 List<Employee> getEmployees(); // 新增一个员工 int save(Employee employee); // 通过id获得员工信息 Employee get(Integer id); // 通过id删除员工 int delete(Integer id); }3、编写 EmployeeMapper.xml 配置文件<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.kuang.mapper.EmployeeMapper"> <resultMap id="EmployeeMap" type="Employee"> <id property="id" column="eid"/> <result property="lastName" column="last_name"/> <result property="email" column="email"/> <result property="gender" column="gender"/> <result property="birth" column="birth"/> <association property="eDepartment" javaType="Department"> <id property="id" column="did"/> <result property="departmentName" column="dname"/> </association> </resultMap> <select id="getEmployees" resultMap="EmployeeMap"> select e.id as eid,last_name,email,gender,birth,d.id as did,d.department_name as dname from department d,employee e where d.id = e.department </select> <insert id="save" parameterType="Employee"> insert into employee (last_name,email,gender,department,birth) values (#{lastName},#{email},#{gender},#{department},#{birth}); </insert> <select id="get" resultType="Employee"> select * from employee where id = #{id} </select> <delete id="delete" parameterType="int"> delete from employee where id = #{id} </delete> </mapper>4、编写EmployeeController类进行测试@RestController public class EmployeeController { @Autowired EmployeeMapper employeeMapper; // 获取所有员工信息 @GetMapping("/getEmployees") public List<Employee> getEmployees(){ return employeeMapper.getEmployees(); } @GetMapping("/save") public int save(){ Employee employee = new Employee(); employee.setLastName("kuangshen"); employee.setEmail("qinjiang@qq.com"); employee.setGender(1); employee.setDepartment(101); employee.setBirth(new Date()); return employeeMapper.save(employee); } // 通过id获得员工信息 @GetMapping("/get/{id}") public Employee get(@PathVariable("id") Integer id){ return employeeMapper.get(id); } // 通过id删除员工 @GetMapping("/delete/{id}") public int delete(@PathVariable("id") Integer id){ return employeeMapper.delete(id); } }12.Web开发静态资源处理12.1 简介1、创建一个SpringBoot应用,选择我们需要的模块,SpringBoot就会默认将我们的需要的模块自动配置好2、手动在配置文件中配置部分配置项目就可以运行起来了3、专注编写业务代码,不需要考虑以前那样一大堆的配置了。要熟悉掌握开发,之前学习的自动配置的原理一定要搞明白!比如SpringBoot到底帮我们配置了什么?我们能不能修改?我们能修改哪些配置?我们能不能扩展?向容器中自动配置组件 : Autoconfiguration*自动配置类,封装配置文件的内容:Properties*没事就找找类,看看自动装配原理!我们之后来进行一个单体项目的小项目测试,让大家能够快速上手开发!12.2 静态资源映射规则首先,我们搭建一个普通的SpringBoot项目,回顾一下HelloWorld程序!写请求非常简单,那我们要引入我们前端资源,我们项目中有许多的静态资源,比如css,js等文件,这个SpringBoot怎么处理呢?如果我们是一个web应用,我们的main下会有一个webapp,我们以前都是将所有的页面导在这里面的,对吧!但是我们现在的pom呢,打包方式是为jar的方式,那么这种方式SpringBoot能不能来给我们写页面呢?当然是可以的,但是SpringBoot对于静态资源放置的位置,是有规定的!我们先来聊聊这个静态资源映射规则:SpringBoot中,SpringMVC的web配置都在 WebMvcAutoConfiguration 这个配置类里面;我们可以去看看 WebMvcAutoConfigurationAdapter 中有很多配置方法;有一个方法:addResourceHandlers 添加资源处理@Override public void addResourceHandlers(ResourceHandlerRegistry registry) { if (!this.resourceProperties.isAddMappings()) { // 已禁用默认资源处理 logger.debug("Default resource handling disabled"); return; } // 缓存控制 Duration cachePeriod = this.resourceProperties.getCache().getPeriod(); CacheControl cacheControl = this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl(); // webjars 配置 if (!registry.hasMappingForPattern("/webjars/**")) { customizeResourceHandlerRegistration(registry.addResourceHandler("/webjars/**") .addResourceLocations("classpath:/META-INF/resources/webjars/") .setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl)); } // 静态资源配置 String staticPathPattern = this.mvcProperties.getStaticPathPattern(); if (!registry.hasMappingForPattern(staticPathPattern)) { customizeResourceHandlerRegistration(registry.addResourceHandler(staticPathPattern) .addResourceLocations(getResourceLocations(this.resourceProperties.getStaticLocations())) .setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl)); } }读一下源代码:比如所有的 /webjars/** , 都需要去 classpath:/META-INF/resources/webjars/ 找对应的资源;12.3 什么是webjars 呢?Webjars本质就是以jar包的方式引入我们的静态资源 , 我们以前要导入一个静态资源文件,直接导入即可。使用SpringBoot需要使用Webjars,我们可以去搜索一下:网站:https://www.webjars.org 要使用jQuery,我们只要要引入jQuery对应版本的pom依赖即可!<dependency> <groupId>org.webjars</groupId> <artifactId>jquery</artifactId> <version>3.4.1</version> </dependency>导入完毕,查看webjars目录结构,并访问Jquery.js文件!访问:只要是静态资源,SpringBoot就会去对应的路径寻找资源,我们这里访问:http://localhost:8080/webjars/jquery/3.4.1/jquery.js12.4 第二种静态资源映射规则那我们项目中要是使用自己的静态资源该怎么导入呢?我们看下一行代码;我们去找staticPathPattern发现第二种映射规则 :/** , 访问当前的项目任意资源,它会去找 resourceProperties 这个类,我们可以点进去看一下分析:// 进入方法 public String[] getStaticLocations() { return this.staticLocations; } // 找到对应的值 private String[] staticLocations = CLASSPATH_RESOURCE_LOCATIONS; // 找到路径 private static final String[] CLASSPATH_RESOURCE_LOCATIONS = { "classpath:/META-INF/resources/", "classpath:/resources/", "classpath:/static/", "classpath:/public/" };ResourceProperties 可以设置和我们静态资源有关的参数;这里面指向了它会去寻找资源的文件夹,即上面数组的内容。所以得出结论,以下四个目录存放的静态资源可以被我们识别:"classpath:/META-INF/resources/" "classpath:/resources/" "classpath:/static/" "classpath:/public/"我们可以在resources根目录下新建对应的文件夹,都可以存放我们的静态文件;比如我们访问 http://localhost:8080/1.js , 他就会去这些文件夹中寻找对应的静态资源文件;12.5 自定义静态资源路径我们也可以自己通过配置文件来指定一下,哪些文件夹是需要我们放静态资源文件的,在application.properties中配置;spring.resources.static-locations=classpath:/coding/,classpath:/kuang/一旦自己定义了静态文件夹的路径,原来的自动配置就都会失效了!12.6 首页处理静态资源文件夹说完后,我们继续向下看源码!可以看到一个欢迎页的映射,就是我们的首页!@Bean public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext applicationContext, FormattingConversionService mvcConversionService, ResourceUrlProvider mvcResourceUrlProvider) { WelcomePageHandlerMapping welcomePageHandlerMapping = new WelcomePageHandlerMapping( new TemplateAvailabilityProviders(applicationContext), applicationContext, getWelcomePage(), // getWelcomePage 获得欢迎页 this.mvcProperties.getStaticPathPattern()); welcomePageHandlerMapping.setInterceptors(getInterceptors(mvcConversionService, mvcResourceUrlProvider)); return welcomePageHandlerMapping; }点进去继续看private Optional<Resource> getWelcomePage() { String[] locations = getResourceLocations(this.resourceProperties.getStaticLocations()); // ::是java8 中新引入的运算符 // Class::function的时候function是属于Class的,应该是静态方法。 // this::function的funtion是属于这个对象的。 // 简而言之,就是一种语法糖而已,是一种简写 return Arrays.stream(locations).map(this::getIndexHtml).filter(this::isReadable).findFirst(); } // 欢迎页就是一个location下的的 index.html 而已 private Resource getIndexHtml(String location) { return this.resourceLoader.getResource(location + "index.html"); }欢迎页,静态资源文件夹下的所有 index.html 页面;被 /** 映射。比如我访问 http://localhost:8080/ ,就会找静态资源文件夹下的 index.html新建一个 index.html ,在我们上面的3个目录中任意一个;然后访问测试 http://localhost:8080/ 看结果!12.7 关于网站图标说明:与其他静态资源一样,Spring Boot在配置的静态内容位置中查找 favicon.ico。如果存在这样的文件,它将自动用作应用程序的favicon。1、关闭SpringBoot默认图标#关闭默认图标 spring.mvc.favicon.enabled=false2、自己放一个图标在静态资源目录下,我放在 public 目录下3、清除浏览器缓存!刷新网页,发现图标已经变成自己的了!13.Thymeleaf模板引擎13.1 模板引擎前端交给我们的页面,是html页面。如果是我们以前开发,我们需要把他们转成jsp页面,jsp好处就是当我们查出一些数据转发到JSP页面以后,我们可以用jsp轻松实现数据的显示,及交互等。jsp支持非常强大的功能,包括能写Java代码,但是呢,我们现在的这种情况,SpringBoot这个项目首先是以jar的方式,不是war,像第二,我们用的还是嵌入式的Tomcat,所以呢,他现在默认是不支持jsp的。那不支持jsp,如果我们直接用纯静态页面的方式,那给我们开发会带来非常大的麻烦,那怎么办呢?SpringBoot推荐你可以来使用模板引擎:模板引擎,我们其实大家听到很多,其实jsp就是一个模板引擎,还有用的比较多的freemarker,包括SpringBoot给我们推荐的Thymeleaf,模板引擎有非常多,但再多的模板引擎,他们的思想都是一样的,什么样一个思想呢我们来看一下这张图:模板引擎的作用就是我们来写一个页面模板,比如有些值呢,是动态的,我们写一些表达式。而这些值,从哪来呢,就是我们在后台封装一些数据。然后把这个模板和这个数据交给我们模板引擎,模板引擎按照我们这个数据帮你把这表达式解析、填充到我们指定的位置,然后把这个数据最终生成一个我们想要的内容给我们写出去,这就是我们这个模板引擎,不管是jsp还是其他模板引擎,都是这个思想。只不过呢,就是说不同模板引擎之间,他们可能这个语法有点不一样。其他的我就不介绍了,我主要来介绍一下SpringBoot给我们推荐的Thymeleaf模板引擎,这模板引擎呢,是一个高级语言的模板引擎,他的这个语法更简单。而且呢,功能更强大。我们呢,就来看一下这个模板引擎,那既然要看这个模板引擎。首先,我们来看SpringBoot里边怎么用。13.2 引入Thymeleaf怎么引入呢,对于springboot来说,什么事情不都是一个start的事情嘛,我们去在项目中引入一下。给大家三个网址:Thymeleaf 官网:https://www.thymeleaf.org/Thymeleaf 在Github 的主页:https://github.com/thymeleaf/thymeleafSpring官方文档:找到我们对应的版本https://docs.spring.io/spring-boot/docs/2.2.5.RELEASE/reference/htmlsingle/#using-boot-starter 找到对应的pom依赖:可以适当点进源码看下本来的包!<!--thymeleaf--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency>Maven会自动下载jar包,我们可以去看下下载的东西;13.3 Thymeleaf分析前面呢,我们已经引入了Thymeleaf,那这个要怎么使用呢?我们首先得按照SpringBoot的自动配置原理看一下我们这个Thymeleaf的自动配置规则,在按照那个规则,我们进行使用。我们去找一下Thymeleaf的自动配置类:ThymeleafProperties@ConfigurationProperties( prefix = "spring.thymeleaf" ) public class ThymeleafProperties { private static final Charset DEFAULT_ENCODING; public static final String DEFAULT_PREFIX = "classpath:/templates/"; public static final String DEFAULT_SUFFIX = ".html"; private boolean checkTemplate = true; private boolean checkTemplateLocation = true; private String prefix = "classpath:/templates/"; private String suffix = ".html"; private String mode = "HTML"; private Charset encoding; }我们可以在其中看到默认的前缀和后缀!我们只需要把我们的html页面放在类路径下的templates下,thymeleaf就可以帮我们自动渲染了。使用thymeleaf什么都不需要配置,只需要将他放在指定的文件夹下即可!13.4 测试1、编写一个TestController@Controller public class TestController { @RequestMapping("/t1") public String test1(){ //classpath:/templates/test.html return "test"; } }2、编写一个测试页面 test.html 放在 templates 目录下<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>测试页面</h1> </body> </html>3、启动项目请求测试13.5 Thymeleaf 语法学习要学习语法,还是参考官网文档最为准确,我们找到对应的版本看一下;Thymeleaf 官网:https://www.thymeleaf.org/ , 简单看一下官网!我们去下载Thymeleaf的官方文档!我们做个最简单的练习 :我们需要查出一些数据,在页面中展示1、修改测试请求,增加数据传输;@RequestMapping("/t1") public String test1(Model model){ //存入数据 model.addAttribute("msg","Hello,Thymeleaf"); //classpath:/templates/test.html return "test"; }2、我们要使用thymeleaf,需要在html文件中导入命名空间的约束,方便提示。我们可以去官方文档的#3中看一下命名空间拿来过来:xmlns:th="http://www.thymeleaf.org"3、我们去编写下前端页面<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>狂神说</title> </head> <body> <h1>测试页面</h1> <!--th:text就是将div中的内容设置为它指定的值,和之前学习的Vue一样--> <div th:text="${msg}"></div> </body> </html>4、启动测试!OK,入门搞定,我们来认真研习一下Thymeleaf的使用语法!1、我们可以使用任意的 th:attr 来替换Html中原生属性的值!2、我们能写哪些表达式呢?Simple expressions:(表达式语法) Variable Expressions: ${...}:获取变量值;OGNL; 1)、获取对象的属性、调用方法 2)、使用内置的基本对象:#18 #ctx : the context object. #vars: the context variables. #locale : the context locale. #request : (only in Web Contexts) the HttpServletRequest object. #response : (only in Web Contexts) the HttpServletResponse object. #session : (only in Web Contexts) the HttpSession object. #servletContext : (only in Web Contexts) the ServletContext object. 3)、内置的一些工具对象: #execInfo : information about the template being processed. #uris : methods for escaping parts of URLs/URIs #conversions : methods for executing the configured conversion service (if any). #dates : methods for java.util.Date objects: formatting, component extraction, etc. #calendars : analogous to #dates , but for java.util.Calendar objects. #numbers : methods for formatting numeric objects. #strings : methods for String objects: contains, startsWith, prepending/appending, etc. #objects : methods for objects in general. #bools : methods for boolean evaluation. #arrays : methods for arrays. #lists : methods for lists. #sets : methods for sets. #maps : methods for maps. #aggregates : methods for creating aggregates on arrays or collections. ================================================================================== Selection Variable Expressions: *{...}:选择表达式:和${}在功能上是一样; Message Expressions: #{...}:获取国际化内容 Link URL Expressions: @{...}:定义URL; Fragment Expressions: ~{...}:片段引用表达式 Literals(字面量) Text literals: 'one text' , 'Another one!' ,… Number literals: 0 , 34 , 3.0 , 12.3 ,… Boolean literals: true , false Null literal: null Literal tokens: one , sometext , main ,… Text operations:(文本操作) String concatenation: + Literal substitutions: |The name is ${name}| Arithmetic operations:(数学运算) Binary operators: + , - , * , / , % Minus sign (unary operator): - Boolean operations:(布尔运算) Binary operators: and , or Boolean negation (unary operator): ! , not Comparisons and equality:(比较运算) Comparators: > , < , >= , <= ( gt , lt , ge , le ) Equality operators: == , != ( eq , ne ) Conditional operators:条件运算(三元运算符) If-then: (if) ? (then) If-then-else: (if) ? (then) : (else) Default: (value) ?: (defaultvalue) Special tokens: No-Operation: _13.5 练习测试1、 我们编写一个Controller,放一些数据@RequestMapping("/t2") public String test2(Map<String,Object> map){ //存入数据 map.put("msg","<h1>Hello</h1>"); map.put("users", Arrays.asList("qinjiang","kuangshen")); //classpath:/templates/test.html return "test"; }2、测试页面取出数据<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>狂神说</title> </head> <body> <h1>测试页面</h1> <div th:text="${msg}"></div> <!--不转义--> <div th:utext="${msg}"></div> <!--遍历数据--> <!--th:each每次遍历都会生成当前这个标签:官网#9--> <h4 th:each="user :${users}" th:text="${user}"></h4> <h4> <!--行内写法:官网#12--> <span th:each="user:${users}">[[${user}]]</span> </h4> </body> </html>3、启动项目测试!我们看完语法,很多样式,我们即使现在学习了,也会忘记,所以我们在学习过程中,需要使用什么,根据官方文档来查询,才是最重要的,要熟练使用官方文档!14.项目集成Swagger14.1 Swagger简介前后端分离前端 -> 前端控制层、视图层后端 -> 后端控制层、服务层、数据访问层前后端通过API进行交互前后端相对独立且松耦合产生的问题前后端集成,前端或者后端无法做到“及时协商,尽早解决”,最终导致问题集中爆发解决方案首先定义schema [ 计划的提纲 ],并实时跟踪最新的API,降低集成风险Swagger号称世界上最流行的API框架Restful Api 文档在线自动生成器 => API 文档 与API 定义同步更新直接运行,在线测试API支持多种语言 (如:Java,PHP等)官网:https://swagger.io/14.2 SpringBoot集成SwaggerSpringBoot集成Swagger => springfox,两个jar包Springfox-swagger2swagger-springmvc使用Swagger要求:jdk 1.8 + 否则swagger2无法运行步骤:1、新建一个SpringBoot-web项目2、添加Maven依赖<!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger2 --> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.9.2</version> </dependency> <!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger-ui --> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.9.2</version> </dependency>3、编写HelloController,测试确保运行成功!4、要使用Swagger,我们需要编写一个配置类-SwaggerConfig来配置 Swagger@Configuration //配置类 @EnableSwagger2// 开启Swagger2的自动配置 public class SwaggerConfig { }5、访问测试 :http://localhost:8080/swagger-ui.html ,可以看到swagger的界面;14.3 配置Swagger1、Swagger实例Bean是Docket,所以通过配置Docket实例来配置Swaggger。@Bean //配置docket以配置Swagger具体参数 public Docket docket() { return new Docket(DocumentationType.SWAGGER_2); }2、可以通过apiInfo()属性配置文档信息//配置文档信息 private ApiInfo apiInfo() { Contact contact = new Contact("联系人名字", "http://xxx.xxx.com/联系人访问链接", "联系人邮箱"); return new ApiInfo( "Swagger学习", // 标题 "学习演示如何配置Swagger", // 描述 "v1.0", // 版本 "http://terms.service.url/组织链接", // 组织链接 contact, // 联系人信息 "Apach 2.0 许可", // 许可 "许可链接", // 许可连接 new ArrayList<>()// 扩展 ); }3、Docket 实例关联上 apiInfo()@Bean public Docket docket() { return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo()); }4、重启项目,访问测试 http://localhost:8080/swagger-ui.html 看下效果;14.4 配置扫描接口1、构建Docket时通过select()方法配置怎么扫描接口。@Bean public Docket docket() { return new Docket(DocumentationType.SWAGGER_2) .apiInfo(apiInfo()) .select()// 通过.select()方法,去配置扫描接口,RequestHandlerSelectors配置如何扫描接口 .apis(RequestHandlerSelectors.basePackage("com.kuang.swagger.controller")) .build(); }2、重启项目测试,由于我们配置根据包的路径扫描接口,所以我们只能看到一个类3、除了通过包路径配置扫描接口外,还可以通过配置其他方式扫描接口,这里注释一下所有的配置方式:any() // 扫描所有,项目中的所有接口都会被扫描到 none() // 不扫描接口 // 通过方法上的注解扫描,如withMethodAnnotation(GetMapping.class)只扫描get请求 withMethodAnnotation(final Class<? extends Annotation> annotation) // 通过类上的注解扫描,如.withClassAnnotation(Controller.class)只扫描有controller注解的类中的接口 withClassAnnotation(final Class<? extends Annotation> annotation) basePackage(final String basePackage) // 根据包路径扫描接口4、除此之外,我们还可以配置接口扫描过滤:@Bean public Docket docket() { return new Docket(DocumentationType.SWAGGER_2) .apiInfo(apiInfo()) .select()// 通过.select()方法,去配置扫描接口,RequestHandlerSelectors配置如何扫描接口 .apis(RequestHandlerSelectors.basePackage("com.kuang.swagger.controller")) // 配置如何通过path过滤,即这里只扫描请求以/kuang开头的接口 .paths(PathSelectors.ant("/kuang/**")) .build(); }5、这里的可选值还有any() // 任何请求都扫描 none() // 任何请求都不扫描 regex(final String pathRegex) // 通过正则表达式控制 ant(final String antPattern) // 通过ant()控制14.5 配置Swagger开关1、通过enable()方法配置是否启用swagger,如果是false,swagger将不能在浏览器中访问了@Bean public Docket docket() { return new Docket(DocumentationType.SWAGGER_2) .apiInfo(apiInfo()) .enable(false) //配置是否启用Swagger,如果是false,在浏览器将无法访问 .select()// 通过.select()方法,去配置扫描接口,RequestHandlerSelectors配置如何扫描接口 .apis(RequestHandlerSelectors.basePackage("com.kuang.swagger.controller")) // 配置如何通过path过滤,即这里只扫描请求以/kuang开头的接口 .paths(PathSelectors.ant("/kuang/**")) .build(); }2、如何动态配置当项目处于test、dev环境时显示swagger,处于prod时不显示?@Bean public Docket docket(Environment environment) { // 设置要显示swagger的环境 Profiles of = Profiles.of("dev", "test"); // 判断当前是否处于该环境 // 通过 enable() 接收此参数判断是否要显示 boolean b = environment.acceptsProfiles(of); return new Docket(DocumentationType.SWAGGER_2) .apiInfo(apiInfo()) .enable(b) //配置是否启用Swagger,如果是false,在浏览器将无法访问 .select()// 通过.select()方法,去配置扫描接口,RequestHandlerSelectors配置如何扫描接口 .apis(RequestHandlerSelectors.basePackage("com.kuang.swagger.controller")) // 配置如何通过path过滤,即这里只扫描请求以/kuang开头的接口 .paths(PathSelectors.ant("/kuang/**")) .build(); }3、可以在项目中增加一个dev的配置文件查看效果!14.6 配置API分组1、如果没有配置分组,默认是default。通过groupName()方法即可配置分组:@Bean public Docket docket(Environment environment) { return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo()) .groupName("hello") // 配置分组 // 省略配置.... }2、重启项目查看分组3、如何配置多个分组?配置多个分组只需要配置多个docket即可:@Bean public Docket docket1(){ return new Docket(DocumentationType.SWAGGER_2).groupName("group1"); } @Bean public Docket docket2(){ return new Docket(DocumentationType.SWAGGER_2).groupName("group2"); } @Bean public Docket docket3(){ return new Docket(DocumentationType.SWAGGER_2).groupName("group3"); }4、重启项目查看即可14.7 实体配置1、新建一个实体类@ApiModel("用户实体") public class User { @ApiModelProperty("用户名") public String username; @ApiModelProperty("密码") public String password; }2、只要这个实体在请求接口的返回值上(即使是泛型),都能映射到实体项中:@RequestMapping("/getUser") public User getUser(){ return new User(); }3、重启查看测试注:并不是因为@ApiModel这个注解让实体显示在这里了,而是只要出现在接口方法的返回值上的实体都会显示在这里,而@ApiModel和@ApiModelProperty这两个注解只是为实体添加注释的。@ApiModel为类添加注释@ApiModelProperty为类属性添加注释14.8 常用注解Swagger的所有注解定义在io.swagger.annotations包下下面列一些经常用到的,未列举出来的可以另行查阅说明:Swagger注解简单说明@Api(tags = "xxx模块说明")作用在模块类上@ApiOperation("xxx接口说明")作用在接口方法上@ApiModel("xxxPOJO说明")作用在模型类上:如VO、BO@ApiModelProperty(value = "xxx属性说明",hidden = true)作用在类方法和属性上,hidden设置为true可以隐藏该属性@ApiParam("xxx参数说明")作用在参数、方法和字段上,类似@ApiModelProperty我们也可以给请求的接口配置一些注释@ApiOperation("狂神的接口") @PostMapping("/kuang") @ResponseBody public String kuang(@ApiParam("这个名字会被返回")String username){ return username; }这样的话,可以给一些比较难理解的属性或者接口,增加一些配置信息,让人更容易阅读!相较于传统的Postman或Curl方式测试接口,使用swagger简直就是傻瓜式操作,不需要额外说明文档(写得好本身就是文档)而且更不容易出错,只需要录入数据然后点击Execute,如果再配合自动化框架,可以说基本就不需要人为操作了。Swagger是个优秀的工具,现在国内已经有很多的中小型互联网公司都在使用它,相较于传统的要先出Word接口文档再测试的方式,显然这样也更符合现在的快速迭代开发行情。当然了,提醒下大家在正式环境要记得关闭Swagger,一来出于安全考虑二来也可以节省运行时内存。14.9 拓展:其他皮肤我们可以导入不同的包实现不同的皮肤定义:1、默认的 访问 http://localhost:8080/swagger-ui.html<dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.9.2</version> </dependency>2、bootstrap-ui 访问 http://localhost:8080/doc.html<!-- 引入swagger-bootstrap-ui包 /doc.html--> <dependency> <groupId>com.github.xiaoymin</groupId> <artifactId>swagger-bootstrap-ui</artifactId> <version>1.9.1</version> </dependency>3、Layui-ui 访问 http://localhost:8080/docs.html<!-- 引入swagger-ui-layer包 /docs.html--> <dependency> <groupId>com.github.caspar-chen</groupId> <artifactId>swagger-ui-layer</artifactId> <version>1.1.3</version> </dependency>4、mg-ui 访问 http://localhost:8080/document.html<!-- 引入swagger-ui-layer包 /document.html--> <dependency> <groupId>com.zyplayer</groupId> <artifactId>swagger-mg-ui</artifactId> <version>1.0.6</version> </dependency>15.异步、定时、邮件任务15.1 异步任务1、创建一个service包2、创建一个类AsyncService异步处理还是非常常用的,比如我们在网站上发送邮件,后台会去发送邮件,此时前台会造成响应不动,直到邮件发送完毕,响应才会成功,所以我们一般会采用多线程的方式去处理这些任务。编写方法,假装正在处理数据,使用线程设置一些延时,模拟同步等待的情况;@Service public class AsyncService { public void hello(){ try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("业务进行中...."); } }3、编写controller包4、编写AsyncController类我们去写一个Controller测试一下@RestController public class AsyncController { @Autowired AsyncService asyncService; @GetMapping("/hello") public String hello(){ asyncService.hello(); return "success"; } }5、访问http://localhost:8080/hello进行测试,3秒后出现success,这是同步等待的情况。问题:我们如果想让用户直接得到消息,就在后台使用多线程的方式进行处理即可,但是每次都需要自己手动去编写多线程的实现的话,太麻烦了,我们只需要用一个简单的办法,在我们的方法上加一个简单的注解即可,如下:6、给hello方法添加@Async注解;//告诉Spring这是一个异步方法 @Async public void hello(){ try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("业务进行中...."); }SpringBoot就会自己开一个线程池,进行调用!但是要让这个注解生效,我们还需要在主程序上添加一个注解@EnableAsync ,开启异步注解功能;@EnableAsync //开启异步注解功能 @SpringBootApplication public class SpringbootTaskApplication { public static void main(String[] args) { SpringApplication.run(SpringbootTaskApplication.class, args); } }7、重启测试,网页瞬间响应,后台代码依旧执行!15.2 定时任务项目开发中经常需要执行一些定时任务,比如需要在每天凌晨的时候,分析一次前一天的日志信息,Spring为我们提供了异步执行任务调度的方式,提供了两个接口。TaskExecutor接口TaskScheduler接口两个注解:@EnableScheduling@Scheduledcron表达式:测试步骤:1、创建一个ScheduledService我们里面存在一个hello方法,他需要定时执行,怎么处理呢?@Service public class ScheduledService { //秒 分 时 日 月 周几 //0 * * * * MON-FRI //注意cron表达式的用法; @Scheduled(cron = "0 * * * * 0-7") public void hello(){ System.out.println("hello....."); } }2、这里写完定时任务之后,我们需要在主程序上增加@EnableScheduling 开启定时任务功能@EnableAsync //开启异步注解功能 @EnableScheduling //开启基于注解的定时任务 @SpringBootApplication public class SpringbootTaskApplication { public static void main(String[] args) { SpringApplication.run(SpringbootTaskApplication.class, args); } }3、我们来详细了解下cron表达式;http://www.bejson.com/othertools/cron/4、常用的表达式(1)0/2 * * * * ? 表示每2秒 执行任务 (1)0 0/2 * * * ? 表示每2分钟 执行任务 (1)0 0 2 1 * ? 表示在每月的1日的凌晨2点调整任务 (2)0 15 10 ? * MON-FRI 表示周一到周五每天上午10:15执行作业 (3)0 15 10 ? 6L 2002-2006 表示2002-2006年的每个月的最后一个星期五上午10:15执行作 (4)0 0 10,14,16 * * ? 每天上午10点,下午2点,4点 (5)0 0/30 9-17 * * ? 朝九晚五工作时间内每半小时 (6)0 0 12 ? * WED 表示每个星期三中午12点 (7)0 0 12 * * ? 每天中午12点触发 (8)0 15 10 ? * * 每天上午10:15触发 (9)0 15 10 * * ? 每天上午10:15触发 (10)0 15 10 * * ? 每天上午10:15触发 (11)0 15 10 * * ? 2005 2005年的每天上午10:15触发 (12)0 * 14 * * ? 在每天下午2点到下午2:59期间的每1分钟触发 (13)0 0/5 14 * * ? 在每天下午2点到下午2:55期间的每5分钟触发 (14)0 0/5 14,18 * * ? 在每天下午2点到2:55期间和下午6点到6:55期间的每5分钟触发 (15)0 0-5 14 * * ? 在每天下午2点到下午2:05期间的每1分钟触发 (16)0 10,44 14 ? 3 WED 每年三月的星期三的下午2:10和2:44触发 (17)0 15 10 ? * MON-FRI 周一至周五的上午10:15触发 (18)0 15 10 15 * ? 每月15日上午10:15触发 (19)0 15 10 L * ? 每月最后一日的上午10:15触发 (20)0 15 10 ? * 6L 每月的最后一个星期五上午10:15触发 (21)0 15 10 ? * 6L 2002-2005 2002年至2005年的每月的最后一个星期五上午10:15触发 (22)0 15 10 ? * 6#3 每月的第三个星期五上午10:15触发15.3 邮件任务邮件发送,在我们的日常开发中,也非常的多,Springboot也帮我们做了支持邮件发送需要引入spring-boot-start-mailSpringBoot 自动配置MailSenderAutoConfiguration定义MailProperties内容,配置在application.yml中自动装配JavaMailSender测试邮件发送测试:1、引入pom依赖<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-mail</artifactId> </dependency>看它引入的依赖,可以看到 jakarta.mail<dependency> <groupId>com.sun.mail</groupId> <artifactId>jakarta.mail</artifactId> <version>1.6.4</version> <scope>compile</scope> </dependency>2、查看自动配置类:MailSenderAutoConfiguration这个类中存在bean,JavaMailSenderImpl然后我们去看下配置文件@ConfigurationProperties( prefix = "spring.mail" ) public class MailProperties { private static final Charset DEFAULT_CHARSET; private String host; private Integer port; private String username; private String password; private String protocol = "smtp"; private Charset defaultEncoding; private Map<String, String> properties; private String jndiName; }3、配置文件:spring.mail.username=24736743@qq.com spring.mail.password=你的qq授权码 spring.mail.host=smtp.qq.com # qq需要配置ssl spring.mail.properties.mail.smtp.ssl.enable=true获取授权码:在QQ邮箱中的设置->账户->开启pop3和smtp服务4、Spring单元测试@Autowired JavaMailSenderImpl mailSender; @Test public void contextLoads() { //邮件设置1:一个简单的邮件 SimpleMailMessage message = new SimpleMailMessage(); message.setSubject("通知-明天来狂神这听课"); message.setText("今晚7:30开会"); message.setTo("24736743@qq.com"); message.setFrom("24736743@qq.com"); mailSender.send(message); } @Test public void contextLoads2() throws MessagingException { //邮件设置2:一个复杂的邮件 MimeMessage mimeMessage = mailSender.createMimeMessage(); MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true); helper.setSubject("通知-明天来狂神这听课"); helper.setText("<b style='color:red'>今天 7:30来开会</b>",true); //发送附件 helper.addAttachment("1.jpg",new File("")); helper.addAttachment("2.jpg",new File("")); helper.setTo("24736743@qq.com"); helper.setFrom("24736743@qq.com"); mailSender.send(mimeMessage); }查看邮箱,邮件接收成功!我们只需要使用Thymeleaf进行前后端结合即可开发自己网站邮件收发功能了!16.富文本编辑器16.1 简介思考:我们平时在博客园,或者CSDN等平台进行写作的时候,有同学思考过他们的编辑器是怎么实现的吗?在博客园后台的选项设置中,可以看到一个文本编辑器的选项:其实这个就是富文本编辑器,市面上有许多非常成熟的富文本编辑器,比如:Editor.md——功能非常丰富的编辑器,左端编辑,右端预览,非常方便,完全免费官网:https://pandao.github.io/editor.md/wangEditor——基于javascript和css开发的 Web富文本编辑器, 轻量、简洁、界面美观、易用、开源免费。官网:http://www.wangeditor.com/TinyMCE——TinyMCE是一个轻量级的基于浏览器的所见即所得编辑器,由JavaScript写成。它对IE6+和Firefox1.5+都有着非常良好的支持。功能齐全,界面美观,就是文档是英文的,对开发人员英文水平有一定要求。官网:https://www.tiny.cloud/docs/demo/full-featured/博客园百度ueditor——UEditor是由百度web前端研发部开发所见即所得富文本web编辑器,具有轻量,功能齐全,可定制,注重用户体验等特点,开源基于MIT协议,允许自由使用和修改代码,缺点是已经没有更新了官网:https://ueditor.baidu.com/website/onlinedemo.htmlkindeditor——界面经典。官网:http://kindeditor.net/demo.phpTextbox——Textbox是一款极简但功能强大的在线文本编辑器,支持桌面设备和移动设备。主要功能包含内置的图像处理和存储、文件拖放、拼写检查和自动更正。此外,该工具还实现了屏幕阅读器等辅助技术,并符合WAI-ARIA可访问性标准。官网:https://textbox.io/CKEditor——国外的,界面美观。官网:https://ckeditor.com/ckeditor-5/demo/quill——功能强大,还可以编辑公式等官网:https://quilljs.com/simditor——界面美观,功能较全。官网:https://simditor.tower.im/summernote——UI好看,精美官网:https://summernote.org/jodit——功能齐全官网:https://xdsoft.net/jodit/froala Editor——界面非常好看,功能非常强大,非常好用(非免费)官网:https://www.froala.com/wysiwyg-editor总之,目前可用的富文本编辑器有很多......这只是其中的一部分16.2 Editor.md我这里使用的就是Editor.md,作为一个资深码农,Mardown必然是我们程序猿最喜欢的格式,看下面,就爱上了!我们可以在官网下载它:https://pandao.github.io/editor.md/ , 得到它的压缩包!解压以后,在examples目录下面,可以看到他的很多案例使用!学习,其实就是看人家怎么写的,然后进行模仿就好了!我们可以将整个解压的文件倒入我们的项目,将一些无用的测试和案例删掉即可!16.3 基础工程搭建数据库设计article:文章表字段 备注idint文章的唯一IDauthorvarchar作者titlevarchar标题contentlongtext文章的内容建表SQL:CREATE TABLE `article` ( `id` int(10) NOT NULL AUTO_INCREMENT COMMENT 'int文章的唯一ID', `author` varchar(50) NOT NULL COMMENT '作者', `title` varchar(100) NOT NULL COMMENT '标题', `content` longtext NOT NULL COMMENT '文章的内容', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8基础项目搭建1、建一个SpringBoot项目配置spring: datasource: username: root password: 123456 #?serverTimezone=UTC解决时区的报错 url: jdbc:mysql://localhost:3306/springboot?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8 driver-class-name: com.mysql.cj.jdbc.Driver<resources> <resource> <directory>src/main/java</directory> <includes> <include>**/*.xml</include> </includes> <filtering>true</filtering> </resource> </resources>2、实体类://文章类 @Data @NoArgsConstructor @AllArgsConstructor public class Article implements Serializable { private int id; //文章的唯一ID private String author; //作者名 private String title; //标题 private String content; //文章的内容 }3、mapper接口:@Mapper @Repository public interface ArticleMapper { //查询所有的文章 List<Article> queryArticles(); //新增一个文章 int addArticle(Article article); //根据文章id查询文章 Article getArticleById(int id); //根据文章id删除文章 int deleteArticleById(int id); }<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.kuang.mapper.ArticleMapper"> <select id="queryArticles" resultType="Article"> select * from article </select> <select id="getArticleById" resultType="Article"> select * from article where id = #{id} </select> <insert id="addArticle" parameterType="Article"> insert into article (author,title,content) values (#{author},#{title},#{content}); </insert> <delete id="deleteArticleById" parameterType="int"> delete from article where id = #{id} </delete> </mapper>既然已经提供了 myBatis 的映射配置文件,自然要告诉 spring boot 这些文件的位置mybatis: mapper-locations: classpath:com/kuang/mapper/*.xml type-aliases-package: com.kuang.pojo编写一个Controller测试下,是否ok;16.4 文章编辑整合(重点)1、导入 editor.md 资源 ,删除多余文件2、编辑文章页面 editor.html、需要引入 jQuery;<!DOCTYPE html> <html class="x-admin-sm" lang="zh" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>秦疆'Blog</title> <meta name="renderer" content="webkit"> <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> <meta name="viewport" content="width=device-width,user-scalable=yes, minimum-scale=0.4, initial-scale=0.8,target-densitydpi=low-dpi" /> <!--Editor.md--> <link rel="stylesheet" th:href="@{/editormd/css/editormd.css}"/> <link rel="shortcut icon" href="https://pandao.github.io/editor.md/favicon.ico" type="image/x-icon" /> </head> <body> <div class="layui-fluid"> <div class="layui-row layui-col-space15"> <div class="layui-col-md12"> <!--博客表单--> <form name="mdEditorForm"> <div> 标题:<input type="text" name="title"> </div> <div> 作者:<input type="text" name="author"> </div> <div id="article-content"> <textarea name="content" id="content" style="display:none;"> </textarea> </div> </form> </div> </div> </div> </body> <!--editormd--> <script th:src="@{/editormd/lib/jquery.min.js}"></script> <script th:src="@{/editormd/editormd.js}"></script> <script type="text/javascript"> var testEditor; //window.onload = function(){ } $(function() { testEditor = editormd("article-content", { width : "95%", height : 400, syncScrolling : "single", path : "../editormd/lib/", saveHTMLToTextarea : true, // 保存 HTML 到 Textarea emoji: true, theme: "dark",//工具栏主题 previewTheme: "dark",//预览主题 editorTheme: "pastel-on-dark",//编辑主题 tex : true, // 开启科学公式TeX语言支持,默认关闭 flowChart : true, // 开启流程图支持,默认关闭 sequenceDiagram : true, // 开启时序/序列图支持,默认关闭, //图片上传 imageUpload : true, imageFormats : ["jpg", "jpeg", "gif", "png", "bmp", "webp"], imageUploadURL : "/article/file/upload", onload : function() { console.log('onload', this); }, /*指定需要显示的功能按钮*/ toolbarIcons : function() { return ["undo","redo","|", "bold","del","italic","quote","ucwords","uppercase","lowercase","|", "h1","h2","h3","h4","h5","h6","|", "list-ul","list-ol","hr","|", "link","reference-link","image","code","preformatted-text", "code-block","table","datetime","emoji","html-entities","pagebreak","|", "goto-line","watch","preview","fullscreen","clear","search","|", "help","info","releaseIcon", "index"] }, /*自定义功能按钮,下面我自定义了2个,一个是发布,一个是返回首页*/ toolbarIconTexts : { releaseIcon : "<span bgcolor=\"gray\">发布</span>", index : "<span bgcolor=\"red\">返回首页</span>", }, /*给自定义按钮指定回调函数*/ toolbarHandlers:{ releaseIcon : function(cm, icon, cursor, selection) { //表单提交 mdEditorForm.method = "post"; mdEditorForm.action = "/article/addArticle";//提交至服务器的路径 mdEditorForm.submit(); }, index : function(){ window.location.href = '/'; }, } }); }); </script> </html>3、编写Controller,进行跳转,以及保存文章@Controller @RequestMapping("/article") public class ArticleController { @GetMapping("/toEditor") public String toEditor(){ return "editor"; } @PostMapping("/addArticle") public String addArticle(Article article){ articleMapper.addArticle(article); return "editor"; } }图片上传问题1、前端js中添加配置//图片上传 imageUpload : true, imageFormats : ["jpg", "jpeg", "gif", "png", "bmp", "webp"], imageUploadURL : "/article/file/upload", // //这个是上传图片时的访问地址2、后端请求,接收保存这个图片, 需要导入 FastJson 的依赖!//博客图片上传问题 @RequestMapping("/file/upload") @ResponseBody public JSONObject fileUpload(@RequestParam(value = "editormd-image-file", required = true) MultipartFile file, HttpServletRequest request) throws IOException { //上传路径保存设置 //获得SpringBoot当前项目的路径:System.getProperty("user.dir") String path = System.getProperty("user.dir")+"/upload/"; //按照月份进行分类: Calendar instance = Calendar.getInstance(); String month = (instance.get(Calendar.MONTH) + 1)+"月"; path = path+month; File realPath = new File(path); if (!realPath.exists()){ realPath.mkdir(); } //上传文件地址 System.out.println("上传文件保存地址:"+realPath); //解决文件名字问题:我们使用uuid; String filename = "ks-"+UUID.randomUUID().toString().replaceAll("-", ""); //通过CommonsMultipartFile的方法直接写文件(注意这个时候) file.transferTo(new File(realPath +"/"+ filename)); //给editormd进行回调 JSONObject res = new JSONObject(); res.put("url","/upload/"+month+"/"+ filename); res.put("success", 1); res.put("message", "upload success!"); return res; }3、解决文件回显显示的问题,设置虚拟目录映射!在我们自己拓展的MvcConfig中进行配置即可!@Configuration public class MyMvcConfig implements WebMvcConfigurer { // 文件保存在真实目录/upload/下, // 访问的时候使用虚路径/upload,比如文件名为1.png,就直接/upload/1.png就ok了。 @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/upload/**") .addResourceLocations("file:"+System.getProperty("user.dir")+"/upload/"); } }表情包问题自己手动下载,emoji 表情包,放到图片路径下:修改editormd.js文件// Emoji graphics files url path editormd.emoji = { path : "../editormd/plugins/emoji-dialog/emoji/", ext : ".png" };16.5文章展示1、Controller 中增加方法@GetMapping("/{id}") public String show(@PathVariable("id") int id,Model model){ Article article = articleMapper.getArticleById(id); model.addAttribute("article",article); return "article"; }2、编写页面 article.html<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"> <title th:text="${article.title}"></title> </head> <body> <div> <!--文章头部信息:标题,作者,最后更新日期,导航--> <h2 style="margin: auto 0" th:text="${article.title}"></h2> 作者:<span style="float: left" th:text="${article.author}"></span> <!--文章主体内容--> <div id="doc-content"> <textarea style="display:none;" placeholder="markdown" th:text="${article.content}"></textarea> </div> </div> <link rel="stylesheet" th:href="@{/editormd/css/editormd.preview.css}" /> <script th:src="@{/editormd/lib/jquery.min.js}"></script> <script th:src="@{/editormd/lib/marked.min.js}"></script> <script th:src="@{/editormd/lib/prettify.min.js}"></script> <script th:src="@{/editormd/lib/raphael.min.js}"></script> <script th:src="@{/editormd/lib/underscore.min.js}"></script> <script th:src="@{/editormd/lib/sequence-diagram.min.js}"></script> <script th:src="@{/editormd/lib/flowchart.min.js}"></script> <script th:src="@{/editormd/lib/jquery.flowchart.min.js}"></script> <script th:src="@{/editormd/editormd.js}"></script> <script type="text/javascript"> var testEditor; $(function () { testEditor = editormd.markdownToHTML("doc-content", {//注意:这里是上面DIV的id htmlDecode: "style,script,iframe", emoji: true, taskList: true, tocm: true, tex: true, // 默认不解析 flowChart: true, // 默认不解析 sequenceDiagram: true, // 默认不解析 codeFold: true });}); </script> </body> </html>重启项目,访问进行测试!大功告成!17. Dubbo和Zookeeper集成17.1 分布式理论什么是分布式系统?在《分布式系统原理与范型》一书中有如下定义:“分布式系统是若干独立计算机的集合,这些计算机对于用户来说就像单个相关系统”;分布式系统是由一组通过网络进行通信、为了完成共同的任务而协调工作的计算机节点组成的系统。分布式系统的出现是为了用廉价的、普通的机器完成单个计算机无法完成的计算、存储任务。其目的是利用更多的机器,处理更多的数据。分布式系统(distributed system)是建立在网络之上的软件系统。首先需要明确的是,只有当单个节点的处理能力无法满足日益增长的计算、存储任务的时候,且硬件的提升(加内存、加磁盘、使用更好的CPU)高昂到得不偿失的时候,应用程序也不能进一步优化的时候,我们才需要考虑分布式系统。因为,分布式系统要解决的问题本身就是和单机系统一样的,而由于分布式系统多节点、通过网络通信的拓扑结构,会引入很多单机系统没有的问题,为了解决这些问题又会引入更多的机制、协议,带来更多的问题。。。Dubbo文档随着互联网的发展,网站应用的规模不断扩大,常规的垂直应用架构已无法应对,分布式服务架构以及流动计算架构势在必行,急需一个治理系统确保架构有条不紊的演进。在Dubbo的官网文档有这样一张图单一应用架构当网站流量很小时,只需一个应用,将所有功能都部署在一起,以减少部署节点和成本。此时,用于简化增删改查工作量的数据访问框架(ORM)是关键。适用于小型网站,小型管理系统,将所有功能都部署到一个功能里,简单易用。缺点:1、性能扩展比较难2、协同开发问题3、不利于升级维护垂直应用架构当访问量逐渐增大,单一应用增加机器带来的加速度越来越小,将应用拆成互不相干的几个应用,以提升效率。此时,用于加速前端页面开发的Web框架(MVC)是关键。通过切分业务来实现各个模块独立部署,降低了维护和部署的难度,团队各司其职更易管理,性能扩展也更方便,更有针对性。缺点:公用模块无法重复利用,开发性的浪费分布式服务架构当垂直应用越来越多,应用之间交互不可避免,将核心业务抽取出来,作为独立的服务,逐渐形成稳定的服务中心,使前端应用能更快速的响应多变的市场需求。此时,用于提高业务复用及整合的分布式服务框架(RPC)是关键。流动计算架构当服务越来越多,容量的评估,小服务资源的浪费等问题逐渐显现,此时需增加一个调度中心基于访问压力实时管理集群容量,提高集群利用率。此时,用于提高机器利用率的资源调度和治理中心(SOA)[ Service Oriented Architecture]是关键。17.2 什么是RPCRPC【Remote Procedure Call】是指远程过程调用,是一种进程间通信方式,他是一种技术的思想,而不是规范。它允许程序调用另一个地址空间(通常是共享网络的另一台机器上)的过程或函数,而不用程序员显式编码这个远程调用的细节。即程序员无论是调用本地的还是远程的函数,本质上编写的调用代码基本相同。也就是说两台服务器A,B,一个应用部署在A服务器上,想要调用B服务器上应用提供的函数/方法,由于不在一个内存空间,不能直接调用,需要通过网络来表达调用的语义和传达调用的数据。为什么要用RPC呢?就是无法在一个进程内,甚至一个计算机内通过本地调用的方式完成的需求,比如不同的系统间的通讯,甚至不同的组织间的通讯,由于计算能力需要横向扩展,需要在多台机器组成的集群上部署应用。RPC就是要像调用本地的函数一样去调远程函数;推荐阅读文章:https://www.jianshu.com/p/2accc2840a1bRPC基本原理步骤解析:RPC两个核心模块:通讯,序列化。17.3 测试环境搭建DubboApache Dubbo |ˈdʌbəʊ| 是一款高性能、轻量级的开源Java RPC框架,它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现。dubbo官网 http://dubbo.apache.org/zh-cn/index.html1.了解Dubbo的特性2.查看官方文档dubbo基本概念服务提供者(Provider):暴露服务的服务提供方,服务提供者在启动时,向注册中心注册自己提供的服务。服务消费者(Consumer):调用远程服务的服务消费方,服务消费者在启动时,向注册中心订阅自己所需的服务,服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。注册中心(Registry):注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者监控中心(Monitor):服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心调用关系说明l 服务容器负责启动,加载,运行服务提供者。l 服务提供者在启动时,向注册中心注册自己提供的服务。l 服务消费者在启动时,向注册中心订阅自己所需的服务。l 注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。l 服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。l 服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。Dubbo环境搭建点进dubbo官方文档,推荐我们使用Zookeeper 注册中心什么是zookeeper呢?可以查看官方文档Window下安装zookeeper1、下载zookeeper :地址, 我们下载3.4.14 , 最新版!解压zookeeper2、运行/bin/zkServer.cmd ,初次运行会报错,没有zoo.cfg配置文件;可能遇到问题:闪退 !解决方案:编辑zkServer.cmd文件末尾添加pause 。这样运行出错就不会退出,会提示错误信息,方便找到原因。3、修改zoo.cfg配置文件将conf文件夹下面的zoo_sample.cfg复制一份改名为zoo.cfg即可。注意几个重要位置:dataDir=./ 临时数据存储的目录(可写相对路径)clientPort=2181 zookeeper的端口号修改完成后再次启动zookeeper4、使用zkCli.cmd测试ls /:列出zookeeper根下保存的所有节点[zk: 127.0.0.1:2181(CONNECTED) 4] ls / [zookeeper]create –e /kuangshen 123:创建一个kuangshen节点,值为123get /kuangshen:获取/kuangshen节点的值我们再来查看一下节点window下安装dubbo-admindubbo本身并不是一个服务软件。它其实就是一个jar包,能够帮你的java程序连接到zookeeper,并利用zookeeper消费、提供服务。但是为了让用户更好的管理监控众多的dubbo服务,官方提供了一个可视化的监控程序dubbo-admin,不过这个监控即使不装也不影响使用。我们这里来安装一下:1、下载dubbo-admin地址 :https://github.com/apache/dubbo-admin/tree/master2、解压进入目录修改 dubbo-admin\src\main\resources \application.properties 指定zookeeper地址erver.port=7001 spring.velocity.cache=false spring.velocity.charset=UTF-8 spring.velocity.layout-url=/templates/default.vm spring.messages.fallback-to-system-locale=false spring.messages.basename=i18n/message spring.root.password=root spring.guest.password=guest dubbo.registry.address=zookeeper://127.0.0.1:21813、在项目目录下打包dubbo-adminmvn clean package -Dmaven.test.skip=true第一次打包的过程有点慢,需要耐心等待!直到成功!4、执行 dubbo-admin\target 下的dubbo-admin-0.0.1-SNAPSHOT.jarjava -jar dubbo-admin-0.0.1-SNAPSHOT.jar【注意:zookeeper的服务一定要打开!】执行完毕,我们去访问一下 http://localhost:7001/ , 这时候我们需要输入登录账户和密码,我们都是默认的root-root;登录成功后,查看界面安装完成!17.4 SpringBoot + Dubbo + zookeeper框架搭建1. 启动zookeeper !2. IDEA创建一个空项目;3.创建一个模块,实现服务提供者:provider-server , 选择web依赖即可4.项目创建完毕,我们写一个服务,比如卖票的服务;编写接口package com.kuang.provider.service; public interface TicketService { public String getTicket(); }编写实现类package com.kuang.provider.service; public class TicketServiceImpl implements TicketService { @Override public String getTicket() { return "《狂神说Java》"; } }5.创建一个模块,实现服务消费者:consumer-server , 选择web依赖即可6.项目创建完毕,我们写一个服务,比如用户的服务;编写servicepackage com.kuang.consumer.service; public class UserService { //我们需要去拿去注册中心的服务 }需求:现在我们的用户想使用买票的服务,这要怎么弄呢 ?服务提供者1、将服务提供者注册到注册中心,我们需要整合Dubbo和zookeeper,所以需要导包我们从dubbo官网进入github,看下方的帮助文档,找到dubbo-springboot,找到依赖包<!-- Dubbo Spring Boot Starter --> <dependency> <groupId>org.apache.dubbo</groupId> <artifactId>dubbo-spring-boot-starter</artifactId> <version>2.7.3</version> </dependency> zookeeper的包我们去maven仓库下载,zkclient;<!-- https://mvnrepository.com/artifact/com.github.sgroschupf/zkclient --> <dependency> <groupId>com.github.sgroschupf</groupId> <artifactId>zkclient</artifactId> <version>0.1</version> </dependency>【新版的坑】zookeeper及其依赖包,解决日志冲突,还需要剔除日志依赖;<!-- 引入zookeeper --> <dependency> <groupId>org.apache.curator</groupId> <artifactId>curator-framework</artifactId> <version>2.12.0</version> </dependency> <dependency> <groupId>org.apache.curator</groupId> <artifactId>curator-recipes</artifactId> <version>2.12.0</version> </dependency> <dependency> <groupId>org.apache.zookeeper</groupId> <artifactId>zookeeper</artifactId> <version>3.4.14</version> <!--排除这个slf4j-log4j12--> <exclusions> <exclusion> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> </exclusion> </exclusions> </dependency>2、在springboot配置文件中配置dubbo相关属性!#当前应用名字 dubbo.application.name=provider-server #注册中心地址 dubbo.registry.address=zookeeper://127.0.0.1:2181 #扫描指定包下服务 dubbo.scan.base-packages=com.kuang.provider.service3、在service的实现类中配置服务注解,发布服务!注意导包问题import org.apache.dubbo.config.annotation.Service; import org.springframework.stereotype.Component; @Service //将服务发布出去 @Component //放在容器中 public class TicketServiceImpl implements TicketService { @Override public String getTicket() { return "《狂神说Java》"; } }逻辑理解 :应用启动起来,dubbo就会扫描指定的包下带有@component注解的服务,将它发布在指定的注册中心中!服务消费者1、导入依赖,和之前的依赖一样;<!--dubbo--> <!-- Dubbo Spring Boot Starter --> <dependency> <groupId>org.apache.dubbo</groupId> <artifactId>dubbo-spring-boot-starter</artifactId> <version>2.7.3</version> </dependency> <!--zookeeper--> <!-- https://mvnrepository.com/artifact/com.github.sgroschupf/zkclient --> <dependency> <groupId>com.github.sgroschupf</groupId> <artifactId>zkclient</artifactId> <version>0.1</version> </dependency> <!-- 引入zookeeper --> <dependency> <groupId>org.apache.curator</groupId> <artifactId>curator-framework</artifactId> <version>2.12.0</version> </dependency> <dependency> <groupId>org.apache.curator</groupId> <artifactId>curator-recipes</artifactId> <version>2.12.0</version> </dependency> <dependency> <groupId>org.apache.zookeeper</groupId> <artifactId>zookeeper</artifactId> <version>3.4.14</version> <!--排除这个slf4j-log4j12--> <exclusions> <exclusion> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> </exclusion> </exclusions> </dependency>2、配置参数#当前应用名字 dubbo.application.name=consumer-server #注册中心地址 dubbo.registry.address=zookeeper://127.0.0.1:21813. 本来正常步骤是需要将服务提供者的接口打包,然后用pom文件导入,我们这里使用简单的方式,直接将服务的接口拿过来,路径必须保证正确,即和服务提供者相同;4. 完善消费者的服务类package com.kuang.consumer.service; import com.kuang.provider.service.TicketService; import org.apache.dubbo.config.annotation.Reference; import org.springframework.stereotype.Service; @Service //注入到容器中 public class UserService { @Reference //远程引用指定的服务,他会按照全类名进行匹配,看谁给注册中心注册了这个全类名 TicketService ticketService; public void bugTicket(){ String ticket = ticketService.getTicket(); System.out.println("在注册中心买到"+ticket); } }5. 测试类编写;@RunWith(SpringRunner.class) @SpringBootTest public class ConsumerServerApplicationTests { @Autowired UserService userService; @Test public void contextLoads() { userService.bugTicket(); } }启动测试1. 开启zookeeper2. 打开dubbo-admin实现监控【可以不用做】3. 开启服务者4. 消费者消费测试,结果:监控中心 :ok , 这就是SpingBoot + dubbo + zookeeper实现分布式开发的应用,其实就是一个服务拆分的思想;18.集成SpringSecurity18.1 安全简介在 Web 开发中,安全一直是非常重要的一个方面。安全虽然属于应用的非功能性需求,但是应该在应用开发的初期就考虑进来。如果在应用开发的后期才考虑安全的问题,就可能陷入一个两难的境地:一方面,应用存在严重的安全漏洞,无法满足用户的要求,并可能造成用户的隐私数据被攻击者窃取;另一方面,应用的基本架构已经确定,要修复安全漏洞,可能需要对系统的架构做出比较重大的调整,因而需要更多的开发时间,影响应用的发布进程。因此,从应用开发的第一天就应该把安全相关的因素考虑进来,并在整个应用的开发过程中。市面上存在比较有名的:Shiro,Spring Security !这里需要阐述一下的是,每一个框架的出现都是为了解决某一问题而产生了,那么Spring Security框架的出现是为了解决什么问题呢?首先我们看下它的官网介绍:Spring Security官网地址Spring Security is a powerful and highly customizable authentication and access-control framework. It is the de-facto standard for securing Spring-based applications.Spring Security is a framework that focuses on providing both authentication and authorization to Java applications. Like all Spring projects, the real power of Spring Security is found in how easily it can be extended to meet custom requirementsSpring Security是一个功能强大且高度可定制的身份验证和访问控制框架。它实际上是保护基于spring的应用程序的标准。Spring Security是一个框架,侧重于为Java应用程序提供身份验证和授权。与所有Spring项目一样,Spring安全性的真正强大之处在于它可以轻松地扩展以满足定制需求从官网的介绍中可以知道这是一个权限框架。想我们之前做项目是没有使用框架是怎么控制权限的?对于权限 一般会细分为功能权限,访问权限,和菜单权限。代码会写的非常的繁琐,冗余。怎么解决之前写权限代码繁琐,冗余的问题,一些主流框架就应运而生而Spring Scecurity就是其中的一种。Spring 是一个非常流行和成功的 Java 应用开发框架。Spring Security 基于 Spring 框架,提供了一套 Web 应用安全性的完整解决方案。一般来说,Web 应用的安全性包括用户认证(Authentication)和用户授权(Authorization)两个部分。用户认证指的是验证某个用户是否为系统中的合法主体,也就是说用户能否访问该系统。用户认证一般要求用户提供用户名和密码。系统通过校验用户名和密码来完成认证过程。用户授权指的是验证某个用户是否有权限执行某个操作。在一个系统中,不同用户所具有的权限是不同的。比如对一个文件来说,有的用户只能进行读取,而有的用户可以进行修改。一般来说,系统会为不同的用户分配不同的角色,而每个角色则对应一系列的权限。对于上面提到的两种应用情景,Spring Security 框架都有很好的支持。在用户认证方面,Spring Security 框架支持主流的认证方式,包括 HTTP 基本认证、HTTP 表单验证、HTTP 摘要认证、OpenID 和 LDAP 等。在用户授权方面,Spring Security 提供了基于角色的访问控制和访问控制列表(Access Control List,ACL),可以对应用中的领域对象进行细粒度的控制。18.2 实战测试实验环境搭建1、新建一个初始的springboot项目web模块,thymeleaf模块2、导入静态资源welcome.html |views |level1 1.html 2.html 3.html |level2 1.html 2.html 3.html |level3 1.html 2.html 3.html Login.html3、controller跳转!package com.kuang.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; @Controller public 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; } }4、测试实验环境是否OK!认识SpringSecuritySpring Security 是针对Spring项目的安全框架,也是Spring Boot底层安全模块默认的技术选型,他可以实现强大的Web安全控制,对于安全控制,我们仅需要引入 spring-boot-starter-security 模块,进行少量的配置,即可实现强大的安全管理!记住几个类:WebSecurityConfigurerAdapter:自定义Security策略AuthenticationManagerBuilder:自定义认证策略@EnableWebSecurity:开启WebSecurity模式Spring Security的两个主要目标是 “认证” 和 “授权”(访问控制)。“认证”(Authentication)身份验证是关于验证您的凭据,如用户名/用户ID和密码,以验证您的身份。身份验证通常通过用户名和密码完成,有时与身份验证因素结合使用。“授权” (Authorization)授权发生在系统成功验证您的身份后,最终会授予您访问资源(如信息,文件,数据库,资金,位置,几乎任何内容)的完全权限。这个概念是通用的,而不是只在Spring Security 中存在。认证和授权目前,我们的测试环境,是谁都可以访问的,我们使用 Spring Security 增加上认证和授权的功能1、引入 Spring Security 模块<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>2、编写 Spring Security 配置类参考官网:https://spring.io/projects/spring-security 查看我们自己项目中的版本,找到对应的帮助文档:https://docs.spring.io/spring-security/site/docs/5.3.0.RELEASE/reference/html5 #servlet-applications 8.16.43、编写基础配置类package com.kuang.config; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; @EnableWebSecurity // 开启WebSecurity模式 public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { } }4、定制请求的授权规则@Override protected void configure(HttpSecurity http) throws Exception { // 定制请求的授权规则 // 首页所有人可以访问 http.authorizeRequests().antMatchers("/").permitAll() .antMatchers("/level1/**").hasRole("vip1") .antMatchers("/level2/**").hasRole("vip2") .antMatchers("/level3/**").hasRole("vip3"); }5、测试一下:发现除了首页都进不去了!因为我们目前没有登录的角色,因为请求需要登录的角色拥有对应的权限才可以!6、在configure()方法中加入以下配置,开启自动配置的登录功能!// 开启自动配置的登录功能 // /login 请求来到登录页 // /login?error 重定向到这里表示登录失败 http.formLogin();7、测试一下:发现,没有权限的时候,会跳转到登录的页面!8、查看刚才登录页的注释信息;我们可以定义认证规则,重写configure(AuthenticationManagerBuilder auth)方法//定义认证规则 @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { //在内存中定义,也可以在jdbc中去拿.... auth.inMemoryAuthentication() .withUser("kuangshen").password("123456").roles("vip2","vip3") .and() .withUser("root").password("123456").roles("vip1","vip2","vip3") .and() .withUser("guest").password("123456").roles("vip1","vip2");9、测试,我们可以使用这些账号登录进行测试!发现会报错!There is no PasswordEncoder mapped for the id “null”10、原因,我们要将前端传过来的密码进行某种方式加密,否则就无法登录,修改代码//定义认证规则 @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { //在内存中定义,也可以在jdbc中去拿.... //Spring security 5.0中新增了多种加密方式,也改变了密码的格式。 //要想我们的项目还能够正常登陆,需要修改一下configure中的代码。我们要将前端传过来的密码进行某种方式加密 //spring security 官方推荐的是使用bcrypt加密方式。 auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder()) .withUser("kuangshen").password(new BCryptPasswordEncoder().encode("123456")).roles("vip2","vip3") .and() .withUser("root").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2","vip3") .and() .withUser("guest").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2"); }11、测试,发现,登录成功,并且每个角色只能访问自己认证下的规则!搞定权限控制和注销1、开启自动配置的注销的功能//定制请求的授权规则 @Override protected void configure(HttpSecurity http) throws Exception { //.... //开启自动配置的注销的功能 // /logout 注销请求 http.logout(); }2、我们在前端,增加一个注销的按钮,index.html 导航栏中<a class="item" th:href="@{/logout}"> <i class="address card icon"></i> 注销 </a>3、我们可以去测试一下,登录成功后点击注销,发现注销完毕会跳转到登录页面!4、但是,我们想让他注销成功后,依旧可以跳转到首页,该怎么处理呢?// .logoutSuccessUrl("/"); 注销成功来到首页 http.logout().logoutSuccessUrl("/");5、测试,注销完毕后,发现跳转到首页OK6、我们现在又来一个需求:用户没有登录的时候,导航栏上只显示登录按钮,用户登录之后,导航栏可以显示登录的用户信息及注销按钮!还有就是,比如kuangshen这个用户,它只有 vip2,vip3功能,那么登录则只显示这两个功能,而vip1的功能菜单不显示!这个就是真实的网站情况了!该如何做呢?我们需要结合thymeleaf中的一些功能sec:authorize="isAuthenticated()":是否认证登录!来显示不同的页面Maven依赖:<!-- https://mvnrepository.com/artifact/org.thymeleaf.extras/thymeleaf-extras-springsecurity4 --> <dependency> <groupId>org.thymeleaf.extras</groupId> <artifactId>thymeleaf-extras-springsecurity5</artifactId> <version>3.0.4.RELEASE</version> </dependency>7、修改我们的 前端页面导入命名空间xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5"修改导航栏,增加认证判断<!--登录注销--> <div class="right menu"> <!--如果未登录--> <div sec:authorize="!isAuthenticated()"> <a class="item" th:href="@{/login}"> <i class="address card icon"></i> 登录 </a> </div> <!--如果已登录--> <div sec:authorize="isAuthenticated()"> <a class="item"> <i class="address card icon"></i> 用户名:<span sec:authentication="principal.username"></span> 角色:<span sec:authentication="principal.authorities"></span> </a> </div> <div sec:authorize="isAuthenticated()"> <a class="item" th:href="@{/logout}"> <i class="address card icon"></i> 注销 </a> </div> </div>8、重启测试,我们可以登录试试看,登录成功后确实,显示了我们想要的页面;9、如果注销404了,就是因为它默认防止csrf跨站请求伪造,因为会产生安全问题,我们可以将请求改为post表单提交,或者在spring security中关闭csrf功能;我们试试:在 配置中增加 http.csrf().disable();http.csrf().disable();//关闭csrf功能:跨站请求伪造,默认只能通过post方式提交logout请求 http.logout().logoutSuccessUrl("/");10、我们继续将下面的角色功能块认证完成!<!-- sec:authorize="hasRole('vip1')" --> <div class="column" sec:authorize="hasRole('vip1')"> <div class="ui raised segment"> <div class="ui"> <div class="content"> <h5 class="content">Level 1</h5> <hr> <div><a th:href="@{/level1/1}"><i class="bullhorn icon"></i> Level-1-1</a></div> <div><a th:href="@{/level1/2}"><i class="bullhorn icon"></i> Level-1-2</a></div> <div><a th:href="@{/level1/3}"><i class="bullhorn icon"></i> Level-1-3</a></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 2</h5> <hr> <div><a th:href="@{/level2/1}"><i class="bullhorn icon"></i> Level-2-1</a></div> <div><a th:href="@{/level2/2}"><i class="bullhorn icon"></i> Level-2-2</a></div> <div><a th:href="@{/level2/3}"><i class="bullhorn icon"></i> Level-2-3</a></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 3</h5> <hr> <div><a th:href="@{/level3/1}"><i class="bullhorn icon"></i> Level-3-1</a></div> <div><a th:href="@{/level3/2}"><i class="bullhorn icon"></i> Level-3-2</a></div> <div><a th:href="@{/level3/3}"><i class="bullhorn icon"></i> Level-3-3</a></div> </div> </div> </div> </div>11、测试一下!12、权限控制和注销搞定!记住我现在的情况,我们只要登录之后,关闭浏览器,再登录,就会让我们重新登录,但是很多网站的情况,就是有一个记住密码的功能,这个该如何实现呢?很简单1、开启记住我功能//定制请求的授权规则 @Override protected void configure(HttpSecurity http) throws Exception { //。。。。。。。。。。。 //记住我 http.rememberMe(); }2、我们再次启动项目测试一下,发现登录页多了一个记住我功能,我们登录之后关闭 浏览器,然后重新打开浏览器访问,发现用户依旧存在!思考:如何实现的呢?其实非常简单我们可以查看浏览器的cookie3、我们点击注销的时候,可以发现,spring security 帮我们自动删除了这个 cookie4、结论:登录成功后,将cookie发送给浏览器保存,以后登录带上这个cookie,只要通过检查就可以免登录了。如果点击注销,则会删除这个cookie,具体的原理我们在JavaWeb阶段都讲过了,这里就不在多说了!定制登录页现在这个登录页面都是spring security 默认的,怎么样可以使用我们自己写的Login界面呢?1、在刚才的登录页配置后面指定 loginpagehttp.formLogin().loginPage("/toLogin");2、然后前端也需要指向我们自己定义的 login请求<a class="item" th:href="@{/toLogin}"> <i class="address card icon"></i> 登录 </a>3、我们登录,需要将这些信息发送到哪里,我们也需要配置,login.html 配置提交请求及方式,方式必须为post:在 loginPage()源码中的注释上有写明:<form th:action="@{/login}" method="post"> <div class="field"> <label>Username</label> <div class="ui left icon input"> <input type="text" placeholder="Username" name="username"> <i class="user icon"></i> </div> </div> <div class="field"> <label>Password</label> <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>4、这个请求提交上来,我们还需要验证处理,怎么做呢?我们可以查看formLogin()方法的源码!我们配置接收登录的用户名和密码的参数!http.formLogin() .usernameParameter("username") .passwordParameter("password") .loginPage("/toLogin") .loginProcessingUrl("/login"); // 登陆表单提交请求5、在登录页增加记住我的多选框<input type="checkbox" name="remember"> 记住我6、后端验证处理!//定制记住我的参数! http.rememberMe().rememberMeParameter("remember");7、测试,OK完整配置代码package com.kuang.config; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { //定制请求的授权规则 @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests().antMatchers("/").permitAll() .antMatchers("/level1/**").hasRole("vip1") .antMatchers("/level2/**").hasRole("vip2") .antMatchers("/level3/**").hasRole("vip3"); //开启自动配置的登录功能:如果没有权限,就会跳转到登录页面! // /login 请求来到登录页 // /login?error 重定向到这里表示登录失败 http.formLogin() .usernameParameter("username") .passwordParameter("password") .loginPage("/toLogin") .loginProcessingUrl("/login"); // 登陆表单提交请求 //开启自动配置的注销的功能 // /logout 注销请求 // .logoutSuccessUrl("/"); 注销成功来到首页 http.csrf().disable();//关闭csrf功能:跨站请求伪造,默认只能通过post方式提交logout请求 http.logout().logoutSuccessUrl("/"); //记住我 http.rememberMe().rememberMeParameter("remember"); } //定义认证规则 @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { //在内存中定义,也可以在jdbc中去拿.... //Spring security 5.0中新增了多种加密方式,也改变了密码的格式。 //要想我们的项目还能够正常登陆,需要修改一下configure中的代码。我们要将前端传过来的密码进行某种方式加密 //spring security 官方推荐的是使用bcrypt加密方式。 auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder()) .withUser("kuangshen").password(new BCryptPasswordEncoder().encode("123456")).roles("vip2","vip3") .and() .withUser("root").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2","vip3") .and() .withUser("guest").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2"); } }参考资料http://mp.weixin.qq.com/mp/homepage?__biz=Mzg2NTAzMTExNg==&hid=1&sn=3247dca1433a891523d9e4176c90c499&scene=18#wechat_redirecthttps://www.bilibili.com/video/BV19E411v7Ty
2022年06月05日
936 阅读
0 评论
0 点赞
2022-06-05
leetcode|中等:43. 字符串相乘
1.题目给定两个以字符串形式表示的非负整数 num1 和 num2,返回 num1 和 num2 的乘积,它们的乘积也表示为字符串形式。示例 1:输入: num1 = "2", num2 = "3" 输出: "6"示例 2:输入: num1 = "123", num2 = "456" 输出: "56088"提示:1 <= num1.length, num2.length <= 200num1 和 num2 只能由数字组成。num1 和 num2 都不包含任何前导零,除了数字0本身。2. 题解2.1 思路分析思路1:题目比较简单 分析乘法运算过程并手动进行模拟即可2.2 代码实现思路1:手动模拟乘法计算过程import java.util.Arrays; class Solution { public String multiply(String num1, String num2) { if(num1.equals("0")||num2.equals("0")){ return "0"; } // 计算res的最大可能长度 int maxResLen = num1.length()*num2.length()+2; int[] res = new int[maxResLen]; for (int i = 0; i < maxResLen; i++) { res[i] = 0; } // String num1 --> num1BitList int[] num1BitList = new int[num1.length()]; for (int i = 0; i < num1.length(); i++) { num1BitList[num1.length()-1-i] = num1.charAt(num1.length()-1-i)-'0'; } // String num2 --> num2BitList int[] num2BitList = new int[num2.length()]; for (int i = 0; i < num2.length(); i++) { num2BitList[num2.length()-1-i] = num2.charAt(num2.length()-1-i)-'0'; } // cal num1 * num2 for (int i = 0; i < num1.length(); i++) { for (int j = 0; j < num2.length(); j++) { res[maxResLen-1-i-j] += num1BitList[num1.length()-1-i]*num2BitList[num2.length()-1-j]; } } // handle bit overflow for (int i = maxResLen-1; i >= 0; i--) { int tmp = res[i]; res[i] = 0; int offset = 0; while (tmp>0){ res[i-offset] += tmp%10; offset++; tmp/=10; } } // int[] res --> String StringBuilder sb = new StringBuilder(); boolean beginFlag = false; for (int i = 0; i < maxResLen; i++) { if(res[i]==0&&beginFlag==false){ continue; }else { beginFlag = true; } sb.append(res[i]); } return sb.toString(); } public static void main(String[] args) { Solution solution = new Solution(); String num1 = "123"; String num2 = "456"; String res =solution.multiply(num1,num2); System.out.println(res); } }2.3 提交结果提交结果执行用时内存消耗语言提交时间备注通过3 ms41.4 MBJava2022/06/05 21:01添加备注参考资料https://leetcode.cn/problems/multiply-strings/
2022年06月05日
471 阅读
0 评论
0 点赞
2022-05-18
羽毛球规则
1.发球/击球区域1.1 单打发球和击球区域1.2 双打发球和击球区域2.发球规则2.0 公共部分无论单双打,都需要按照对角线发/接球(具体看后面的图解),发球员和接球员需要在规定区域,发球员发球时两只脚都必须碰地,不可以踩线;发球时,球的位置要求低于发球员的腰部以及手腕,所以,你不能抛球直接“扣杀”;发球员只有一次发球机会,球碰到发球员的球拍或掉在地上即为发球结束(不过平时玩大家对新手都会比较照顾的);在双打中,发球员与接球员的队友所站位置不受限制,你们可以站在同一个侧,也可以左右分开站在两边。2.1 单打当发球方的分数为双数时(含0分),在右半场发球,否则在左半场发球;赢球的一方作为下一回合的发球方。2.2 双打相比单打而言,双打的规则复杂一些,不过也不难,可以用3句话总结:a. 分数为双数时在右半场发球,分数为单数时在左半场发球(跟单打一样);b. 赢球的一方作为下一回合的发球方;c. 发球方得分时两名队员才交换位置,其他情况位置不变。3.计分与换边3.1 计分一般按照按21分制计算,3局2胜定胜负;赢得一球算1分,先得21分一方胜一局;若打到20平,连续得2分一方取胜;最多打至30分,先得30分一方胜;下一局由上一局获胜一方发球开局。3.2 换边第一局比赛结束后,双方换边;若第二局后比分为1:1,双方换边;第三局领先一方达到11分时,双方换边。参考资料羽毛球规则
2022年05月18日
414 阅读
0 评论
0 点赞
2022-05-16
PC电源上供电接口定义
1.24Pin主板电源接头2.ATX 12V 4Pin及ATX 12V/EPS 12V 4+4Pin接头3.6Pin PCIe和6+2Pin PCIe接头PCIE电源接口的定义需要特别注意,其中6Pin接口的第2Pin悬空或者是接有黄色的线缆,第5Pin作为电压监测反馈,当监测到这一针处于接地,来判断接头已经接入。8Pin PCIE接口的情况类似,第4Pin和第6Pin也是作为电压监测,不传输电流。4.Molex四针外围设备(大4Pin)接口四针外围设备接口(Peripheral Power Connectors / Molex Connectors)俗称“大4Pin”或者“D4”(还有很多种叫法),它是电脑中现存的最古老的一种接口,由Molex公司制造和销售,也就被称为Molex接口。其中的接口所用的端子最大能传输13A电流,12V和5V就各可以传输156W和65W功率。但这种情况下会带来很大的压降,按最大安全电流5A来计算,12V和5V分别可以传输60W和25W功率。5.SATA电源接口SATA电源接口共有5组电压,每组电压对应3针,共15针。使用的是Molex 67581-0000端子,每个端子可以传输的电流为1.5A,所以12V、5V和3.3V各可以传输的电流都为4.5A,功率分别为54W、22.5W和14.85W。参考资料电脑、显卡电源6pin、8pin接口定义主板级电源上的几种主要供电接口介绍(24pin、8pin、4pin、SATA供电口等)电脑电源8pin和6+2pin一样吗?电源线那么多,如何区分?
2022年05月16日
2,384 阅读
0 评论
2 点赞
2022-05-16
Jetson 系列开发板(NX/AGX /Nano)搭建pytorch-gpu环境
提醒:Jetson Xavier NX 用不了 nvidia-smi 命令0.查看JetPack版本信息sudo apt-cache show nvidia-jetpackPackage: nvidia-jetpack Version: 4.6-b199 Architecture: arm64 Maintainer: NVIDIA Corporation Installed-Size: 194后面选择安装版本的时候需要根据JetPack版本信息选择版本1.安装miniconda下载地址:https://docs.conda.io/en/latest/miniconda.htmlwget https://github.com/Archiconda/build-tools/releases/download/0.2.3/Archiconda3-0.2.3-Linux-aarch64.sh bash Archiconda3-0.2.3-Linux-aarch64.sh然后创建自己的虚拟环境即可conda create -n base-jupiter python=3.62.安装pytorch-gpu官网的下载地址并不包含aarch64适用的pytorch-gpu以LTS (1.8.2)为例,官下载地址为:https://download.pytorch.org/whl/lts/1.8/torch_lts.html打开可以验证发现并并不包含aarch64适用的pytorch-gpu因此安装gpu版本的需要从NVIDIA官方进行下载,下载地址为:https://forums.developer.nvidia.com/t/pytorch-for-jetson-version-1-11-now-available/72048wget https://nvidia.box.com/shared/static/fjtbno0vpo676a25cgvuqc1wty0fkkg6.whl -O torch-1.10.0-cp36-cp36m-linux_aarch64.whl pip install numpy Cpython pip install torch-1.10.0-cp36-cp36m-linux_aarch64.whl遇到问题及解决办法ImportError: libopenblas.so.0: cannot open shared object file: No such file or directorysudo apt-get install libopenblas-devOSError: libmpi_cxx.so.20: cannot open shared object file: No such file or directorysudo apt-get install libopenmpi-dev import torch 出现 Illegal instruction (core dumped)vim ~/.bashrc# 把以下内容加入到末尾扩充环境变量 export OPENBLAS_CORETYPE=ARMV8source ~/.bashrc3.安装torchvision下载地址:https://github.com/pytorch/vision版本对应关系torchtorchvisionpythonmain / nightlymain / nightly>=3.7, <=3.101.11.00.12.0>=3.7, <=3.101.10.20.11.3>=3.6, <=3.91.10.10.11.2>=3.6, <=3.91.10.00.11.1>=3.6, <=3.91.9.10.10.1>=3.6, <=3.91.9.00.10.0>=3.6, <=3.91.8.20.9.2>=3.6, <=3.9git clone -b v0.11.1 https://github.com/pytorch/vision.git vision-0.11.1 cd vision-0.11.1 export BUILD_VERSION=0.11.1 python setup.py install4.效果测试(base-jupiter) nvidia@nx:~$ python Python 3.6.15 | packaged by conda-forge | (default, Dec 3 2021, 19:12:04) [GCC 9.4.0] on linux Type "help", "copyright", "credits" or "license" for more information. >>> import torch >>> torch.cuda.is_available() True参考资料查看Jetson系列产品JetPack的版本信息NVIDIA Jetson Xavier NX搭建pytorch gpu环境(超详细)NVIDIA JETSONTX2 安装 pytorch 出现错误:import torch 出现 Illegal instruction(core dumped)ImportError: libopenblas.so.0: cannot open shared object file: No such file or directoryJetson AGX Xavier安装Archiconda虚拟环境管理器与在虚拟环境中调用opencvJetson AGX Xavier安装torch、torchvision且成功运行yolov5算法https://github.com/pytorch/vision
2022年05月16日
2,092 阅读
4 评论
0 点赞
2022-05-13
Xubuntu/xfce4桌面环境 美化
1.安装xfce4的主题和图标下载主题,图标样式:http://xfce-look.org/主题推荐:McMojave 本地备份图标推荐:McMojave-circle 本地备份将下载的主题,移动到桌面主题目录:/usr/share/themes将下载的图标,移动到图标主题目录,/usr/share/icons分别在各自目录解压缩主题文件2.设置xfce4的主题在设置中,打开外观选择你下载的桌面主题和图标主题 在设置中打开窗口管理器,选择你安装的桌面主题3.更换壁纸下载地址效果参考资料https://www.pling.com/p/1305429/https://www.pling.com/p/1315686/Xubuntu (xfce4桌面)仿Mac OS美化ubuntu xfce美化
2022年05月13日
2,097 阅读
0 评论
0 点赞
2022-05-13
cifs:Ubuntu 挂载 Samba 共享文件夹
1.安装cifs-utifssudo apt-get install cifs-utils2.列举指定IP地址所提供的共享文件夹列表sudo apt install smbclient smbclient -L ${ip_addr} -U ${username}%${password}3.挂载共享文件夹sudo mount -t cifs //指定SambaIP/文件夹名 /mnt/ -o username=${username},password=${password},iocharset=utf8,vers=1.04.实现开机自动挂载在/etc/fstab的末尾加入如下内容vim /etc/fstab//指定SambaIP/文件夹名 /mnt/ cifs defaults,username=${username},password=${password},iocharset=utf8,vers=1.0参考资料Ubuntu 挂载 Samba 共享文件夹Ubuntu 下挂载samba 服务
2022年05月13日
778 阅读
0 评论
0 点赞
2022-05-05
SpringMVC学习笔记
1、回顾MVC1.1、什么是MVCMVC是模型(Model)、视图(View)、控制器(Controller)的简写,是一种软件设计规范。是将业务逻辑、数据、显示分离的方法来组织代码。MVC主要作用是降低了视图与业务逻辑间的双向偶合。MVC不是一种设计模式,MVC是一种架构模式。当然不同的MVC存在差异。Model(模型):数据模型,提供要展示的数据,因此包含数据和行为,可以认为是领域模型或JavaBean组件(包含数据和行为),不过现在一般都分离开来:Value Object(数据Dao) 和 服务层(行为Service)。也就是模型提供了模型数据查询和模型数据的状态更新等功能,包括数据和业务。View(视图):负责进行模型的展示,一般就是我们见到的用户界面,客户想看到的东西。Controller(控制器):接收用户请求,委托给模型进行处理(状态改变),处理完毕后把返回的模型数据返回给视图,由视图负责展示。也就是说控制器做了个调度员的工作。最典型的MVC就是JSP + servlet + javabean的模式。1.2、Model1时代在web早期的开发中,通常采用的都是Model1。Model1中,主要分为两层,视图层和模型层。Model1优点:架构简单,比较适合小型项目开发;Model1缺点:JSP职责不单一,职责过重,不便于维护;1.3、Model2时代Model2把一个项目分成三部分,包括视图、控制、模型。用户发请求Servlet接收请求数据,并调用对应的业务逻辑方法业务处理完毕,返回更新后的数据给servletservlet转向到JSP,由JSP来渲染页面响应给前端更新后的页面职责分析:Controller:控制器取得表单数据调用业务逻辑转向指定的页面Model:模型业务逻辑保存数据的状态View:视图显示页面Model2这样不仅提高的代码的复用率与项目的扩展性,且大大降低了项目的维护成本。Model 1模式的实现比较简单,适用于快速开发小规模项目,Model1中JSP页面身兼View和Controller两种角色,将控制逻辑和表现逻辑混杂在一起,从而导致代码的重用性非常低,增加了应用的扩展性和维护的难度。Model2消除了Model1的缺点。1.4、回顾Servlet新建一个Maven工程当做父工程!pom依赖!<dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.1.9.RELEASE</version> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> <version>2.5</version> </dependency> <dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>jsp-api</artifactId> <version>2.2</version> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> </dependencies>建立一个Moudle:springmvc-01-servlet , 添加Web app的支持!导入servlet 和 jsp 的 jar 依赖<dependency> <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> <version>2.5</version> </dependency> <dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>jsp-api</artifactId> <version>2.2</version> </dependency>编写一个Servlet类,用来处理用户的请求package com.kuang.servlet; //实现Servlet接口 public class HelloServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //取得参数 String method = req.getParameter("method"); if (method.equals("add")){ req.getSession().setAttribute("msg","执行了add方法"); } if (method.equals("delete")){ req.getSession().setAttribute("msg","执行了delete方法"); } //业务逻辑 //视图跳转 req.getRequestDispatcher("/WEB-INF/jsp/hello.jsp").forward(req,resp); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doGet(req,resp); } }编写Hello.jsp,在WEB-INF目录下新建一个jsp的文件夹,新建hello.jsp<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Kuangshen</title> </head> <body> ${msg} </body> </html>在web.xml中注册Servlet<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0"> <servlet> <servlet-name>HelloServlet</servlet-name> <servlet-class>com.kuang.servlet.HelloServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>HelloServlet</servlet-name> <url-pattern>/user</url-pattern> </servlet-mapping> </web-app>配置Tomcat,并启动测试localhost:8080/user?method=addlocalhost:8080/user?method=deleteMVC框架要做哪些事情将url映射到java类或java类的方法 .封装用户提交的数据 .处理请求--调用相关的业务处理--封装响应数据 .将响应的数据进行渲染 . jsp / html 等表示层数据 .说明: 常见的服务器端MVC框架有:Struts、Spring MVC、ASP.NET MVC、Zend Framework、JSF;常见前端MVC框架:vue、angularjs、react、backbone;由MVC演化出了另外一些模式如:MVP、MVVM 等等....2、什么是SpringMVC2.1、概述Spring MVC是Spring Framework的一部分,是基于Java实现MVC的轻量级Web框架。查看官方文档:https://docs.spring.io/spring/docs/5.2.0.RELEASE/spring-framework-reference/web.html#spring-web我们为什么要学习SpringMVC呢?Spring MVC的特点:轻量级,简单易学高效 , 基于请求响应的MVC框架与Spring兼容性好,无缝结合约定优于配置功能强大:RESTful、数据验证、格式化、本地化、主题等简洁灵活Spring的web框架围绕DispatcherServlet [ 调度Servlet ] 设计。DispatcherServlet的作用是将请求分发到不同的处理器。从Spring 2.5开始,使用Java 5或者以上版本的用户可以采用基于注解形式进行开发,十分简洁;正因为SpringMVC好 , 简单 , 便捷 , 易学 , 天生和Spring无缝集成(使用SpringIoC和Aop) , 使用约定优于配置 . 能够进行简单的junit测试 . 支持Restful风格 .异常处理 , 本地化 , 国际化 , 数据验证 , 类型转换 , 拦截器 等等......所以我们要学习 .最重要的一点还是用的人多 , 使用的公司多 .2.2、中心控制器 Spring的web框架围绕DispatcherServlet设计。DispatcherServlet的作用是将请求分发到不同的处理器。从Spring 2.5开始,使用Java 5或者以上版本的用户可以采用基于注解的controller声明方式。 Spring MVC框架像许多其他MVC框架一样, 以请求为驱动 , 围绕一个中心Servlet分派请求及提供其他功能,DispatcherServlet是一个实际的Servlet (它继承自HttpServlet 基类)。SpringMVC的原理如下图所示: 当发起请求时被前置的控制器拦截到请求,根据请求参数生成代理请求,找到请求对应的实际控制器,控制器处理请求,创建数据模型,访问数据库,将模型响应给中心控制器,控制器使用模型与视图渲染视图结果,将结果返回给中心控制器,再将结果返回给请求者。2.3、SpringMVC执行原理图为SpringMVC的一个较完整的流程图,实线表示SpringMVC框架提供的技术,不需要开发者实现,虚线表示需要开发者实现。简要分析执行流程DispatcherServlet表示前置控制器,是整个SpringMVC的控制中心。用户发出请求,DispatcherServlet接收请求并拦截请求。我们假设请求的url为 : http://localhost:8080/SpringMVC/hello如上url拆分成三部分:http://localhost:8080服务器域名SpringMVC部署在服务器上的web站点hello表示控制器通过分析,如上url表示为:请求位于服务器localhost:8080上的SpringMVC站点的hello控制器。HandlerMapping为处理器映射。DispatcherServlet调用HandlerMapping,HandlerMapping根据请求url查找Handler。HandlerExecution表示具体的Handler,其主要作用是根据url查找控制器,如上url被查找控制器为:hello。HandlerExecution将解析后的信息传递给DispatcherServlet,如解析控制器映射等。HandlerAdapter表示处理器适配器,其按照特定的规则去执行Handler。Handler让具体的Controller执行。Controller将具体的执行信息返回给HandlerAdapter,如ModelAndView。HandlerAdapter将视图逻辑名或模型传递给DispatcherServlet。DispatcherServlet调用视图解析器(ViewResolver)来解析HandlerAdapter传递的逻辑视图名。视图解析器将解析的逻辑视图名传给DispatcherServlet。DispatcherServlet根据视图解析器解析的视图结果,调用具体的视图。最终视图呈现给用户。3.Hello,SpringMVC3.1 配置版1、新建一个Moudle , springmvc-02-hello , 添加web的支持!2、确定导入了SpringMVC 的依赖!3、配置web.xml , 注册DispatcherServlet<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0"> <!--1.注册DispatcherServlet--> <servlet> <servlet-name>springmvc</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <!--关联一个springmvc的配置文件:【servlet-name】-servlet.xml--> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:springmvc-servlet.xml</param-value> </init-param> <!--启动级别-1--> <load-on-startup>1</load-on-startup> </servlet> <!--/ 匹配所有的请求;(不包括.jsp)--> <!--/* 匹配所有的请求;(包括.jsp)--> <servlet-mapping> <servlet-name>springmvc</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> </web-app>4、编写SpringMVC 的 配置文件!名称:springmvc-servlet.xml : [servletname]-servlet.xml说明,这里的名称要求是按照官方来的<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> </beans>5、添加 处理映射器<bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/>6、添加 处理器适配器<bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"/>7、添加 视图解析器<!--视图解析器:DispatcherServlet给他的ModelAndView--> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="InternalResourceViewResolver"> <!--前缀--> <property name="prefix" value="/WEB-INF/jsp/"/> <!--后缀--> <property name="suffix" value=".jsp"/> </bean>8、编写我们要操作业务Controller ,要么实现Controller接口,要么增加注解;需要返回一个ModelAndView,装数据,封视图;package com.kuang.controller; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.mvc.Controller; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; //注意:这里我们先导入Controller接口 public class HelloController implements Controller { public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception { //ModelAndView 模型和视图 ModelAndView mv = new ModelAndView(); //封装对象,放在ModelAndView中。Model mv.addObject("msg","HelloSpringMVC!"); //封装要跳转的视图,放在ModelAndView中 mv.setViewName("hello"); //: /WEB-INF/jsp/hello.jsp return mv; } }9、将自己的类交给SpringIOC容器,注册bean<!--Handler--> <bean id="/hello" class="com.kuang.controller.HelloController"/>10、写要跳转的jsp页面,显示ModelandView存放的数据,以及我们的正常页面;<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Kuangshen</title> </head> <body> ${msg} </body> </html>11、配置Tomcat 启动测试!可能遇到的问题:访问出现404,排查步骤:查看控制台输出,看一下是不是缺少了什么jar包。如果jar包存在,显示无法输出,就在IDEA的项目发布中,添加lib依赖!重启Tomcat 即可解决!3.2 注解版(推荐)1、新建一个Moudle,springmvc-03-hello-annotation 。添加web支持!2、由于Maven可能存在资源过滤的问题,我们将配置完善<build> <resources> <resource> <directory>src/main/java</directory> <includes> <include>**/*.properties</include> <include>**/*.xml</include> </includes> <filtering>false</filtering> </resource> <resource> <directory>src/main/resources</directory> <includes> <include>**/*.properties</include> <include>**/*.xml</include> </includes> <filtering>false</filtering> </resource> </resources> </build>3、在pom.xml文件引入相关的依赖:主要有Spring框架核心库、Spring MVC、servlet , JSTL等。我们在父依赖中已经引入了!4、配置web.xml注意点: <?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0"> <!--1.注册servlet--> <servlet> <servlet-name>SpringMVC</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <!--通过初始化参数指定SpringMVC配置文件的位置,进行关联--> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:springmvc-servlet.xml</param-value> </init-param> <!-- 启动顺序,数字越小,启动越早 --> <load-on-startup>1</load-on-startup> </servlet> <!--所有请求都会被springmvc拦截 --> <servlet-mapping> <servlet-name>SpringMVC</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> </web-app>/ 和 /* 的区别:< url-pattern > / </ url-pattern > 不会匹配到.jsp, 只针对我们编写的请求;即:.jsp 不会进入spring的 DispatcherServlet类 。< url-pattern > /* </ url-pattern > 会匹配 *.jsp,会出现返回 jsp视图 时再次进入spring的DispatcherServlet 类,导致找不到对应的controller所以报404错。注意web.xml版本问题,要最新版!注册DispatcherServlet关联SpringMVC的配置文件启动级别为1映射路径为 / 【不要用/*,会404】5、添加Spring MVC配置文件在resource目录下添加springmvc-servlet.xml配置文件,配置的形式与Spring容器配置基本类似,为了支持基于注解的IOC,设置了自动扫描包的功能,具体配置信息如下:<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd"> <!-- 自动扫描包,让指定包下的注解生效,由IOC容器统一管理 --> <context:component-scan base-package="com.kuang.controller"/> <!-- 让Spring MVC不处理静态资源 --> <mvc:default-servlet-handler /> <!-- 支持mvc注解驱动 在spring中一般采用@RequestMapping注解来完成映射关系 要想使@RequestMapping注解生效 必须向上下文中注册DefaultAnnotationHandlerMapping 和一个AnnotationMethodHandlerAdapter实例 这两个实例分别在类级别和方法级别处理。 而annotation-driven配置帮助我们自动完成上述两个实例的注入。 --> <mvc:annotation-driven /> <!-- 视图解析器 --> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="internalResourceViewResolver"> <!-- 前缀 --> <property name="prefix" value="/WEB-INF/jsp/" /> <!-- 后缀 --> <property name="suffix" value=".jsp" /> </bean> </beans>在视图解析器中我们把所有的视图都存放在/WEB-INF/目录下,这样可以保证视图安全,因为这个目录下的文件,客户端不能直接访问。让IOC的注解生效静态资源过滤 :HTML . JS . CSS . 图片 , 视频 .....MVC的注解驱动配置视图解析器6、创建Controller编写一个Java控制类:com.kuang.controller.HelloController , 注意编码规范package com.kuang.controller; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; @Controller @RequestMapping("/HelloController") public class HelloController { //真实访问地址 : 项目名/HelloController/hello @RequestMapping("/hello") public String sayHello(Model model){ //向模型中添加属性msg与值,可以在JSP页面中取出并渲染 model.addAttribute("msg","hello,SpringMVC"); //web-inf/jsp/hello.jsp return "hello"; } }@Controller是为了让Spring IOC容器初始化时自动扫描到;@RequestMapping是为了映射请求路径,这里因为类与方法上都有映射所以访问时应该是/HelloController/hello;方法中声明Model类型的参数是为了把Action中的数据带到视图中;方法返回的结果是视图的名称hello,加上配置文件中的前后缀变成WEB-INF/jsp/hello.jsp。7、创建视图层在WEB-INF/ jsp目录中创建hello.jsp , 视图可以直接取出并展示从Controller带回的信息;可以通过EL表示取出Model中存放的值,或者对象;<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>SpringMVC</title> </head> <body> ${msg} </body> </html>8、配置Tomcat运行配置Tomcat , 开启服务器 , 访问 对应的请求路径!OK,运行成功!3.3 小结实现步骤其实非常的简单:新建一个web项目导入相关jar包编写web.xml , 注册DispatcherServlet编写springmvc配置文件接下来就是去创建对应的控制类 , controller最后完善前端视图和controller之间的对应测试运行调试.使用springMVC必须配置的三大件:处理器映射器、处理器适配器、视图解析器通常,我们只需要手动配置视图解析器,而处理器映射器和处理器适配器只需要开启注解驱动即可,而省去了大段的xml配置再来回顾下原理吧~4. 控制器Controller4.1 控制器Controller控制器复杂提供访问应用程序的行为,通常通过接口定义或注解定义两种方法实现。控制器负责解析用户的请求并将其转换为一个模型。在Spring MVC中一个控制器类可以包含多个方法在Spring MVC中,对于Controller的配置方式有很多种4.2 实现Controller接口Controller是一个接口,在org.springframework.web.servlet.mvc包下,接口中只有一个方法;//实现该接口的类获得控制器功能 public interface Controller { //处理请求且返回一个模型与视图对象 ModelAndView handleRequest(HttpServletRequest var1, HttpServletResponse var2) throws Exception; }测试新建一个Moudle,springmvc-04-controller 。将刚才的03 拷贝一份, 我们进行操作!删掉HelloControllermvc的配置文件只留下 视图解析器!编写一个Controller类,ControllerTest1//定义控制器 //注意点:不要导错包,实现Controller接口,重写方法; public class ControllerTest1 implements Controller { public ModelAndView handleRequest(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception { //返回一个模型视图对象 ModelAndView mv = new ModelAndView(); mv.addObject("msg","Test1Controller"); mv.setViewName("test"); return mv; } }编写完毕后,去Spring配置文件中注册请求的bean;name对应请求路径,class对应处理请求的类<bean name="/t1" class="com.kuang.controller.ControllerTest1"/>编写前端test.jsp,注意在WEB-INF/jsp目录下编写,对应我们的视图解析器<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Kuangshen</title> </head> <body> ${msg} </body> </html>配置Tomcat运行测试,我这里没有项目发布名配置的就是一个 / ,所以请求不用加项目名,OK!说明:实现接口Controller定义控制器是较老的办法缺点是:一个控制器中只有一个方法,如果要多个方法则需要定义多个Controller;定义的方式比较麻烦;4.3 使用注解@Controller(推荐)@Controller注解类型用于声明Spring类的实例是一个控制器(在讲IOC时还提到了另外3个注解);Spring可以使用扫描机制来找到应用程序中所有基于注解的控制器类,为了保证Spring能找到你的控制器,需要在配置文件中声明组件扫描。<!-- 自动扫描指定的包,下面所有注解类交给IOC容器管理 --> <context:component-scan base-package="com.kuang.controller"/>增加一个ControllerTest2类,使用注解实现;//@Controller注解的类会自动添加到Spring上下文中 @Controller public class ControllerTest2{ //映射访问路径 @RequestMapping("/t2") public String index(Model model){ //Spring MVC会自动实例化一个Model对象用于向视图中传值 model.addAttribute("msg", "ControllerTest2"); //返回视图位置 return "test"; } }运行tomcat测试可以发现,我们的两个请求都可以指向一个视图,但是页面结果的结果是不一样的,从这里可以看出视图是被复用的,而控制器与视图之间是弱偶合关系。注解方式是平时使用的最多的方式!4.4 RequestMapping@RequestMapping@RequestMapping注解用于映射url到控制器类或一个特定的处理程序方法。可用于类或方法上。用于类上,表示类中的所有响应请求的方法都是以该地址作为父路径。为了测试结论更加准确,我们可以加上一个项目名测试 myweb只注解在方法上面@Controller public class TestController { @RequestMapping("/h1") public String test(){ return "test"; } }访问路径:http://localhost:8080 / 项目名 / h1同时注解类与方法@Controller @RequestMapping("/admin") public class TestController { @RequestMapping("/h1") public String test(){ return "test"; } }访问路径:http://localhost:8080 / 项目名/ admin /h1 , 需要先指定类的路径再指定方法的路径;4.5 RestFul 风格概念Restful就是一个资源定位及资源操作的风格。不是标准也不是协议,只是一种风格。基于这个风格设计的软件可以更简洁,更有层次,更易于实现缓存等机制。功能资源:互联网所有的事物都可以被抽象为资源资源操作:使用POST、DELETE、PUT、GET,使用不同方法对资源进行操作。分别对应 添加、 删除、修改、查询。传统方式操作资源 :通过不同的参数来实现不同的效果!方法单一,post 和 get http://127.0.0.1/item/queryItem.action?id=1 查询,GET http://127.0.0.1/item/saveItem.action 新增,POST http://127.0.0.1/item/updateItem.action 更新,POST http://127.0.0.1/item/deleteItem.action?id=1 删除,GET或POST使用RESTful操作资源 :可以通过不同的请求方式来实现不同的效果!如下:请求地址一样,但是功能可以不同! http://127.0.0.1/item/1 查询,GET http://127.0.0.1/item 新增,POST http://127.0.0.1/item 更新,PUT http://127.0.0.1/item/1 删除,DELETE学习测试在新建一个类 RestFulController@Controller public class RestFulController { }在Spring MVC中可以使用 @PathVariable 注解,让方法参数的值对应绑定到一个URI模板变量上。@Controller public class RestFulController { //映射访问路径 @RequestMapping("/commit/{p1}/{p2}") public String index(@PathVariable int p1, @PathVariable int p2, Model model){ int result = p1+p2; //Spring MVC会自动实例化一个Model对象用于向视图中传值 model.addAttribute("msg", "结果:"+result); //返回视图位置 return "test"; } }我们来测试请求查看下思考:使用路径变量的好处?使路径变得更加简洁;获得参数更加方便,框架会自动进行类型转换。通过路径变量的类型可以约束访问参数,如果类型不一样,则访问不到对应的请求方法,如这里访问是的路径是/commit/1/a,则路径与方法不匹配,而不会是参数转换失败。 ![图片](https://mmbiz.qpic.cn/mmbiz_png/uJDAUKrGC7JOmNdhqNbrRK9XaseXIDsumkKQDDBx9D7E67IK9dmVtHnjn0WjPSOZxKnwz9AXrTPRLy6Xu42yrg/640?wx_fmt=png&wxfrom=5&wx_lazy=1&wx_co=1) 我们来修改下对应的参数类型,再次测试//映射访问路径 @RequestMapping("/commit/{p1}/{p2}") public String index(@PathVariable int p1, @PathVariable String p2, Model model){ String result = p1+p2; //Spring MVC会自动实例化一个Model对象用于向视图中传值 model.addAttribute("msg", "结果:"+result); //返回视图位置 return "test"; }使用method属性指定请求类型用于约束请求的类型,可以收窄请求范围。指定请求谓词的类型如GET, POST, HEAD, OPTIONS, PUT, PATCH, DELETE, TRACE等我们来测试一下:增加一个方法//映射访问路径,必须是POST请求 @RequestMapping(value = "/hello",method = {RequestMethod.POST}) public String index2(Model model){ model.addAttribute("msg", "hello!"); return "test"; }我们使用浏览器地址栏进行访问默认是Get请求,会报错405:如果将POST修改为GET则正常了;//映射访问路径,必须是Get请求 @RequestMapping(value = "/hello",method = {RequestMethod.GET}) public String index2(Model model){ model.addAttribute("msg", "hello!"); return "test"; }小结:Spring MVC 的 @RequestMapping 注解能够处理 HTTP 请求的方法, 比如 GET, PUT, POST, DELETE 以及 PATCH。所有的地址栏请求默认都会是 HTTP GET 类型的。方法级别的注解变体有如下几个:组合注解@GetMapping @PostMapping @PutMapping @DeleteMapping @PatchMapping@GetMapping 是一个组合注解,平时使用的会比较多!它所扮演的是 @RequestMapping(method =RequestMethod.GET) 的一个快捷方式。5.结果跳转方式5.1 ModelAndView设置ModelAndView对象 , 根据view的名称 , 和视图解析器跳到指定的页面 .页面 : {视图解析器前缀} + viewName +{视图解析器后缀}<!-- 视图解析器 --> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="internalResourceViewResolver"> <!-- 前缀 --> <property name="prefix" value="/WEB-INF/jsp/" /> <!-- 后缀 --> <property name="suffix" value=".jsp" /> </bean>对应的controller类public class ControllerTest1 implements Controller { public ModelAndView handleRequest(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception { //返回一个模型视图对象 ModelAndView mv = new ModelAndView(); mv.addObject("msg","ControllerTest1"); mv.setViewName("test"); return mv; } }5.2 ServletAPI通过设置ServletAPI , 不需要视图解析器 .1、通过HttpServletResponse进行输出2、通过HttpServletResponse实现重定向3、通过HttpServletResponse实现转发@Controller public class ResultGo { @RequestMapping("/result/t1") public void test1(HttpServletRequest req, HttpServletResponse rsp) throws IOException { rsp.getWriter().println("Hello,Spring BY servlet API"); } @RequestMapping("/result/t2") public void test2(HttpServletRequest req, HttpServletResponse rsp) throws IOException { rsp.sendRedirect("/index.jsp"); } @RequestMapping("/result/t3") public void test3(HttpServletRequest req, HttpServletResponse rsp) throws Exception { //转发 req.setAttribute("msg","/result/t3"); req.getRequestDispatcher("/WEB-INF/jsp/test.jsp").forward(req,rsp); } }5.3 SpringMVC通过SpringMVC来实现转发和重定向 - 无需视图解析器;测试前,需要将视图解析器注释掉@Controller public class ResultSpringMVC { @RequestMapping("/rsm/t1") public String test1(){ //转发 return "/index.jsp"; } @RequestMapping("/rsm/t2") public String test2(){ //转发二 return "forward:/index.jsp"; } @RequestMapping("/rsm/t3") public String test3(){ //重定向 return "redirect:/index.jsp"; } }通过SpringMVC来实现转发和重定向 - 有视图解析器;重定向 , 不需要视图解析器 , 本质就是重新请求一个新地方嘛 , 所以注意路径问题.可以重定向到另外一个请求实现 .@Controller public class ResultSpringMVC2 { @RequestMapping("/rsm2/t1") public String test1(){ //转发 return "test"; } @RequestMapping("/rsm2/t2") public String test2(){ //重定向 return "redirect:/index.jsp"; //return "redirect:hello.do"; //hello.do为另一个请求/ } }6.数据处理6.1 处理提交数据1、提交的域名称和处理方法的参数名一致提交数据 : http://localhost:8080/hello?name=kuangshen处理方法 :@RequestMapping("/hello") public String hello(String name){ System.out.println(name); return "hello"; }后台输出 : kuangshen2、提交的域名称和处理方法的参数名不一致提交数据 : http://localhost:8080/hello?username=kuangshen处理方法 ://@RequestParam("username") : username提交的域的名称 . @RequestMapping("/hello") public String hello(@RequestParam("username") String name){ System.out.println(name); return "hello"; }后台输出 : kuangshen3、提交的是一个对象要求提交的表单域和对象的属性名一致 , 参数使用对象即可1、实体类public class User { private int id; private String name; private int age; //构造 //get/set //tostring() }2、提交数据 : http://localhost:8080/mvc04/user?name=kuangshen&id=1&age=153、处理方法 :@RequestMapping("/user") public String user(User user){ System.out.println(user); return "hello"; }后台输出 : User { id=1, name='kuangshen', age=15 }说明:如果使用对象的话,前端传递的参数名和对象名必须一致,否则就是null。6.2 数据显示到前端第一种 : 通过ModelAndView我们前面一直都是如此 . 就不过多解释public class ControllerTest1 implements Controller { public ModelAndView handleRequest(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception { //返回一个模型视图对象 ModelAndView mv = new ModelAndView(); mv.addObject("msg","ControllerTest1"); mv.setViewName("test"); return mv; } }第二种 : 通过ModelMapModelMap@RequestMapping("/hello") public String hello(@RequestParam("username") String name, ModelMap model){ //封装要显示到视图中的数据 //相当于req.setAttribute("name",name); model.addAttribute("name",name); System.out.println(name); return "hello"; }第三种 : 通过ModelModel@RequestMapping("/ct2/hello") public String hello(@RequestParam("username") String name, Model model){ //封装要显示到视图中的数据 //相当于req.setAttribute("name",name); model.addAttribute("msg",name); System.out.println(name); return "test"; }对比就对于新手而言简单来说使用区别就是:Model 只有寥寥几个方法只适合用于储存数据,简化了新手对于Model对象的操作和理解; ModelMap 继承了 LinkedMap ,除了实现了自身的一些方法,同样的继承 LinkedMap 的方法和特性; ModelAndView 可以在储存数据的同时,可以进行设置返回的逻辑视图,进行控制展示层的跳转。当然更多的以后开发考虑的更多的是性能和优化,就不能单单仅限于此的了解。请使用80%的时间打好扎实的基础,剩下18%的时间研究框架,2%的时间去学点英文,框架的官方文档永远是最好的教程。6.3 乱码问题测试步骤:1、我们可以在首页编写一个提交的表单<form action="/e/t" method="post"> <input type="text" name="name"> <input type="submit"> </form>2、后台编写对应的处理类@Controller public class Encoding { @RequestMapping("/e/t") public String test(Model model,String name){ model.addAttribute("msg",name); //获取表单提交的值 return "test"; //跳转到test页面显示输入的值 } }3、输入中文测试,发现乱码不得不说,乱码问题是在我们开发中十分常见的问题,也是让我们程序猿比较头大的问题!以前乱码问题通过过滤器解决 , 而SpringMVC给我们提供了一个过滤器 , 可以在web.xml中配置 .修改了xml文件需要重启服务器!<filter> <filter-name>encoding</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>utf-8</param-value> </init-param> </filter> <filter-mapping> <filter-name>encoding</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>但是我们发现 , 有些极端情况下.这个过滤器对get的支持不好 .处理方法 :1、修改tomcat配置文件 :设置编码!<Connector URIEncoding="utf-8" port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" />2、自定义过滤器package com.kuang.filter; import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.util.Map; /** * 解决get和post请求 全部乱码的过滤器 */ public class GenericEncodingFilter implements Filter { @Override public void destroy() { } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { //处理response的字符编码 HttpServletResponse myResponse=(HttpServletResponse) response; myResponse.setContentType("text/html;charset=UTF-8"); // 转型为与协议相关对象 HttpServletRequest httpServletRequest = (HttpServletRequest) request; // 对request包装增强 HttpServletRequest myrequest = new MyRequest(httpServletRequest); chain.doFilter(myrequest, response); } @Override public void init(FilterConfig filterConfig) throws ServletException { } } //自定义request对象,HttpServletRequest的包装类 class MyRequest extends HttpServletRequestWrapper { private HttpServletRequest request; //是否编码的标记 private boolean hasEncode; //定义一个可以传入HttpServletRequest对象的构造函数,以便对其进行装饰 public MyRequest(HttpServletRequest request) { super(request);// super必须写 this.request = request; } // 对需要增强方法 进行覆盖 @Override public Map getParameterMap() { // 先获得请求方式 String method = request.getMethod(); if (method.equalsIgnoreCase("post")) { // post请求 try { // 处理post乱码 request.setCharacterEncoding("utf-8"); return request.getParameterMap(); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } } else if (method.equalsIgnoreCase("get")) { // get请求 Map<String, String[]> parameterMap = request.getParameterMap(); if (!hasEncode) { // 确保get手动编码逻辑只运行一次 for (String parameterName : parameterMap.keySet()) { String[] values = parameterMap.get(parameterName); if (values != null) { for (int i = 0; i < values.length; i++) { try { // 处理get乱码 values[i] = new String(values[i] .getBytes("ISO-8859-1"), "utf-8"); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } } } } hasEncode = true; } return parameterMap; } return super.getParameterMap(); } //取一个值 @Override public String getParameter(String name) { Map<String, String[]> parameterMap = getParameterMap(); String[] values = parameterMap.get(name); if (values == null) { return null; } return values[0]; // 取回参数的第一个值 } //取所有值 @Override public String[] getParameterValues(String name) { Map<String, String[]> parameterMap = getParameterMap(); String[] values = parameterMap.get(name); return values; } }这个也是我在网上找的一些大神写的,一般情况下,SpringMVC默认的乱码处理就已经能够很好的解决了!然后在web.xml中配置这个过滤器即可!乱码问题,需要平时多注意,在尽可能能设置编码的地方,都设置为统一编码 UTF-8!7.整合SSM7.1 环境要求环境:IDEAMySQL 5.7.19Tomcat 9Maven 3.6要求:需要熟练掌握MySQL数据库,Spring,JavaWeb及MyBatis知识,简单的前端知识;7.2 数据库环境创建一个存放书籍数据的数据库表CREATE DATABASE `ssmbuild`; USE `ssmbuild`; DROP TABLE IF EXISTS `books`; CREATE TABLE `books` ( `bookID` INT(10) NOT NULL AUTO_INCREMENT COMMENT '书id', `bookName` VARCHAR(100) NOT NULL COMMENT '书名', `bookCounts` INT(11) NOT NULL COMMENT '数量', `detail` VARCHAR(200) NOT NULL COMMENT '描述', KEY `bookID` (`bookID`) ) ENGINE=INNODB DEFAULT CHARSET=utf8 INSERT INTO `books`(`bookID`,`bookName`,`bookCounts`,`detail`)VALUES (1,'Java',1,'从入门到放弃'), (2,'MySQL',10,'从删库到跑路'), (3,'Linux',5,'从进门到进牢');7.3 基本环境搭建1、新建一Maven项目!ssmbuild , 添加web的支持2、导入相关的pom依赖!<dependencies> <!--Junit--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> <!--数据库驱动--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.47</version> </dependency> <!-- 数据库连接池 --> <dependency> <groupId>com.mchange</groupId> <artifactId>c3p0</artifactId> <version>0.9.5.2</version> </dependency> <!--Servlet - JSP --> <dependency> <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> <version>2.5</version> </dependency> <dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>jsp-api</artifactId> <version>2.2</version> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> <!--Mybatis--> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.2</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>2.0.2</version> </dependency> <!--Spring--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.1.9.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.1.9.RELEASE</version> </dependency> </dependencies>3、Maven资源过滤设置<build> <resources> <resource> <directory>src/main/java</directory> <includes> <include>**/*.properties</include> <include>**/*.xml</include> </includes> <filtering>false</filtering> </resource> <resource> <directory>src/main/resources</directory> <includes> <include>**/*.properties</include> <include>**/*.xml</include> </includes> <filtering>false</filtering> </resource> </resources> </build>4、建立基本结构和配置框架!com.kuang.pojocom.kuang.daocom.kuang.servicecom.kuang.controllermybatis-config.xml<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> </configuration>applicationContext.xml<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> </beans>Mybatis层编写1、数据库配置文件 database.propertiesjdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/ssmbuild?useSSL=true&useUnicode=true&characterEncoding=utf8 jdbc.username=root jdbc.password=1234562、IDEA关联数据库3、编写MyBatis的核心配置文件<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <typeAliases> <package name="com.kuang.pojo"/> </typeAliases> <mappers> <mapper resource="com/kuang/dao/BookMapper.xml"/> </mappers> </configuration>4、编写数据库对应的实体类 com.kuang.pojo.Books使用lombok插件!package com.kuang.pojo; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @Data @AllArgsConstructor @NoArgsConstructor public class Books { private int bookID; private String bookName; private int bookCounts; private String detail; }5、编写Dao层的 Mapper接口!package com.kuang.dao; import com.kuang.pojo.Books; import java.util.List; public interface BookMapper { //增加一个Book int addBook(Books book); //根据id删除一个Book int deleteBookById(int id); //更新Book int updateBook(Books books); //根据id查询,返回一个Book Books queryBookById(int id); //查询全部Book,返回list集合 List<Books> queryAllBook(); }6、编写接口对应的 Mapper.xml 文件。需要导入MyBatis的包;<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.kuang.dao.BookMapper"> <!--增加一个Book--> <insert id="addBook" parameterType="Books"> insert into ssmbuild.books(bookName,bookCounts,detail) values (#{bookName}, #{bookCounts}, #{detail}) </insert> <!--根据id删除一个Book--> <delete id="deleteBookById" parameterType="int"> delete from ssmbuild.books where bookID=#{bookID} </delete> <!--更新Book--> <update id="updateBook" parameterType="Books"> update ssmbuild.books set bookName = #{bookName},bookCounts = #{bookCounts},detail = #{detail} where bookID = #{bookID} </update> <!--根据id查询,返回一个Book--> <select id="queryBookById" resultType="Books"> select * from ssmbuild.books where bookID = #{bookID} </select> <!--查询全部Book--> <select id="queryAllBook" resultType="Books"> SELECT * from ssmbuild.books </select> </mapper>7、编写Service层的接口和实现类接口:package com.kuang.service; import com.kuang.pojo.Books; import java.util.List; //BookService:底下需要去实现,调用dao层 public interface BookService { //增加一个Book int addBook(Books book); //根据id删除一个Book int deleteBookById(int id); //更新Book int updateBook(Books books); //根据id查询,返回一个Book Books queryBookById(int id); //查询全部Book,返回list集合 List<Books> queryAllBook(); }实现类:package com.kuang.service; import com.kuang.dao.BookMapper; import com.kuang.pojo.Books; import java.util.List; public class BookServiceImpl implements BookService { //调用dao层的操作,设置一个set接口,方便Spring管理 private BookMapper bookMapper; public void setBookMapper(BookMapper bookMapper) { this.bookMapper = bookMapper; } public int addBook(Books book) { return bookMapper.addBook(book); } public int deleteBookById(int id) { return bookMapper.deleteBookById(id); } public int updateBook(Books books) { return bookMapper.updateBook(books); } public Books queryBookById(int id) { return bookMapper.queryBookById(id); } public List<Books> queryAllBook() { return bookMapper.queryAllBook(); } }OK,到此,底层需求操作编写完毕!7.4 Spring层1、配置Spring整合MyBatis,我们这里数据源使用c3p0连接池;2、我们去编写Spring整合Mybatis的相关的配置文件;spring-dao.xml<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"> <!-- 配置整合mybatis --> <!-- 1.关联数据库文件 --> <context:property-placeholder location="classpath:database.properties"/> <!-- 2.数据库连接池 --> <!--数据库连接池 dbcp 半自动化操作 不能自动连接 c3p0 自动化操作(自动的加载配置文件 并且设置到对象里面) --> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <!-- 配置连接池属性 --> <property name="driverClass" value="${jdbc.driver}"/> <property name="jdbcUrl" value="${jdbc.url}"/> <property name="user" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> <!-- c3p0连接池的私有属性 --> <property name="maxPoolSize" value="30"/> <property name="minPoolSize" value="10"/> <!-- 关闭连接后不自动commit --> <property name="autoCommitOnClose" value="false"/> <!-- 获取连接超时时间 --> <property name="checkoutTimeout" value="10000"/> <!-- 当获取连接失败重试次数 --> <property name="acquireRetryAttempts" value="2"/> </bean> <!-- 3.配置SqlSessionFactory对象 --> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <!-- 注入数据库连接池 --> <property name="dataSource" ref="dataSource"/> <!-- 配置MyBaties全局配置文件:mybatis-config.xml --> <property name="configLocation" value="classpath:mybatis-config.xml"/> </bean> <!-- 4.配置扫描Dao接口包,动态实现Dao接口注入到spring容器中 --> <!--解释 :https://www.cnblogs.com/jpfss/p/7799806.html--> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <!-- 注入sqlSessionFactory --> <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/> <!-- 给出需要扫描Dao接口包 --> <property name="basePackage" value="com.kuang.dao"/> </bean> </beans>3、Spring整合service层<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- 扫描service相关的bean --> <context:component-scan base-package="com.kuang.service" /> <!--BookServiceImpl注入到IOC容器中--> <bean id="BookServiceImpl" class="com.kuang.service.BookServiceImpl"> <property name="bookMapper" ref="bookMapper"/> </bean> <!-- 配置事务管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!-- 注入数据库连接池 --> <property name="dataSource" ref="dataSource" /> </bean> </beans>Spring层搞定!再次理解一下,Spring就是一个大杂烩,一个容器!对吧!7.5 SpringMVC层1、web.xml<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0"> <!--DispatcherServlet--> <servlet> <servlet-name>DispatcherServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <!--一定要注意:我们这里加载的是总的配置文件,之前被这里坑了!--> <param-value>classpath:applicationContext.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>DispatcherServlet</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> <!--encodingFilter--> <filter> <filter-name>encodingFilter</filter-name> <filter-class> org.springframework.web.filter.CharacterEncodingFilter </filter-class> <init-param> <param-name>encoding</param-name> <param-value>utf-8</param-value> </init-param> </filter> <filter-mapping> <filter-name>encodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <!--Session过期时间--> <session-config> <session-timeout>15</session-timeout> </session-config> </web-app>2、spring-mvc.xml<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd"> <!-- 配置SpringMVC --> <!-- 1.开启SpringMVC注解驱动 --> <mvc:annotation-driven /> <!-- 2.静态资源默认servlet配置--> <mvc:default-servlet-handler/> <!-- 3.配置jsp 显示ViewResolver视图解析器 --> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="viewClass" value="org.springframework.web.servlet.view.JstlView" /> <property name="prefix" value="/WEB-INF/jsp/" /> <property name="suffix" value=".jsp" /> </bean> <!-- 4.扫描web相关的bean --> <context:component-scan base-package="com.kuang.controller" /> </beans>3、Spring配置整合文件,applicationContext.xml<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <import resource="spring-dao.xml"/> <import resource="spring-service.xml"/> <import resource="spring-mvc.xml"/> </beans>配置文件,暂时结束!Controller 和 视图层编写1、BookController 类编写 , 方法一:查询全部书籍@Controller @RequestMapping("/book") public class BookController { @Autowired @Qualifier("BookServiceImpl") private BookService bookService; @RequestMapping("/allBook") public String list(Model model) { List<Books> list = bookService.queryAllBook(); model.addAttribute("list", list); return "allBook"; } }2、编写首页 index.jsp<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %> <!DOCTYPE HTML> <html> <head> <title>首页</title> <style type="text/css"> a { text-decoration: none; color: black; font-size: 18px; } h3 { width: 180px; height: 38px; margin: 100px auto; text-align: center; line-height: 38px; background: deepskyblue; border-radius: 4px; } </style> </head> <body> <h3> <a href="${pageContext.request.contextPath}/book/allBook">点击进入列表页</a> </h3> </body> </html>3、书籍列表页面 allbook.jsp<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>书籍列表</title> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <!-- 引入 Bootstrap --> <link href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet"> </head> <body> <div class="container"> <div class="row clearfix"> <div class="col-md-12 column"> <div class="page-header"> <h1> <small>书籍列表 —— 显示所有书籍</small> </h1> </div> </div> </div> <div class="row"> <div class="col-md-4 column"> <a class="btn btn-primary" href="${pageContext.request.contextPath}/book/toAddBook">新增</a> </div> </div> <div class="row clearfix"> <div class="col-md-12 column"> <table class="table table-hover table-striped"> <thead> <tr> <th>书籍编号</th> <th>书籍名字</th> <th>书籍数量</th> <th>书籍详情</th> <th>操作</th> </tr> </thead> <tbody> <c:forEach var="book" items="${requestScope.get('list')}"> <tr> <td>${book.getBookID()}</td> <td>${book.getBookName()}</td> <td>${book.getBookCounts()}</td> <td>${book.getDetail()}</td> <td> <a href="${pageContext.request.contextPath}/book/toUpdateBook?id=${book.getBookID()}">更改</a> | <a href="${pageContext.request.contextPath}/book/del/${book.getBookID()}">删除</a> </td> </tr> </c:forEach> </tbody> </table> </div> </div> </div>4、BookController 类编写 , 方法二:添加书籍@RequestMapping("/toAddBook") public String toAddPaper() { return "addBook"; } @RequestMapping("/addBook") public String addPaper(Books books) { System.out.println(books); bookService.addBook(books); return "redirect:/book/allBook"; }5、添加书籍页面:addBook.jsp<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>新增书籍</title> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <!-- 引入 Bootstrap --> <link href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet"> </head> <body> <div class="container"> <div class="row clearfix"> <div class="col-md-12 column"> <div class="page-header"> <h1> <small>新增书籍</small> </h1> </div> </div> </div> <form action="${pageContext.request.contextPath}/book/addBook" method="post"> 书籍名称:<input type="text" name="bookName"><br><br><br> 书籍数量:<input type="text" name="bookCounts"><br><br><br> 书籍详情:<input type="text" name="detail"><br><br><br> <input type="submit" value="添加"> </form> </div>6、BookController 类编写 , 方法三:修改书籍@RequestMapping("/toUpdateBook") public String toUpdateBook(Model model, int id) { Books books = bookService.queryBookById(id); System.out.println(books); model.addAttribute("book",books ); return "updateBook"; } @RequestMapping("/updateBook") public String updateBook(Model model, Books book) { System.out.println(book); bookService.updateBook(book); Books books = bookService.queryBookById(book.getBookID()); model.addAttribute("books", books); return "redirect:/book/allBook"; }7、修改书籍页面 updateBook.jsp<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>修改信息</title> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <!-- 引入 Bootstrap --> <link href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet"> </head> <body> <div class="container"> <div class="row clearfix"> <div class="col-md-12 column"> <div class="page-header"> <h1> <small>修改信息</small> </h1> </div> </div> </div> <form action="${pageContext.request.contextPath}/book/updateBook" method="post"> <input type="hidden" name="bookID" value="${book.getBookID()}"/> 书籍名称:<input type="text" name="bookName" value="${book.getBookName()}"/> 书籍数量:<input type="text" name="bookCounts" value="${book.getBookCounts()}"/> 书籍详情:<input type="text" name="detail" value="${book.getDetail() }"/> <input type="submit" value="提交"/> </form> </div>8、BookController 类编写 , 方法四:删除书籍@RequestMapping("/del/{bookId}") public String deleteBook(@PathVariable("bookId") int id) { bookService.deleteBookById(id); return "redirect:/book/allBook"; }配置Tomcat,进行运行!到目前为止,这个SSM项目整合已经完全的OK了,可以直接运行进行测试!这个练习十分的重要,大家需要保证,不看任何东西,自己也可以完整的实现出来!7.6 小结及展望这个是同学们的第一个SSM整合案例,一定要烂熟于心!SSM框架的重要程度是不言而喻的,学到这里,大家已经可以进行基本网站的单独开发。但是这只是增删改查的基本操作。可以说学到这里,大家才算是真正的步入了后台开发的门。也就是能找一个后台相关工作的底线。或许很多人,工作就做这些事情,但是对于个人的提高来说,还远远不够!我们后面还要学习一些 SpringMVC 的知识!Ajax 和 Json文件上传和下载拦截器8.Json8.1 什么是JSON?JSON(JavaScript Object Notation, JS 对象标记) 是一种轻量级的数据交换格式,目前使用特别广泛。采用完全独立于编程语言的文本格式来存储和表示数据。简洁和清晰的层次结构使得 JSON 成为理想的数据交换语言。易于人阅读和编写,同时也易于机器解析和生成,并有效地提升网络传输效率。在 JavaScript 语言中,一切都是对象。因此,任何JavaScript 支持的类型都可以通过 JSON 来表示,例如字符串、数字、对象、数组等。看看他的要求和语法格式:对象表示为键值对,数据由逗号分隔花括号保存对象方括号保存数组JSON 键值对是用来保存 JavaScript 对象的一种方式,和 JavaScript 对象的写法也大同小异,键/值对组合中的键名写在前面并用双引号 "" 包裹,使用冒号 : 分隔,然后紧接着值:{"name": "QinJiang"} {"age": "3"} {"sex": "男"}很多人搞不清楚 JSON 和 JavaScript 对象的关系,甚至连谁是谁都不清楚。其实,可以这么理解:JSON 是 JavaScript 对象的字符串表示法,它使用文本表示一个 JS 对象的信息,本质是一个字符串。var obj = {a: 'Hello', b: 'World'}; //这是一个对象,注意键名也是可以使用引号包裹的 var json = '{"a": "Hello", "b": "World"}'; //这是一个 JSON 字符串,本质是一个字符串JSON 和 JavaScript 对象互转要实现从JSON字符串转换为JavaScript 对象,使用 JSON.parse() 方法:var obj = JSON.parse('{"a": "Hello", "b": "World"}'); //结果是 {a: 'Hello', b: 'World'}要实现从JavaScript 对象转换为JSON字符串,使用 JSON.stringify() 方法:var json = JSON.stringify({a: 'Hello', b: 'World'}); //结果是 '{"a": "Hello", "b": "World"}'代码测试1、新建一个module ,springmvc-05-json , 添加web的支持2、在web目录下新建一个 json-1.html , 编写测试内容<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>JSON_秦疆</title> </head> <body> <script type="text/javascript"> //编写一个js的对象 var user = { name:"秦疆", age:3, sex:"男" }; //将js对象转换成json字符串 var str = JSON.stringify(user); console.log(str); //将json字符串转换为js对象 var user2 = JSON.parse(str); console.log(user2.age,user2.name,user2.sex); </script> </body> </html>3、在IDEA中使用浏览器打开,查看控制台输出!8.2 Controller返回JSON数据Jackson应该是目前比较好的json解析工具了当然工具不止这一个,比如还有阿里巴巴的 fastjson 等等。我们这里使用Jackson,使用它需要导入它的jar包;<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-core --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.9.8</version> </dependency>配置SpringMVC需要的配置web.xml<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0"> <!--1.注册servlet--> <servlet> <servlet-name>SpringMVC</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <!--通过初始化参数指定SpringMVC配置文件的位置,进行关联--> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:springmvc-servlet.xml</param-value> </init-param> <!-- 启动顺序,数字越小,启动越早 --> <load-on-startup>1</load-on-startup> </servlet> <!--所有请求都会被springmvc拦截 --> <servlet-mapping> <servlet-name>SpringMVC</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> <filter> <filter-name>encoding</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>utf-8</param-value> </init-param> </filter> <filter-mapping> <filter-name>encoding</filter-name> <url-pattern>/</url-pattern> </filter-mapping> </web-app>springmvc-servlet.xml<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd"> <!-- 自动扫描指定的包,下面所有注解类交给IOC容器管理 --> <context:component-scan base-package="com.kuang.controller"/> <!-- 视图解析器 --> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="internalResourceViewResolver"> <!-- 前缀 --> <property name="prefix" value="/WEB-INF/jsp/" /> <!-- 后缀 --> <property name="suffix" value=".jsp" /> </bean> </beans>我们随便编写一个User的实体类,然后我们去编写我们的测试Controller;package com.kuang.pojo; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; //需要导入lombok @Data @AllArgsConstructor @NoArgsConstructor public class User { private String name; private int age; private String sex; }这里我们需要两个新东西,一个是@ResponseBody,一个是ObjectMapper对象,我们看下具体的用法编写一个Controller;@Controller public class UserController { @RequestMapping("/json1") @ResponseBody public String json1() throws JsonProcessingException { //创建一个jackson的对象映射器,用来解析数据 ObjectMapper mapper = new ObjectMapper(); //创建一个对象 User user = new User("秦疆1号", 3, "男"); //将我们的对象解析成为json格式 String str = mapper.writeValueAsString(user); //由于@ResponseBody注解,这里会将str转成json格式返回;十分方便 return str; } }配置Tomcat , 启动测试一下!http://localhost:8080/json1发现出现了乱码问题,我们需要设置一下他的编码格式为utf-8,以及它返回的类型;通过@RequestMaping的produces属性来实现,修改下代码//produces:指定响应体返回类型和编码 @RequestMapping(value = "/json1",produces = "application/json;charset=utf-8")再次测试, http://localhost:8080/json1 , 乱码问题OK!【注意:使用json记得处理乱码问题】8.3 代码优化乱码统一解决上一种方法比较麻烦,如果项目中有许多请求则每一个都要添加,可以通过Spring配置统一指定,这样就不用每次都去处理了!我们可以在springmvc的配置文件上添加一段消息StringHttpMessageConverter转换配置!<mvc:annotation-driven> <mvc:message-converters register-defaults="true"> <bean class="org.springframework.http.converter.StringHttpMessageConverter"> <constructor-arg value="UTF-8"/> </bean> <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"> <property name="objectMapper"> <bean class="org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean"> <property name="failOnEmptyBeans" value="false"/> </bean> </property> </bean> </mvc:message-converters> </mvc:annotation-driven>返回json字符串统一解决在类上直接使用 @RestController ,这样子,里面所有的方法都只会返回 json 字符串了,不用再每一个都添加@ResponseBody !我们在前后端分离开发中,一般都使用 @RestController ,十分便捷!@RestController public class UserController { //produces:指定响应体返回类型和编码 @RequestMapping(value = "/json1") public String json1() throws JsonProcessingException { //创建一个jackson的对象映射器,用来解析数据 ObjectMapper mapper = new ObjectMapper(); //创建一个对象 User user = new User("秦疆1号", 3, "男"); //将我们的对象解析成为json格式 String str = mapper.writeValueAsString(user); //由于@ResponseBody注解,这里会将str转成json格式返回;十分方便 return str; } }启动tomcat测试,结果都正常输出!8.4 测试集合输出增加一个新的方法@RequestMapping("/json2") public String json2() throws JsonProcessingException { //创建一个jackson的对象映射器,用来解析数据 ObjectMapper mapper = new ObjectMapper(); //创建一个对象 User user1 = new User("秦疆1号", 3, "男"); User user2 = new User("秦疆2号", 3, "男"); User user3 = new User("秦疆3号", 3, "男"); User user4 = new User("秦疆4号", 3, "男"); List<User> list = new ArrayList<User>(); list.add(user1); list.add(user2); list.add(user3); list.add(user4); //将我们的对象解析成为json格式 String str = mapper.writeValueAsString(list); return str; }运行结果 : 十分完美,没有任何问题!8.5 输出时间对象增加一个新的方法@RequestMapping("/json3") public String json3() throws JsonProcessingException { ObjectMapper mapper = new ObjectMapper(); //创建时间一个对象,java.util.Date Date date = new Date(); //将我们的对象解析成为json格式 String str = mapper.writeValueAsString(date); return str; }运行结果 :默认日期格式会变成一个数字,是1970年1月1日到当前日期的毫秒数!Jackson 默认是会把时间转成timestamps形式解决方案:取消timestamps形式 , 自定义时间格式@RequestMapping("/json4") public String json4() throws JsonProcessingException { ObjectMapper mapper = new ObjectMapper(); //不使用时间戳的方式 mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false); //自定义日期格式对象 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); //指定日期格式 mapper.setDateFormat(sdf); Date date = new Date(); String str = mapper.writeValueAsString(date); return str; }运行结果 : 成功的输出了时间!8.6 抽取为工具类如果要经常使用的话,这样是比较麻烦的,我们可以将这些代码封装到一个工具类中;我们去编写下package com.kuang.utils; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; import java.text.SimpleDateFormat; public class JsonUtils { public static String getJson(Object object) { return getJson(object,"yyyy-MM-dd HH:mm:ss"); } public static String getJson(Object object,String dateFormat) { ObjectMapper mapper = new ObjectMapper(); //不使用时间差的方式 mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false); //自定义日期格式对象 SimpleDateFormat sdf = new SimpleDateFormat(dateFormat); //指定日期格式 mapper.setDateFormat(sdf); try { return mapper.writeValueAsString(object); } catch (JsonProcessingException e) { e.printStackTrace(); } return null; } }我们使用工具类,代码就更加简洁了!@RequestMapping("/json5") public String json5() throws JsonProcessingException { Date date = new Date(); String json = JsonUtils.getJson(date); return json; }大功告成!完美!8.7 FastJsonfastjson.jar是阿里开发的一款专门用于Java开发的包,可以方便的实现json对象与JavaBean对象的转换,实现JavaBean对象与json字符串的转换,实现json对象与json字符串的转换。实现json的转换方法很多,最后的实现结果都是一样的。fastjson 的 pom依赖!<dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.60</version> </dependency>fastjson 三个主要的类:JSONObject 代表 json 对象JSONObject实现了Map接口, 猜想 JSONObject底层操作是由Map实现的。JSONObject对应json对象,通过各种形式的get()方法可以获取json对象中的数据,也可利用诸如size(),isEmpty()等方法获取"键:值"对的个数和判断是否为空。其本质是通过实现Map接口并调用接口中的方法完成的。JSONArray 代表 json 对象数组内部是有List接口中的方法来完成操作的。JSON代表 JSONObject和JSONArray的转化JSON类源码分析与使用仔细观察这些方法,主要是实现json对象,json对象数组,javabean对象,json字符串之间的相互转化。代码测试,我们新建一个FastJsonDemo 类package com.kuang.controller; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.kuang.pojo.User; import java.util.ArrayList; import java.util.List; public class FastJsonDemo { public static void main(String[] args) { //创建一个对象 User user1 = new User("秦疆1号", 3, "男"); User user2 = new User("秦疆2号", 3, "男"); User user3 = new User("秦疆3号", 3, "男"); User user4 = new User("秦疆4号", 3, "男"); List<User> list = new ArrayList<User>(); list.add(user1); list.add(user2); list.add(user3); list.add(user4); System.out.println("*******Java对象 转 JSON字符串*******"); String str1 = JSON.toJSONString(list); System.out.println("JSON.toJSONString(list)==>"+str1); String str2 = JSON.toJSONString(user1); System.out.println("JSON.toJSONString(user1)==>"+str2); System.out.println("\n****** JSON字符串 转 Java对象*******"); User jp_user1=JSON.parseObject(str2,User.class); System.out.println("JSON.parseObject(str2,User.class)==>"+jp_user1); System.out.println("\n****** Java对象 转 JSON对象 ******"); JSONObject jsonObject1 = (JSONObject) JSON.toJSON(user2); System.out.println("(JSONObject) JSON.toJSON(user2)==>"+jsonObject1.getString("name")); System.out.println("\n****** JSON对象 转 Java对象 ******"); User to_java_user = JSON.toJavaObject(jsonObject1, User.class); System.out.println("JSON.toJavaObject(jsonObject1, User.class)==>"+to_java_user); } }这种工具类,我们只需要掌握使用就好了,在使用的时候在根据具体的业务去找对应的实现。和以前的commons-io那种工具包一样,拿来用就好了!9.Ajax研究9.1 简介AJAX = Asynchronous JavaScript and XML(异步的 JavaScript 和 XML)。AJAX 是一种在无需重新加载整个网页的情况下,能够更新部分网页的技术。Ajax 不是一种新的编程语言,而是一种用于创建更好更快以及交互性更强的Web应用程序的技术。在 2005 年,Google 通过其 Google Suggest 使 AJAX 变得流行起来。Google Suggest能够自动帮你完成搜索单词。Google Suggest 使用 AJAX 创造出动态性极强的 web 界面:当您在谷歌的搜索框输入关键字时,JavaScript 会把这些字符发送到服务器,然后服务器会返回一个搜索建议的列表。就和国内百度的搜索框一样!传统的网页(即不用ajax技术的网页),想要更新内容或者提交一个表单,都需要重新加载整个网页。使用ajax技术的网页,通过在后台服务器进行少量的数据交换,就可以实现异步局部更新。使用Ajax,用户可以创建接近本地桌面应用的直接、高可用、更丰富、更动态的Web用户界面。9.2 伪造Ajax我们可以使用前端的一个标签来伪造一个ajax的样子。iframe标签1、新建一个module :sspringmvc-06-ajax , 导入web支持!2、编写一个 ajax-frame.html 使用 iframe 测试,感受下效果<!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <title>kuangshen</title> </head> <body> <script type="text/javascript"> window.onload = function(){ var myDate = new Date(); document.getElementById('currentTime').innerText = myDate.getTime(); }; function LoadPage(){ var targetUrl = document.getElementById('url').value; console.log(targetUrl); document.getElementById("iframePosition").src = targetUrl; } </script> <div> <p>请输入要加载的地址:<span id="currentTime"></span></p> <p> <input id="url" type="text" value="https://www.baidu.com/"/> <input type="button" value="提交" onclick="LoadPage()"> </p> </div> <div> <h3>加载页面位置:</h3> <iframe id="iframePosition" style="width: 100%;height: 500px;"></iframe> </div> </body> </html>3、使用IDEA开浏览器测试一下!利用AJAX可以做:注册时,输入用户名自动检测用户是否已经存在。登陆时,提示用户名密码错误删除数据行时,将行ID发送到后台,后台在数据库中删除,数据库删除成功后,在页面DOM中将数据行也删除。....等等9.3 jQuery.ajax纯JS原生实现Ajax我们不去讲解这里,直接使用jquery提供的,方便学习和使用,避免重复造轮子,有兴趣的同学可以去了解下JS原生XMLHttpRequest !Ajax的核心是XMLHttpRequest对象(XHR)。XHR为向服务器发送请求和解析服务器响应提供了接口。能够以异步方式从服务器获取新数据。jQuery 提供多个与 AJAX 有关的方法。通过 jQuery AJAX 方法,您能够使用 HTTP Get 和 HTTP Post 从远程服务器上请求文本、HTML、XML 或 JSON – 同时您能够把这些外部数据直接载入网页的被选元素中。jQuery 不是生产者,而是大自然搬运工。jQuery Ajax本质就是 XMLHttpRequest,对他进行了封装,方便调用!jQuery.ajax(...) 部分参数: url:请求地址 type:请求方式,GET、POST(1.9.0之后用method) headers:请求头 data:要发送的数据 contentType:即将发送信息至服务器的内容编码类型(默认: "application/x-www-form-urlencoded; charset=UTF-8") async:是否异步 timeout:设置请求超时时间(毫秒) beforeSend:发送请求前执行的函数(全局) complete:完成之后执行的回调函数(全局) success:成功之后执行的回调函数(全局) error:失败之后执行的回调函数(全局) accepts:通过请求头发送给服务器,告诉服务器当前客户端可接受的数据类型 dataType:将服务器端返回的数据转换成指定类型 "xml": 将服务器端返回的内容转换成xml格式 "text": 将服务器端返回的内容转换成普通文本格式 "html": 将服务器端返回的内容转换成普通文本格式,在插入DOM中时,如果包含JavaScript标签,则会尝试去执行。 "script": 尝试将返回值当作JavaScript去执行,然后再将服务器端返回的内容转换成普通文本格式 "json": 将服务器端返回的内容转换成相应的JavaScript对象 "jsonp": JSONP 格式使用 JSONP 形式调用函数时,如 "myurl?callback=?" jQuery 将自动替换 ? 为正确的函数名,以执行回调函数我们来个简单的测试,使用最原始的HttpServletResponse处理 , .最简单 , 最通用1、配置web.xml 和 springmvc的配置文件,复制上面案例的即可 【记得静态资源过滤和注解驱动配置上】<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd"> <!-- 自动扫描指定的包,下面所有注解类交给IOC容器管理 --> <context:component-scan base-package="com.kuang.controller"/> <mvc:default-servlet-handler /> <mvc:annotation-driven /> <!-- 视图解析器 --> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="internalResourceViewResolver"> <!-- 前缀 --> <property name="prefix" value="/WEB-INF/jsp/" /> <!-- 后缀 --> <property name="suffix" value=".jsp" /> </bean> </beans>2、编写一个AjaxController@Controller public class AjaxController { @RequestMapping("/a1") public void ajax1(String name , HttpServletResponse response) throws IOException { if ("admin".equals(name)){ response.getWriter().print("true"); }else{ response.getWriter().print("false"); } } }3、导入jquery , 可以使用在线的CDN , 也可以下载导入<script src="https://code.jquery.com/jquery-3.1.1.min.js"></script> <script src="${pageContext.request.contextPath}/statics/js/jquery-3.1.1.min.js"></script>4、编写index.jsp测试<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>$Title$</title> <%--<script src="https://code.jquery.com/jquery-3.1.1.min.js"></script>--%> <script src="${pageContext.request.contextPath}/statics/js/jquery-3.1.1.min.js"></script> <script> function a1(){ $.post({ url:"${pageContext.request.contextPath}/a1", data:{'name':$("#txtName").val()}, success:function (data,status) { alert(data); alert(status); } }); } </script> </head> <body> <%--onblur:失去焦点触发事件--%> 用户名:<input type="text" id="txtName" onblur="a1()"/> </body> </html>5、启动tomcat测试!打开浏览器的控制台,当我们鼠标离开输入框的时候,可以看到发出了一个ajax的请求!是后台返回给我们的结果!测试成功!9.4 Springmvc实现实体类user@Data @AllArgsConstructor @NoArgsConstructor public class User { private String name; private int age; private String sex; }我们来获取一个集合对象,展示到前端页面@RequestMapping("/a2") public List<User> ajax2(){ List<User> list = new ArrayList<User>(); list.add(new User("秦疆1号",3,"男")); list.add(new User("秦疆2号",3,"男")); list.add(new User("秦疆3号",3,"男")); return list; //由于@RestController注解,将list转成json格式返回 }前端页面<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Title</title> </head> <body> <input type="button" id="btn" value="获取数据"/> <table width="80%" align="center"> <tr> <td>姓名</td> <td>年龄</td> <td>性别</td> </tr> <tbody id="content"> </tbody> </table> <script src="${pageContext.request.contextPath}/statics/js/jquery-3.1.1.min.js"></script> <script> $(function () { $("#btn").click(function () { $.post("${pageContext.request.contextPath}/a2",function (data) { console.log(data) var html=""; for (var i = 0; i <data.length ; i++) { html+= "<tr>" + "<td>" + data[i].name + "</td>" + "<td>" + data[i].age + "</td>" + "<td>" + data[i].sex + "</td>" + "</tr>" } $("#content").html(html); }); }) }) </script> </body> </html>成功实现了数据回显!可以体会一下Ajax的好处!9.5 注册提示效果我们再测试一个小Demo,思考一下我们平时注册时候,输入框后面的实时提示怎么做到的;如何优化我们写一个Controller@RequestMapping("/a3") public String ajax3(String name,String pwd){ String msg = ""; //模拟数据库中存在数据 if (name!=null){ if ("admin".equals(name)){ msg = "OK"; }else { msg = "用户名输入错误"; } } if (pwd!=null){ if ("123456".equals(pwd)){ msg = "OK"; }else { msg = "密码输入有误"; } } return msg; //由于@RestController注解,将msg转成json格式返回 }前端页面 login.jsp<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>ajax</title> <script src="${pageContext.request.contextPath}/statics/js/jquery-3.1.1.min.js"></script> <script> function a1(){ $.post({ url:"${pageContext.request.contextPath}/a3", data:{'name':$("#name").val()}, success:function (data) { if (data.toString()=='OK'){ $("#userInfo").css("color","green"); }else { $("#userInfo").css("color","red"); } $("#userInfo").html(data); } }); } function a2(){ $.post({ url:"${pageContext.request.contextPath}/a3", data:{'pwd':$("#pwd").val()}, success:function (data) { if (data.toString()=='OK'){ $("#pwdInfo").css("color","green"); }else { $("#pwdInfo").css("color","red"); } $("#pwdInfo").html(data); } }); } </script> </head> <body> <p> 用户名:<input type="text" id="name" onblur="a1()"/> <span id="userInfo"></span> </p> <p> 密码:<input type="text" id="pwd" onblur="a2()"/> <span id="pwdInfo"></span> </p> </body> </html>【记得处理json乱码问题】测试一下效果,动态请求响应,局部刷新,就是如此!9.6 获取baidu接口Demo<!DOCTYPE HTML> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <title>JSONP百度搜索</title> <style> #q{ width: 500px; height: 30px; border:1px solid #ddd; line-height: 30px; display: block; margin: 0 auto; padding: 0 10px; font-size: 14px; } #ul{ width: 520px; list-style: none; margin: 0 auto; padding: 0; border:1px solid #ddd; margin-top: -1px; display: none; } #ul li{ line-height: 30px; padding: 0 10px; } #ul li:hover{ background-color: #f60; color: #fff; } </style> <script> // 2.步骤二 // 定义demo函数 (分析接口、数据) function demo(data){ var Ul = document.getElementById('ul'); var html = ''; // 如果搜索数据存在 把内容添加进去 if (data.s.length) { // 隐藏掉的ul显示出来 Ul.style.display = 'block'; // 搜索到的数据循环追加到li里 for(var i = 0;i<data.s.length;i++){ html += '<li>'+data.s[i]+'</li>'; } // 循环的li写入ul Ul.innerHTML = html; } } // 1.步骤一 window.onload = function(){ // 获取输入框和ul var Q = document.getElementById('q'); var Ul = document.getElementById('ul'); // 事件鼠标抬起时候 Q.onkeyup = function(){ // 如果输入框不等于空 if (this.value != '') { // ☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆JSONPz重点☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆ // 创建标签 var script = document.createElement('script'); //给定要跨域的地址 赋值给src //这里是要请求的跨域的地址 我写的是百度搜索的跨域地址 script.src = 'https://sp0.baidu.com/5a1Fazu8AA54nxGko9WTAnF6hhy/su?wd='+this.value+'&cb=demo'; // 将组合好的带src的script标签追加到body里 document.body.appendChild(script); } } } </script> </head> <body> <input type="text" id="q" /> <ul id="ul"> </ul> </body> </html>10.拦截器10.1 概述SpringMVC的处理器拦截器类似于Servlet开发中的过滤器Filter,用于对处理器进行预处理和后处理。开发者可以自己定义一些拦截器来实现特定的功能。过滤器与拦截器的区别:拦截器是AOP思想的具体应用。过滤器servlet规范中的一部分,任何java web工程都可以使用在url-pattern中配置了/*之后,可以对所有要访问的资源进行拦截拦截器拦截器是SpringMVC框架自己的,只有使用了SpringMVC框架的工程才能使用拦截器只会拦截访问的控制器方法, 如果访问的是jsp/html/css/image/js是不会进行拦截的10.2 自定义拦截器那如何实现拦截器呢?想要自定义拦截器,必须实现 HandlerInterceptor 接口。1、新建一个Moudule , springmvc-07-Interceptor , 添加web支持2、配置web.xml 和 springmvc-servlet.xml 文件3、编写一个拦截器package com.kuang.interceptor; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class MyInterceptor implements HandlerInterceptor { //在请求处理的方法之前执行 //如果返回true执行下一个拦截器 //如果返回false就不执行下一个拦截器 public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception { System.out.println("------------处理前------------"); return true; } //在请求处理方法执行之后执行 public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception { System.out.println("------------处理后------------"); } //在dispatcherServlet处理后执行,做清理工作. public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception { System.out.println("------------清理------------"); } }4、在springmvc的配置文件中配置拦截器<!--关于拦截器的配置--> <mvc:interceptors> <mvc:interceptor> <!--/** 包括路径及其子路径--> <!--/admin/* 拦截的是/admin/add等等这种 , /admin/add/user不会被拦截--> <!--/admin/** 拦截的是/admin/下的所有--> <mvc:mapping path="/**"/> <!--bean配置的就是拦截器--> <bean class="com.kuang.interceptor.MyInterceptor"/> </mvc:interceptor> </mvc:interceptors>5、编写一个Controller,接收请求package com.kuang.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; //测试拦截器的控制器 @Controller public class InterceptorController { @RequestMapping("/interceptor") @ResponseBody public String testFunction() { System.out.println("控制器中的方法执行了"); return "hello"; } }6、前端 index.jsp<a href="${pageContext.request.contextPath}/interceptor">拦截器测试</a>7、启动tomcat 测试一下!10.3 验证用户是否登录 (认证用户)实现思路1、有一个登陆页面,需要写一个controller访问页面。2、登陆页面有一提交表单的动作。需要在controller中处理。判断用户名密码是否正确。如果正确,向session中写入用户信息。返回登陆成功。3、拦截用户请求,判断用户是否登陆。如果用户已经登陆。放行, 如果用户未登陆,跳转到登陆页面测试:1、编写一个登陆页面 login.jsp<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Title</title> </head> <h1>登录页面</h1> <hr> <body> <form action="${pageContext.request.contextPath}/user/login"> 用户名:<input type="text" name="username"> <br> 密码:<input type="password" name="pwd"> <br> <input type="submit" value="提交"> </form> </body> </html>2、编写一个Controller处理请求package com.kuang.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import javax.servlet.http.HttpSession; @Controller @RequestMapping("/user") public class UserController { //跳转到登陆页面 @RequestMapping("/jumplogin") public String jumpLogin() throws Exception { return "login";d } //跳转到成功页面 @RequestMapping("/jumpSuccess") public String jumpSuccess() throws Exception { return "success"; } //登陆提交 @RequestMapping("/login") public String login(HttpSession session, String username, String pwd) throws Exception { // 向session记录用户身份信息 System.out.println("接收前端==="+username); session.setAttribute("user", username); return "success"; } //退出登陆 @RequestMapping("logout") public String logout(HttpSession session) throws Exception { // session 过期 session.invalidate(); return "login"; } }3、编写一个登陆成功的页面 success.jsp<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Title</title> </head> <body> <h1>登录成功页面</h1> <hr> ${user} <a href="${pageContext.request.contextPath}/user/logout">注销</a> </body> </html>4、在 index 页面上测试跳转!启动Tomcat 测试,未登录也可以进入主页!<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>$Title$</title> </head> <body> <h1>首页</h1> <hr> <%--登录--%> <a href="${pageContext.request.contextPath}/user/jumplogin">登录</a> <a href="${pageContext.request.contextPath}/user/jumpSuccess">成功页面</a> </body> </html>5、编写用户登录拦截器package com.kuang.interceptor; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.io.IOException; public class LoginInterceptor implements HandlerInterceptor { public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws ServletException, IOException { // 如果是登陆页面则放行 System.out.println("uri: " + request.getRequestURI()); if (request.getRequestURI().contains("login")) { return true; } HttpSession session = request.getSession(); // 如果用户已登陆也放行 if(session.getAttribute("user") != null) { return true; } // 用户没有登陆跳转到登陆页面 request.getRequestDispatcher("/WEB-INF/jsp/login.jsp").forward(request, response); return false; } public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception { } public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception { } }6、在Springmvc的配置文件中注册拦截器<!--关于拦截器的配置--> <mvc:interceptors> <mvc:interceptor> <mvc:mapping path="/**"/> <bean id="loginInterceptor" class="com.kuang.interceptor.LoginInterceptor"/> </mvc:interceptor> </mvc:interceptors>7、再次重启Tomcat测试!OK,测试登录拦截功能无误.11.文件上传和下载11.1 准备工作文件上传是项目开发中最常见的功能之一 ,springMVC 可以很好的支持文件上传,但是SpringMVC上下文中默认没有装配MultipartResolver,因此默认情况下其不能处理文件上传工作。如果想使用Spring的文件上传功能,则需要在上下文中配置MultipartResolver。前端表单要求:为了能上传文件,必须将表单的method设置为POST,并将enctype设置为multipart/form-data。只有在这样的情况下,浏览器才会把用户选择的文件以二进制数据发送给服务器;对表单中的 enctype 属性做个详细的说明:application/x-www=form-urlencoded:默认方式,只处理表单域中的 value 属性值,采用这种编码方式的表单会将表单域中的值处理成 URL 编码方式。multipart/form-data:这种编码方式会以二进制流的方式来处理表单数据,这种编码方式会把文件域指定文件的内容也封装到请求参数中,不会对字符编码。text/plain:除了把空格转换为 "+" 号外,其他字符都不做编码处理,这种方式适用直接通过表单发送邮件。<form action="" enctype="multipart/form-data" method="post"> <input type="file" name="file"/> <input type="submit"> </form>一旦设置了enctype为multipart/form-data,浏览器即会采用二进制流的方式来处理表单数据,而对于文件上传的处理则涉及在服务器端解析原始的HTTP响应。在2003年,Apache Software Foundation发布了开源的Commons FileUpload组件,其很快成为Servlet/JSP程序员上传文件的最佳选择。Servlet3.0规范已经提供方法来处理文件上传,但这种上传需要在Servlet中完成。而Spring MVC则提供了更简单的封装。Spring MVC为文件上传提供了直接的支持,这种支持是用即插即用的MultipartResolver实现的。Spring MVC使用Apache Commons FileUpload技术实现了一个MultipartResolver实现类:CommonsMultipartResolver。因此,SpringMVC的文件上传还需要依赖Apache Commons FileUpload的组件。11.2 文件上传1、导入文件上传的jar包,commons-fileupload , Maven会自动帮我们导入他的依赖包 commons-io包;<!--文件上传--> <dependency> <groupId>commons-fileupload</groupId> <artifactId>commons-fileupload</artifactId> <version>1.3.3</version> </dependency> <!--servlet-api导入高版本的--> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>4.0.1</version> </dependency>2、配置bean:multipartResolver【注意!!!这个bena的id必须为:multipartResolver , 否则上传文件会报400的错误!在这里栽过坑,教训!】<!--文件上传配置--> <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <!-- 请求的编码格式,必须和jSP的pageEncoding属性一致,以便正确读取表单的内容,默认为ISO-8859-1 --> <property name="defaultEncoding" value="utf-8"/> <!-- 上传文件大小上限,单位为字节(10485760=10M) --> <property name="maxUploadSize" value="10485760"/> <property name="maxInMemorySize" value="40960"/> </bean>CommonsMultipartFile 的 常用方法:String getOriginalFilename():获取上传文件的原名InputStream getInputStream():获取文件流void transferTo(File dest):将上传文件保存到一个目录文件中我们去实际测试一下3、编写前端页面<form action="/upload" enctype="multipart/form-data" method="post"> <input type="file" name="file"/> <input type="submit" value="upload"> </form>4、Controllerpackage com.kuang.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.multipart.commons.CommonsMultipartFile; import javax.servlet.http.HttpServletRequest; import java.io.*; @Controller public class FileController { //@RequestParam("file") 将name=file控件得到的文件封装成CommonsMultipartFile 对象 //批量上传CommonsMultipartFile则为数组即可 @RequestMapping("/upload") public String fileUpload(@RequestParam("file") CommonsMultipartFile file , HttpServletRequest request) throws IOException { //获取文件名 : file.getOriginalFilename(); String uploadFileName = file.getOriginalFilename(); //如果文件名为空,直接回到首页! if ("".equals(uploadFileName)){ return "redirect:/index.jsp"; } System.out.println("上传文件名 : "+uploadFileName); //上传路径保存设置 String path = request.getServletContext().getRealPath("/upload"); //如果路径不存在,创建一个 File realPath = new File(path); if (!realPath.exists()){ realPath.mkdir(); } System.out.println("上传文件保存地址:"+realPath); InputStream is = file.getInputStream(); //文件输入流 OutputStream os = new FileOutputStream(new File(realPath,uploadFileName)); //文件输出流 //读取写出 int len=0; byte[] buffer = new byte[1024]; while ((len=is.read(buffer))!=-1){ os.write(buffer,0,len); os.flush(); } os.close(); is.close(); return "redirect:/index.jsp"; } }5、测试上传文件,OK!采用file.Transto 来保存上传的文件1、编写Controller/* * 采用file.Transto 来保存上传的文件 */ @RequestMapping("/upload2") public String fileUpload2(@RequestParam("file") CommonsMultipartFile file, HttpServletRequest request) throws IOException { //上传路径保存设置 String path = request.getServletContext().getRealPath("/upload"); File realPath = new File(path); if (!realPath.exists()){ realPath.mkdir(); } //上传文件地址 System.out.println("上传文件保存地址:"+realPath); //通过CommonsMultipartFile的方法直接写文件(注意这个时候) file.transferTo(new File(realPath +"/"+ file.getOriginalFilename())); return "redirect:/index.jsp"; }2、前端表单提交地址修改3、访问提交测试,OK!11.3 文件下载文件下载步骤:1、设置 response 响应头2、读取文件 -- InputStream3、写出文件 -- OutputStream4、执行操作5、关闭流 (先开后关)代码实现:@RequestMapping(value="/download") public String downloads(HttpServletResponse response ,HttpServletRequest request) throws Exception{ //要下载的图片地址 String path = request.getServletContext().getRealPath("/upload"); String fileName = "基础语法.jpg"; //1、设置response 响应头 response.reset(); //设置页面不缓存,清空buffer response.setCharacterEncoding("UTF-8"); //字符编码 response.setContentType("multipart/form-data"); //二进制传输数据 //设置响应头 response.setHeader("Content-Disposition", "attachment;fileName="+URLEncoder.encode(fileName, "UTF-8")); File file = new File(path,fileName); //2、 读取文件--输入流 InputStream input=new FileInputStream(file); //3、 写出文件--输出流 OutputStream out = response.getOutputStream(); byte[] buff =new byte[1024]; int index=0; //4、执行 写出操作 while((index= input.read(buff))!= -1){ out.write(buff, 0, index); out.flush(); } out.close(); input.close(); return null; }前端<a href="/download">点击下载</a>测试,文件下载OK,大家可以和我们之前学习的JavaWeb原生的方式对比一下,就可以知道这个便捷多了!参考资料【狂神说Java】SpringBoot最新教程IDEA版通俗易懂SpringMVC运行原理
2022年05月05日
902 阅读
0 评论
0 点赞
2022-04-29
面试题:进程、线程及协程的区别
1.概念进程: 进程是一个具有一定独立功能的程序关于某个数据集合上的一次运行活动,是系统资源分配和独立运行的最小单位;线程: 线程是进程的一个执行单元,是任务调度和系统执行的最小单位;协程: 协程是一种用户态的轻量级线程,协程的调度完全由用户控制。2.进程与线程的区别1、根本区别: 进程是操作系统资源分配和独立运行的最小单位;线程是任务调度和系统执行的最小单位。2、地址空间区别: 每个进程都有独立的地址空间,一个进程崩溃不影响其它进程;一个进程中的多个线程共享该 进程的地址空间,一个线程的非法操作会使整个进程崩溃。3、上下文切换开销区别: 每个进程有独立的代码和数据空间,进程之间上下文切换开销较大;线程组共享代码和数据空间,线程之间切换的开销较小。3.进程与线程的联系一个进程由共享空间(包括堆、代码区、数据区、进程空间和打开的文件描述符)和一个或多个线程组成,各个线程之间共享进程的内存空间,而一个标准的线程由线程ID、程序计数器PC、寄存器和栈组成。进程和线程之间的联系如下图所示:4.进程与线程的选择1、线程的创建或销毁的代价比进程小,需要频繁创建和销毁时应优先选用线程;2、线程上下文切换的速度比进程快,需要大量计算时优先选用线程;3、线程在CPU上的使用效率更高,需要多核分布时优先选用线程,需要多机分布时优先选用进程4、线程的安全性、稳定性没有进程好,需要更稳定安全时优先使用进程。综上,线程创建和销毁的代价低、上下文切换速度快、对系统资源占用小、对CPU的使用效率高,因此一般情况下优先选择线程进行高并发编程;但线程组的所有线程共用一个进程的内存空间,安全稳定性相对较差,若其中一个线程发生崩溃,可能会使整个进程,因此对安全稳定性要求较高时,需要优先选择进程进行高并发编程。5.协程协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈。因此,协程能保留上一次调用时的状态(即所有局部状态的一个特定组合),每次过程重入时,就相当于进入上一次调用的状态。这个过程完全由程序控制,不需要内核进行调度。协程与线程的关系如下图所示:6.协程与线程的区别1、根本区别: 协程是用户态的轻量级线程,不受内核调度;线程是任务调度和系统执行的最小单位,需要内核调度。2、运行机制区别: 线程和进程是同步机制,而协程是异步机制。3、上下文切换开销区别: 线程运行状态切换及上下文切换需要内核调度,会消耗系统资源;而协程完全由程序控制,状态切换及上下文切换不需要内核参与。参考资料进程、线程及协程的区别
2022年04月29日
832 阅读
1 评论
0 点赞
2022-04-28
spring5学习笔记
1.spring简介1.1 介绍spring:春天--给软件行业带来了春天2002年,首次推出了spring框架的雏形:interface212004年3月24日诞生Rod Johnson,Spring Framework创始人spring理念:使现有的技术更加容易复用,本身是一个大杂烩,整合了现有的技术框架官网:https://spring.io/Spring Framework 中文文档:https://www.docs4dev.com/docs/zh/spring-framework/官方下载地址 : https://repo.spring.io/libs-release-local/org/springframework/spring/GitHub : https://github.com/spring-projectsjava两套框架SSH:Struct2+Spring+HibernateSSH:SpringMVC+Spring+Mybatis1.2 优点spring是一个开源免费的框架(容器)spring是一个轻量级的、非入侵式的框架支持事务处理支持对框架整合的指出1.3 核心特点控制反转(IOC)面向切面编程(AOP)1.4 组成Spring 框架是一个分层架构,由 7 个定义良好的模块组成。Spring 模块构建在核心容器之上,核心容器定义了创建、配置和管理 bean 的方式 .组成 Spring 框架的每个模块(或组件)都可以单独存在,或者与其他一个或多个模块联合实现。每个模块的功能如下:核心容器:核心容器提供 Spring 框架的基本功能。核心容器的主要组件是 BeanFactory,它是工厂模式的实现。BeanFactory 使用控制反转(IOC) 模式将应用程序的配置和依赖性规范与实际的应用程序代码分开。Spring 上下文:Spring 上下文是一个配置文件,向 Spring 框架提供上下文信息。Spring 上下文包括企业服务,例如 JNDI、EJB、电子邮件、国际化、校验和调度功能。Spring AOP:通过配置管理特性,Spring AOP 模块直接将面向切面的编程功能 , 集成到了 Spring 框架中。所以,可以很容易地使 Spring 框架管理任何支持 AOP的对象。Spring AOP 模块为基于 Spring 的应用程序中的对象提供了事务管理服务。通过使用 Spring AOP,不用依赖组件,就可以将声明性事务管理集成到应用程序中。Spring DAO:JDBC DAO 抽象层提供了有意义的异常层次结构,可用该结构来管理异常处理和不同数据库供应商抛出的错误消息。异常层次结构简化了错误处理,并且极大地降低了需要编写的异常代码数量(例如打开和关闭连接)。Spring DAO 的面向 JDBC 的异常遵从通用的 DAO 异常层次结构。Spring ORM:Spring 框架插入了若干个 ORM 框架,从而提供了 ORM 的对象关系工具,其中包括 JDO、Hibernate 和 iBatis SQL Map。所有这些都遵从 Spring 的通用事务和 DAO 异常层次结构。Spring Web 模块:Web 上下文模块建立在应用程序上下文模块之上,为基于 Web 的应用程序提供了上下文。所以,Spring 框架支持与 Jakarta Struts 的集成。Web 模块还简化了处理多部分请求以及将请求参数绑定到域对象的工作。Spring MVC 框架:MVC 框架是一个全功能的构建 Web 应用程序的 MVC 实现。通过策略接口,MVC 框架变成为高度可配置的,MVC 容纳了大量视图技术,其中包括 JSP、Velocity、Tiles、iText 和 POI。1.5 拓展在Spring的官网有这个介绍:现代化的Java开发!说白就是基于Spring的开发!Spring Boot与Spring CloudSpring Boot 是 Spring 的一套快速配置脚手架,可以基于Spring Boot 快速开发单个微服务;Spring Cloud是基于Spring Boot实现的;Spring Boot专注于快速、方便集成的单个微服务个体,Spring Cloud关注全局的服务治理框架;Spring Boot使用了约束优于配置的理念,很多集成方案已经帮你选择好了,能不配置就不配置 , Spring Cloud很大的一部分是基于Spring Boot来实现,Spring Boot可以离开Spring Cloud独立使用开发项目,但是Spring Cloud离不开Spring Boot,属于依赖的关系。SpringBoot在SpringClound中起到了承上启下的作用,如果你要学习SpringCloud必须要学习SpringBoot。。因为现在大多数公司都在使用SpringBoot进行快速开发,学习SpringBoot的前提,需要完全掌握Spring及SpringMVC!1.6 弊端弊端:发展了太久之后,违背了原来的理念!配置十分繁琐,人称:“配置地狱!”1.7 小结总结一句话:Spring是一个轻量级的控制反转(IOC)和面向切面编程(AOP)的框架。2. IOC理论推导2.1 分析实现0、新建一个空白的maven项目我们先用我们原来的方式写一段代码 .1、先写一个UserDao接口public interface UserDao { public void getUser(); }2、再去写Dao的实现类public class UserDaoImpl implements UserDao { @Override public void getUser() { System.out.println("获取用户数据"); } }3、然后去写UserService的接口public interface UserService { public void getUser(); }4、最后写Service的实现类public class UserServiceImpl implements UserService { private UserDao userDao = new UserDaoImpl(); @Override public void getUser() { userDao.getUser(); } }5、测试一下@Test public void test(){ UserService service = new UserServiceImpl(); service.getUser(); }这是我们原来的方式 , 开始大家也都是这么去写的对吧 . 那我们现在修改一下 .把Userdao的实现类增加一个 .public class UserDaoMySqlImpl implements UserDao { @Override public void getUser() { System.out.println("MySql获取用户数据"); } }紧接着我们要去使用MySql的话 , 我们就需要去service实现类里面修改对应的实现public class UserServiceImpl implements UserService { private UserDao userDao = new UserDaoMySqlImpl(); @Override public void getUser() { userDao.getUser(); } }在假设, 我们再增加一个Userdao的实现类 .public class UserDaoOracleImpl implements UserDao { @Override public void getUser() { System.out.println("Oracle获取用户数据"); } }那么我们要使用Oracle , 又需要去service实现类里面修改对应的实现 . 假设我们的这种需求非常大 , 这种方式就根本不适用了, 甚至反人类对吧 , 每次变动 , 都需要修改大量代码 . 这种设计的耦合性太高了, 牵一发而动全身 .那我们如何去解决呢 ? 我们可以在需要用到他的地方 , 不去实现它 , 而是留出一个接口 , 利用set , 我们去代码里修改下 .public class UserServiceImpl implements UserService { private UserDao userDao; // 利用set实现 public void setUserDao(UserDao userDao) { this.userDao = userDao; } @Override public void getUser() { userDao.getUser(); } }现在去我们的测试类里 , 进行测试 ;@Test public void test(){ UserServiceImpl service = new UserServiceImpl(); service.setUserDao( new UserDaoMySqlImpl() ); service.getUser(); //那我们现在又想用Oracle去实现呢 service.setUserDao( new UserDaoOracleImpl() ); service.getUser(); }大家发现了区别没有 ? 可能很多人说没啥区别 . 但是同学们 , 他们已经发生了根本性的变化 , 很多地方都不一样了 . 仔细去思考一下 , 以前所有东西都是由程序去进行控制创建 , 而现在是由我们自行控制创建对象 , 把主动权交给了调用者 . 程序不用去管怎么创建,怎么实现了 . 它只负责提供一个接口 .这种思想 , 从本质上解决了问题 , 我们程序员不再去管理对象的创建了 , 更多的去关注业务的实现 . 耦合性大大降低 . 这也就是IOC的原型 !2.2 IOC本质控制反转IoC(Inversion of Control),是一种设计思想,DI(依赖注入)是实现IoC的一种方法,也有人认为DI只是IoC的另一种说法。没有IoC的程序中 , 我们使用面向对象编程 , 对象的创建与对象间的依赖关系完全硬编码在程序中,对象的创建由程序自己控制,控制反转后将对象的创建转移给第三方,个人认为所谓控制反转就是:获得依赖对象的方式反转了。IoC是Spring框架的核心内容,使用多种方式完美的实现了IoC,可以使用XML配置,也可以使用注解,新版本的Spring也可以零配置实现IoC。Spring容器在初始化时先读取配置文件,根据配置文件或元数据创建与组织对象存入容器中,程序使用时再从Ioc容器中取出需要的对象。采用XML方式配置Bean的时候,Bean的定义信息是和实现分离的,而采用注解的方式可以把两者合为一体,Bean的定义信息直接以注解的形式定义在实现类中,从而达到了零配置的目的。控制反转是一种通过描述(XML或注解)并通过第三方去生产或获取特定对象的方式。在Spring中实现控制反转的是IoC容器,其实现方法是依赖注入(Dependency Injection,DI)。3.HelloSpring3.1 实现HelloSpring0、导入Jar包<dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.1.10.RELEASE</version> </dependency>1、编写一个Hello实体类package com.kuang.pojo; public class Hello { private String name; public String getName(){ return name; } public void setName(String name){ this.name = name; } public void show(){ System.out.println("Hello"+name); } }2、编写我们的spring文件 , 这里我们命名为beans.xml<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!--bean就是java对象 , 由Spring创建和管理--> <bean id="hello" class="com.kuang.pojo.Hello"> <property name="name" value="Spring"/> </bean> </beans>3、我们可以去进行测试了 .@Test public void test(){ //解析beans.xml文件 , 生成管理相应的Bean对象 ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); //getBean : 参数即为spring配置文件中bean的id . Hello hello = (Hello) context.getBean("hello"); hello.show(); }3.2 思考Hello 对象是谁创建的 ? hello 对象是由Spring创建的Hello 对象的属性是怎么设置的 ? hello 对象的属性是由Spring容器设置的这个过程就叫控制反转 :控制 : 谁来控制对象的创建 , 传统应用程序的对象是由程序本身控制创建的 , 使用Spring后 , 对象是由Spring来创建的反转 : 程序本身不创建对象 , 而变成被动的接收对象 .依赖注入 : 就是利用set方法来进行注入的.IOC是一种编程思想,由主动的编程变成被动的接收可以通过newClassPathXmlApplicationContext去浏览一下底层源码 .3.3 修改案例一我们在案例一中, 新增一个Spring配置文件beans.xml<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="MysqlImpl" class="com.kuang.dao.impl.UserDaoMySqlImpl"/> <bean id="OracleImpl" class="com.kuang.dao.impl.UserDaoOracleImpl"/> <bean id="ServiceImpl" class="com.kuang.service.impl.UserServiceImpl"> <!--注意: 这里的name并不是属性 , 而是set方法后面的那部分 , 首字母小写--> <!--引用另外一个bean , 不是用value 而是用 ref--> <property name="userDao" ref="OracleImpl"/> </bean> </beans>测试!@Test public void test2(){ ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); UserServiceImpl serviceImpl = (UserServiceImpl) context.getBean("ServiceImpl"); serviceImpl.getUser(); }OK , 到了现在 , 我们彻底不用再程序中去改动了 , 要实现不同的操作 , 只需要在xml配置文件中进行修改 , 所谓的IoC,一句话搞定 : 对象由Spring 来创建 , 管理 , 装配 !3.4 IOC创建对象方式3.4.1 通过无参构造方法来创建1、User.javapublic class User { private String name; public User() { System.out.println("user无参构造方法"); } public void setName(String name) { this.name = name; } public void show(){ System.out.println("name="+ name ); } }2、beans.xml<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="user" class="com.kuang.pojo.User"> <property name="name" value="kuangshen"/> </bean> </beans>3、测试类@Test public void test(){ ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); //在执行getBean的时候, user已经创建好了 , 通过无参构造 User user = (User) context.getBean("user"); //调用对象的方法 . user.show(); }结果可以发现,在调用show方法之前,User对象已经通过无参构造初始化了!3.4.2 通过有参构造方法来创建1、UserT . javapublic class UserT { private String name; public UserT(String name) { this.name = name; } public void setName(String name) { this.name = name; } public void show(){ System.out.println("name="+ name ); } }2、beans.xml 有三种方式编写<!-- 第一种根据index参数下标设置 --> <bean id="userT" class="com.kuang.pojo.UserT"> <!-- index指构造方法 , 下标从0开始 --> <constructor-arg index="0" value="kuangshen2"/> </bean> <!-- 第二种根据参数名字设置 --> <bean id="userT" class="com.kuang.pojo.UserT"> <!-- name指参数名 --> <constructor-arg name="name" value="kuangshen2"/> </bean> <!-- 第三种根据参数类型设置 --> <bean id="userT" class="com.kuang.pojo.UserT"> <constructor-arg type="java.lang.String" value="kuangshen2"/> </bean>3、测试@Test public void testT(){ ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); UserT user = (UserT) context.getBean("userT"); user.show(); }结论:在配置文件加载的时候。其中管理的对象都已经初始化了!3.5 其他Spring配置3.5.1 别名alias 设置别名 , 为bean设置别名 , 可以设置多个别名<!--设置别名:在获取Bean的时候可以使用别名获取--> <alias name="userT" alias="userNew"/>3.5.2 Bean的配置<!--bean就是java对象,由Spring创建和管理--> <!-- id 是bean的标识符,要唯一,如果没有配置id,name就是默认标识符 如果配置id,又配置了name,那么name是别名 name可以设置多个别名,可以用逗号,分号,空格隔开 如果不配置id和name,可以根据applicationContext.getBean(.class)获取对象; class是bean的全限定名=包名+类名 --> <bean id="hello" name="hello2 h2,h3;h4" class="com.kuang.pojo.Hello"> <property name="name" value="Spring"/> </bean>3.5.3 import团队的合作通过import来实现 .<import resource="{path}/beans.xml"/>4.依赖注入(DI)4.1 概念依赖注入(Dependency Injection,DI)。依赖 : 指Bean对象的创建依赖于容器 . Bean对象的依赖资源 .注入 : 指Bean对象所依赖的资源 , 由容器来设置和装配 .4.2 构造器注入我们在之前的案例已经讲过了4.3 Set 注入 (重点)要求被注入的属性 , 必须有set方法 , set方法的方法名由set + 属性首字母大写 , 如果属性是boolean类型 , 没有set方法 , 是 is .测试pojo类 :Address.java public class Address { private String address; public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } }Student.java package com.kuang.pojo; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; public class Student { private String name; private Address address; private String[] books; private List<String> hobbys; private Map<String,String> card; private Set<String> games; private String wife; private Properties info; public void setName(String name) { this.name = name; } public void setAddress(Address address) { this.address = address; } public void setBooks(String[] books) { this.books = books; } public void setHobbys(List<String> hobbys) { this.hobbys = hobbys; } public void setCard(Map<String, String> card) { this.card = card; } public void setGames(Set<String> games) { this.games = games; } public void setWife(String wife) { this.wife = wife; } public void setInfo(Properties info) { this.info = info; } public void show(){ System.out.println("name="+ name + ",address="+ address.getAddress() + ",books=" ); for (String book:books){ System.out.print("<<"+book+">>\t"); } System.out.println("\n爱好:"+hobbys); System.out.println("card:"+card); System.out.println("games:"+games); System.out.println("wife:"+wife); System.out.println("info:"+info); } }1、常量注入 <bean id="student" class="com.kuang.pojo.Student"> <property name="name" value="小明"/> </bean>测试: @Test public void test01(){ ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); Student student = (Student) context.getBean("student"); System.out.println(student.getName()); }2、Bean注入 注意点:这里的值是一个引用,ref <bean id="addr" class="com.kuang.pojo.Address"> <property name="address" value="重庆"/> </bean> <bean id="student" class="com.kuang.pojo.Student"> <property name="name" value="小明"/> <property name="address" ref="addr"/> </bean>3、数组注入 <bean id="student" class="com.kuang.pojo.Student"> <property name="name" value="小明"/> <property name="address" ref="addr"/> <property name="books"> <array> <value>西游记</value> <value>红楼梦</value> <value>水浒传</value> </array> </property> </bean>4、List注入 <property name="hobbys"> <list> <value>听歌</value> <value>看电影</value> <value>爬山</value> </list> </property>5、Map注入 <property name="card"> <map> <entry key="中国邮政" value="456456456465456"/> <entry key="建设" value="1456682255511"/> </map> </property>6、set注入 <property name="games"> <set> <value>LOL</value> <value>BOB</value> <value>COC</value> </set> </property>7、Null注入 <property name="wife"><null/></property>8、Properties注入 <property name="info"> <props> <prop key="学号">20190604</prop> <prop key="性别">男</prop> <prop key="姓名">小明</prop> </props> </property>测试结果:4.4 p命名和c命名注入User.java :【注意:这里没有有参构造器!】 public class User { private String name; private int age; public void setName(String name) { this.name = name; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "User{" + "name='" + name + '\'' + ", age=" + age + '}'; } }1、P命名空间注入 : 需要在头文件中加入约束文件 导入约束 : xmlns:p="http://www.springframework.org/schema/p" <!--P(属性: properties)命名空间 , 属性依然要设置set方法--> <bean id="user" class="com.kuang.pojo.User" p:name="狂神" p:age="18"/>2、c 命名空间注入 : 需要在头文件中加入约束文件 导入约束 : xmlns:c="http://www.springframework.org/schema/c" <!--C(构造: Constructor)命名空间 , 属性依然要设置set方法--> <bean id="user" class="com.kuang.pojo.User" c:name="狂神" c:age="18"/>发现问题:爆红了,刚才我们没有写有参构造!解决:把有参构造器加上,这里也能知道,c 就是所谓的构造器注入!测试代码: @Test public void test02(){ ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); User user = (User) context.getBean("user"); System.out.println(user); }4.5 Bean的作用域在Spring中,那些组成应用程序的主体及由Spring IoC容器所管理的对象,被称之为bean。简单地讲,bean就是由IoC容器初始化、装配及管理的对象 .几种作用域中,request、session作用域仅在基于web的应用中使用(不必关心你所采用的是什么web应用框架),只能用在基于web的Spring ApplicationContext环境。Singleton当一个bean的作用域为Singleton,那么Spring IoC容器中只会存在一个共享的bean实例,并且所有对bean的请求,只要id与该bean定义相匹配,则只会返回bean的同一实例。Singleton是单例类型,就是在创建起容器时就同时自动创建了一个bean的对象,不管你是否使用,他都存在了,每次获取到的对象都是同一个对象。注意,Singleton作用域是Spring中的缺省作用域。要在XML中将bean定义成singleton,可以这样配置: <bean id="ServiceImpl" class="cn.csdn.service.ServiceImpl" scope="singleton">测试: @Test public void test03(){ ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); User user = (User) context.getBean("user"); User user2 = (User) context.getBean("user"); System.out.println(user==user2); }Prototype当一个bean的作用域为Prototype,表示一个bean定义对应多个对象实例。Prototype作用域的bean会导致在每次对该bean请求(将其注入到另一个bean中,或者以程序的方式调用容器的getBean()方法)时都会创建一个新的bean实例。Prototype是原型类型,它在我们创建容器的时候并没有实例化,而是当我们获取bean的时候才会去创建一个对象,而且我们每次获取到的对象都不是同一个对象。根据经验,对有状态的bean应该使用prototype作用域,而对无状态的bean则应该使用singleton作用域。在XML中将bean定义成prototype,可以这样配置: <bean id="account" class="com.foo.DefaultAccount" scope="prototype"/> 或者 <bean id="account" class="com.foo.DefaultAccount" singleton="false"/>Request当一个bean的作用域为Request,表示在一次HTTP请求中,一个bean定义对应一个实例;即每个HTTP请求都会有各自的bean实例,它们依据某个bean定义创建而成。该作用域仅在基于web的Spring ApplicationContext情形下有效。考虑下面bean定义: <bean id="loginAction" class=cn.csdn.LoginAction" scope="request"/>针对每次HTTP请求,Spring容器会根据loginAction bean的定义创建一个全新的LoginAction bean实例,且该loginAction bean实例仅在当前HTTP request内有效,因此可以根据需要放心的更改所建实例的内部状态,而其他请求中根据loginAction bean定义创建的实例,将不会看到这些特定于某个请求的状态变化。当处理请求结束,request作用域的bean实例将被销毁。Session当一个bean的作用域为Session,表示在一个HTTP Session中,一个bean定义对应一个实例。该作用域仅在基于web的Spring ApplicationContext情形下有效。考虑下面bean定义: <bean id="userPreferences" class="com.foo.UserPreferences" scope="session"/>针对某个HTTP Session,Spring容器会根据userPreferences bean定义创建一个全新的userPreferences bean实例,且该userPreferences bean仅在当前HTTP Session内有效。与request作用域一样,可以根据需要放心的更改所创建实例的内部状态,而别的HTTP Session中根据userPreferences创建的实例,将不会看到这些特定于某个HTTP Session的状态变化。当HTTP Session最终被废弃的时候,在该HTTP Session作用域内的bean也会被废弃掉。5.Bean的自动装配5.1 自动装配说明自动装配是使用spring满足bean依赖的一种方法spring会在应用上下文中为某个bean寻找其依赖的bean。Spring中bean有三种装配机制,分别是:在xml中显式配置;在java中显式配置;隐式的bean发现机制和自动装配。这里我们主要讲第三种:自动化的装配bean。Spring的自动装配需要从两个角度来实现,或者说是两个操作:组件扫描(component scanning):spring会自动发现应用上下文中所创建的bean;自动装配(autowiring):spring自动满足bean之间的依赖,也就是我们说的IoC/DI;组件扫描和自动装配组合发挥巨大威力,使得显示的配置降低到最少。推荐不使用自动装配xml配置 , 而使用注解 .5.2 测试环境搭建1、新建一个项目2、新建两个实体类,Cat Dog 都有一个叫的方法public class Cat { public void shout() { System.out.println("miao~"); } } public class Dog { public void shout() { System.out.println("wang~"); } }3、新建一个用户类 Userpublic class User { private Cat cat; private Dog dog; private String str; }4、编写Spring配置文件<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="dog" class="com.kuang.pojo.Dog"/> <bean id="cat" class="com.kuang.pojo.Cat"/> <bean id="user" class="com.kuang.pojo.User"> <property name="cat" ref="cat"/> <property name="dog" ref="dog"/> <property name="str" value="qinjiang"/> </bean> </beans>5、测试public class MyTest { @Test public void testMethodAutowire() { ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); User user = (User) context.getBean("user"); user.getCat().shout(); user.getDog().shout(); } }结果正常输出,环境OK5.3 autowire byName (按名称自动装配)由于在手动配置xml过程中,常常发生字母缺漏和大小写等错误,而无法对其进行检查,使得开发效率降低。采用自动装配将避免这些错误,并且使配置简单化。测试:1、修改bean配置,增加一个属性 autowire="byName"<bean id="user" class="com.kuang.pojo.User" autowire="byName"> <property name="str" value="qinjiang"/> </bean>2、再次测试,结果依旧成功输出!3、我们将 cat 的bean id修改为 catXXX4、再次测试, 执行时报空指针java.lang.NullPointerException。因为按byName规则找不对应set方法,真正的setCat就没执行,对象就没有初始化,所以调用时就会报空指针错误。小结:当一个bean节点带有 autowire byName的属性时。将查找其类中所有的set方法名,例如setCat,获得将set去掉并且首字母小写的字符串,即cat。去spring容器中寻找是否有此字符串名称id的对象。如果有,就取出注入;如果没有,就报空指针异常。5.4 autowire byType (按类型自动装配)使用autowire byType首先需要保证:同一类型的对象,在spring容器中唯一。如果不唯一,会报不唯一的异常。测试:1、将user的bean配置修改一下 : autowire="byType"2、测试,正常输出3、在注册一个cat 的bean对象!<bean id="dog" class="com.kuang.pojo.Dog"/> <bean id="cat" class="com.kuang.pojo.Cat"/> <bean id="cat2" class="com.kuang.pojo.Cat"/> <bean id="user" class="com.kuang.pojo.User" autowire="byType"> <property name="str" value="qinjiang"/> </bean>4、测试,报错:NoUniqueBeanDefinitionException5、删掉cat2,将cat的bean名称改掉!测试!因为是按类型装配,所以并不会报异常,也不影响最后的结果。甚至将id属性去掉,也不影响结果。这就是按照类型自动装配!5.5 使用注解jdk1.5开始支持注解,spring2.5开始全面支持注解。准备工作:利用注解的方式注入属性。1、在spring配置文件中引入context文件头xmlns:context="http://www.springframework.org/schema/context" http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/context/spring-aop.xsd">2、开启属性注解支持!<context:annotation-config/>@Autowired@Autowired是按类型自动转配的,不支持id匹配。需要导入 spring-aop的包!测试:1、将User类中的set方法去掉,使用@Autowired注解public class User { @Autowired private Cat cat; @Autowired private Dog dog; private String str; public Cat getCat() { return cat; } public Dog getDog() { return dog; } public String getStr() { return str; } }2、此时配置文件内容<context:annotation-config/> <bean id="dog" class="com.kuang.pojo.Dog"/> <bean id="cat" class="com.kuang.pojo.Cat"/> <bean id="user" class="com.kuang.pojo.User"/>3、测试,成功输出结果!【小狂神科普时间】@Autowired(required=false) 说明:false,对象可以为null;true,对象必须存对象,不能为null。//如果允许对象为null,设置required = false,默认为true @Autowired(required = false) private Cat cat;@Qualifier@Autowired是根据类型+id自动装配的,加上@Qualifier则可以根据byName的方式自动装配@Qualifier不能单独使用。测试实验步骤:1、配置文件修改内容,保证类型存在对象。且名字不为类的默认名字!<bean id="dog1" class="com.kuang.pojo.Dog"/> <bean id="dog2" class="com.kuang.pojo.Dog"/> <bean id="cat1" class="com.kuang.pojo.Cat"/> <bean id="cat2" class="com.kuang.pojo.Cat"/>2、没有加Qualifier测试,直接报错3、在属性上添加Qualifier注解@Autowired @Qualifier(value = "cat2") private Cat cat; @Autowired @Qualifier(value = "dog2") private Dog dog;测试,成功输出!@Resource@Resource如有指定的name属性,先按该属性进行byName方式查找装配;其次再进行默认的byName方式进行装配;如果以上都不成功,则按byType的方式自动装配。都不成功,则报异常。实体类:public class User { //如果允许对象为null,设置required = false,默认为true @Resource(name = "cat2") private Cat cat; @Resource private Dog dog; private String str; }beans.xml<bean id="dog" class="com.kuang.pojo.Dog"/> <bean id="cat1" class="com.kuang.pojo.Cat"/> <bean id="cat2" class="com.kuang.pojo.Cat"/> <bean id="user" class="com.kuang.pojo.User"/>测试:结果OK配置文件2:beans.xml , 删掉cat2<bean id="dog" class="com.kuang.pojo.Dog"/> <bean id="cat1" class="com.kuang.pojo.Cat"/>实体类上只保留注解@Resource private Cat cat; @Resource private Dog dog;结果:OK结论:先进行byName查找,失败;再进行byType查找,成功。@Autowired与@Resource异同:1、@Autowired与@Resource都可以用来装配bean。都可以写在字段上,或写在setter方法上。2、@Autowired默认按类型装配(属于spring规范),默认情况下必须要求依赖对象必须存在,如果要允许null 值,可以设置它的required属性为false,如:@Autowired(required=false) ,如果我们想使用名称装配可以结合@Qualifier注解进行使用3、@Resource(属于J2EE复返),默认按照名称进行装配,名称可以通过name属性进行指定。如果没有指定name属性,当注解写在字段上时,默认取字段名进行按照名称查找,如果注解写在setter方法上默认取属性名进行装配。当找不到与名称匹配的bean时才按照类型进行装配。但是需要注意的是,如果name属性一旦指定,就只会按照名称进行装配。它们的作用相同都是用注解方式注入对象,但执行顺序不同。@Autowired先byType,@Resource先byName。6. 静态/动态代理模式为什么要学习代理模式,因为AOP的底层机制就是动态代理!代理模式:静态代理动态代理学习aop之前 , 我们要先了解一下代理模式!6.1 静态代理静态代理角色分析抽象角色 : 一般使用接口或者抽象类来实现真实角色 : 被代理的角色代理角色 : 代理真实角色 ; 代理真实角色后 , 一般会做一些附属的操作 .客户 : 使用代理角色来进行一些操作 .代码实现Rent . java 即抽象角色//抽象角色:租房 public interface Rent { public void rent(); }Host . java 即真实角色//真实角色: 房东,房东要出租房子 public class Host implements Rent{ public void rent() { System.out.println("房屋出租"); } }Proxy . java 即代理角色//代理角色:中介 public class Proxy implements Rent { private Host host; public Proxy() { } public Proxy(Host host) { this.host = host; } //租房 public void rent(){ seeHouse(); host.rent(); fare(); } //看房 public void seeHouse(){ System.out.println("带房客看房"); } //收中介费 public void fare(){ System.out.println("收中介费"); } }Client . java 即客户//客户类,一般客户都会去找代理! public class Client { public static void main(String[] args) { //房东要租房 Host host = new Host(); //中介帮助房东 Proxy proxy = new Proxy(host); //你去找中介! proxy.rent(); } }分析:在这个过程中,你直接接触的就是中介,就如同现实生活中的样子,你看不到房东,但是你依旧租到了房东的房子通过代理,这就是所谓的代理模式,程序源自于生活,所以学编程的人,一般能够更加抽象的看待生活中发生的事情。静态代理的好处:可以使得我们的真实角色更加纯粹 . 不再去关注一些公共的事情 .公共的业务由代理来完成 . 实现了业务的分工 ,公共业务发生扩展时变得更加集中和方便 .缺点 :类多了 , 多了代理类 , 工作量变大了 . 开发效率降低 .我们想要静态代理的好处,又不想要静态代理的缺点,所以 , 就有了动态代理 !6.2 静态代理再理解同学们练习完毕后,我们再来举一个例子,巩固大家的学习!练习步骤:1、创建一个抽象角色,比如咋们平时做的用户业务,抽象起来就是增删改查!//抽象角色:增删改查业务 public interface UserService { void add(); void delete(); void update(); void query(); }2、我们需要一个真实对象来完成这些增删改查操作//真实对象,完成增删改查操作的人 public class UserServiceImpl implements UserService { public void add() { System.out.println("增加了一个用户"); } public void delete() { System.out.println("删除了一个用户"); } public void update() { System.out.println("更新了一个用户"); } public void query() { System.out.println("查询了一个用户"); } }3、需求来了,现在我们需要增加一个日志功能,怎么实现!思路1 :在实现类上增加代码 【麻烦!】思路2:使用代理来做,能够不改变原来的业务情况下,实现此功能就是最好的了!4、设置一个代理类来处理日志!代理角色//代理角色,在这里面增加日志的实现 public class UserServiceProxy implements UserService { private UserServiceImpl userService; public void setUserService(UserServiceImpl userService) { this.userService = userService; } public void add() { log("add"); userService.add(); } public void delete() { log("delete"); userService.delete(); } public void update() { log("update"); userService.update(); } public void query() { log("query"); userService.query(); } public void log(String msg){ System.out.println("执行了"+msg+"方法"); } }5、测试访问类:public class Client { public static void main(String[] args) { //真实业务 UserServiceImpl userService = new UserServiceImpl(); //代理类 UserServiceProxy proxy = new UserServiceProxy(); //使用代理类实现日志功能! proxy.setUserService(userService); proxy.add(); } }OK,到了现在代理模式大家应该都没有什么问题了,重点大家需要理解其中的思想;我们在不改变原来的代码的情况下,实现了对原有功能的增强,这是AOP中最核心的思想聊聊AOP:纵向开发,横向开发6.3 动态代理动态代理的角色和静态代理的一样 .动态代理的代理类是动态生成的 . 静态代理的代理类是我们提前写好的动态代理分为两类 : 一类是基于接口动态代理 , 一类是基于类的动态代理基于接口的动态代理----JDK动态代理基于类的动态代理--cglib现在用的比较多的是 javasist 来生成动态代理 . 百度一下javasist我们这里使用JDK的原生代码来实现,其余的道理都是一样的!、JDK的动态代理需要了解两个类核心 : InvocationHandler 和 Proxy , 打开JDK帮助文档看看【InvocationHandler:调用处理程序】Object invoke(Object proxy, 方法 method, Object[] args); //参数 //proxy - 调用该方法的代理实例 //method -所述方法对应于调用代理实例上的接口方法的实例。方法对象的声明类将是该方法声明的接口,它可以是代理类继承该方法的代理接口的超级接口。 //args -包含的方法调用传递代理实例的参数值的对象的阵列,或null如果接口方法没有参数。原始类型的参数包含在适当的原始包装器类的实例中,例如java.lang.Integer或java.lang.Boolean 。【Proxy : 代理】//生成代理类 public Object getProxy(){ return Proxy.newProxyInstance(this.getClass().getClassLoader(), rent.getClass().getInterfaces(),this); }代码实现 抽象角色和真实角色和之前的一样!Rent . java 即抽象角色//抽象角色:租房 public interface Rent { public void rent(); }Host . java 即真实角色//真实角色: 房东,房东要出租房子 public class Host implements Rent{ public void rent() { System.out.println("房屋出租"); } }ProxyInvocationHandler. java 即代理角色public class ProxyInvocationHandler implements InvocationHandler { private Rent rent; public void setRent(Rent rent) { this.rent = rent; } //生成代理类,重点是第二个参数,获取要代理的抽象角色!之前都是一个角色,现在可以代理一类角色 public Object getProxy(){ return Proxy.newProxyInstance(this.getClass().getClassLoader(), rent.getClass().getInterfaces(),this); } // proxy : 代理类 method : 代理类的调用处理程序的方法对象. // 处理代理实例上的方法调用并返回结果 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { seeHouse(); //核心:本质利用反射实现! Object result = method.invoke(rent, args); fare(); return result; } //看房 public void seeHouse(){ System.out.println("带房客看房"); } //收中介费 public void fare(){ System.out.println("收中介费"); } }Client . java//租客 public class Client { public static void main(String[] args) { //真实角色 Host host = new Host(); //代理实例的调用处理程序 ProxyInvocationHandler pih = new ProxyInvocationHandler(); pih.setRent(host); //将真实角色放置进去! Rent proxy = (Rent)pih.getProxy(); //动态生成对应的代理类! proxy.rent(); } }核心:一个动态代理 , 一般代理某一类业务 , 一个动态代理可以代理多个类,代理的是接口!、6.4 深化理解我们来使用动态代理实现代理我们后面写的UserService!我们也可以编写一个通用的动态代理实现的类!所有的代理对象设置为Object即可!public class ProxyInvocationHandler implements InvocationHandler { private Object target; public void setTarget(Object target) { this.target = target; } //生成代理类 public Object getProxy(){ return Proxy.newProxyInstance(this.getClass().getClassLoader(), target.getClass().getInterfaces(),this); } // proxy : 代理类 // method : 代理类的调用处理程序的方法对象. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { log(method.getName()); Object result = method.invoke(target, args); return result; } public void log(String methodName){ System.out.println("执行了"+methodName+"方法"); } }测试!public class Test { public static void main(String[] args) { //真实对象 UserServiceImpl userService = new UserServiceImpl(); //代理对象的调用处理程序 ProxyInvocationHandler pih = new ProxyInvocationHandler(); pih.setTarget(userService); //设置要代理的对象 UserService proxy = (UserService)pih.getProxy(); //动态生成代理类! proxy.delete(); } }测试,增删改查,查看结果!动态代理的好处静态代理有的它都有,静态代理没有的,它也有!可以使得我们的真实角色更加纯粹 . 不再去关注一些公共的事情 .公共的业务由代理来完成 . 实现了业务的分工 ,公共业务发生扩展时变得更加集中和方便 .一个动态代理 , 一般代理某一类业务一个动态代理可以代理多个类,代理的是接口!7.AOP7.1 什么是AOPAOP(Aspect Oriented Programming)意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。7.2 Aop在Spring中的作用提供声明式事务;允许用户自定义切面以下名词需要了解下:横切关注点:跨越应用程序多个模块的方法或功能。即是,与我们业务逻辑无关的,但是我们需要关注的部分,就是横切关注点。如日志 , 安全 , 缓存 , 事务等等 ....切面(ASPECT):横切关注点 被模块化 的特殊对象。即,它是一个类。通知(Advice):切面必须要完成的工作。即,它是类中的一个方法。目标(Target):被通知对象。代理(Proxy):向目标对象应用通知之后创建的对象。切入点(PointCut):切面通知 执行的 “地点”的定义。连接点(JointPoint):与切入点匹配的执行点。SpringAOP中,通过Advice定义横切逻辑,Spring中支持5种类型的Advice:即 Aop 在 不改变原有代码的情况下 , 去增加新的功能 .7.3 使用Spring实现Aop【重点】使用AOP织入,需要导入一个依赖包!<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver --> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.4</version> </dependency>第一种方式:通过 Spring API 实现首先编写我们的业务接口和实现类public interface UserService { public void add(); public void delete(); public void update(); public void search(); } public class UserServiceImpl implements UserService{ @Override public void add() { System.out.println("增加用户"); } @Override public void delete() { System.out.println("删除用户"); } @Override public void update() { System.out.println("更新用户"); } @Override public void search() { System.out.println("查询用户"); } }然后去写我们的增强类 , 我们编写两个 , 一个前置增强 一个后置增强public class BeforeLog implements MethodBeforeAdvice { //method : 要执行的目标对象的方法 //objects : 被调用的方法的参数 //Object : 目标对象 @Override public void before(Method method, Object[] objects, Object o) throws Throwable { System.out.println( o.getClass().getName() + "的" + method.getName() + "方法被执行了"); } } public class AfterLog implements AfterReturningAdvice { //returnValue 返回值 //method被调用的方法 //args 被调用的方法的对象的参数 //target 被调用的目标对象 @Override public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable { System.out.println("执行了" + target.getClass().getName() +"的"+method.getName()+"方法," +"返回值:"+returnValue); } }最后去spring的文件中注册 , 并实现aop切入实现 , 注意导入约束 .<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!--注册bean--> <bean id="userService" class="com.kuang.service.UserServiceImpl"/> <bean id="beforLog" class="com.kuang.log.BeforeLog"/> <bean id="afterLog" class="com.kuang.log.AfterLog"/> <!--aop的配置--> <aop:config> <!--切入点 expression:表达式匹配要执行的方法--> <aop:pointcut id="pointcut" expression="execution(* com.kuang.service.UserServiceImpl.*(..))"/> <!--执行环绕; advice-ref执行方法 . pointcut-ref切入点--> <aop:advisor advice-ref="beforLog" pointcut-ref="pointcut"/> <aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/> </aop:config> </beans>测试public class MyTest { @Test public void test(){ ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); UserService userService = (UserService) context.getBean("userService"); userService.search(); } }Aop的重要性 : 很重要 . 一定要理解其中的思路 , 主要是思想的理解这一块 .Spring的Aop就是将公共的业务 (日志 , 安全等) 和领域业务结合起来 , 当执行领域业务时 , 将会把公共业务加进来 . 实现公共业务的重复利用 . 领域业务更纯粹 , 程序猿专注领域业务 , 其本质还是动态代理 .第二种方式:自定义类来实现Aop目标业务类不变依旧是userServiceImpl第一步 : 写我们自己的一个切入类public class DiyPointcut { public void before(){ System.out.println("---------方法执行前---------"); } public void after(){ System.out.println("---------方法执行后---------"); } }去spring中配置<!--第二种方式自定义实现--> <!--注册bean--> <bean id="diy" class="com.kuang.config.DiyPointcut"/> <!--aop的配置--> <aop:config> <!--第二种方式:使用AOP的标签实现--> <aop:aspect ref="diy"> <aop:pointcut id="diyPonitcut" expression="execution(* com.kuang.service.UserServiceImpl.*(..))"/> <aop:before pointcut-ref="diyPonitcut" method="before"/> <aop:after pointcut-ref="diyPonitcut" method="after"/> </aop:aspect> </aop:config>测试:public class MyTest { @Test public void test(){ ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); UserService userService = (UserService) context.getBean("userService"); userService.add(); } }第三种方式:使用注解实现第一步:编写一个注解实现的增强类package com.kuang.config; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; @Aspect public class AnnotationPointcut { @Before("execution(* com.kuang.service.UserServiceImpl.*(..))") public void before(){ System.out.println("---------方法执行前---------"); } @After("execution(* com.kuang.service.UserServiceImpl.*(..))") public void after(){ System.out.println("---------方法执行后---------"); } @Around("execution(* com.kuang.service.UserServiceImpl.*(..))") public void around(ProceedingJoinPoint jp) throws Throwable { System.out.println("环绕前"); System.out.println("签名:"+jp.getSignature()); //执行目标方法proceed Object proceed = jp.proceed(); System.out.println("环绕后"); System.out.println(proceed); } }第二步:在Spring配置文件中,注册bean,并增加支持注解的配置<!--第三种方式:注解实现--> <bean id="annotationPointcut" class="com.kuang.config.AnnotationPointcut"/> <aop:aspectj-autoproxy/>aop:aspectj-autoproxy:说明通过aop命名空间的<aop:aspectj-autoproxy />声明自动为spring容器中那些配置@aspectJ切面的bean创建代理,织入切面。当然,spring 在内部依旧采用AnnotationAwareAspectJAutoProxyCreator进行自动代理的创建工作,但具体实现的细节已经被<aop:aspectj-autoproxy />隐藏起来了 <aop:aspectj-autoproxy />有一个proxy-target-class属性,默认为false,表示使用jdk动态代理织入增强,当配为<aop:aspectj-autoproxy poxy-target-class="true"/>时,表示使用CGLib动态代理技术织入增强。不过即使proxy-target-class设置为false,如果目标类没有声明接口,则spring将自动使用CGLib动态代理。8.整合MyBatis8.1 步骤1、导入相关jar包<!--junit--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> <!--mybatis--> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.2</version> </dependency> <!--mysql-connector-java--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.47</version> </dependency> <!--spring相关--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.1.10.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.1.10.RELEASE</version> </dependency> <!--aspectJ AOP 织入器--> <!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver --> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.4</version> </dependency> <!--mybatis-spring整合包 【重点】--> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>2.0.2</version> </dependency>配置Maven静态资源过滤问题!<build> <resources> <resource> <directory>src/main/java</directory> <includes> <include>**/*.properties</include> <include>**/*.xml</include> </includes> <filtering>true</filtering> </resource> </resources> </build>2、编写配置文件3、代码实现8.2 回忆MyBatis编写pojo实体类package com.kuang.pojo; public class User { private int id; //id private String name; //姓名 private String pwd; //密码 }实现mybatis的配置文件<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <typeAliases> <package name="com.kuang.pojo"/> </typeAliases> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=utf8"/> <property name="username" value="root"/> <property name="password" value="123456"/> </dataSource> </environment> </environments> <mappers> <package name="com.kuang.dao"/> </mappers> </configuration>UserDao接口编写public interface UserMapper { public List<User> selectUser(); }接口对应的Mapper映射文件<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.kuang.dao.UserMapper"> <select id="selectUser" resultType="User"> select * from user </select> </mapper>测试类@Test public void selectUser() throws IOException { String resource = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); SqlSession sqlSession = sqlSessionFactory.openSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); List<User> userList = mapper.selectUser(); for (User user: userList){ System.out.println(user); } sqlSession.close(); }8.3 MyBatis-Spring学习引入Spring之前需要了解mybatis-spring包中的一些重要类;http://www.mybatis.org/spring/zh/index.html什么是 MyBatis-Spring?MyBatis-Spring 会帮助你将 MyBatis 代码无缝地整合到 Spring 中。知识基础在开始使用 MyBatis-Spring 之前,你需要先熟悉 Spring 和 MyBatis 这两个框架和有关它们的术语。这很重要MyBatis-Spring 需要以下版本:MyBatis-SpringMyBatisSpring 框架Spring BatchJava2.03.5+5.0+4.0+Java 8+1.33.4+3.2.2+2.1+Java 6+如果使用 Maven 作为构建工具,仅需要在 pom.xml 中加入以下代码即可:<dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>2.0.2</version> </dependency>要和 Spring 一起使用 MyBatis,需要在 Spring 应用上下文中定义至少两样东西:一个 SqlSessionFactory 和至少一个数据映射器类。在 MyBatis-Spring 中,可使用SqlSessionFactoryBean来创建 SqlSessionFactory。要配置这个工厂 bean,只需要把下面代码放在 Spring 的 XML 配置文件中:<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource" /> </bean>注意:SqlSessionFactory需要一个 DataSource(数据源)。这可以是任意的 DataSource,只需要和配置其它 Spring 数据库连接一样配置它就可以了。在基础的 MyBatis 用法中,是通过 SqlSessionFactoryBuilder 来创建 SqlSessionFactory 的。而在 MyBatis-Spring 中,则使用 SqlSessionFactoryBean 来创建。在 MyBatis 中,你可以使用 SqlSessionFactory 来创建 SqlSession。一旦你获得一个 session 之后,你可以使用它来执行映射了的语句,提交或回滚连接,最后,当不再需要它的时候,你可以关闭 session。SqlSessionFactory有一个唯一的必要属性:用于 JDBC 的 DataSource。这可以是任意的 DataSource 对象,它的配置方法和其它 Spring 数据库连接是一样的。一个常用的属性是 configLocation,它用来指定 MyBatis 的 XML 配置文件路径。它在需要修改 MyBatis 的基础配置非常有用。通常,基础配置指的是 < settings> 或 < typeAliases>元素。需要注意的是,这个配置文件并不需要是一个完整的 MyBatis 配置。确切地说,任何环境配置(),数据源()和 MyBatis 的事务管理器()都会被忽略。SqlSessionFactoryBean 会创建它自有的 MyBatis 环境配置(Environment),并按要求设置自定义环境的值。SqlSessionTemplate 是 MyBatis-Spring 的核心。作为 SqlSession 的一个实现,这意味着可以使用它无缝代替你代码中已经在使用的 SqlSession。模板可以参与到 Spring 的事务管理中,并且由于其是线程安全的,可以供多个映射器类使用,你应该总是用 SqlSessionTemplate 来替换 MyBatis 默认的 DefaultSqlSession 实现。在同一应用程序中的不同类之间混杂使用可能会引起数据一致性的问题。可以使用 SqlSessionFactory 作为构造方法的参数来创建 SqlSessionTemplate 对象。<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate"> <constructor-arg index="0" ref="sqlSessionFactory" /> </bean>现在,这个 bean 就可以直接注入到你的 DAO bean 中了。你需要在你的 bean 中添加一个 SqlSession 属性,就像下面这样:public class UserDaoImpl implements UserDao { private SqlSession sqlSession; public void setSqlSession(SqlSession sqlSession) { this.sqlSession = sqlSession; } public User getUser(String userId) { return sqlSession.getMapper...; } }按下面这样,注入 SqlSessionTemplate:<bean id="userDao" class="org.mybatis.spring.sample.dao.UserDaoImpl"> <property name="sqlSession" ref="sqlSession" /> </bean>8.4 整合实现一1、引入Spring配置文件beans.xml<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">2、配置数据源替换mybaits的数据源<!--配置数据源:数据源有非常多,可以使用第三方的,也可使使用Spring的--> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=utf8"/> <property name="username" value="root"/> <property name="password" value="123456"/> </bean>3、配置SqlSessionFactory,关联MyBatis<!--配置SqlSessionFactory--> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource"/> <!--关联Mybatis--> <property name="configLocation" value="classpath:mybatis-config.xml"/> <property name="mapperLocations" value="classpath:com/kuang/dao/*.xml"/> </bean>4、注册sqlSessionTemplate,关联sqlSessionFactory;<!--注册sqlSessionTemplate , 关联sqlSessionFactory--> <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate"> <!--利用构造器注入--> <constructor-arg index="0" ref="sqlSessionFactory"/> </bean>5、增加Dao接口的实现类;私有化sqlSessionTemplatepublic class UserMapperImpl implements UserMapper { //sqlSession不用我们自己创建了,Spring来管理 private SqlSessionTemplate sqlSession; public void setSqlSession(SqlSessionTemplate sqlSession) { this.sqlSession = sqlSession; } public List<User> selectUser() { UserMapper mapper = sqlSession.getMapper(UserMapper.class); return mapper.selectUser(); } }6、注册bean实现<bean id="UserMapper" class="com.kuang.mapper.UserMapperImpl"> <property name="sqlSession" ref="sqlSession"/> </bean>7、测试 @Test public void test2(){ ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); UserMapper mapper = (UserMapper) context.getBean("userDao"); List<User> user = mapper.selectUser(); System.out.println(user); }结果成功输出!现在我们的Mybatis配置文件的状态!发现都可以被Spring整合!<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <typeAliases> <package name="com.kuang.pojo"/> </typeAliases> </configuration>8.5 整合实现二mybatis-spring1.2.3版以上的才有这个 .官方文档截图 :dao继承Support类 , 直接利用 getSqlSession() 获得 , 然后直接注入SqlSessionFactory . 比起方式1 , 不需要管理SqlSessionTemplate , 而且对事务的支持更加友好 . 可跟踪源码查看测试:1、将我们上面写的UserDaoImpl修改一下public class UserDaoImpl extends SqlSessionDaoSupport implements UserMapper { public List<User> selectUser() { UserMapper mapper = getSqlSession().getMapper(UserMapper.class); return mapper.selectUser(); } }2、修改bean的配置<bean id="userDao" class="com.kuang.dao.UserDaoImpl"> <property name="sqlSessionFactory" ref="sqlSessionFactory" /> </bean>3、测试@Test public void test2(){ ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); UserMapper mapper = (UserMapper) context.getBean("userDao"); List<User> user = mapper.selectUser(); System.out.println(user); }9.声明式事务9.1 回顾事务事务在项目开发过程非常重要,涉及到数据的一致性的问题,不容马虎!事务管理是企业级应用程序开发中必备技术,用来确保数据的完整性和一致性。事务就是把一系列的动作当成一个独立的工作单元,这些动作要么全部完成,要么全部不起作用。事务四个属性ACID原子性(atomicity)事务是原子性操作,由一系列动作组成,事务的原子性确保动作要么全部完成,要么完全不起作用一致性(consistency)一旦所有事务动作完成,事务就要被提交。数据和资源处于一种满足业务规则的一致性状态中隔离性(isolation)可能多个事务会同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏持久性(durability)事务一旦完成,无论系统发生什么错误,结果都不会受到影响。通常情况下,事务的结果被写到持久化存储器中9.2 测试将上面的代码拷贝到一个新项目中在之前的案例中,我们给userDao接口新增两个方法,删除和增加用户;//添加一个用户 public int addUser(User user); //根据id删除用户 public int deleteUser(int id);mapper文件,我们故意把 deletes 写错,测试!<insert id="addUser" parameterType="com.kuang.pojo.User"> insert into user (id,name,pwd) values (#{id},#{name},#{pwd}) </insert> <delete id="deleteUser" parameterType="int"> deletes from user where id = #{id} </delete>编写接口的实现类,在实现类中,我们去操作一波public class UserDaoImpl extends SqlSessionDaoSupport implements UserMapper { //增加一些操作 public List<User> selectUser() { User user = new User(4,"小明","123456"); UserMapper mapper = getSqlSession().getMapper(UserMapper.class); mapper.addUser(user); mapper.deleteUser(4); return mapper.selectUser(); } //新增 public int addUser(User user) { UserMapper mapper = getSqlSession().getMapper(UserMapper.class); return mapper.addUser(user); } //删除 public int deleteUser(int id) { UserMapper mapper = getSqlSession().getMapper(UserMapper.class); return mapper.deleteUser(id); } }测试@Test public void test2(){ ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); UserMapper mapper = (UserMapper) context.getBean("userDao"); List<User> user = mapper.selectUser(); System.out.println(user); }报错:sql异常,delete写错了结果 :插入成功!没有进行事务的管理;我们想让他们都成功才成功,有一个失败,就都失败,我们就应该需要事务!以前我们都需要自己手动管理事务,十分麻烦!但是Spring给我们提供了事务管理,我们只需要配置即可;9.3 Spring中的事务管理Spring在不同的事务管理API之上定义了一个抽象层,使得开发人员不必了解底层的事务管理API就可以使用Spring的事务管理机制。Spring支持编程式事务管理和声明式的事务管理。编程式事务管理将事务管理代码嵌到业务方法中来控制事务的提交和回滚缺点:必须在每个事务操作业务逻辑中包含额外的事务管理代码声明式事务管理一般情况下比编程式事务好用。将事务管理代码从业务方法中分离出来,以声明的方式来实现事务管理。将事务管理作为横切关注点,通过aop方法模块化。Spring中通过Spring AOP框架支持声明式事务管理。使用Spring管理事务,注意头文件的约束导入 : txxmlns:tx="http://www.springframework.org/schema/tx" http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">事务管理器无论使用Spring的哪种事务管理策略(编程式或者声明式)事务管理器都是必须的。就是 Spring的核心事务管理抽象,管理封装了一组独立于技术的方法。JDBC事务<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean>配置好事务管理器后我们需要去配置事务的通知<!--配置事务通知--> <tx:advice id="txAdvice" transaction-manager="transactionManager"> <tx:attributes> <!--配置哪些方法使用什么样的事务,配置事务的传播特性--> <tx:method name="add" propagation="REQUIRED"/> <tx:method name="delete" propagation="REQUIRED"/> <tx:method name="update" propagation="REQUIRED"/> <tx:method name="search*" propagation="REQUIRED"/> <tx:method name="get" read-only="true"/> <tx:method name="*" propagation="REQUIRED"/> </tx:attributes> </tx:advice>spring事务传播特性:事务传播行为就是多个事务方法相互调用时,事务如何在这些方法间传播。spring支持7种事务传播行为:propagation_requierd:如果当前没有事务,就新建一个事务,如果已存在一个事务中,加入到这个事务中,这是最常见的选择。propagation_supports:支持当前事务,如果没有当前事务,就以非事务方法执行。propagation_mandatory:使用当前事务,如果没有当前事务,就抛出异常。propagation_required_new:新建事务,如果当前存在事务,把当前事务挂起。propagation_not_supported:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。propagation_never:以非事务方式执行操作,如果当前事务存在则抛出异常。propagation_nested:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与propagation_required类似的操作Spring 默认的事务传播行为是 PROPAGATION_REQUIRED,它适合于绝大多数的情况。假设 ServiveX#methodX() 都工作在事务环境下(即都被 Spring 事务增强了),假设程序中存在如下的调用链:Service1#method1()->Service2#method2()->Service3#method3(),那么这 3 个服务类的 3 个方法通过 Spring 的事务传播机制都工作在同一个事务中。就好比,我们刚才的几个方法存在调用,所以会被放在一组事务当中!配置AOP导入aop的头文件!<!--配置aop织入事务--> <aop:config> <aop:pointcut id="txPointcut" expression="execution(* com.kuang.dao.*.*(..))"/> <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/> </aop:config>进行测试删掉刚才插入的数据,再次测试!@Test public void test2(){ ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); UserMapper mapper = (UserMapper) context.getBean("userDao"); List<User> user = mapper.selectUser(); System.out.println(user); }思考问题?为什么需要配置事务?如果不配置,就需要我们手动提交控制事务;事务在项目开发过程非常重要,涉及到数据的一致性的问题,不容马虎!参考资料【狂神说Java】Spring5最新完整教程IDEA版通俗易懂
2022年04月28日
862 阅读
0 评论
0 点赞
2022-04-27
leetcode|中等:36. 有效的数独
1.题目请你判断一个 9 x 9 的数独是否有效。只需要 根据以下规则 ,验证已经填入的数字是否有效即可。数字 1-9 在每一行只能出现一次。数字 1-9 在每一列只能出现一次。数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。(请参考示例图)注意:一个有效的数独(部分已被填充)不一定是可解的。只需要根据以上规则,验证已经填入的数字是否有效即可。空白格用 '.' 表示。示例 1:输入:board = [["5","3",".",".","7",".",".",".","."] ,["6",".",".","1","9","5",".",".","."] ,[".","9","8",".",".",".",".","6","."] ,["8",".",".",".","6",".",".",".","3"] ,["4",".",".","8",".","3",".",".","1"] ,["7",".",".",".","2",".",".",".","6"] ,[".","6",".",".",".",".","2","8","."] ,[".",".",".","4","1","9",".",".","5"] ,[".",".",".",".","8",".",".","7","9"]] 输出:true示例 2:输入:board = [["8","3",".",".","7",".",".",".","."] ,["6",".",".","1","9","5",".",".","."] ,[".","9","8",".",".",".",".","6","."] ,["8",".",".",".","6",".",".",".","3"] ,["4",".",".","8",".","3",".",".","1"] ,["7",".",".",".","2",".",".",".","6"] ,[".","6",".",".",".",".","2","8","."] ,[".",".",".","4","1","9",".",".","5"] ,[".",".",".",".","8",".",".","7","9"]] 输出:false 解释:除了第一行的第一个数字从 5 改为 8 以外,空格内其他数字均与 示例1 相同。 但由于位于左上角的 3x3 宫内有两个 8 存在, 因此这个数独是无效的。提示:board.length == 9board[i].length == 9board[i][j] 是一位数字(1-9)或者 '.'2. 题解2.1 思路分析思路1:题目比较简单 暴力遍历即可2.2 代码实现思路1:class Solution { public boolean isValidSudoku(char[][] board) { // 0.计数数组 int[] counter = new int[9]; // 1.数字 1-9 在每一行只能出现一次。 for (int i = 0; i < 9; i++) { Arrays.fill(counter,0); for (int j = 0; j < 9; j++) { if(board[i][j]!='.'){ int index = board[i][j]-'1'; counter[index]++; if(counter[index]>1){ return false; } } } } // 2.数字 1-9 在每一列只能出现一次。 for (int i = 0; i < 9; i++) { Arrays.fill(counter,0); for (int j = 0; j < 9; j++) { if(board[j][i]!='.'){ int index = board[j][i]-'1'; counter[index]++; if(counter[index]>1){ return false; } } } } // 3.数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。 for (int i = 0; i < 3; i++) { for (int j = 0; j < 3; j++) { Arrays.fill(counter,0); int x_offset = 3*i; int y_offset = 3*j; for (int k = 0; k < 3; k++) { for (int l = 0; l < 3; l++) { if(board[x_offset+k][y_offset+l]!='.'){ int index = board[x_offset+k][y_offset+l]-'1'; counter[index]++; if(counter[index]>1){ return false; } } } } } } return true; } }2.3 提交结果提交结果执行用时内存消耗语言提交时间备注通过1 ms40.8 MBJava2022/04/27 19:31添加备注参考资料https://leetcode-cn.com/problems/valid-sudoku/
2022年04月27日
678 阅读
0 评论
0 点赞
2022-04-27
leetcode|中等:31. 下一个排列
1.题目整数数组的一个 排列 就是将其所有成员以序列或线性顺序排列。例如,arr = [1,2,3] ,以下这些都可以视作 arr 的排列:[1,2,3]、[1,3,2]、[3,1,2]、[2,3,1] 。整数数组的 下一个排列 是指其整数的下一个字典序更大的排列。更正式地,如果数组的所有排列根据其字典顺序从小到大排列在一个容器中,那么数组的 下一个排列 就是在这个有序容器中排在它后面的那个排列。如果不存在下一个更大的排列,那么这个数组必须重排为字典序最小的排列(即,其元素按升序排列)。例如,arr = [1,2,3] 的下一个排列是 [1,3,2] 。类似地,arr = [2,3,1] 的下一个排列是 [3,1,2] 。而 arr = [3,2,1] 的下一个排列是 [1,2,3] ,因为 [3,2,1] 不存在一个字典序更大的排列。给你一个整数数组 nums ,找出 nums 的下一个排列。必须 原地 修改,只允许使用额外常数空间。2. 题解2.1 思路分析思路1: 1.倒序遍历数组, 找到第一个前一个数比后一个数小的位置(即nums[i] < nums[i+1]); firstIndex = i 2.这个时候我们不能直接把后一个数nums[i+1] 跟前一个数nums[i]交换就完事了; 还应该从nums[i+1]-->数组末尾这一段的数据中 找出最优的那个值 如何最优? 即比nums[i]稍微大那么一丢丢的数, 也就是 nums[i+1]-->数组末尾中, 比nums[i]大的数中最小的那个值 下标记为secondeIndex 3.找到之后, 跟num[i]交换, 这还不算是下一个排列, num[i]后面的数值还不够小, 所以还应当对 nums[i+1]-->数组末尾 进升序排列 eg:nums = [1,2,7,4,3,1], 1.倒序遍历数组, 找出第一组 前一个数比后一个数小的两个数 即[2,7] 2.2所处的这个位置就是需要找出比它稍微大的数的位置; 3.我们从[7,4,3,1]中找出比2大的数中的最小值, 也就是3, 找到后跟2交换即可; 当然了, 如果没找到的话, 直接跳到第5步, 直接升序排列输出. 4.目前nums=[1,3,7,4,2,1], 明显可以看出来还不算下一个排列 5.对3后面的数, 升序排列, 即最终结果: nums = [1,3,1,2,4,7]2.2 代码实现思路1:import java.util.*; public class Solution { public void nextPermutation(int[] nums) { // 1.倒序遍历数组,找到第一个满足nums[i] < nums[i+1]的位置 for (int firstIndex = nums.length-2; firstIndex >=0; firstIndex--) { if(nums[firstIndex]<nums[firstIndex+1]){ int secondeIndex = firstIndex+1; // 2.在 nums[i+1]-->数组末尾 找比nums[i]大的数中最小的那个值 for (int i = secondeIndex+1; i < nums.length; i++) { if(nums[i]>nums[firstIndex]&&nums[i]<nums[secondeIndex]){ secondeIndex = i; } } // 3.交换nums[firstIndex]和nums[secondeIndex] int temp = nums[secondeIndex]; nums[secondeIndex] = nums[firstIndex]; nums[firstIndex] = temp; // 4.对nums[firstIndex+1]之后的数进行升序排列 Arrays.sort(nums,firstIndex+1,nums.length); return; } } // 5.如果没找到直接升序排列并返回 Arrays.sort(nums); return; } public static void main(String[] args) { Solution solution = new Solution(); int[] nums = new int[]{1,3,2}; solution.nextPermutation(nums); System.out.println(Arrays.toString(nums)); } } 2.3 提交结果提交结果执行用时内存消耗语言提交时间备注通过1 ms41.8 MBJava2022/04/27 18:36添加备注参考资料https://leetcode-cn.com/problems/next-permutation/
2022年04月27日
661 阅读
0 评论
0 点赞
2022-04-26
leetcode|困难:30. 串联所有单词的子串
leetcode|困难:30. 串联所有单词的子串1.题目给定一个字符串 s 和一些 长度相同 的单词 words 。找出 s 中恰好可以由 words 中所有单词串联形成的子串的起始位置。注意子串要与 words 中的单词完全匹配,中间不能有其他字符 ,但不需要考虑 words 中单词串联的顺序。示例 1:输入:s = "barfoothefoobarman", words = ["foo","bar"] 输出:[0,9] 解释: 从索引 0 和 9 开始的子串分别是 "barfoo" 和 "foobar" 。 输出的顺序不重要, [9,0] 也是有效答案。示例 2:输入:s = "wordgoodgoodgoodbestword", words = ["word","good","best","word"] 输出:[]示例 3:输入:s = "barfoofoobarthefoobarman", words = ["bar","foo","the"] 输出:[6,9,12]提示:1 <= s.length <= 104s 由小写英文字母组成1 <= words.length <= 50001 <= words[i].length <= 30words[i] 由小写英文字母组成2. 题解2.1 思路分析思路1:滑动窗口+词频统计 对words进行词频统计 然后以words包含的所有字符数作为窗口大小进行滑动+窗口词频统计 判断词频是否相等并记录相对的位置 思路2:#TODO 借鉴KMP对思路1进行优化 避免进行一些冗余比较2.2 代码实现思路1:滑动窗口+词频统计class Solution { public List<Integer> findSubstring(String s, String[] words) { List<Integer> res = new ArrayList<>(); // 记录下来每个单词的长度和单词的总长度 int wordLen = words[0].length(); int allWordLen = words.length*wordLen; // 统计words的词频 Map<String,Integer> wordFrequnce = new HashMap<>(); for (int i = 0; i < words.length; i++) { if(!wordFrequnce.containsKey(words[i])) { wordFrequnce.put(words[i], 1); }else { wordFrequnce.replace(words[i], wordFrequnce.get(words[i])+1); } } // 采用滑动窗口进行遍历,每次滑动一个字符 for (int i = 0; i <= s.length()-allWordLen; i++) { // 统计滑动窗口内的词频 Map<String,Integer> winWordFrequnce = new HashMap<>(); for (int j = 0; j < allWordLen; j+=wordLen) { String tmpWord = s.substring(i+j,i+j+wordLen); if(!winWordFrequnce.containsKey(tmpWord)) { winWordFrequnce.put(tmpWord, 1); }else { winWordFrequnce.replace(tmpWord, winWordFrequnce.get(tmpWord)+1); } } // 判断二者的词频是否一致 boolean sameFlag = winWordFrequnce.equals(wordFrequnce); // 将开始所以加入到res if(sameFlag){ res.add(i); } } return res; } }2.3 提交结果提交结果执行用时内存消耗语言提交时间备注通过247 ms42.2 MBJava2022/04/26 13:56添加备注参考资料https://leetcode-cn.com/problems/substring-with-concatenation-of-all-words
2022年04月26日
689 阅读
0 评论
0 点赞
2022-04-26
leetcode|中等:29. 两数相除
1.题目给定两个整数,被除数 dividend 和除数 divisor。将两数相除,要求不使用乘法、除法和 mod 运算符。返回被除数 dividend 除以除数 divisor 得到的商。整数除法的结果应当截去(truncate)其小数部分,例如:truncate(8.345) = 8 以及 truncate(-2.7335) = -2示例 1:输入: dividend = 10, divisor = 3 输出: 3 解释: 10/3 = truncate(3.33333..) = truncate(3) = 3示例 2:输入: dividend = 7, divisor = -3 输出: -2 解释: 7/-3 = truncate(-2.33333..) = -2提示:被除数和除数均为 32 位有符号整数。除数不为 0。假设我们的环境只能存储 32 位有符号整数,其数值范围是 $[−2^{31}, 2^{31} − 1]$。本题中,如果除法结果溢出,则返回 $2^{31} − 1$。2. 题解2.1 思路分析思路1:用减法来模拟乘法 O(n)会运行超时 思路2: 思路1优化:借鉴计算机网络的流量拥塞控制机制,对被除数进行快速倍增2.2 代码实现思路1:用减法来模拟乘法public class Solution { public int divide(int dividend, int divisor) { // 解决计算结果上溢的问题 if(dividend==Integer.MIN_VALUE&&divisor==-1){ return Integer.MAX_VALUE; } // 判断结果的正负并把负数取绝对值+类型提升int-->long System.out.println("dividend="+dividend+",divisor="+divisor); boolean positiveFlag = true; long dividendLong = dividend; long divisorLong = divisor; if(dividend<0){ positiveFlag = !positiveFlag; dividendLong = -(long)dividend; } if(divisor<0){ positiveFlag = !positiveFlag; divisorLong = -(long)divisor; } System.out.println("dividendLong="+dividendLong+",divisorLong="+divisorLong); // 用减法模拟乘法 long res = 0; while (dividendLong>=divisorLong){ dividendLong -= divisorLong; res++; } // 结果符号修正 res = positiveFlag?res:-res; return (int)res; } public static void main(String[] args) { Solution solution = new Solution(); int in = 3; System.out.println(solution.divide(Integer.MIN_VALUE,3)); } }思路2: 思路1优化:借鉴计算机网络的流量拥塞控制机制,对被除数进行快速倍增public class Solution { public int divide(int dividend, int divisor) { // 解决计算结果上溢的问题 if(dividend==Integer.MIN_VALUE&&divisor==-1){ return Integer.MAX_VALUE; } // 判断结果的正负并把负数取绝对值+类型提升int-->long boolean positiveFlag = true; long dividendLong = dividend; long divisorLong = divisor; if(dividend<0){ positiveFlag = !positiveFlag; dividendLong = -(long)dividend; } if(divisor<0){ positiveFlag = !positiveFlag; divisorLong = -(long)divisor; } // 用减法模拟乘法+快速倍乘 long res = 0; while (dividendLong>=divisorLong) { long base = divisorLong; while (dividendLong >= base) { dividendLong -= base; res += (base/divisorLong); base *= 2; } } // 结果符号修正 res = positiveFlag?res:-res; return (int)res; } public static void main(String[] args) { Solution solution = new Solution(); int in = 3; System.out.println(solution.divide(Integer.MIN_VALUE,2)); } }2.3 提交结果思路1提交结果执行用时内存消耗语言提交时间备注超出时间限制N/AN/AJava2022/04/26 10:10添加备注思路2提交结果执行用时内存消耗语言提交时间备注通过1 ms38.5 MBJava2022/04/26 10:29添加备注参考资料https://leetcode-cn.com/problems/divide-two-integers/
2022年04月26日
576 阅读
0 评论
0 点赞
1
...
8
9
10
...
24