首页
statistical
wallpaper
Search
1
Serv00虚拟主机
385 阅读
2
Azure 100(AZ100) 学生申请
316 阅读
3
项目搭建
277 阅读
4
Raft论文研读 Ⅳ
257 阅读
5
Raft论文研读 Ⅱ
256 阅读
Java
VPS
BlockChain
Paper
Other
备战
登录
Search
标签搜索
Raft
VPS
Java
共识算法
区块链
论文
项目搭建
Docker
改进Raft
SE
Web
Git
SSM
Spring Boot
Blog
Nat
科学上网
ChatGpt
探针
AZ100
ZhenXI
累计撰写
35
篇文章
累计收到
12
条评论
首页
栏目
Java
VPS
BlockChain
Paper
Other
备战
页面
statistical
wallpaper
搜索到
7
篇与
的结果
2024-05-18
Spring AI
Spring AI 是一个应用框架,旨在将人工智能功能无缝集成到应用程序中。它利用了Spring生态系统的熟悉模式和原则,注重模块化和易用性,不仅适用于经验丰富的AI开发者,也适用于AI领域的新手。
2024年05月18日
120 阅读
0 评论
1 点赞
2024-04-18
Mysql 8.0.39 忘记 Root 密码
如果您在 Linux 上忘记了 MySQL 8.0+ 的 `root` 用户密码,可以通过以下步骤来重置密码~
2024年04月18日
6 阅读
0 评论
0 点赞
2022-07-31
Spring Boot
1 Spring Boot1.1 Spring Boot概述Spring Boot设计目的是用来简化Spring应用的创建、运行、调试、部署等。使用Spring Boot可以做到专注于Spring应用的开发,而无需过多关注XML的配置。Spring Boot使用“约定优于配置”的理念,简单来说,它提供了一堆依赖打包,并已经按照使用习惯解决了依赖问题。1.2 Spring Boot的核心功能可独立运行的Spring项目:Spring Boot可以以jar包的形式独立运行。内嵌的Servlet容器:Spring Boot可以选择内嵌Tomcat、Jetty或者Undertow,无须以war包形式部署项目。简化的Maven配置:Spring提供推荐的基础 POM 文件来简化Maven 配置。自动配置Spring:Spring Boot会根据项目依赖来自动配置Spring 框架,极大地减少项目要使用的配置。提供生产就绪型功能:提供可以直接在生产环境中使用的功能,如性能指标、应用信息和应用健康检查。无代码生成和xml配置:Spring Boot不生成代码。完全不需要任何xml配置即可实现Spring的所有配置。1.3 SpringBootB的相关好处使用 Spring 项目引导页面可以在⼏秒构建⼀个项⽬;方便对外输出各种形式的服务,如 REST API、WebSocket、Web、Streaming、Tasks;非常简洁的安全策略集成;⽀持关系数据库和⾮关系数据库;⽀持运行期内嵌容器,如 Tomcat、Jetty;强⼤的开发包,⽀持热启动;⾃动管理依赖;⾃带应⽤监控;⽀持各种 IED,如 IntelliJ IDEA 、NetBeans。和Spirng程序相比,SpringBoot程序在开发的过程中各个层面均具有优势类配置文件SpringSpringBootpom文件中的坐标手工添加勾选添加web3.0配置类手工制作无Spring/SpringMVC配置类手工制作无控制器手工制作手工制作2 Spring Boot快速创建2.1 使用Spring Initializr引导页面进行创建①创建新模块,选择Spring Initializr,并配置模块相关基础信息②选择当前模块需要使用的技术集③开发控制器类package com.liu.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/user") public class UserController { @GetMapping @RequestMapping("quick1") public String findUsers() { System.out.println("Spring boot is running"); return "Spring is running"; } }④运行自动生成的Application类2.2 进入Spring Boot官网进行创建①点击Spring Initializr后进入到创建SpringBoot程序的界面上,下面是输入信息的过程,和前面的一样,只是界面变了而已,根据自己的要求,在左侧选择对应信息和输入对应的信息即可。右侧的ADD DEPENDENCIES用于选择使用何种技术,和之前勾选的Spring WEB是在做同一件事,仅仅是界面不同而已,点击后打开网页版的技术选择界面②所有信息设置完毕后,点击下面左侧按钮,生成一个文件包,保存后得到一个压缩文件,这个文件打开后就是创建的SpringBoot工程文件夹。解压缩此文件后,得到工程目录,在Idea中导入即可使用,和之前创建的东西完全一样。下面就可以自己创建一个Controller测试一下是否能用了。2.3 使用阿里云地址创建Spring Boot项目①创建新模块,选择Spring Initializr,选择Server URL为start.aliyun.com,并配置模块相关基础信息②选择当前模块需要使用的技术集③运行自动生成的Application类进行测试3 Spring Boot简介3.1 parent:进行版本的统一管理①parent概述SpringBoot为了解决最合理的依赖版本配置方案,于是将所有的技术版本的常见使用方案都给开发者整理了出来,以后开发者使用时直接用它提供的版本方案,就不用担心冲突问题了,相当于SpringBoot做了无数个技术版本搭配的列表,这个技术搭配列表的名字叫做parent。 parent自身具有很多个版本,每个parent版本中包含有几百个其他技术的版本号,不同的parent间使用的各种技术的版本号有可能会发生变化。当开发者使用某些技术时,直接使用SpringBoot提供的parent就行了,由parent帮助开发者统一的进行各种技术的版本管理。②Spring Boot项目中引用的parent项目中的pom.xml中继承了一个坐标,打开后可以查阅到其中又继承了一个坐标,这个坐标中定义了两组信息。第一组是各式各样的依赖版本号属性,下面列出依赖版本属性的局部,可以看的出来,定义了若干个技术的依赖版本号;第二组是各式各样的的依赖坐标信息,可以看出依赖坐标定义中没有具体的依赖版本号,而是引用了第一组信息中定义的依赖版本属性值。<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.7.3</version> <relativePath/> <!-- lookup parent from repository --> </parent>③parent功能定义了 Java 编译版本为 1.8 。使用 UTF-8 格式编码。继承自 spring-boot-dependencies,这个里边定义了依赖的版本,也正是因为继承了这个依赖,所以我们在写4、依赖时才不需要写版本号。执行打包操作的配置。自动化的资源过滤。自动化的插件配置。针对 application.properties 和 application.yml 的资源过滤,包括通过 profile 定义的不同环境的配置文件,例如 application-dev.properties 和 application-dev.yml。3.2 starter:减少依赖配置①starter概述SpringBoot把所有的技术使用的固定搭配格式都给开发出来,开发者使用的时候,就不用一次写一堆依赖了,直接用Spring Boot做好的这个东西就好了,对于这样的固定技术搭配,SpringBoot给它起了个名字叫做starter。starter定义了使用某种技术时对于依赖的固定搭配格式,也是一种最佳解决方案,使用starter可以帮助开发者减少依赖配置。②Spring Boot项目中引用的starter项目中的pom.xml定义了使用SpringMVC技术,但是并没有写SpringMVC的坐标,而是添加了一个名字中包含starter的依赖,在spring-boot-starter-web中又定义了若干个具体依赖的坐标。<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency>③starter与parent的区别starter是一个坐标中定了若干个坐标,以前写多个的,现在写一个,是用来减少依赖配置的书写量的。parent是定义了几百个依赖版本号,以前写依赖需要自己手工控制版本,现在由SpringBoot统一管理,这样就不存在版本冲突了,是用来减少依赖冲突的。3.3 引导类运行这个类就可以启动SpringBoot工程:@SpringBootApplication public class Springboot0101QuickstartApplication { public static void main(String[] args) { SpringApplication.run(Springboot0101QuickstartApplication.class, args); } }SpringBoot本身是为了加速Spring程序的开发的,而Spring程序运行的基础是需要创建自己的Spring容器对象(IoC容器)并将所有的对象交给Spring的容器管理,也就是一个一个的Bean。当前这个类运行后就会产生一个Spring容器对象,并且可以将这个对象保存起来,通过容器对象直接操作Bean。@SpringBootApplication public class Springboot01QuickstartApplication { public static void main(String[] args) { ConfigurableApplicationContext applicationContext = SpringApplication.run(Springboot01QuickstartApplication.class, args); UserController userController = applicationContext.getBean(UserController.class); System.out.println(userController); } }通过上述操作不难看出,其实SpringBoot程序启动还是创建了一个Spring容器对象。这个类在SpringBoot程序中是所有功能的入口,称这个类为引导类。作为一个引导类最典型的特征就是当前类上方声明了一个注解@SpringBootApplication打开该注解可以发现,这个类就是学习Spring的注解核心配置类。3.4 内嵌tomcat打开查看web的starter,发现有一个tomcat的starter,这里面有一个核心的坐标,tomcat-embed-core,叫做tomcat内嵌核心。就是这个东西把tomcat功能引入到了我们的程序中。更换内嵌TomcatSpringBoot提供了3款内置的服务器tomcat(默认):apache出品,粉丝多,应用面广,负载了若干较重的组件jetty:更轻量级,负载性能远不及tomcatundertow:负载性能勉强跑赢tomcat想用哪个,加个坐标就OK。前提是把tomcat排除掉,因为tomcat是默认加载的。<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jetty</artifactId> </dependency> </dependencies>现在就已经成功替换了web服务器,核心思想就是用什么加入对应坐标就可以了。如果有starter,优先使用starter。4 Rest风格,RestFul开发在学习Spring MVC时学过REST风格,但是讲课老师讲的不太深入,对于一些注解并没有进行讲解。于是重新复习一遍。4.1 RESTFUL概述RESTFUL是一种网络应用程序的设计风格和开发方式,基于HTTP,可以使用 XML 格式定义或 JSON 格式定义。最常用的数据格式是JSON。由于JSON能直接被JavaScript读取,所以,使用JSON格式的REST风格的API具有简单、易读、易用的特点。REST 是 Representational State Transfer 的缩写,如果一个架构符合 REST 原则,就称它为 RESTful 架构RESTful 架构可以充分的利用 HTTP 协议的各种功能,是 HTTP 协议的最佳实践。RESTful API 是一种软件架构风格、设计风格,可以让软件更加清晰,更简洁,更有层次,可维护性更好4.2 RESTFUL API 请求设计请求方式含义GET(SELECT)从服务器取出资源(一项或多项)POST(CREATE)在服务器新建一个资源PUT(UPDATE)在服务器更新资源(更新完整资源)PATCH(UPDATE)在服务器更新资源, PATCH更新个别属性DELETE(DELETE)从服务器删除资源4.3 在Spring MVC中使用RESTFUL开发设计@Controller public class ZoosController { @RequestMapping(value = "/zoos",method = RequestMethod.GET) @ResponseBody public String findAll(){ System.out.println("ZoosController.findAll"); return "zoosController.findAll"; } @RequestMapping(value = "/zoos/{id}",method = RequestMethod.GET) @ResponseBody public String findOne(@PathVariable Integer id){ System.out.println("ZoosController.findOne"); return "zoosController.findOne"; } @RequestMapping(value = "/zoos",method = RequestMethod.PUT) @ResponseBody public String update(@RequestBody Zoo zoo){ System.out.println("ZoosController.update"); return "zoosController.update"; } @RequestMapping(value = "/zoos/{id}",method = RequestMethod.DELETE) @ResponseBody public String delete(@PathVariable Integer id){ System.out.println("ZoosController.delete"); return "zoosController.delete"; } @RequestMapping(value = "/zoos",method = RequestMethod.POST) @ResponseBody public String insert(){ System.out.println("ZoosController.insert"); return "zoosController.insert"; } }简化开发相关注解注解作用@RestController由 @Controller + @ResponseBody组成(返回 JSON 数据格式)@PathVariableURL 中的 {xxx} 占位符可以通过@PathVariable(“xxx“) 绑定到控制器处理方法的形参中@RequestMapping注解用于请求地址的解析,是最常用的一种注解@GetMapping查询请求@PostMapping添加请求@PutMapping更新请求@DeleteMapping删除请求@RequestParam将请求参数绑定到你控制器的方法参数上(是springmvc中接收普通参数的注解)@RestController @RequestMapping("/zoos") @ResponseBody public class ZoosController { @GetMapping public String findAll(){ System.out.println("ZoosController.findAll"); return "zoosController.findAll"; } @GetMapping(value = "/{id}") public String findOne(@PathVariable Integer id){ System.out.println("ZoosController.findOne"); return "zoosController.findOne"; } @PutMapping public String update(@RequestBody Zoo zoo){ System.out.println("ZoosController.update"); return "zoosController.update"; } @DeleteMapping(value = "/{id}") public String delete(@PathVariable Integer id){ System.out.println("ZoosController.delete"); return "zoosController.delete"; } @PostMapping public String insert(){ System.out.println("ZoosController.insert"); return "zoosController.insert"; } }5 SpringBoot基础配置5.1 属性配置SpringBoot通过配置文件application.properties就可以修改默认的配置,properties格式的文件书写规范是key=value。SpringBoot程序可以在application.properties文件中进行属性配置。application.properties文件中只要输入要配置的属性关键字就可以根据提示进行设置。SpringBoot将配置信息集中在一个文件中写,不管你是服务器的配置,还是数据库的配置,总之都写在一起,逃离一个项目十几种配置文件格式的尴尬局面。更改端口号# 服务器的端口配置 server.port=80关闭运行日志图表(banner)# 关闭banner spring.main.banner-mode=off # 修改banner spring.banner.image.location=wallhaven-2879mg.png设置运行日志的显示级别# 日志 logging.level.root = error5.2 配置文件分类SpringBoot除了支持properties格式的配置文件,还支持另外两种格式的配置文件。分别如下:application.properties(properties格式)server.port=80application.yml(yml格式)server: port: 81application.yaml(yaml格式)server: port: 82仔细看会发现yml格式和yaml格式除了文件名后缀不一样,格式完全一样,是这样的,yml和yaml文件格式就是一模一样的,只是文件后缀不同,所以可以合并成一种格式来看。5.3 配置文件优先级其实三个文件如果共存的话,谁生效说的就是配置文件加载的优先级别。application.properties > application.yml > application.yaml配置文件间的加载优先级 properties(最高)> yml > yaml(最低),不同配置文件中相同配置按照加载优先级相互覆盖,不同配置文件中不同配置全部保留。5.4 yaml文件的使用YAML(YAML Ain't Markup Language),一种数据序列化格式。具有容易阅读、容易与脚本语言交互、以数据为核心,重数据轻格式的特点。常见的文件扩展名有两种:.yml格式(主流).yaml格式对于文件自身在书写时,具有严格的语法格式要求,具体如下:大小写敏感属性层级关系使用多行描述,每行结尾使用冒号结束使用缩进表示层级关系,同层级左侧对齐,只允许使用空格(不允许使用Tab键)属性值前面添加空格(属性名与属性值之间使用冒号+空格作为分隔)号 表示注释常见的数据书写格式:boolean: TRUE #TRUE,true,True,FALSE,false,False均可 float: 3.14 #6.8523015e+5 #支持科学计数法 int: 123 #0b1010_0111_0100_1010_1110 #支持二进制、八进制、十六进制 null: ~ #使用~表示null string: HelloWorld #字符串可以直接书写 string2: "Hello World" #可以使用双引号包裹特殊字符 date: 2018-02-17 #日期必须使用yyyy-MM-dd格式 datetime: 2018-02-17T15:02:31+08:00 #时间和日期之间使用T连接,最后使用+代表时区 subject: - Java - 前端 - 大数据 enterprise: name: itcast age: 16 subject: - Java - 前端 - 大数据 likes: [王者荣耀,刺激战场] #数组书写缩略格式 users: #对象数组格式一 - name: Tom age: 4 - name: Jerry age: 5 users: #对象数组格式二 - name: Tom age: 4 - name: Jerry age: 5 users2: [ { name:Tom , age:4 } , { name:Jerry , age:5 } ] #对象数组缩略格式5.5 yaml文件数据读取读取单一数据yaml中保存的单个数据,可以使用Spring中的注解直接读取,使用@Value可以读取单个数据,属性名引用方式:${一级属性名.二级属性名……}address: 河南 user: userName: admin password: 123456 likes: - game - music - video baseDir: c:\windows\system32 tempDir: ${baseDir}\temp@RestController @RequestMapping("/user") public class UserController { @Value("${address}") private String address; @Value("${user.userName}") private String userName; @Value("${likes[1]}") private String likes1; @Value("${tempDir}") private String tempDir; @GetMapping("/quick1") public String findUsers() { System.out.println("Spring boot is running"); System.out.println(address); System.out.println(userName); System.out.println(likes1); System.out.println(tempDir); }读取全部数据SpringBoot提供了一个对象,能够把所有的数据都封装到这一个对象中,这个对象叫做Environment,使用自动装配注解可以将所有的yaml数据封装到这个对象中。@RestController @RequestMapping("/user") public class UserController { //自动装配,把所有对象加载到environment对象中 @Autowired private Environment environment; @GetMapping("/quick1") public String findUsers() { System.out.println("Spring boot is running"); //使用environment获得yMl文件中的信息 String address = environment.getProperty("address"); System.out.println(address); } }读取对象数据单一数据读取书写比较繁琐,全数据封装又封装的太厉害了,每次拿数据还要一个一个的getProperties(),总之用起来都不是很舒服。由于Java是一个面向对象的语言,很多情况下,我们会将一组数据封装成一个对象。SpringBoot也提供了可以将一组yaml对象数据封装一个Java对象的操作。①首先定义一个对象,并将该对象纳入Spring管控的范围,也就是定义成一个bean,然后使用注解@ConfigurationProperties指定该对象加载哪一组yaml中配置的信息。@Component @ConfigurationProperties(prefix = "datasource") public class MyDataSource { private String driver; private String url; private String username; private String password; @Override public String toString() { return "MyDataSource{" + "driver='" + driver + '\'' + ", url='" + url + '\'' + ", username='" + username + '\'' + ", password='" + password + '\'' + '}'; } public String getDriver() { return driver; } public void setDriver(String driver) { this.driver = driver; } public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } }这个@ConfigurationProperties必须告诉他加载的数据前缀是什么,这样当前前缀下的所有属性就封装到这个对象中。记得数据属性名要与对象的变量名一一对应啊,不然没法封装。其实以后如果你要定义一组数据自己使用,就可以先写一个对象,然后定义好属性,下面到配置中根据这个格式书写即可。datasource: driver: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost username: root password: 1314115.6 yaml配置中遇到的问题①配置yml文件时,设置键名为大写报错:dataSource: driver: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost username: root password: 131411Invalid characters: 'S' Bean: userController Reason: Canonical names should be kebab-case ('-' separated), lowercase alpha-numeric characters and must start with a letter 无效字符:'S' Bean:userController 原因:规范名称应为 kebab-case('-' 分隔)、小写字母数字字符且必须以字母开头6 Spring Boot整合JunitSpring整合JUnit的制作方式@ContextConfiguration("classpath:applicationContext.xml") @RunWith(SpringJUnit4ClassRunner.class) public class TransactionManagerTest { @Autowired private AccountService accountService; @Test public void test() { accountService.transfer(); } }@RunWith是设置Spring专用于测试的类运行器,简单说就是Spring程序执行程序有自己的一套独立的运行程序的方式,不能使用JUnit提供的类运行方式了,必须指定一下。@ContextConfiguration是用来设置Spring核心配置文件或配置类的,简单说就是加载Spring的环境你要告诉Spring具体的环境配置是在哪里写的。SpringBoot就抓住上述两条没有技术含量的内容书写进行开发简化,能走默认值的走默认值,能不写的就不写,具体格式如下:①导入test的starter坐标<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency>②使用一个注解@SpringBootTest替换了前面两个注解。@SpringBootTest class SpringDemo1ApplicationTests { @Autowired private BookMapper bookMapper; @Test void contextLoads() { bookMapper.save(); } }7 Spring Boot整合Mybatis①创建模块时勾选要使用的技术,MyBatis,由于要操作数据库,还要勾选对应数据库②配置数据源相关信息spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/test username: root password: 131411③使用Lombok创建实体类@Data public class User { private Integer id; private String username; private String password; private String gender; private String address; }④创建映射接口@Mapper public interface UserMapper { @Select("select * from tb_user") List<User> findAll(); @Insert("insert into tb_user(username, password, gender, address) values (#{username},#{password},#{gender},#{address})") void insert(User user); @Update("update tb_user set username = #{username} where id=#{id}") int update(@Param("username") String username, @Param("id") int id); @Delete("delete from tb_user where id=#{id}") void delete(int id); }⑤进行测试@SpringBootTest class SpringBootMybatisApplicationTests { @Autowired private UserMapper userMapper; @Test void contextLoads() { List<User> userList = userMapper.findAll(); for (User user : userList) { System.out.println(user); } } @Test void userInserts() { User user = new User(); user.setUsername("刘畅"); user.setPassword("1314"); user.setGender("男"); user.setAddress("河南"); userMapper.insert(user); } @Test void userUpdates() { userMapper.update("六珍惜", 1); } @Test void userDeletes() { userMapper.delete(1); } }⑥部分测试结果8 Spring Boot整合Mybatis-plus①导入对应的starter<!-- https://mvnrepository.com/artifact/com.baomidou/mybatis-plus-boot-starter --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.2</version> </dependency>②配置数据源相关信息spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/test username: root password: 131411③创建映射接口@Mapper public interface UserMapper extends BaseMapper<User> { }④进行测试@SpringBootTest class SpringBootMybatisplusApplicationTests { @Autowired private UserMapper userMapper; @Test void contextLoads() { List<User> userList = userMapper.selectList(null); for (User user : userList) { System.out.println(user); } } @Test void findUserById(){ User user = userMapper.selectById(1); System.out.println(user); } }9 Spring Boot整合druid数据源①导入对应的starter<dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.2.11</version> </dependency>②配置数据源相关信息spring: datasource: druid: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/test username: root password: 131411③创建映射接口@Mapper public interface UserMapper { @Select("select * from tb_user") List<User> findAll(); @Insert("insert into tb_user(username, password, gender, address) values (#{username},#{password},#{gender},#{address})") void insert(User user); @Update("update tb_user set username = #{username} where id=#{id}") int update(@Param("username") String username, @Param("id") int id); @Delete("delete from tb_user where id=#{id}") void delete(int id); }④进行测试@SpringBootTest class SpringBootMybatisApplicationTests { @Autowired private UserMapper userMapper; @Test void contextLoads() { List<User> userList = userMapper.findAll(); for (User user : userList) { System.out.println(user); } } @Test void userInserts() { User user = new User(); user.setUsername("刘畅"); user.setPassword("1314"); user.setGender("男"); user.setAddress("河南"); userMapper.insert(user); } @Test void userUpdates() { userMapper.update("六珍惜", 1); } @Test void userDeletes() { userMapper.delete(1); } }学习总结:本周学习进度有些慢,本周主要学习了Spring Boot基础篇的相关知识。通过本次的学习,对SpringMVC的相关知识更加的熟悉;通过进行SSM整合,使用注解开发Spring程序更加得心应手;对于Spring Boot的学习,必须要打牢Spring的基础,只要有牢固的Spring基础,SpringBoot的开发更加的简单。下周学习计划:完成Spring Boot基础篇、运维实用篇等课程。由于临近开学,需要整理解决一些开学事务,导致学习时间有所减少,SpringBoot的课程在开学前无法学习完毕,不过在开学后会尽快完成任务,达到Java全栈初级工程师水平。继续努力!SpringBoot学习笔记 21 基于Spring Boot整合SSMP1.1 模块创建导入MyBatisPlus与Druid对应的starter,当然mysql的驱动不能少<dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.2</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.10</version> </dependency>1.2 数据层开发①创建实体类:User@Data public class User { private Integer id; private String username; private String password; private String gender; private String address; }②配置数据源server: port: 80 spring: datasource: druid: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/test username: root password: 131411 mybatis-plus: global-config: db-config: table-prefix: tb_③编写映射接口@Mapper public interface UserMapper extends BaseMapper<User> { }④测试@SpringBootTest public class UserMapperTest { @Autowired private UserMapper userMapper; @Test public void test(){ List<User> userList = userMapper.selectList(null); for (User user : userList) { System.out.println(user); } } @Test public void test2(){ User user = userMapper.selectById(1); System.out.println(user); } @Test public void test3(){ User user = new User(); user.setUsername("齐大"); user.setPassword("0000"); user.setGender("男"); user.setAddress("齐齐哈尔"); userMapper.insert(user); } @Test public void test4(){ userMapper.deleteById(191561735); } @Test public void test5(){ User user = new User(); user.setId(15); user.setUsername("郑大"); user.setPassword("0000"); user.setGender("男"); user.setAddress("河南郑州"); userMapper.updateById(user); } }发现问题一:自动增长的Id值太大。解决方式一:通过配置的方式:mybatis-plus: global-config: db-config: table-prefix: tb_ id-type: auto #设置主键id字段的生成策略为参照数据库设定的策略,当前数据库设置id生成策略为自增解决方式二:通过注解:在实体类User类的id属性上添加此注解。@TableId(value = "id", type = IdType.AUTO) private Integer id;发现问题二:控制台没有打印SQL语句,不利于查看解决方式:通过配置的形式就可以查阅执行期SQL语句,配置如下:mybatis-plus: global-config: db-config: table-prefix: tb_ id-type: auto configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 开启打印日志⑤分页查询测试前面仅仅是使用了MP提供的基础CRUD功能,实际上MP给我们提供了几乎所有的基础操作。其中selectPage方法需要传入一个封装分页数据的对象,可以通过new的形式创建这个对象,当然这个对象也是MP提供的。创建此对象时就需要指定分页的两个基本数据当前显示第几页每页显示几条数据可以通过创建Page对象时利用构造方法初始化这两个数据IPage page = new Page(2,5);将该对象传入到查询方法selectPage后,可以得到查询结果,但是我们会发现当前操作查询结果返回值仍然是一个IPage对象。IPage page = bookDao.selectPage(page, null);原来这个IPage对象中封装了若干个数据,而查询的结果作为IPage对象封装的一个数据存在的,可以理解为查询结果得到后,又塞到了这个IPage对象中,其实还是为了高度的封装,一个IPage描述了分页所有的信息。下面5个操作就是IPage对象中封装的所有信息了。@Test public void test6() { IPage<User> page = new Page<User>(1,5); userMapper.selectPage(page,null); System.out.println(page.getCurrent()); System.out.println(page.getSize()); System.out.println(page.getPages()); System.out.println(page.getTotal()); System.out.println(page.getRecords()); }到这里就知道这些数据如何获取了,但是当你去执行这个操作时,你会发现并不像我们分析的这样,实际上这个分页当前是无效的。为什么这样呢?这个要源于MP的内部机制。对于MySQL的分页操作使用limit关键字进行,而并不是所有的数据库都使用limit关键字实现的,这个时候MP为了制作的兼容性强,将分页操作设置为基础查询操作的升级版,你可以理解为IPhone6与IPhone6S-PLUS的关系。基础操作中有查询全部的功能,而在这个基础上只需要升级一下(PLUS)就可以得到分页操作。所以MP将分页操作做成了一个开关,你用分页功能就把开关开启,不用就不需要开启这个开关。而我们现在没有开启这个开关,所以分页操作是没有的。这个开关是通过MP的拦截器的形式存在的,其中的原理这里不分析了,有兴趣的小伙伴可以学习MyBatisPlus这门课程进行详细解读。具体设置方式如下定义MP拦截器并将其设置为Spring管控的bean@Configuration public class MPConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor(){ MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor()); return interceptor; } }上述代码第一行是创建MP的拦截器栈,这个时候拦截器栈中没有具体的拦截器,第二行是初始化了分页拦截器,并添加到拦截器栈中。如果后期开发其他功能,需要添加全新的拦截器,按照第二行的格式继续add进去新的拦截器就可以了。⑥按条件查询模糊匹配对应的操作,由like条件书写变为了like方法的调用。@Test public void test7() { QueryWrapper<User> queryWrapper = new QueryWrapper<>(); queryWrapper.like("username","刘"); userMapper.selectList(queryWrapper); }第一句QueryWrapper对象是一个用于封装查询条件的对象,该对象可以动态使用API调用的方法添加条件,最终转化成对应的SQL语句。第二句就是一个条件了,需要什么条件,使用QueryWapper对象直接调用对应操作即可。比如做大于小于关系,就可以使用lt或gt方法,等于使用eq方法,等等。这组API使用还是比较简单的,但是关于属性字段名的书写存在着安全隐患,比如查询字段name,当前是以字符串的形态书写的,万一写错,编译器还没有办法发现,只能将问题抛到运行器通过异常堆栈告诉开发者,不太友好。MP针对字段检查进行了功能升级,全面支持Lambda表达式,就有了下面这组API。由QueryWrapper对象升级为LambdaQueryWrapper对象,这下就解决了上述问题的出现。@Test public void test8() { String name = "liu"; LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.like(name!=null,User::getUsername,name); userMapper.selectList(queryWrapper); }1.3 业务层开发传统业务层开发①业务层接口定义public interface UserService { Boolean save(User user); Boolean update(User user); Boolean delete(Integer id); User findById(Integer id); List<User> getUsers(); IPage<User> getPages(int currentPage, int pageSize); }②业务层实现类如下,转调数据层即可@Service public class UserServiceImpl implements UserService { @Autowired private UserMapper userMapper; @Override public Boolean save(User user) { return userMapper.insert(user) > 0; } @Override public Boolean update(User user) { return userMapper.updateById(user) > 0; } @Override public Boolean delete(Integer id) { return userMapper.deleteById(user) > 0; } @Override public User findById(Integer id) { return userMapper.selectById(id); } @Override public List<User> getUsers() { return userMapper.selectList(null); } @Override public IPage<User> getPages(int currentPage, int pageSize) { Page<User> page = new Page<>(); return userMapper.selectPage(page,null); } }③对业务层接口进行测试@SpringBootTest public class UserServiceTest { @Autowired private UserService userService; @Test public void test() { System.out.println(userService.findById(1)); } @Test public void test2() { IPage<User> page = userService.getPages(1, 5); System.out.println(page.getRecords()); } @Test public void test3() { List<User> userList = userService.getUsers(); for (User user : userList) { System.out.println(user); } } @Test public void test4() { User user = new User(); user.setId(15); user.setUsername("郑大"); user.setPassword("0000"); user.setGender("男"); user.setAddress("河南郑州"); userService.save(user); } @Test public void test5() { User user = new User(); user.setId(15); user.setUsername("he"); user.setPassword("0000"); user.setGender("男"); user.setAddress("河南郑州"); userService.update(user); } }使用mybatis-plus业务层快速开发MP技术不仅提供了数据层快速开发方案,业务层MP也给了一个通用接口,实际开发慎用。①业务层接口快速开发public interface IUserService extends IService<User> { }②业务层接口实现类快速开发,关注继承的类需要传入两个泛型,一个是数据层接口,另一个是实体类@Service public class IUserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService { }③对业务层快速开发接口进行测试@SpringBootTest public class IUserServiceTest { @Autowired private IUserService userService; @Test public void test() { System.out.println(userService.getById(1)); } @Test public void test2() { IPage<User> page = new Page<User>(1, 5); userService.page(page); System.out.println(page.getRecords()); } @Test public void test3() { List<User> userList = userService.list(); for (User user : userList) { System.out.println(user); } } @Test public void test4() { User user = new User(); user.setId(15); user.setUsername("郑大"); user.setPassword("0000"); user.setGender("男"); user.setAddress("河南郑州"); userService.save(user); } @Test public void test5() { User user = new User(); user.setId(15); user.setUsername("he"); user.setPassword("0000"); user.setGender("男"); user.setAddress("河南郑州"); userService.updateById(user); } @Test public void test6() { userService.removeById(16); } }1.4 表现层开发不规范的表现层开发如下①编写表现层相关方法@RestController @RequestMapping("/users") public class UserController { @Autowired private IUserService userService; @GetMapping public List<User> getUsers() { return userService.list(); } @PostMapping public Boolean save(@RequestBody User user) { return userService.save(user); } @PutMapping public Boolean update(@RequestBody User user) { return userService.updateById(user); } @DeleteMapping("/{id}") public Boolean delete(@PathVariable Integer id) { return userService.removeById(id); } @GetMapping("/{id}") public User getUser(@PathVariable Integer id) { return userService.getById(id); } @GetMapping("/{currentPage}/{pageSize}") public IPage<User> getPages(@PathVariable int currentPage,@PathVariable int pageSize) { return userService.getPages(currentPage, pageSize); } }②使用PostMan进行测试:为什么说是不规范呢?目前我们通过Postman测试后业务层接口功能时通的,但是这样的结果给到前端开发者会出现一个小问题。不同的操作结果所展示的数据格式差异化严重。每种不同操作返回的数据格式都不一样,而且还不知道以后还会有什么格式,这样的结果让前端人员看了是很容易让人崩溃的,必须将所有操作的操作结果数据格式统一起来,需要设计表现层返回结果的模型类,用于后端与前端进行数据格式统一,也称为前后端数据协议。规范表现层的开发①创建一个标准的返回类型R,其中flag用于标识操作是否成功,data用于封装操作数据package com.liu.controller.utils; import lombok.Data; /** * @author LiuChang * @version 1.0.0 * @description TODO * @date 2022/8/26 10:03 */ @Data public class R { private boolean flag; private Object data; public R() { } public R(boolean flag) { this.flag = flag; } public R(boolean flag, Object data) { this.flag = flag; this.data = data; } }②规范表现层方法@RestController @RequestMapping("/users") public class UserController { @Autowired private IUserService userService; @GetMapping public R getUsers() { return new R(true,userService.list()); } @PostMapping public R save(@RequestBody User user) { return new R(userService.save(user)); } @PutMapping public R update(@RequestBody User user) { return new R(userService.updateById(user)); } @DeleteMapping("/{id}") public R delete(@PathVariable Integer id) { return new R(userService.removeById(id)); } @GetMapping("/{id}") public R getUser(@PathVariable Integer id) { return new R(true,userService.getById(id)); } @GetMapping("/{currentPage}/{pageSize}") public R getPages(@PathVariable int currentPage,@PathVariable int pageSize) { return new R(true,userService.getPages(currentPage, pageSize)); } }1.5 页面基础功能开发①查询所有功能页面添加条件字段对应的数据模型绑定名称<el-table size="small" current-row-key="id" :data="dataList" stripe highlight-current-row> <el-table-column type="index" align="center" label="序号"></el-table-column> <el-table-column prop="username" label="用户名称" align="center"></el-table-column> <el-table-column prop="password" label="用户密码" align="center"></el-table-column> <el-table-column prop="gender" label="用户性别" align="center"></el-table-column> <el-table-column prop="address" label="用户地址" align="center"></el-table-column> <el-table-column label="操作" align="center"> <template slot-scope="scope"> <el-button type="primary" size="mini" @click="handleUpdate(scope.row)">编辑</el-button> <el-button type="danger" size="mini" @click="handleDelete(scope.row)">删除</el-button> </template> </el-table-column> </el-table>页面封装字段data:{ dataList: [],//当前页要展示的列表数据 }编写方法获取数据//钩子函数,VUE对象初始化完成后自动执行 created() { this.getAll(); }, //列表 getAll() { axios.get("/users").then((res)=>{ console.log(res.data); this.dataList = res.data.data; }) },②添加功能页面添加条件字段对应的数据模型绑定名称<!-- 新增标签弹层 --> <div class="add-form"> <el-dialog title="新增用户" :visible.sync="dialogFormVisible"> <el-form ref="dataAddForm" :model="formData" :rules="rules" label-position="right" label-width="100px"> <el-row> <el-col :span="12"> <el-form-item label="用户名称" prop="username"> <el-input v-model="formData.username"/> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="用户密码" prop="password"> <el-input v-model="formData.password"/> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="用户性别" prop="gender"> <el-input v-model="formData.gender"/> </el-form-item> </el-col> </el-row> <el-row> <el-col :span="24"> <el-form-item label="地址"> <el-input v-model="formData.address" type="address"></el-input> </el-form-item> </el-col> </el-row> </el-form> <div slot="footer" class="dialog-footer"> <el-button @click="cancel()">取消</el-button> <el-button type="primary" @click="handleAdd()">确定</el-button> </div> </el-dialog> </div>页面封装字段data:{ dialogFormVisible: false,//添加表单是否可见 formData: {},//表单数据 }编写方法插入数据//弹出添加窗口 handleCreate() { this.dialogFormVisible = true; this.resetForm(); }, //重置表单 resetForm() { this.formData = {}; }, //添加 handleAdd () { axios.post("/users",this.formData).then((res)=> { //判断当前操作是否成功 if (res.data.flag) { this.dialogFormVisible = false; this.$message.success("添加成功"); }else { this.$message.error("添加失败"); } }).finally(()=> { this.getAll(); }) }, //取消 cancel(){ this.dialogFormVisible = false; this.$message.info("操作取消"); },③删除功能页面添加条件字段对应的数据模型绑定名称 <el-button type="danger" size="mini" @click="handleDelete(scope.row)">删除</el-button>编写方法删除数据// 删除 handleDelete(row) { console.log(row); this.$confirm("是否删除?","提示",{type:"info",}).then(()=>{ axios.delete("/users/"+row.id).then((res)=>{ if (res.data.flag) { this.$message.success("删除成功"); }else { this.$message.error("删除失败"); } }).finally(()=> { this.getAll(); }) }).catch(()=>{ this.$message.info("取消操作"); }) },④修改操作页面添加条件字段对应的数据模型绑定名称<el-button type="primary" size="mini" @click="handleUpdate(scope.row)">编辑</el-button> <div class="add-form"> <el-dialog title="编辑检查项" :visible.sync="dialogFormVisible4Edit"> <el-form ref="dataEditForm" :model="formData" :rules="rules" label-position="right" label-width="100px"> <el-row> <el-col :span="12"> <el-form-item label="用户名称" prop="username"> <el-input v-model="formData.username"/> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="用户密码" prop="password"> <el-input v-model="formData.password"/> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="用户性别" prop="gender"> <el-input v-model="formData.gender"/> </el-form-item> </el-col> </el-row> <el-row> <el-col :span="24"> <el-form-item label="地址"> <el-input v-model="formData.address" type="address"></el-input> </el-form-item> </el-col> </el-row> </el-form> <div slot="footer" class="dialog-footer"> <el-button @click="cancel()">取消</el-button> <el-button type="primary" @click="handleEdit()">确定</el-button> </div> </el-dialog> </div>页面封装字段data:{ dialogFormVisible4Edit:false,//编辑表单是否可见 }编写方法更新数据//弹出编辑窗口 handleUpdate(row) { axios.get("/users/" + row.id).then((res)=>{ if (res.data.flag && res.data.data!=null) { this.dialogFormVisible4Edit = true; this.formData = res.data.data; }else { this.$message.error("数据同步失败,自动刷新"); } }).finally(()=> { this.getAll(); }); }, //修改 handleEdit() { axios.put("/users",this.formData).then((res)=> { //判断当前操作是否成功 if (res.data.flag) { this.dialogFormVisible4Edit = false; this.$message.success("修改成功"); }else { this.$message.error("修改失败"); } }).finally(()=> { this.getAll(); }) },⑤分页查询页面添加条件字段对应的数据模型绑定名称<!--分页组件--> <div class="pagination-container"> <el-pagination class="pagiantion" @current-change="handleCurrentChange" :current-page="pagination.currentPage" :page-size="pagination.pageSize" layout="total, prev, pager, next, jumper" :total="pagination.total"> </el-pagination> </div>页面封装字段data:{ pagination: {//分页相关模型数据 currentPage: 1,//当前页码 pageSize:10,//每页显示的记录数 total:0,//总记录数 }编写方法分页查询数据//列表 getAll() { axios.get("/users/"+this.pagination.currentPage+"/"+this.pagination.pageSize).then((res)=>{ this.pagination.currentPage = res.data.data.current; this.pagination.pageSize = res.data.data.size; this.pagination.total = res.data.data.total; this.dataList = res.data.data.records; }) }, //切换页码 handleCurrentChange(currentPage) { this.pagination.currentPage = currentPage; this.getAll();⑥条件查询LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.like(Strings.isNotEmpty(user.getUsername()),User::getUsername,user.getUsername()); queryWrapper.like(Strings.isNotEmpty(user.getPassword()),User::getPassword,user.getPassword()); queryWrapper.like(Strings.isNotEmpty(user.getGender()),User::getGender,user.getGender()); queryWrapper.like(Strings.isNotEmpty(user.getAddress()),User::getAddress,user.getAddress());getAll() { //1.获取查询条件,拼接查询条件 param = "?username="+this.pagination.username; param += "&password="+this.pagination.password; param += "&gender="+this.pagination.gender; param += "&address="+this.pagination.address; axios.get("/users/"+this.pagination.currentPage+"/"+this.pagination.pageSize+param).then((res)=>{ this.pagination.currentPage = res.data.data.current; this.pagination.pageSize = res.data.data.size; this.pagination.total = res.data.data.total; this.dataList = res.data.data.records; }) },2 Spring Boot项目打包<plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <excludes> <exclude> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </exclude> </excludes> </configuration> </plugin>Spring Boot的jar包在Window环境中运行:Spring Boot的jar包在Linux环境中运行:先查看了自己的ubuntu系统版本号:进入IDEA查看Maven自己是否配置成功:查看java版本号:使用Linux环境运行SpringBoot的jar包:运行成功之后发现了相关错误:可以看到是由于用户权限不够,于是使用sudo加权加权后发现本数据库中没有相关表,于是直接运行相关sql语句:再次运行可以发现项目已经成功跑出来了:可以看到后台输出的日志:3 临时属性配置SpringBoot提供了灵活的配置方式,如果你发现你的项目中有个别属性需要重新配置,可以使用临时属性的方式快速修改某些配置。在启动的时候添加上对应参数就可以了。打开SpringBoot引导类的运行界面,在里面找到配置项。其中Program arguments对应的位置就是添加临时属性的。对于IDEA没有出现的需要在Add中寻找:可以看到端口号已经修改成了8080端口:也可以在启动类中进行相关设置:4 SpringBoot高级配置4.1 SpringBoot加载第三方Bean使用@ConfigurationProperties注解其实可以为第三方bean加载属性。@Component @Data @ConfigurationProperties(prefix = "servers") public class ServletConfig { private String ipAddress; private int port; private long timeout; }servers: ipAddress: 192.168.1.2 port: 8080 timeout: 30 datasource: driverClassName: com.mysql.jdbc.cj.Driver@SpringBootApplication public class Springboot13configurationApplication { //加载第三方Bean @Bean @ConfigurationProperties(prefix = "datasource") public DruidDataSource dataSource(){ DruidDataSource dataSource = new DruidDataSource(); return dataSource; } public static void main(String[] args) { ConfigurableApplicationContext context = SpringApplication.run(Springboot13configurationApplication.class, args); ServletConfig config = context.getBean(ServletConfig.class); System.out.println(config); DruidDataSource dataSource = context.getBean(DruidDataSource.class); System.out.println(dataSource.getDriverClassName()); } }绑定第三方数据源:@DurationUnit(ChronoUnit.HOURS) private Duration serverTimeout; @DataSizeUnit(DataUnit.KILOBYTES) private DataSize dataSize;servers: ipAddress: 192.168.1.2 port: 8080 timeout: 30 serverTimeout: 3 dataSize: 1284.2 校验配置SpringBoot给出了强大的数据校验功能,可以有效的避免此类问题的发生。在JAVAEE的JSR303规范中给出了具体的数据校验标准,开发者可以根据自己的需要选择对应的校验框架,此处使用Hibernate提供的校验框架来作为实现进行数据校验。<!--导入JSP303规范--> <dependency> <groupId>javax.validation</groupId> <artifactId>validation-api</artifactId> </dependency> <dependency> <groupId>org.hibernate.validator</groupId> <artifactId>hibernate-validator</artifactId> </dependency>@Component @Data @ConfigurationProperties(prefix = "servers") //开启对当前Bean的属性注入校验 @Validated public class ServletConfig { private String ipAddress; @Max(value = 8888,message = "最大值不能超过8888") @Min(value = 202,message = "最小值不能小于202") private int port; private long timeout; @DurationUnit(ChronoUnit.HOURS) private Duration serverTimeout; @DataSizeUnit(DataUnit.KILOBYTES) private DataSize dataSize; }4.3 测试配置加载测试专用配置@Configuration public class MsgConfig { @Bean public String msg() { return "Bean msg"; } }@SpringBootTest @Import({MsgConfig.class}) public class ConfigurationTest { @Autowired private String msg; @Test void test1(){ System.out.println(msg); } }测试类中启动web环境@RestController @RequestMapping("/books") public class BookController { @GetMapping public String getById(){ System.out.println("getById() called"); return "springboot"; } }//开启web环境 @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) //开启虚拟调用MVC @AutoConfigureMockMvc public class WebTest { @Test void testWeb(@Autowired MockMvc mvc) throws Exception { //执行get请求 MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/books"); mvc.perform(builder); } }匹配响应执行状态@Test void testStatus(@Autowired MockMvc mvc) throws Exception { MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/books1"); ResultActions actions = mvc.perform(builder); //设定预期值,与真实的值进行比较,成功测试通过,失败测试失败 StatusResultMatchers status = MockMvcResultMatchers.status(); //预计本次调用时成功的:状态码为200 ResultMatcher ok = status.isOk(); //添加预计值到本次调用过程中进行匹配 actions.andExpect(ok); }匹配响应体@Test void testBody(@Autowired MockMvc mvc) throws Exception { MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/books"); ResultActions actions = mvc.perform(builder); //设定预期值,与真实的值进行比较,成功测试通过,失败测试失败 ContentResultMatchers content = MockMvcResultMatchers.content(); //预计本次调用时成功的:body ResultMatcher result = content.string("springboot1"); //添加预计值到本次调用过程中进行匹配 actions.andExpect(result); }匹配json响应体格式:@Test void testBody(@Autowired MockMvc mvc) throws Exception { MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/books"); ResultActions actions = mvc.perform(builder); //设定预期值,与真实的值进行比较,成功测试通过,失败测试失败 ContentResultMatchers content = MockMvcResultMatchers.content(); //预计本次调用时成功的:body ResultMatcher result = content.json("{\"name\":\"瓦尔登湖\",\"id\":1,\"type\":\"自然科学\",\"description\":\"一位隐士记录生活\"}"); //添加预计值到本次调用过程中进行匹配 actions.andExpect(result); }匹配响应头@Test void testContentType(@Autowired MockMvc mvc) throws Exception { MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/books"); ResultActions actions = mvc.perform(builder); //设定预期值,与真实的值进行比较,成功测试通过,失败测试失败 HeaderResultMatchers header = MockMvcResultMatchers.header(); ResultMatcher matcher = header.string("Content-Type", "application/json"); //添加预计值到本次调用过程中进行匹配 actions.andExpect(matcher); }业务层测试事务回滚@SpringBootTest //Spring添加事务注解 @Transactional @Rollback(true) class SpringBootMybatisApplicationTests { @Autowired private UserMapper userMapper; @Test void userInserts() { User user = new User(); user.setUsername("刘畅"); user.setPassword("1314"); user.setGender("男"); user.setAddress("河南"); userMapper.insert(user); } }测试用例设置随机数据testcase: user: id: ${random.int} username: ${random.value} password: ${random.value} gender: ${random.value} address: ${random.value}@SpringBootTest //Spring添加事务注解 @Transactional @Rollback(true) class SpringBootMybatisApplicationTests { @Autowired private UserMapper userMapper; @Autowired private User user; @Test public void test(){ System.out.println(user.getId()); System.out.println(user.getUsername()); System.out.println(user.getPassword()); System.out.println(user.getGender()); System.out.println(user.getAddress()); } }4.4 数据层解决方案配置内嵌Hikari数据源spring: datasource: url: jdbc:mysql://localhost:3306/test hikari: driver-class-name: com.mysql.cj.jdbc.Driver username: root password: 131411配置内嵌 H2数据库:<dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency>spring: h2: console: path: /h2 enabled: true datasource: url: jdbc:h2:~/test hikari: driver-class-name: org.h2.Driver username: sa password: 123456 mybatis-plus: global-config: db-config: table-prefix: tb_ server: port: 80连接H2数据库:进入H2数据库界面:查询表操作:创建表操作:插入表数据操作:查询所有操作:发生报错:Database may be already in use: "C:/Users/77339/test.mv.db". Possible solutions: close all other connection(s); use the server mode [90020-214]经过查询发现:是数据库被占用,将服务器停掉之后,问题就解决掉了。NoSQL:Redis与SpringBoot框架结合:创建新模块,选择Redis缓存。spring: redis: host: localhost port: 6379 client-type: jedis@SpringBootTest class SpringBootRedisApplicationTests { @Autowired private RedisTemplate redisTemplate; @Test void set() { ValueOperations ops = redisTemplate.opsForValue(); ops.set("age",23); } @Test void get() { ValueOperations ops = redisTemplate.opsForValue(); Object age = ops.get("age"); System.out.println(age); } @Test void setHash(){ HashOperations hash = redisTemplate.opsForHash(); hash.put("info","a","aa"); } @Test void getHash(){ HashOperations hash = redisTemplate.opsForHash(); Object o = hash.get("info", "a"); System.out.println(o); } }@SpringBootTest public class StringRedis { @Autowired private StringRedisTemplate stringRedisTemplate; @Test void get() { ValueOperations<String, String> ops = stringRedisTemplate.opsForValue(); String name = ops.get("name"); System.out.println(name); } }ES分布式全文搜索引擎1.启动ES2.操作索引
2022年07月31日
24 阅读
0 评论
1 点赞
2022-07-31
Java SSM
1 Spring概述Spring是分层的 Java SE/EE应用 full-stack 轻量级开源框架,以 IoC(Inverse Of Control:反转控制)和 AOP(Aspect Oriented Programming:面向切面编程)为内核。提供了展现层 SpringMVC和持久层 Spring JDBCTemplate以及业务层事务管理等众多的企业级应用技术,还能整合开源世界众多著名的第三方框架和类库,逐渐成为使用最多的Java EE 企业应用开源框架。Spring优势:方便解耦,简化开发。AOP 编程的支持。声明式事务的支持。方便程序的测试。2 Spring入门①导入 Spring 开发的基本包坐标。<!--导入Spring基本坐标--> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.3.16</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.13.1</version> <scope>test</scope> </dependency> </dependencies>②编写 Dao 接口和实现类。package com.liu.dao; public interface UserDao { void save(); }package com.liu.dao.impl; import com.liu.dao.UserDao; public class UserDaoImpl implements UserDao { @Override public void save() { System.out.println("save() running"); } }③创建 Spring 核心配置文件:applicationContext.xml。④在 Spring 配置文件中配置 UserDaoImpl。<?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="userDao" class="com.liu.dao.impl.UserDaoImpl"></bean> </beans>⑤使用 Spring 的 API 获得 Bean 实例。@Test public void test1() { ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml"); UserDao userDao = (UserDao) app.getBean("userDao"); System.out.println(userDao); }2.1 Bean标签基本配置Bean标签用于配置对象交由Spring来创建。默认情况下它调用的是类中的无参构造函数,如果没有无参构造函数则不能创建成功。基本属性:id:Bean实例在Spring容器中的唯一标识。class:Bean的全限定名称。scope:指对象的作用范围。init-method:指定类中的初始化方法名称。destroy-method:指定类中销毁方法名称。scope属性范围:取值范围说明singleton默认值,单例的prototype多例的requestWEB 项目中,Spring 创建一个 Bean 的对象,将对象存入到 request 域中sessionWEB 项目中,Spring 创建一个 Bean 的对象,将对象存入到 session 域中global sessionWEB 项目中,应用在 Portlet 环境,如果没有 Portlet 环境那么globalSession 相当于 session当scope的取值为singleton时Bean的实例化个数:1个Bean的实例化时机:当Spring核心文件被加载时,实例化配置的Bean实例Bean的生命周期:对象创建:当应用加载,创建容器时,对象就被创建了;对象运行:只要容器在,对象一直活着;对象销毁:当应用卸载,销毁容器时,对象就被销毁了①在 Spring 配置文件中配置bean标签的scope属性为singleton。<bean id="userDao" class="com.liu.dao.impl.UserDaoImpl" scope="singleton"></bean>②使用 Spring 的 API 获得 Bean 实例。@Test public void test1() { //配置scope为:singleton ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml"); UserDao userDao = (UserDao) app.getBean("userDao"); UserDao userDao1 = (UserDao) app.getBean("userDao"); System.out.println(userDao); System.out.println(userDao1); /** * com.liu.dao.impl.UserDaoImpl@564fabc8 * com.liu.dao.impl.UserDaoImpl@564fabc8 * */ }③测试结果。两个对象的地址是一样的!当scope的取值为prototype时Bean的实例化个数:多个。Bean的实例化时机:当调用getBean()方法时实例化Bean。Bean的生命周期:对象创建:当使用对象时,创建新的对象实例;对象运行:只要对象在使用中,就一直活着;对象销毁:当对象长时间不用时,被 Java 的垃圾回收器回收了。①在 Spring 配置文件中配置bean标签的scope属性为prototype。<bean id="userDao" class="com.liu.dao.impl.UserDaoImpl" scope="prototype"></bean>②使用 Spring 的 API 获得 Bean 实例。@Test public void test2() { //配置scope为:prototype ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml"); UserDao userDao = (UserDao) app.getBean("userDao"); UserDao userDao1 = (UserDao) app.get Bean("userDao"); System.out.println(userDao); System.out.println(userDao1); /** * com.liu.dao.impl.UserDaoImpl@399f45b1 * com.liu.dao.impl.UserDaoImpl@38c6f217 */ }③测试结果。两个对象的地址不同!init-method:指定类中的初始化方法名称。destroy-method:指定类中销毁方法名称。①在 Spring 配置文件中配置bean标签的init-method、destroy-method属性为该类的初始化和销毁方法。<bean id="userDao" class="com.liu.dao.impl.UserDaoImpl" scope="singleton" init-method="init" destroy-method="destroy"></bean>②使用 Spring 的 API 获得 Bean 实例。@Test public void test3(){ //配置init,destroy方法 ClassPathXmlApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml"); UserDao userDao = (UserDao) app.getBean("userDao"); System.out.println(userDao); app.close(); }③测试结果。2.2 Bean实例化的三种方式使用无参构造方法实例化它会根据默认无参构造方法来创建类对象,如果bean中没有默认无参构造函数,将会创建失败。<bean id="userDao" class="com.liu.dao.impl.UserDaoImpl"/>工厂静态方法实例化工厂的静态方法返回Bean实例。①创建工厂类,并创建静态方法,使用静态方法创建UserDao对象。package com.liu.factory; import com.liu.dao.UserDao; import com.liu.dao.impl.UserDaoImpl; public class StaticFactory { public static UserDao getUserDao() { return new UserDaoImpl(); //使用工厂创建UserDao } }②在 Spring 配置文件中配置工厂类对象,并配置工厂方法。<bean id="userDao" class="com.liu.factory.StaticFactory" factory-method="getUserDao"/>③使用 Spring 的 API 获得 Bean 实例。@Test public void test4(){ //通过静态工厂获取对象 ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml"); UserDao userDao = (UserDao) app.getBean("userDao"); System.out.println(userDao); }④测试结果。成功创建对象。 工厂实例方法实例化工厂的非静态方法返回Bean实例。①创建工厂类,并创建实例方法,使用实例方法创建UserDao对象。package com.liu.factory; import com.liu.dao.UserDao; import com.liu.dao.impl.UserDaoImpl; public class StaticFactory { public UserDao getUserDao() { return new UserDaoImpl(); //使用工厂创建UserDao } }②在 Spring 配置文件中配置工厂类对象,然后配置UserDao对象,选择工厂类的工厂方法。<bean id="factory" class="com.liu.factory.DynamicFactory"/> <bean id="userDao" factory-bean="factory" factory-method="getUserDao"/>③使用 Spring 的 API 获得 Bean 实例。@Test public void test5(){ //通过实例工厂获取对象 ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml"); UserDao userDao = (UserDao) app.getBean("userDao"); System.out.println(userDao); }④测试结果。成功创建对象。2.3 Bean的依赖注入依赖注入(Dependency Injection):它是 Spring 框架核心 IOC 的具体实现。在编写程序时,通过控制反转,把对象的创建交给了 Spring,但是代码中不可能出现没有依赖的情况。IOC 解耦只是降低他们的依赖关系,但不会消除。例如:业务层仍会调用持久层的方法。那这种业务层和持久层的依赖关系,在使用 Spring 之后,就让 Spring 来维护了。简单的说,就是坐等框架把持久层对象传入业务层,而不用我们自己去获取。依赖注入方式一:set方法①创建UserService接口以及UserServiceImpl实现类,并生成set方法。package com.liu.service; public interface UserService { void save(); }package com.liu.service.impl; import com.liu.dao.UserDao; import com.liu.service.UserService; public class UserServiceImpl implements UserService { //set依赖注入 private UserDao userDao; public void setUserDao(UserDao userDao) { this.userDao = userDao; } @Override public void save() { userDao.save(); } }②在 Spring 配置文件中配置Spring容器调用set方法进行注入。注意:ref属性代表引用类型,对于普通基本类型采用value属性。<!--依赖注入,set方法--> <bean id="userService" class="com.liu.service.impl.UserServiceImpl"> <property name="userDao" ref="userDao"/> </bean>简写:添加p命名空间:xmlns:p="http://www.springframework.org/schema/p"<bean id="userService" class="com.liu.service.impl.UserServiceImpl" p:userDao-ref="userDao"/>③使用 Spring 的 API 获得 UserService 实例。public class UserServiceTest { @Test public void test() { ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml"); UserService userService = (UserService) app.getBean("userService"); userService.save(); } }④测试结果:成功在UserService中依赖注入了UserDao对象。依赖注入方式二:构造方法①创建UserService接口以及UserServiceImpl实现类,并创建有参构造方法。package com.liu.service.impl; import com.liu.dao.UserDao; import com.liu.service.UserService; public class UserServiceImpl implements UserService { //构造方法 依赖注入 private UserDao userDao; public UserServiceImpl(UserDao userDao) { this.userDao = userDao; } @Override public void save() { userDao.save(); } }②在 Spring 配置文件中配置Spring容器调用有参构造方法进行注入。<!--依赖注入:构造方法--> <bean id="userService" class="com.liu.service.impl.UserServiceImpl"> <constructor-arg name="userDao" ref="userDao"/> </bean>③使用 Spring 的 API 获得 UserService 实例。@Test public void test() { ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml"); UserService userService = (UserService) app.getBean("userService"); userService.save(); }④测试结果:成功在UserService中依赖注入了UserDao对象。基本数据类型的依赖注入通过set方法注入:①创建基本数据类型,并创建set方法。public class UserDaoImpl implements UserDao { private String username; private int age; public void setUsername(String username) { this.username = username; } public void setAge(int age) { this.age = age; } public UserDaoImpl() { System.out.println("new UserDaoImpl~~~"); } @Override public void save() { System.out.println("Username: " + username+",age: " +age); System.out.println("UserDao save() running"); } }②在 Spring 配置文件中配置Spring容器调用set方法进行注入。<bean id="userDao" class="com.liu.dao.impl.UserDaoImpl"> <property name="username" value="liuChang"/> <property name="age" value="23"/> </bean>③使用 Spring 的 API 获得 UserService 实例。@Test public void test() { ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml"); UserService userService = (UserService) app.getBean("userService"); userService.save(); }④测试结果:成功在UserService中依赖注入了基本的数据类型对象。集合类型的依赖注入通过set方法注入:①创建集合数据类型,并创建set方法。private List<String> stringList; private Map<String, User> userMap; private Properties properties; public void setStringList(List<String> stringList) { this.stringList = stringList; } public void setUserMap(Map<String, User> userMap) { this.userMap = userMap; } public void setProperties(Properties properties) { this.properties = properties; } @Override public void save() { System.out.println(stringList); System.out.println(userMap); System.out.println(properties); System.out.println("UserDao save() running"); }package com.liu.pojo; public class User { private Integer id; private String username; private String password; @Override public String toString() { return "User{" + "id=" + id + ", username='" + username + '\'' + ", password='" + password + '\'' + '}'; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } }②在 Spring 配置文件中配置Spring容器调用set方法进行注入。<bean id="user1" class="com.liu.pojo.User"> <property name="id" value="1"/> <property name="username" value="LiuChang"/> <property name="password" value="131411"/> </bean> <bean id="user2" class="com.liu.pojo.User"> <property name="id" value="2"/> <property name="username" value="See"/> <property name="password" value="111111"/> </bean> <bean id="userDao" class="com.liu.dao.impl.UserDaoImpl"> <property name="username" value="liuChang"/> <property name="age" value="23"/> <property name="stringList"> <list> <value>aaa</value> <value>ccc</value> <value>bbb</value> </list> </property> <property name="userMap"> <map> <entry key="user1" value-ref="user1"/> <entry key="user2" value-ref="user2"/> </map> </property> <property name="properties"> <props> <prop key="p1">JDBC</prop> <prop key="p2">com.mysql.cj.jdbc.Driver</prop> </props> </property> </bean>③使用 Spring 的 API 获得 UserService 实例。@Test public void test() { ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml"); UserService userService = (UserService) app.getBean("userService"); userService.save(); }④测试结果:成功在UserService中依赖注入了集合数据对象。3 Spring的APIApplicationContext的实现类1)ClassPathXmlApplicationContext 它是从类的根路径下加载配置文件 推荐使用这种2)FileSystemXmlApplicationContext 它是从磁盘路径上加载配置文件,配置文件可以在磁盘的任意位置。3)AnnotationConfigApplicationContext当使用注解配置容器对象时,需要使用此类来创建 spring 容器。它用来读取注解。4 Spring配置数据源4.1 手动创建数据源①导入数据源的坐标和数据库驱动坐标<dependencies> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.29</version> </dependency> <dependency> <groupId>c3p0</groupId> <artifactId>c3p0</artifactId> <version>0.9.1.2</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.24</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.13.1</version> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.3.16</version> </dependency> </dependencies>②创建数据源对象③设置数据源的基本连接数据④使用数据源获取连接资源和归还连接资源package com.liu; import com.alibaba.druid.pool.DruidDataSource; import com.alibaba.druid.pool.DruidPooledConnection; import com.mchange.v2.c3p0.ComboPooledDataSource; import org.junit.Test; import java.beans.PropertyVetoException; import java.sql.Connection; public class DataSourceTest { @Test //测试手动创建c3p0数据源 public void test() throws Exception { ComboPooledDataSource dataSource = new ComboPooledDataSource(); dataSource.setDriverClass("com.mysql.cj.jdbc.Driver"); dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test"); dataSource.setUser("root"); dataSource.setPassword("131411"); Connection connection = dataSource.getConnection(); System.out.println(connection); connection.close(); } //手动创建druid数据源 @Test public void testDruid() throws Exception { DruidDataSource dataSource = new DruidDataSource(); dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver"); dataSource.setUrl("jdbc:mysql://localhost:3306/test"); dataSource.setUsername("root"); dataSource.setPassword("131411"); DruidPooledConnection connection = dataSource.getConnection(); System.out.println(connection); connection.close(); } }⑤测试数据源是否手动配置成功。4.2 抽取jdbc配置文件①properties配置文件的创建。jdbc.driver=com.mysql.cj.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/test jdbc.username=root jdbc.password=131411②读取配置文件,并创建数据源对象,获取连接。@Test public void test3() throws Exception { //读取配置文件 ResourceBundle rb = ResourceBundle.getBundle("jdbc"); String driver = rb.getString("jdbc.driver"); String url = rb.getString("jdbc.url"); String username = rb.getString("jdbc.username"); String password = rb.getString("jdbc.password"); ComboPooledDataSource dataSource = new ComboPooledDataSource(); dataSource.setDriverClass(driver); dataSource.setJdbcUrl(url); dataSource.setUser(username); dataSource.setPassword(password); Connection connection = dataSource.getConnection(); System.out.println(connection); connection.close(); }③测试是否成功。4.3 Spring配置数据源①在Spring核心配置文件:Sping创建c3p0数据源。<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="com.mysql.cj.jdbc.Driver"/> <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test"/> <property name="user" value="root" /> <property name="password" value="131411"/> </bean>②使用 Spring 的 API 获得 DataSource 实例。//测试使用Spring容器产生数据源 @Test public void test4() throws Exception { ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml"); DataSource dataSource = (DataSource) app.getBean(DataSource.class); Connection connection = dataSource.getConnection(); System.out.println(connection); connection.close(); }③测试是否获取连接。优化:Spring抽取properties文件,创建数据源①在Spring核心配置文件:抽取properties文件,并在Sping中创建druid数据源。<!--添加context--> <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"> <!--加载properties文件--> <context:property-placeholder location="classpath:jdbc.properties"/> <bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="${jdbc.driver}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </bean> </beans>②使用 Spring 的 API 获得 DataSource 实例。//测试使用Spring加载properties产生数据源 @Test public void test5() throws Exception { ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml"); DataSource dataSource = (DataSource) app.getBean("druidDataSource"); Connection connection = dataSource.getConnection(); System.out.println(connection); connection.close(); }③测试是否获取连接。5 Spring注解开发Spring是轻代码而重配置的框架,配置比较繁重,影响开发效率,所以注解开发是一种趋势,注解代替xml配置文件可以简化配置,提高开发效率。5.1 Spring原始注解开发Spring原始注解主要是替代Bean的配置。注解说明@Component使用在类上用于实例化Bean@Controller使用在web层类上用于实例化Bean@Service使用在service层类上用于实例化Bean@Repository使用在dao层类上用于实例化Bean@Autowired使用在字段上用于根据类型依赖注入@Qualifier结合@Autowired一起使用用于根据名称进行依赖注入@Resource相当于@Autowired+@Qualifier,按照名称进行注入@Value注入普通属性@Scope标注Bean的作用范围@PostConstruct使用在方法上标注该方法是Bean的初始化方法@PreDestroy使用在方法上标注该方法是Bean的销毁方法①使用注解标注UserDao对象。package com.liu.dao.impl; import com.liu.dao.UserDao; import org.springframework.stereotype.Component; import org.springframework.stereotype.Repository; //<bean id="userDao" class="com.liu.dao.impl.UserDaoImpl"/> //@Component("userDao") //使用Dao层注解 @Repository("userDao") public class UserDaoImpl implements UserDao { @Override public void save() { System.out.println("save Ruing"); } }②使用注解标注UserService类,并使用注解进行依赖注入。package com.liu.service.impl; import com.liu.dao.UserDao; import com.liu.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; import org.springframework.stereotype.Service; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import javax.annotation.Resource; //<bean id="userService" class="com.liu.service.impl.UserServiceImpl"/> //@Component("userService") //使用service层注解 @Service("userService") //bean标签的scope属性:单 双 @Scope("singleton") public class UserServiceImpl implements UserService { //<property name="userDao" ref="userDao"/> //如果使用注解,则set方法可以不写 //@Autowired //按照数据类型从Spring容器中进行匹配的 //@Qualifier("userDao") //是按照id值从容器中进行匹配的,此处需要结合@Autowired @Resource(name="userDao") //相当于@Autowired加@Qualifier private UserDao userDao; //<property name="id" value="1"/> @Value("${jdbc.driver}") private String driver; @Override public void save() { System.out.println(driver); userDao.save(); } //<bean id="userDao" class="com.liu.dao.impl.UserDaoImpl" scope="singleton" init-method="init" destroy-method="destroy"/> @PostConstruct public void init() { System.out.println("UserService.init()"); } @PreDestroy public void destroy() { System.out.println("UserService.destroy()"); } }③注意!必须要配置扫描!<!--告诉Spring在哪个包下的bean需要扫描--> <context:component-scan base-package="com.liu"/>④使用Spring API 创建UserService类。//注解开发测试 @Test public void test() { ClassPathXmlApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml"); UserService userService = (UserService)app.getBean("userService"); userService.save(); app.close(); }⑤测试结果:成功创建。5.2 Spring新注解开发使用上面的注解还不能全部替代xml配置文件,还需要使用注解替代的配置如下:非自定义的Bean的配置:加载properties文件的配置:<context:property-placeholder>组件扫描的配置:<context:component-scan>引入其他文件:注解说明@Configuration用于指定当前类是一个 Spring 配置类,当创建容器时会从该类上加载注解@ComponentScan用于指定 Spring 在初始化容器时要扫描的包。 作用和在 Spring 的 xml 配置文件中的 <context:component-scan base-package="com.itheima"/>一样@Bean使用在方法上,标注将该方法的返回值存储到 Spring 容器中@PropertySource用于加载.properties 文件中的配置@Import用于导入其他配置类①使用新注解加载properties文件package com.liu.config; import com.alibaba.druid.pool.DruidDataSource; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.PropertySource; import javax.sql.DataSource; //<!--加载properties文件--> //<context:property-placeholder location="classpath:jdbc.properties"/> @PropertySource("classpath:jdbc.properties") public class DataSourceConfiguration { @Value("${jdbc.driver}") private String driver; @Value("${jdbc.url}") private String url; @Value("${jdbc.username}") private String username; @Value("${jdbc.password}") private String password; @Bean("dataSource") public DataSource getDataSource() { DruidDataSource dataSource = new DruidDataSource(); dataSource.setDriverClassName(driver); dataSource.setUrl(url); dataSource.setUsername(username); dataSource.setPassword(password); return dataSource; } }②使用新注解配置核心配置类。package com.liu.config; import com.alibaba.druid.pool.DruidDataSource; import com.alibaba.druid.pool.DruidPooledConnection; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.*; import org.springframework.stereotype.Component; import javax.sql.DataSource; //标志该类是Spring的核心配置类 @Configuration //<context:component-scan base-package="com.liu"/> @ComponentScan("com.liu") //<import resource=""/> @Import(DataSourceConfiguration.class) public class SpringConfiguration { }③使用Spring API创建DataSouce类。//新注解开发 @Test public void test2() throws SQLException { ApplicationContext app = new AnnotationConfigApplicationContext(SpringConfiguration.class); UserService userService = (UserService)app.getBean("userService"); System.out.println(userService); DataSource dataSource = (DataSource) app.getBean("dataSource"); Connection connection = dataSource.getConnection(); System.out.println(connection); connection.close(); }④进行测试。6 Spring集成Junit①导入spring集成Junit的坐标<dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.3.16</version> </dependency>②使用@Runwith注解替换原来的运行期③使用@ContextConfiguration指定配置文件或配置类④使用@Autowired注入需要测试的对象⑤创建测试方法进行测试package com.liu; import com.liu.config.SpringConfiguration; import com.liu.service.UserService; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import javax.sql.DataSource; import java.sql.Connection; import java.sql.SQLException; @RunWith(SpringJUnit4ClassRunner.class) //配置配置文件 //@ContextConfiguration("classpath:applicationContext.xml") //配置核心类 @ContextConfiguration(classes = {SpringConfiguration.class}) public class SpringJunitTest { @Autowired private UserService userService; @Autowired private DataSource dataSource; @Test public void test() { userService.save(); } @Test public void test1() throws SQLException { Connection connection = dataSource.getConnection(); System.out.println(connection); connection.close(); } }⑥测试结果。7 Sping AOP7.1 什么是 AOPAOP 为 Aspect Oriented Programming 的缩写,意思为面向切面编程,是通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP 是 OOP 的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。7.2 AOP 的作用及其优势作用:在程序运行期间,在不修改源码的情况下对方法进行功能增强优势:减少重复代码,提高开发效率,并且便于维护7.3 AOP 的底层实现实际上,AOP 的底层是通过 Spring 提供的的动态代理技术实现的。在运行期间,Spring通过动态代理技术动态的生成代理对象,代理对象方法执行时进行增强功能的介入,在去调用目标对象的方法,从而完成功能的增强。7.4 AOP 的动态代理技术常用的动态代理技术JDK 代理 : 基于接口的动态代理技术cglib 代理:基于父类的动态代理技术7.5 AOP 相关概念Spring 的 AOP 实现底层就是对上面的动态代理的代码进行了封装,封装后我们只需要对需要关注的部分进行代码编写,并通过配置的方式完成指定目标的方法增强。理解 AOP 的相关术语,常用的术语如下:Target(目标对象):代理的目标对象。Proxy(代理):一个类被 AOP 织入增强后,就产生一个结果代理类。Joinpoint(连接点):所谓连接点是指那些被拦截到的点。在spring中,这些点指的是方法,因为spring只支持方法类型的连接点。Pointcut(切入点):所谓切入点是指我们要对哪些 Joinpoint 进行拦截的定义。Advice(通知/ 增强):所谓通知是指拦截到 Joinpoint 之后所要做的事情就是通知。Aspect(切面):是切入点和通知(引介)的结合。Weaving(织入):是指把增强应用到目标对象来创建新的代理对象的过程。spring采用动态代理织入,而AspectJ采用编译期织入和类装载期织入。7.6 AOP 开发明确的事项①需要编写的内容编写核心业务代码(目标类的目标方法)编写切面类,切面类中有通知(增强功能方法)在配置文件中,配置织入关系,即将哪些通知与哪些连接点进行结合②AOP 技术实现的内容Spring 框架监控切入点方法的执行。一旦监控到切入点方法被运行,使用代理机制,动态创建目标对象的代理对象,根据通知类别,在代理对象的对应位置,将通知对应的功能织入,完成完整的代码逻辑运行。③AOP 底层使用哪种代理方式在 spring 中,框架会根据目标类是否实现了接口来决定采用哪种动态代理的方式。7.7 基于XML的AOP开发(1) 切点表达式的写法表达式语法:execution([修饰符] 返回值类型 包名.类名.方法名(参数))访问修饰符可以省略返回值类型、包名、类名、方法名可以使用星号* 代表任意包名与类名之间一个点 . 代表当前包下的类,两个点 .. 表示当前包及其子包下的类参数列表可以使用两个点 .. 表示任意个数,任意类型的参数列表例如:execution(public void com.itheima.aop.Target.method()) execution(void com.itheima.aop.Target.*(..)) execution(* com.itheima.aop.*.*(..)) execution(* com.itheima.aop..*.*(..)) execution(* *..*.*(..))(2) 通知的类型通知的配置语法:<aop:通知类型 method=“切面类中方法名” pointcut=“切点表达式"></aop:通知类型>(3) AOP入门①导入 AOP 相关坐标<dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.3.16</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.4</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.13.1</version> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.3.16</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>5.3.16</version> </dependency>②创建目标接口和目标类(内部有切点)package com.liu.aop; public interface TargetInterface { void save(); }package com.liu.aop; public class Target implements TargetInterface{ @Override public void save() { System.out.println("save running~~~"); } }③创建切面类(内部有增强方法)package com.liu.aop; public class MyAspect { public void before(){ System.out.println("前置增强......"); } }④将目标类和切面类的对象创建权交给 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" 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 id="target" class="com.liu.aop.Target"/> <!--切面对象--> <bean id="myAspect" class="com.liu.aop.MyAspect"/> <!--配置织入:告诉Spring框架 哪些方法(切点)需要进行哪些增强--> <aop:config> <!--声明切面--> <aop:aspect ref="myAspect"> <!--切面:切点加通知--> <aop:before method="before" pointcut="execution(public void com.liu.aop.Target.save())"/> </aop:aspect> </aop:config> </beans>⑥测试代码package com.liu; import com.liu.aop.TargetInterface; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:applicationContext.xml") public class AopTest { @Autowired private TargetInterface targetInterface; @Test public void test() { targetInterface.save(); } }⑦测试结果(4) 各种通知类型①创建切面类的各种通知方法。package com.liu.aop; import org.aspectj.lang.ProceedingJoinPoint; public class MyAspect { public void before(){ System.out.println("前置增强......"); } public void after(){ System.out.println("后置增强....."); } //Proceeding Join Point:进行连接点 -->切点 public Object around(ProceedingJoinPoint pjp) throws Throwable { System.out.println("环绕前增强。。。"); //切点方法 Object proceed = pjp.proceed(); System.out.println("环绕后增强。。。。"); return proceed; } public void afterThrowing(){ System.out.println("异常抛出增强...."); } public void afterEnding(){ System.out.println("最终增强...."); } }②配置织入。<!--配置织入:告诉Spring框架 哪些方法(切点)需要进行哪些增强--> <aop:config> <!--声明切面--> <aop:aspect ref="myAspect"> <!--切面:切点加通知--> <aop:before method="before" pointcut="execution(* com.liu.aop.*.*(..))"/> <aop:after-returning method="after" pointcut="execution(* com.liu.aop.*.*(..))"/> <aop:around method="around" pointcut="execution(* com.liu.aop.*.*(..))"/> <aop:after-throwing method="afterThrowing" pointcut="execution(* com.liu.aop.*.*(..))"/> <aop:after method="afterEnding" pointcut="execution(* com.liu.aop.*.*(..))"/> </aop:aspect> </aop:config>③进行测试。(5) 抽取切点表达式当多个增强的切点表达式相同时,可以将切点表达式进行抽取,在增强中使用 pointcut-ref 属性代替 pointcut 属性来引用抽取后的切点表达式。<!--配置织入:告诉Spring框架 哪些方法(切点)需要进行哪些增强--> <aop:config> <!--声明切面--> <aop:aspect ref="myAspect"> <!--抽取切点表达式--> <aop:pointcut id="pointcut" expression="execution(* com.liu.aop.*.*(..))"/> <!--切面:切点加通知--> <aop:before method="before" pointcut-ref="pointcut"/> <aop:after-returning method="after" pointcut-ref="pointcut"/> <aop:around method="around" pointcut-ref="pointcut"/> <aop:after method="afterEnding" pointcut-ref="pointcut"/> </aop:aspect> </aop:config>7.8 基于注解的AOP开发通知的配置语法:@通知注解(“切点表达式")①创建目标接口和目标类(内部有切点)package com.liu.annoAop; import org.springframework.stereotype.Component; @Component("target") public class Target implements TargetInterface { @Override public void save() { System.out.println("save running~~~"); } }②创建切面类(内部有增强方法)③将目标类和切面类的对象创建权交给 spring④在切面类中使用注解配置织入关系package com.liu.annoAop; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; import org.springframework.stereotype.Component; @Component("myAspect") @Aspect //标注当前类是一个切面类 public class MyAspect { @Before("pointcut()")//配置前置通知 public void before(){ System.out.println("前置增强......"); } @AfterReturning("pointcut()")//配置后置通知 public void after(){ System.out.println("后置增强....."); } //Proceeding Join Point:进行连接点 -->切点 @Around("pointcut()")//配置环绕通知 public Object around(ProceedingJoinPoint pjp) throws Throwable { System.out.println("环绕前增强。。。"); //切点方法 Object proceed = pjp.proceed(); System.out.println("环绕后增强。。。。"); return proceed; } @AfterThrowing("pointcut()") //配置异常抛出通知 public void afterThrowing(){ System.out.println("异常抛出增强...."); } @After("pointcut()")//配置最终通知 public void afterEnding(){ System.out.println("最终增强...."); } //定义切点表达式 @Pointcut("execution(* com.liu.annoAop.*.*(..))") public void pointcut(){} }⑤在配置文件中开启组件扫描和 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" 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/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!--添加组件扫描--> <context:component-scan base-package="com.liu.annoAop"/> <!--配置Aop自动代理--> <aop:aspectj-autoproxy/> </beans>⑥测试package com.liu; import com.liu.annoAop.TargetInterface; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:applicationContext-anno.xml") public class AnnoAopTest { @Autowired private TargetInterface targetInterface; @Test public void test() { targetInterface.save(); } }8 Spring JdbcTemplateJdbcTemplate是spring框架中提供的一个对象,是对原始繁琐的Jdbc API对象的简单封装。spring框架为我们提供了很多的操作模板类。例如:操作关系型数据的JdbcTemplate和HibernateTemplate,操作nosql数据库的RedisTemplate,操作消息队列的JmsTemplate等等。①导入spring-jdbc和spring-tx坐标<dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.3.16</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>5.3.16</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.29</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.24</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.13.1</version> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.3.16</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.3.16</version> </dependency> </dependencies>②创建实体对象package com.liu.pojo; public class User { private int id; private String username; private String password; private String gender; private String addr; //省略了 setter 和 getter public int getId() { return id; } public void setId(int id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getGender() { return gender; } public void setGender(String gender) { this.gender = gender; } public String getAddr() { return addr; } public void setAddr(String addr) { this.addr = addr; } @Override public String toString() { return "User{" + "id=" + id + ", username='" + username + '\'' + ", password='" + password + '\'' + ", gender='" + gender + '\'' + ", addr='" + addr + '\'' + '}'; } }③使用Spring读取properties文件,并且创建JdbcTemplate对象jdbc.driver=com.mysql.cj.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/test jdbc.username=root jdbc.password=131411<!--加载properties文件--> <context:property-placeholder location="classpath:jdbc.properties"/> <bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="${jdbc.driver}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </bean> <!--JdbcTemplate--> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="druidDataSource"/> </bean>④执行数据库操作package com.liu.JdbcTemplate; import com.liu.pojo.User; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.BeanPropertyRowMapper; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import java.sql.ResultSet; import java.util.List; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:applicationContext.xml") public class SpringJdbcTemplate { @Autowired private JdbcTemplate jdbcTemplate; @Test public void test() { int update = jdbcTemplate.update("insert into tb_user(username, password,gender,addr) values(?, ?, ?, ?)", "畅", "131411", "男", "河南"); System.out.println(update); } @Test public void test1() { int update = jdbcTemplate.update("update tb_user set password = ? where username =?", "131411", "流"); System.out.println(update); } @Test public void test2() { jdbcTemplate.update("delete from tb_user where username = ?","流"); } //查询操作 @Test public void test3() { List<User> userList = jdbcTemplate.query("select * from tb_user", new BeanPropertyRowMapper<User>(User.class)); for (User user : userList) { System.out.println(user); } } @Test public void test4() { User user = jdbcTemplate.queryForObject("select * from tb_user where username = ?", new BeanPropertyRowMapper<User>(User.class), "畅"); System.out.println(user); } @Test public void test5() { Long count = jdbcTemplate.queryForObject("select count(*) from tb_user", Long.class); System.out.println(count); } }9 Spring事务控制9.1 事务控制对象PlatformTransactionManagerPlatformTransactionManager 接口是 spring 的事务管理器,它里面提供了我们常用的操作事务的方法。注意:PlatformTransactionManager 是接口类型,不同的 Dao 层技术则有不同的实现类,例如:Dao 层技术是jdbc 或 mybatis 时:org.springframework.jdbc.datasource.DataSourceTransactionManager Dao 层技术是hibernate时:org.springframework.orm.hibernate5.HibernateTransactionManagerTransactionDefinitionTransactionDefinition 是事务的定义信息对象,里面有如下方法:事务隔离级别设置隔离级别,可以解决事务并发产生的问题,如脏读、不可重复读和虚读。ISOLATION_DEFAULTISOLATION_READ_UNCOMMITTEDISOLATION_READ_COMMITTEDISOLATION_REPEATABLE_READISOLATION_SERIALIZABLE事务传播行为REQUIRED:如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。一般的选择(默认值)SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行(没有事务)MANDATORY:使用当前的事务,如果当前没有事务,就抛出异常REQUERS_NEW:新建事务,如果当前在事务中,把当前事务挂起。NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起NEVER:以非事务方式运行,如果当前存在事务,抛出异常NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行 REQUIRED 类似的操作超时时间:默认值是-1,没有超时限制。如果有,以秒为单位进行设置是否只读:建议查询时设置为只读TransactionStatusTransactionStatus 接口提供的是事务具体的运行状态,方法介绍如下。9.2 基于 XML 的声明式事务控制①引入tx命名空间②配置事务增强③配置事务 AOP 织入<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" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation=" 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/aop/spring-aop.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!--加载properties文件--> <context:property-placeholder location="classpath:jdbc.properties"/> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="${jdbc.driver}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </bean> <!--JdbcTemplate--> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSource"/> </bean> <bean id="accountDao" class="com.liu.dao.impl.AccountDaoImpl"> <property name="jdbcTemplate" ref="jdbcTemplate"/> </bean> <bean id="accountService" class="com.liu.service.impl.AccountServiceImpl"> <property name="accountDao" ref="accountDao"/> </bean> <!--平台事务管理器--> <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="transfer" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="false"/> <tx:method name="save" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="false"/> <tx:method name="findAll" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="true"/> <tx:method name="update*" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="true"/> <tx:method name="*"/> <tx:method name="*"/> </tx:attributes> </tx:advice> <!--事务的aop增强--> <aop:config> <aop:pointcut id="myPointcut" expression="execution(* com.liu.service.impl.*.*(..))"/> <aop:advisor advice-ref="txAdvice" pointcut-ref="myPointcut"/> </aop:config> <!--告诉Spring在哪个包下的bean需要扫描--> <context:component-scan base-package="com.liu"/> </beans>④测试事务控制转账业务代码package com.liu; import com.liu.service.AccountService; import com.liu.service.impl.AccountServiceImpl; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @ContextConfiguration("classpath:applicationContext.xml") @RunWith(SpringJUnit4ClassRunner.class) public class TransactionManagerTest { @Autowired private AccountService accountService; @Test public void test() { accountService.transfer(); } }⑤测试结果:9.3 基于注解声明式事务控制①使用注解标识accountDao,以及注入JdbcTemplatepackage com.liu.dao.impl; import com.liu.dao.AccountDao; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Repository; @Repository("accountDao") public class AccountDaoImpl implements AccountDao { @Autowired private JdbcTemplate jdbcTemplate; public void setJdbcTemplate(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; } @Override public void out() { jdbcTemplate.update("update account set money = ? where id = ?","500",1); } @Override public void in() { jdbcTemplate.update("update account set money = ? where id = ?","1500",2); } }②使用注解标识accountService类,以及使用 @Transactional 在需要进行事务控制的类或是方法上修饰,注解可用的属性同 xml 配置方式,例如隔离级别、传播行为等。package com.liu.service.impl; import com.liu.dao.impl.AccountDaoImpl; import com.liu.service.AccountService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Isolation; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; @Service("accountService") @Transactional public class AccountServiceImpl implements AccountService { @Autowired private AccountDaoImpl accountDao; @Transactional(isolation = Isolation.READ_COMMITTED,propagation = Propagation.REQUIRED) @Override public void transfer() { accountDao.out(); int i = 1/0; accountDao.in(); } }③Xml配置文件中要开启事务的注解驱动<tx:annotation-driven />,以及包扫描。<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" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation=" 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/aop/spring-aop.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!--加载properties文件--> <context:property-placeholder location="classpath:jdbc.properties"/> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="${jdbc.driver}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </bean> <!--JdbcTemplate--> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSource"/> </bean> <!--平台事务管理器--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> <!--告诉Spring在哪个包下的bean需要扫描--> <context:component-scan base-package="com.liu"/> <!--事务的注解驱动--> <tx:annotation-driven/> </beans>④测试。package com.liu; import com.liu.service.AccountService; import com.liu.service.impl.AccountServiceImpl; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @ContextConfiguration("classpath:applicationContext.xml") @RunWith(SpringJUnit4ClassRunner.class) public class TransactionManagerTest { @Autowired private AccountService accountService; @Test public void test() { accountService.transfer(); } }10 学习总结在这三天中,比较系统的再次复习了Spring的使用,并且使用Git提交了自己学习Spring的每一个过程代码:总共提交了19次代码,完成了Spring的基本学习。通过这次学习,对Spring的使用更加的熟悉了,但是对于注解的一些单词还是需要多练习才能记住,以及对于Spring的一些原理性东西,现在还是一知半解,以后需要学习掌握Spring框架的原理,并且多加练习使用Spring。1 Mybatis基本增删改查①添加MyBatis的坐标<dependencies> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.9</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.13.1</version> <scope>test</scope> </dependency> <!--mysql 驱动--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.29</version> </dependency> <!-- 添加slf4j日志api --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.36</version> </dependency> <!-- 添加logback-classic依赖 --> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.2.3</version> </dependency> <!-- 添加logback-core依赖 --> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-core</artifactId> <version>1.2.3</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.24</version> </dependency> </dependencies>②编写User实体类package com.liu.pojo; public class User { private int id; private String username; private String password; private String gender; private String addr; //省略了 setter 和 getter public int getId() { return id; } public void setId(int id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getGender() { return gender; } public void setGender(String gender) { this.gender = gender; } public String getAddr() { return addr; } public void setAddr(String addr) { this.addr = addr; } @Override public String toString() { return "User{" + "id=" + id + ", username='" + username + '\'' + ", password='" + password + '\'' + ", gender='" + gender + '\'' + ", addr='" + addr + '\'' + '}'; } }③创建UserMapper接口package com.liu.mapper; import com.liu.pojo.User; import java.util.List; public interface UserMapper { List<User> findAll(); void addUser(User user); int updateUser(User user); void deleteUser(int id); }④编写映射文件UserMapper.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.liu.mapper.UserMapper"> <update id="updateUser"> update tb_user set username = #{username},password = #{password},gender = #{gender},addr = #{addr} where id = #{id} </update> <delete id="deleteUser"> delete from tb_user where id = #{id} </delete> <select id="findAll" resultType="com.liu.pojo.User"> select * from tb_user </select> <insert id="addUser" parameterType="com.liu.pojo.User"> insert into tb_user(username,password,gender,addr) values(#{username},#{password},#{gender},#{addr}) </insert> </mapper>⑤编写核心文件SqlMapConfig.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> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="com.mysql.cj.jdbc.Driver"/> <property name="url" value="jdbc:mysql:///test?useSSL=false"/> <property name="username" value="root"/> <property name="password" value="131411"/> </dataSource> </environment> </environments> <mappers> <mapper resource="com/liu/mapper/UserMapper.xml"/> </mappers> </configuration>⑥编写测试类public class MybatisTest { //获取sqlSessionFactory String resource = "sqlMapConfig.xml"; InputStream inputStream; { try { inputStream = Resources.getResourceAsStream(resource); } catch (IOException e) { e.printStackTrace(); } } SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); //获取sqlSession SqlSession sqlSession = sqlSessionFactory.openSession(); //获取userMapper接口的代理对象 UserMapper userMapper = sqlSession.getMapper(UserMapper.class); @Test public void test() throws IOException { List<User> users = userMapper.findAll(); for (User user : users) { System.out.println(user); } sqlSession.close(); } @Test public void test1() throws IOException { User user = new User(); user.setUsername("帅博"); user.setPassword("123456"); user.setGender("男"); user.setAddr("河南"); userMapper.addUser(user); //提交事务 sqlSession.commit(); sqlSession.close(); } @Test public void test2(){ User user = new User(); user.setId(8); user.setUsername("帅博11"); user.setPassword("12345611"); user.setGender("男"); user.setAddr("河南"); int i = userMapper.updateUser(user); //提交事务 sqlSession.commit(); sqlSession.close(); } @Test public void test3() { userMapper.deleteUser(8); //提交事务 sqlSession.commit(); sqlSession.close(); } }2 MyBatis常用配置解析2.1 Mybatis配置文档结构需要注意配置的结构:比如,在第二次复习时,使用properties标签时,由于没有写在configuration标签内部发生报错。正确写法:以及对于设置别名,也需要按照上述结构,比如,将类型别名写在映射器之下,程序将会发生ClassNotFound错误。正确写法:2.2 Mybatis常用标签①environments标签数据库环境的配置,支持多环境配置。详细可以进官网查看。②mapper标签该标签的作用是加载映射的,加载方式有如下几种:使用相对于类路径的资源引用,例如:<mapper resource="org/mybatis/builder/AuthorMapper.xml"/>使用完全限定资源定位符(URL),例如:<mapper url="file:///var/mappers/AuthorMapper.xml"/>使用映射器接口实现类的完全限定类名,例如:<mapper class="org.mybatis.builder.AuthorMapper"/>将包内的映射器接口实现全部注册为映射器,例如:<package name="org.mybatis.builder"/>③properties标签实际开发中,习惯将数据源的配置信息单独抽取成一个properties文件,该标签可以加载额外配置的properties文件。<properties resource="properties.properties"/> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="${jdbc.driver}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </dataSource> </environment> </environments>④typeAliases标签类型别名是为Java 类型设置一个短的名字。类型别名可为 Java 类型设置一个缩写名字。<typeAliases> <typeAlias alias="Author" type="domain.blog.Author"/> <typeAlias alias="Blog" type="domain.blog.Blog"/> <typeAlias alias="Comment" type="domain.blog.Comment"/> <typeAlias alias="Post" type="domain.blog.Post"/> <typeAlias alias="Section" type="domain.blog.Section"/> <typeAlias alias="Tag" type="domain.blog.Tag"/> </typeAliases>也可以指定一个包名,MyBatis 会在包名下面搜索需要的 Java Bean,在没有注解的情况下,会使用 Bean 的首字母小写的非限定类名来作为它的别名。推荐使用这种。<!--取别名--> <typeAliases> <package name="com.liu.pojo"/> </typeAliases>下面是一些为常见的 Java 类型内建的类型别名。它们都是不区分大小写的,注意,为了应对原始类型的命名重复,采取了特殊的命名风格。别名映射的类型_bytebyte_longlong_shortshort_intint_integerint_doubledouble_floatfloat_booleanbooleanstringStringbyteBytelongLongshortShortintIntegerintegerIntegerdoubleDoublefloatFloatbooleanBooleandateDatedecimalBigDecimalbigdecimalBigDecimalobjectObjectmapMaphashmapHashMaplistListarraylistArrayListcollectionCollectioniteratorIterator2.3 Mybatis常用APISqlSession工厂构建器SqlSessionFactoryBuilder常用API:SqlSessionFactory build(InputStream inputStream)通过加载mybatis的核心文件的输入流的形式构建一个SqlSessionFactory对象String resource = "org/mybatis/builder/mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); SqlSessionFactory factory = builder.build(inputStream);其中, Resources 工具类,这个类在 org.apache.ibatis.io 包中。Resources 类帮助你从类路径下、文件系统或一个 web URL 中加载资源文件。SqlSession工厂对象SqlSessionFactorySqlSessionFactory 有多个个方法创建SqlSession 实例。常用的有如下两个:SqlSession会话对象SqlSession 实例在 MyBatis 中是非常强大的一个类。在这里你会看到所有执行语句、提交或回滚事务和获取映射器实例的方法。执行语句的方法主要有:<T> T selectOne(String statement, Object parameter) <E> List<E> selectList(String statement, Object parameter) int insert(String statement, Object parameter) int update(String statement, Object parameter) int delete(String statement, Object parameter)操作事务的方法主要有:void commit() void rollback() 更为详细的Mybatis API可以查看官网:https://mybatis.net.cn/java-api.html3 Mybatis高级配置3.1 Mybatis动态SQLif 标签:if 标签:条件判断test 属性:逻辑表达式where 标签:作用:替换where关键字会动态的去掉第一个条件前的 and如果所有的参数没有值则不加where关键字choose标签:choose 标签类似于Java 中的switch语句。when 标签类似于Java 中的case语句。foreach标签:<foreach>标签用于遍历集合,它的属性:collection:代表要遍历的集合元素,注意编写时不要写#{}open:代表语句的开始部分close:代表结束部分item:代表遍历集合的每个元素,生成的变量名sperator:代表分隔符①在UserDao中写查询方法//动态SQL List<User> findBySome(User user); List<User> findByIds(int[] ids);②使用标签编写sql语句<!--Sql语句抽取--> <sql id="selectUser">select *from tb_user</sql> <!--动态SQL--> <select id="findBySome" parameterType="user" resultType="User"> <include refid="selectUser"/> <where> <if test="id!=null"> and id = #{id} </if> <if test="username!=null"> and username = #{username} </if> <if test="password!=null"> and password = #{password} </if> <if test="gender!=null"> and gender = #{gender} </if> <if test="addr!=null"> and addr = #{addr} </if> </where> </select> <select id="findByIds" resultType="user"> <include refid="selectUser"/> <where> <foreach collection="array" open="id in(" close=")" item="id" separator=","> #{id} </foreach> </where> </select>③进行测试@Test public void test4(){ User user = new User(); user.setId(9); user.setUsername("帅博"); //user.setPassword("123456"); user.setGender("男"); List<User> users = userMapper.findBySome(user); for (User user1 : users) { System.out.println(user1); } } @Test public void test5(){ int[] ids = {1,2,3,9}; List<User> userList = userMapper.findByIds(ids); for (User user : userList) { System.out.println(user); } }3.2 实现分页查询MyBatis可以使用第三方的插件来对功能进行扩展,分页助手PageHelper是将分页的复杂操作进行封装,使用简单的方式即可获得分页的相关数据。①导入通用PageHelper的坐标<dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper</artifactId> <version>5.1.3</version> </dependency> <dependency> <groupId>com.github.jsqlparser</groupId> <artifactId>jsqlparser</artifactId> <version>1.0</version> </dependency>②在mybatis核心配置文件中配置PageHelper插件<plugins> <plugin interceptor="com.github.pagehelper.PageHelper"> <property name="dialect" value="mysql"/> </plugin> </plugins>③测试分页数据获取@Test public void test6(){ //设置分页相关参数 PageHelper.startPage(1,3); List<User> userList = userMapper.findAll(); for (User user : userList) { System.out.println(user); } //获得与分页相关参数 PageInfo<User> pageInfo = new PageInfo<>(userList); System.out.println("当前页"+pageInfo.getPageNum()); System.out.println("每页显示条数"+pageInfo.getPageSize()); System.out.println("总页数"+pageInfo.getPages()); System.out.println("是否第一页"+pageInfo.isIsFirstPage()); System.out.println("是否最后一页"+pageInfo.isIsLastPage()); }④测试结果发现报错问题,经过查询发现,是pagehelper插件版本与mybatis版本相差过大。解决办法:选择低版本的pagehelper。<dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper</artifactId> <version>4.2.1</version> </dependency>3.3 Mybatis多表查询MyBatis多表配置方式:一对一配置:使用<resultMap>做配置一对多配置:使用<resultMap>+<collection>做配置多对多配置:使用<resultMap>+<collection>做配置①实体类:User、Order、Rolepublic class User { private Integer id; private String username; private String password; private Integer orderId; //当前用户存在哪些设备 private List<Order> orderList; public List<Order> getOrderList() { return orderList; } public void setOrderList(List<Order> orderList) { this.orderList = orderList; } //描述当前用户属于什么角色 private List<Role> roles; public List<Role> getRoles() { return roles; } public void setRoles(List<Role> roles) { this.roles = roles; } public User() { } public User(Integer id, String username, String password, Integer orderId) { this.id = id; this.username = username; this.password = password; this.orderId = orderId; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public Integer getOrderId() { return orderId; } public void setOrderId(Integer orderId) { this.orderId = orderId; } @Override public String toString() { return "User{" + "id=" + id + ", username='" + username + '\'' + ", password='" + password + '\'' + ", roles=" + roles + '}'; } }package com.liu.pojo; public class Order { private Integer orderId; private String ordername; private User user; public User getUser() { return user; } public void setUser(User user) { this.user = user; } public Order() { } public Order(Integer orderId, String ordername) { this.orderId = orderId; this.ordername = ordername; } public Integer getOrderId() { return orderId; } public void setOrderId(Integer orderId) { this.orderId = orderId; } public String getOrdername() { return ordername; } public void setOrdername(String ordername) { this.ordername = ordername; } @Override public String toString() { return "Order{" + "orderId=" + orderId + ", ordername='" + ordername + '\'' + ", user=" + user + '}'; } }public class Role { private Integer id; private String rolename; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getRolename() { return rolename; } public void setRolename(String rolename) { this.rolename = rolename; } @Override public String toString() { return "Role{" + "id=" + id + ", rolename='" + rolename + '\'' + '}'; } }②Mapper接口:public interface OrderMapper { List<Order> findAll(); //一对一 List<Order> findByUser(); }public interface UserMapper { List<User> findAll(); //一对多 List<User> findByOrder(); //多对多 List<User> findByRole(); }③UserMapper.xml、OrderMapper.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.liu.mapper.UserMapper"> <select id="findAll" resultType="user"> select * from user </select> <resultMap id="userMap" type="user"> <id column="id" property="id"/> <result column="username" property="username"/> <result column="password" property="password"/> <result column="oid" property="orderId"/> <!--配置集合--> <collection property="orderList" ofType="order"> <id column="oid" property="orderId"/> <result column="ordername" property="ordername"/> </collection> </resultMap> <select id="findByOrder" resultMap="userMap"> select *,o.orderId oid from `order` o,`user` u where o.orderId = u.orderId </select> <resultMap id="userRoleMap" type="user"> <id column="id" property="id"/> <result column="username" property="username"/> <result column="password" property="password"/> <result column="orderId" property="orderId"/> <collection property="roles" ofType="role"> <id column="roleId" property="id"/> <result column="rolename" property="rolename"/> </collection> </resultMap> <select id="findByRole" resultMap="userRoleMap"> select * from user u,role r ,user_role ur where u.id=ur.userId and r.id=ur.roleId </select> </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.liu.mapper.OrderMapper"> <select id="findAll" resultType="order"> select * from `order` </select> <resultMap id="orderMap" type="order"> <id column="oid" property="orderId"/> <result column="ordername" property="ordername"/> <!--<result column="username" property="user.username"/> <result column="password" property="user.password"/> <result column="uid" property="user.id"/> <result column="oid" property="user.orderId"/>--> <association property="user" javaType="user"> <id column="uid" property="id"/> <result column="username" property="username"/> <result column="password" property="password"/> <result column="oid" property="orderId"/> </association> </resultMap> <select id="findByUser" resultMap="orderMap"> select o.orderId oid,u.username,u.id uid,u.`password`,o.ordername from `order` o,user u where o.orderId = u.id </select> </mapper>④测试://一对一查询 @Test public void test1() { List<Order> orders = orderMapper.findByUser(); for (Order order : orders) { System.out.println(order); } } //一对多查询 @Test public void test2() { List<User> userList = userMapper.findByOrder(); for (User user : userList) { System.out.println(user); } } //多对多查询 @Test public void test3() { List<User> userList = userMapper.findByRole(); for (User user : userList) { System.out.println(user); } }4 Mybatis注解开发4.1 简单查询@Insert:实现新增@Update:实现更新@Delete:实现删除@Select:实现查询①设置mapper加载映射<mappers> <package name="com.liu.mapper"/> </mappers>②编写注解SQLpublic interface UserMapper { @Select("select * from tb_user") List<User> findAll(); @Insert("insert into tb_user(username, password, gender, addr) values (#{username},#{password},#{gender},#{addr})") void insert(User user); @Update("update tb_user set username = #{username} where id=#{id}") int update(@Param("username") String username, @Param("id") int id); @Delete("delete from tb_user where id=#{id}") void delete(int id); }③进行测试@Test public void test() { List<User> userList = userMapper.findAll(); for (User user : userList) { System.out.println(user); } } @Test public void Test2(){ User user = new User(); user.setUsername("刘畅"); user.setPassword("1314"); user.setGender("男"); user.setAddr("河南"); userMapper.insert(user); sqlSession.commit(); } @Test public void Test3(){ int update = userMapper.update("六珍惜", 1); sqlSession.commit(); } @Test public void Test4(){ userMapper.delete(5); sqlSession.commit(); }4.2 复杂映射开发@Result:实现结果集封装@Results:可以与@Result 一起使用,封装多个结果集@One:实现一对一结果集封装@Many:实现一对多结果集封装实现复杂关系映射之前我们可以在映射文件中通过配置来实现,使用注解开发后,我们可以使用@Results注解,@Result注解,@One注解,@Many注解组合完成复杂关系的配置①编写复杂注释public interface OrderMapper { //一对一 @Select("select o.orderId oid,u.username,u.id uid,u.`password`,o.ordername\n" + " from `order` o,user u\n" + " where o.orderId = u.id") @Results({ @Result(column="oid",property="orderId"), @Result(column="ordername",property="ordername"), @Result(column="username",property="user.username"), @Result(column="password",property="user.password"), @Result(column="uid",property="user.id"), @Result(column="oid",property="user.orderId") } ) List<Order> findByUser(); //一对一常用写法 @Select(("select *from `order`")) @Results({ @Result(column="orderId",property="orderId"), @Result(column="ordername",property="ordername"), @Result( property = "user", //要封装的属性名称 column = "orderId",//根据哪个字段取查询user表 javaType = User.class, //要封装的实体类型 one = @One(select = "com.liu.mapper.UserMapper.findById") ) }) List<Order> findByUser2(); //用来进行一对多查询 @Select("select * from `order` where orderId = #{orderId}") List<Order> findById(int orderId); }public interface UserMapper { //一对多 @Select("select * from user") @Results({ @Result(column = "id",property = "id"), @Result(column="username",property="username"), @Result(column="password",property="password"), @Result(column="orderId",property="orderId"), @Result( property = "orderList", column = "orderId", javaType = List.class, many = @Many(select = "com.liu.mapper.OrderMapper.findById") ) }) List<User> findByOrder(); //多对多 @Select("select *from user") @Results({ @Result(column = "id",property = "id"), @Result(column="username",property="username"), @Result(column="password",property="password"), @Result(column="orderId",property="orderId"), @Result( property = "roles", column = "orderId", javaType = List.class, many = @Many(select = "com.liu.mapper.RoleMapper.findByUid") ) }) List<User> findByRole(); //用来进行一对一查询 @Select("select * from user where id = #{id}") User findById(int id); }public interface RoleMapper { //用来多对多查询 @Select("select *from user_role,role where user_role.roleId = role.id and user_role.userId = #{id}") List<Role> findByUid(int id); }②进行测试@Test public void test() { List<Order> orderList = orderMapper.findByUser(); for (Order order : orderList) { System.out.println(order); } } @Test public void test1() { List<Order> orderList = orderMapper.findByUser2(); for (Order order : orderList) { System.out.println(order); } } @Test public void test2() { List<User> userList = userMapper.findByOrder(); for (User user : userList) { System.out.println(user); } } @Test public void test3(){ List<User> userList = userMapper.findByRole(); for (User user : userList) { System.out.println(user); } }5 Spring集成web环境1.1 ApplicationContext应用上下文获取方式应用上下文对象是通过new ClasspathXmlApplicationContext(spring配置文件) 方式获取的,但是每次从容器中获得Bean时都要编写new ClasspathXmlApplicationContext(spring配置文件) ,这样的弊端是配置文件加载多次,应用上下文对象创建多次。在Web项目中,可以使用ServletContextListener监听Web应用的启动,我们可以在Web应用启动时,就加载Spring的配置文件,创建应用上下文对象ApplicationContext,在将其存储到最大的域servletContext域中,这样就可以在任意位置从域中获得应用上下文ApplicationContext对象了。1.2 Spring提供获取应用上下文的工具上面的分析不用手动实现,Spring提供了一个监听器ContextLoaderListener就是对上述功能的封装,该监听器内部加载Spring配置文件,创建应用上下文对象,并存储到ServletContext域中,提供了一个客户端工具WebApplicationContextUtils供使用者获得应用上下文对象。所以我们需要做的只有两件事:① 在web.xml中配置ContextLoaderListener监听器(导入spring-web坐标)<dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>5.3.16</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.4</version> <scope>compile</scope> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> <scope>provided</scope> </dependency><!--全局初始化参数--> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:applicationContext.xml</param-value> </context-param> <!--配置监听器--> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener>② 使用WebApplicationContextUtils获得应用上下文对象ApplicationContext@WebServlet("/UserServlet") public class UserServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { ServletContext servletContext = this.getServletContext(); WebApplicationContext app = WebApplicationContextUtils.getWebApplicationContext(servletContext); UserService userService = app.getBean(UserService.class); userService.save(); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } }③运行结果6 Spring MVC2.1 SpringMVC概述SpringMVC 是一种基于 Java 的实现 MVC 设计模型的请求驱动类型的轻量级 Web 框架,属于 Spring FrameWork 的后续产品,已经融合在 Spring Web Flow 里面。Spring 框架提供了构建 Web 应用程序的全功能 MVC 模块。使用 Spring 可插入的 MVC 架构,从而在使用 Spring 进行 WEB 开发时,可以选择使用 Spring的 Spring MVC 框架或集成其他 MVC 开发框架,如 Struts1(现在一般不用),Struts2 等。SpringMVC 已经成为目前最主流的 MVC 框架之一,并且随着 Spring3.0 的发布,全面超越 Struts2,成为最优秀的 MVC 框架。它通过一套注解,让一个简单的 Java 类成为处理请求的控制器,而无须实现任何接口。同时它还支持RESTful 编程风格的请求。2.2 SpingMVC的主要改变SpringMVC的执行流程是:当客户端发起一个请求的时候,由tomcat接收到了客户端的请求封装代表请求的request对象以及代表响应的response对象。然后向web应用调用请求资源,web的组件servlet用来接收请求资源。servlet中存在着共有行为(接收数据,封装实体,指派视图)以及特有行为(表单校验、cookie)。共有行为是一样的,可以进行抽取出来。而SpringMVC就是把Servlet的共有行为和特有行为分隔开,当请求到来时,先找Servlet的共有行为后再找Servlet的特有行为。而此时,servlet的特有行为可以为一个pojo,即一个javaBean。springMVC把servlet的共有行为充当前端控制器。2.3 SpringMVC的快速入门需求:客户端发起请求,服务器端接收请求,执行逻辑并进行视图跳转。开发步骤:①导入SpringMVC相关坐标在pom.xml中配置SpringMVC的坐标<dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.3.16</version> </dependency>②配置SpringMVC核心控制器DispathcerServlet在web-INF下配置web.xml中配置SpringMVC核心控制器DispathcerServlet<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:spring-mvc.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>③创建Controller类和视图页面在controller层创建Controller类:package com.liu.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; public class UserController { public String save(){ System.out.println("UserController save running...."); return "success.jsp"; } }在webapp文件夹中创建一个success.jsp文件为视图页面<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Title</title> </head> <body> <h1>Success成功~~~</h1> </body> </html>④使用注解配置Controller类中业务方法的映射地址在Controller类上写上Spring的@Controller注解,把该类配置到Spring容器之中。在业务方法上配置@RequestMapping用来建立请求URL和处理请求方法的之间的联系package com.liu.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; @Controller public class UserController { @RequestMapping("/quick")//用来建立请求URL和处理请求方法的之间的联系 public String save(){ System.out.println("UserController save running...."); return "success.jsp"; } }⑤配置SpringMVC核心文件 spring-mvc.xml配置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" 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"> <!--组件扫描--> <context:component-scan base-package="com.liu.controller"/> </beans>⑥客户端发起请求测试配置tomcat:运行访问/quick:2.4 SpringMVC流程图示2.5 SpringMVC的组件1. 前端控制器:DispatcherServlet用户请求到达前端控制器,它就相当于 MVC 模式中的 C,DispatcherServlet 是整个流程控制的中心,由它调用其它组件处理用户的请求,DispatcherServlet 的存在降低了组件之间的耦合性。2. 处理器映射器:HandlerMappingHandlerMapping 负责根据用户请求找到 Handler 即处理器,SpringMVC 提供了不同的映射器实现不同的映射方式,例如:配置文件方式,实现接口方式,注解方式等。3. 处理器适配器:HandlerAdapter通过 HandlerAdapter 对处理器进行执行,这是适配器模式的应用,通过扩展适配器可以对更多类型的处理器进行执行。4. 处理器:Handler 它就是我们开发中要编写的具体业务控制器。由 DispatcherServlet 把用户请求转发到 Handler。由Handler 对具体的用户请求进行处理。5. 视图解析器:View ResolverView Resolver 负责将处理结果生成 View 视图,View Resolver 首先根据逻辑视图名解析成物理视图名,即具体的页面地址,再生成 View 视图对象,最后对 View 进行渲染将处理结果通过页面展示给用户。6. 视图:ViewSpringMVC 框架提供了很多的 View 视图类型的支持,包括:jstlView、freemarkerView、pdfView等。最常用的视图就是 jsp。一般情况下需要通过页面标签或页面模版技术将模型数据通过页面展示给用户,需要由程序员根据业务需求开发具体的页面2.6 SpringMVC的注解开发@RequestMapping作用:用于建立请求 URL 和处理请求方法之间的对应关系位置:类上,请求URL 的第一级访问目录。此处不写的话,就相当于应用的根目录方法上,请求 URL 的第二级访问目录,与类上的使用@ReqquestMapping标注的一级目录一起组成访问虚拟路径@RequestMapping("/user") public class UserController { @RequestMapping("/quick")//用来建立请求URL和处理请求方法的之间的联系 public String save(){}属性:value:用于指定请求的URL。它和path属性的作用是一样的method:用于指定请求的方式params:用于指定限制请求参数的条件。它支持简单的表达式。要求请求参数的key和value必须和配置的一模一样例如:params = {"accountName"},表示请求参数必须有accountNameparams = {"moeny!100"},表示请求参数中money不能是1001.mvc命名空间引入命名空间:xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" 约束地址:http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd2.组件扫描SpringMVC基于Spring容器,所以在进行SpringMVC操作时,需要将Controller存储到Spring容器中,如果使用@Controller注解标注的话,就需要使用<context:component-scan base-package=“com.itheima.controller"/>进行组件扫描。2.7 Spring MVC的XML配置解析视图解析器SpringMVC有默认组件配置,默认组件都是DispatcherServlet.properties配置文件中配置的,该配置文件地址org/springframework/web/servlet/DispatcherServlet.properties,该文件中配置了默认的视图解析器,如下:org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolverREDIRECT_URL_PREFIX = "redirect:" --重定向前缀 FORWARD_URL_PREFIX = "forward:" --转发前缀(默认值) prefix = ""; --视图名称前缀 suffix = ""; --视图名称后缀我们可以通过属性注入的方式修改视图的的前后缀<!--配置内部资源视图解析器--> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <!-- /jsp/success.jsp --> <property name="prefix" value="/jsp/"></property> <property name="suffix" value=".jsp"></property> </bean>2.8 SpringMVC的执行流程①用户发送请求至前端控制器DispatcherServlet。②DispatcherServlet收到请求调用HandlerMapping处理器映射器。③处理器映射器找到具体的处理器(可以根据xml配置、注解进行查找),生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet。④DispatcherServlet调用HandlerAdapter处理器适配器。⑤HandlerAdapter经过适配调用具体的处理器(Controller,也叫后端控制器)。⑥Controller执行完成返回ModelAndView。⑦HandlerAdapter将controller执行结果ModelAndView返回给DispatcherServlet。⑧DispatcherServlet将ModelAndView传给ViewReslover视图解析器。⑨ViewReslover解析后返回具体View。⑩DispatcherServlet根据View进行渲染视图(即将模型数据填充至视图中)。DispatcherServlet响应用户。2.9 SpringMVC的数据响应①页面跳转(1)返回字符串形式直接返回字符串:此种方式会将返回的字符串与视图解析器的前后缀拼接后跳转。@RequestMapping("/quick")//用来建立请求URL和处理请求方法的之间的联系 public String save(){ System.out.println("UserController save running...."); return "success"; }返回带有前缀的字符串:转发:forward:/WEB-INF/views/index.jsp重定向:redirect:/index.jsp(因为webinfo文件夹[即WEB-INF]是受保护的,所以要把重定向的jsp放到可以直接访问的地方)(2)返回ModelAndView对象@RequestMapping("/quick2") public ModelAndView save2(){ /* * Model:模型 作用封装数据 View:视图 作用展示数据*/ ModelAndView modelAndView = new ModelAndView(); modelAndView.addObject("user","liuChang"); //设置视图 modelAndView.setViewName("success"); return modelAndView; }通过Spring框架为你自动注入ModelAndView类://MVC框架调用时通过注入给你创造实参 @RequestMapping("/quick3") public ModelAndView save3(ModelAndView modelAndView){ modelAndView.setViewName("success"); return modelAndView; }(3)存储数据在进行转发时,往往要向request域中存储数据,在jsp页面中显示,那么Controller中怎样向request域中存储数据呢?1.通过SpringMVC框架注入的request对象setAttribute()方法设置@RequestMapping("/quick5") public String save5(HttpServletRequest request){ request.setAttribute("user","通过MVC框架注入使用request对象"); return "success"; }2.通过ModelAndView的addObject()方法设置//MVC框架调用时通过注入给你创造实参 @RequestMapping("/quick3") public ModelAndView save3(ModelAndView modelAndView){ modelAndView.addObject("user","你好"); modelAndView.setViewName("success"); return modelAndView; }3.使用Model类对象来存储数据@RequestMapping("/quick4") public String save4(Model model){ model.addAttribute("user","Model添加数据"); return "success"; }②回写数据(1)直接返回字符串Web基础阶段,客户端访问服务器端,如果想直接回写字符串作为响应体返回的话,只需要使用response.getWriter().print(“hello world”) 即可,那么在Controller中想直接回写字符串该怎样呢?① 通过SpringMVC框架注入的response对象,使用response.getWriter().print(“hello world”) 回写数据,此时不需要视图跳转,业务方法返回值为void。//通过HttpServletResponse回写数据 @RequestMapping("/quick6") public void save6(HttpServletResponse response) throws IOException { response.setCharacterEncoding("UTF-8"); response.getWriter().println("通过HttpServletResponse回写数据"); }② 将需要回写的字符串直接返回,但此时需要通过@ResponseBody注解告知SpringMVC框架,方法返回的字符串不是跳转是直接在http响应体中返回。@RequestMapping("/quick7") @ResponseBody public String save7() { return "通过ResponseBody注解,告知SpringMvc框架,该方法不进行页面跳转,直接回写"; }③在异步项目中,客户端与服务器端往往要进行json格式字符串交互,此时我们可以手动拼接json字符串返回。1.导入jackson坐标。<dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-core</artifactId> <version>2.13.1</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.13.1</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-annotations</artifactId> <version>2.13.1</version> </dependency>2.通过jackson转换json格式字符串,回写字符串。@RequestMapping("/quick8") @ResponseBody public String save8() throws JsonProcessingException { User user = new User(); user.setName("John"); user.setEmail("john.doe@example.com"); user.setPassword("123456"); user.setId(1); ObjectMapper objectMapper = new ObjectMapper(); String json = objectMapper.writeValueAsString(user); return json; }(2)返回对象或集合通过SpringMVC帮助我们对对象或集合进行json字符串的转换并回写,为处理器适配器配置消息转换参数,指定使用jackson进行对象或集合的转换,因此需要在spring-mvc.xml中进行如下配置:<!--配置处理映射器--> <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"> <property name="messageConverters"> <list> <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"/> </list> </property> </bean>@RequestMapping("/quick9") @ResponseBody public User save9() throws JsonProcessingException { User user = new User(); user.setName("Mac"); user.setEmail("MAC.doe@example.com"); user.setPassword("123456"); user.setId(1); return user; }在方法上添加@ResponseBody就可以返回json格式的字符串,但是这样配置比较麻烦,配置的代码比较多,因此,我们可以使用mvc的注解驱动代替上述配置。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/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd "> <!--mvc注解驱动--> <mvc:annotation-driven/>在 SpringMVC 的各个组件中,处理器映射器、处理器适配器、视图解析器称为 SpringMVC 的三大组件。使用mvc:annotation-driven自动加载 RequestMappingHandlerMapping(处理映射器)和RequestMappingHandlerAdapter( 处 理 适 配 器 ),可用在Spring-xml.xml配置文件中使用mvc:annotation-driven替代注解处理器和适配器的配置。同时使用mvc:annotation-driven默认底层就会集成jackson进行对象或集合的json格式字符串的转换。2.10 SpringMVC 获得请求数据(1)获得基本类型参数Controller中的业务方法的参数名称要与请求参数的name一致,参数值会自动映射匹配。@RequestMapping("/quick10") @ResponseBody public void save10(String username,int id){ System.out.println(username); System.out.println(id); }(2)获得POJO类型参数Controller中的业务方法的POJO参数的属性名与请求参数的name一致,参数值会自动映射匹配。@RequestMapping("/quick11") @ResponseBody public void save11(User user){ System.out.println(user); }(3)获得数组类型参数Controller中的业务方法数组名称与请求参数的name一致,参数值会自动映射匹配。@RequestMapping("/quick12") @ResponseBody public void save12(String[] strings){ System.out.println(Arrays.asList(strings)); }(4)获得集合类型参数获得集合参数时,要将集合参数包装到一个POJO中才可以。public class Vo { private List<User> userList; public Vo() { } public Vo(List<User> userList) { this.userList = userList; } public List<User> getUserList() { return userList; } public void setUserList(List<User> userList) { this.userList = userList; } @Override public String toString() { return "Vo{" + "userList=" + userList + '}'; } }@RequestMapping("/quick13") @ResponseBody public void save13(Vo vo){ System.out.println(vo); }<form action="${pageContext.request.contextPath}/user/quick13" method="post"> <%--表明是第几个User对象的username age--%> <input type="text" name ="userList[0].name"><br/> <input type="text" name ="userList[0].id"><br/> <input type="text" name ="userList[1].name"><br/> <input type="text" name ="userList[1].id"><br/> <input type="submit" value="提交"> </form>(5)解决全局乱码在webapp下的WEB-INF的web.xml文件中配置全局的filter:<!--配置全局过滤的filter--> <!--为了解决中文乱码的问题--> <filter> <filter-name>CharacterEncodingFilter</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>CharacterEncodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>(6)参数绑定注解@requestParam当请求的参数名称与Controller的业务方法参数名称不一致时,就需要通过@RequestParam注解显示的绑定。注解@RequestParam还有如下参数可以使用:● value:与请求参数名称● required:此在指定的请求参数是否必须包括,默认是true,提交时如果没有此参数则报错● defaultValue:当没有指定请求参数时,则使用指定的默认值赋值@RequestMapping("/quick14") @ResponseBody //@RequestParam注解是进行参数绑定 public void save14(@RequestParam(value = "user") String username){ System.out.println(username); }(7)获得Restful风格的参数Restful是一种软件架构风格、设计风格,而不是标准,只是提供了一组设计原则和约束条件。主要用于客户端和服务器交互类的软件,基于这个风格设计的软件可以更简洁,更有层次,更易于实现缓存机制等。Restful风格的请求是使用“url+请求方式”表示一次请求目的的,HTTP 协议里面四个表示操作方式的动词如下:● GET:用于获取资源● POST:用于新建资源● PUT:用于更新资源● DELETE:用于删除资源例如:● /user/1 GET : 得到 id = 1 的 user● /user/1 DELETE: 删除 id = 1 的 user● /user/1 PUT: 更新 id = 1 的 user● /user POST: 新增 user上述url地址/user/1中的1就是要获得的请求参数,在SpringMVC中可以使用占位符进行参数绑定。地址/user/1可以写成/user/{id},占位符{id}对应的就是1的值。在业务方法中我们可以使用@PathVariable注解进行占位符的匹配获取工作。@RequestMapping("/quick15/{name}") @ResponseBody public void save15(@PathVariable(value = "name") String username){ System.out.println(username); }(8)自定义类型转换器SpringMVC 默认已经提供了一些常用的类型转换器,例如客户端提交的字符串转换成int型进行参数设置。但是不是所有的数据类型都提供了转换器,没有提供的就需要自定义转换器,例如:日期类型的数据就需要自定义转换器。自定义类型转换器的开发步骤:① 定义转换器类实现Converter接口public class DateConverter implements Converter<String, Date> { @Override public Date convert(String source) { //将日期字符串转日期对象 SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); Date date = null; try { date = dateFormat.parse(source); } catch (ParseException e) { e.printStackTrace(); } return date; } }② 在配置文件中声明转换器<!--mvc注解驱动--> <mvc:annotation-driven conversion-service="conversionService"/>③ 在<annotation-driven>中引用转换器<!--声明转换器--> <bean class="org.springframework.context.support.ConversionServiceFactoryBean"> <property name="converters"> <list> <bean class="com.liu.Converter.DateConverter"/> </list> </property> </bean>④获得请求数据@RequestMapping("/quick16") @ResponseBody public void save16(@RequestParam(value = "date") Date date){ System.out.println(date); }(9)获得Servlet相关APISpringMVC支持使用原始ServletAPI对象作为控制器方法的参数进行注入,常用的对象如下:HttpServletRequestHttpServletResponseHttpSession@RequestMapping("/quick17") @ResponseBody public void save17(HttpServletRequest request, HttpServletResponse response, HttpSession session) { System.out.println(request); System.out.println(response); System.out.println(session); }(10)获得请求头使用@RequestHeader可以获得请求头信息,相当于web阶段学习的request.getHeader(name)@RequestHeader注解的属性如下:value:请求头的名称required:是否必须携带此请求头@RequestMapping(value = "/quick18") @ResponseBody public void save18(@RequestHeader(value = "User-Agent",required = false) String user_agent) { System.out.println(user_agent); }使用@CookieValue可以获得指定Cookie的值@CookieValue注解的属性如下:value:指定cookie的名称required:是否必须携带此cookie@RequestMapping(value = "/quick19") @ResponseBody public void save19(@CookieValue(value = "JSESSIONID",required = false) String jsessionId) { System.out.println(jsessionId); }(11)文件上传文件上传客户端三要素表单项type=“file”表单的提交方式是post表单的enctype属性是多部分表单形式,及enctype=“multipart/form-data”单文件上传步骤:① 导入fileupload和io坐标<dependency> <groupId>commons-fileupload</groupId> <artifactId>commons-fileupload</artifactId> <version>1.3.1</version> </dependency> <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.6</version> </dependency>② 配置文件上传解析器<!--配置文件上传解析器--> <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <!--上传文件的编码类型--> <property name="defaultEncoding" value="UTF-8"/> <!--上传文件的总大小--> <property name="maxUploadSize" value="5242800"/> <!--上传单个文件的大小--> <property name="maxUploadSizePerFile" value="5242800"/> </bean>③ 编写文件上传代码@RequestMapping("/quick20") @ResponseBody public void save20(String username, MultipartFile uploadFile) throws IOException { System.out.println(username); //获得上传文件的名称 String originalFilename = uploadFile.getOriginalFilename(); uploadFile.transferTo(new File("D:\\Java\\java练习项目\\Spring" + originalFilename)); }多文件上传实现:<form action="${pageContext.request.contextPath}/user/quick23" method="post" enctype="multipart/form-data"> 名称:<input type="text" name="username"><br/> 文件1:<input type="file" name="uploadFile"><br/> 文件2:<input type="file" name="uploadFile"><br/> 文件3:<input type="file" name="uploadFile"><br/> <input type="submit" value="提交"> </form>@RequestMapping(value = "/quick21") @ResponseBody public void save21(String username, MultipartFile[] uploadFile) throws IOException { System.out.println(username); for (MultipartFile multipartFile : uploadFile) { String originalFilename = multipartFile.getOriginalFilename(); multipartFile.transferTo(new File("D:\\Java\\java练习项目\\Spring" + originalFilename)); } }2.11 学习Spring MVC遇到的问题①JSP页面取不到ModelAndView中的值当学习SpringMVC的请求和响应时遇到:SpringMVC在JSP页面取不到ModelAndView中的值的问题原因:创建maven工程时,idea自动添加的web.xml中的jsp页面头约束版本太低。解决方法:把这个约束版本换成下面那个约束版本即可。<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd" > <web-app> </web-app><?xml version="1.0" encoding="UTF-8"?> <web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"> </web-app>7 SSM整合3.1 导入相关坐标<dependencies> <!--spring相关--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.0.5.RELEASE</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.7</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.0.5.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>5.0.5.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.0.5.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.0.5.RELEASE</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.0</version> </dependency> <!--mybatis相关--> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.4.5</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>1.3.1</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.29</version> </dependency> <dependency> <groupId>c3p0</groupId> <artifactId>c3p0</artifactId> <version>0.9.1.2</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> <dependency> <groupId>jstl</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> </dependencies>3.2 实现mybatis①编写properties文件:jdbc.driver=com.mysql.cj.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/test jdbc.username=root jdbc.password=131411②编写mybatis核心配置文件:sqlMapConfig.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> <properties resource="properties.properties"/> <!--取别名--> <typeAliases> <package name="com.liu.pojo"/> </typeAliases> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="${jdbc.driver}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </dataSource> </environment> </environments> <mappers> <package name="com.liu.mapper"/> </mappers> </configuration>③编写实体类与mapper接口方法package com.liu.pojo; public class User { private int id; private String username; private String password; private String gender; private String addr; //省略了 setter 和 getter public int getId() { return id; } public void setId(int id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getGender() { return gender; } public void setGender(String gender) { this.gender = gender; } public String getAddr() { return addr; } public void setAddr(String addr) { this.addr = addr; } @Override public String toString() { return "User{" + "id=" + id + ", username='" + username + '\'' + ", password='" + password + '\'' + ", gender='" + gender + '\'' + ", addr='" + addr + '\'' + '}'; } }public interface UserMapper { @Insert("insert into tb_user(username, password, gender, addr) values (#{username},#{password},#{gender},#{addr})") void save(User user); @Select("select *from tb_user") List<User> findAll(); }直接采用注解的方式,不需要再配置mapper映射文件。④测试Dao层是否成功。public class UserTest { //获取sqlSessionFactory String resource = "sqlMapConfig.xml"; InputStream inputStream = null; { try { inputStream = Resources.getResourceAsStream(resource); } catch (IOException e) { e.printStackTrace(); } } SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); //获取sqlSession SqlSession sqlSession = sqlSessionFactory.openSession(); //获取userMapper接口的代理对象 UserMapper userMapper = sqlSession.getMapper(UserMapper.class); @Test public void test() { List<User> userList = userMapper.findAll(); for (User user : userList) { System.out.println(user); } } @Test public void Test2(){ User user = new User(); user.setUsername("辰乐"); user.setPassword("1314"); user.setGender("男"); user.setAddr("河南"); userMapper.save(user); sqlSession.commit(); } }3.3 Spring配置①核心文件的配置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" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" 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/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!--告诉Spring在哪个包下的bean需要扫描--> <context:component-scan base-package="com.liu"> <!--排除controller扫描--> <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/> </context:component-scan> </beans>Sping-MVC核心文件: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:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:mvc="http://www.springframework.org/schema/mvc" 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/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!--主要扫描controller--> <context:component-scan base-package="com.liu.controller"/> <!--配置mvc注解驱动--> <mvc:annotation-driven/> <!--内部资源视图解析器--> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <!-- /jsp/success.jsp --> <property name="prefix" value="/WEB-INF/page/"/> <property name="suffix" value=".jsp"/> </bean> <!--开发静态资源访问权限--> <mvc:default-servlet-handler/> </beans>WEB配置文件:web.xml<?xml version="1.0" encoding="UTF-8"?> <web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"> <!--Spring 监听器--> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:applicationContext.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <!--springmvc的前端控制器--> <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:spring-mvc.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> <!--配置全局过滤的filter--> <!--为了解决中文乱码的问题--> <filter> <filter-name>CharacterEncodingFilter</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>CharacterEncodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> </web-app>②Mapper层public interface UserMapper { @Insert("insert into tb_user(username, password, gender, addr) values (#{username},#{password},#{gender},#{addr})") void save(User user); @Select("select *from tb_user") List<User> findAll(); }③Service层public interface UserService { void save(User user); List<User> findAll(); }@Service("userService") public class UserServiceImpl implements UserService { @Override public void save(User user) { UserMapper userMapper = UserMapperUtils.getUserMapper(); userMapper.save(user); } @Override public List<User> findAll() { UserMapper userMapper = UserMapperUtils.getUserMapper(); return userMapper.findAll(); } }④Controller层@Controller @RequestMapping("/user") public class UserController { @Autowired private UserService userService; @RequestMapping(value = "/save",produces = "text/html;charset=UTF-8") @ResponseBody public String save(User user){ userService.save(user); return "保存成功"; } @RequestMapping("/findAll") public ModelAndView findAll(ModelAndView modelAndView){ List<User> userList = userService.findAll(); modelAndView.addObject("userList",userList); modelAndView.setViewName("userList"); return modelAndView; } }⑤jsp文件<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Title</title> </head> <body> <h1>添加账户信息表单</h1> <form name="userForm" action="${pageContext.request.contextPath}/user/save" method="post"> 用户名称: <input type="text" name="username" value="${username}"> 用户密码: <input type="text" name="password" value="${password}"> 用户性别: <input type="text" name="gender" value="${gender}"> 用户地址: <input type="text" name="addr" value="${addr}"> <input type="submit" value="Save"/> </form> </body> </html><%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <html> <head> <title>Title</title> </head> <body> <h1>展示用户列表</h1> <table border="1"> <tr> <th>用户id</th> <th>用户名称</th> <th>用户密码</th> <th>用户性别</th> <th>用户地址</th> </tr> <c:forEach items="${userList}" var="user"> <tr> <td>${user.id}</td> <td>${user.username}</td> <td>${user.password}</td> <td>${user.gender}</td> <td>${user.addr}</td> </tr> </c:forEach> </table> </body> </html>3.4 运行测试8 Mybatis二次复习遇到的问题问题一:发现问题:mybatis的映射文件中的mapper空间名不能自定义名字,自定义后爆红。第一次编辑的时候没出现任何问题,第二次复习的时候报错,疯狂爆红。查询各种原因,最后发现是在后面的学习中下载了MybatisX的插件,导致映射文件只能进行傻瓜式操作,自定义不了了。解决办法:配置全包路径。问题二:查询order表时,显示sql错误。经过查阅,了解到order是mysql的关键字,需要加:问题三:练习多表查询时,代码爆红。尝试将mybatis-x插件关闭,成功不爆红,代码运行正常,结果正确。问题四:依旧是Mybatis-X插件问题...当使用association标签添加user属性时,问题三成功解决,但是,在填写user对象的最后一个属性时又爆红了...当我用ctrl键,发现所该标签加载的property属性是加载的本包下的User类,而javaType属性加载的是其它包下的User类?当我尝试关闭Mybatis-X插件时,发现又不爆红了...运行测试:成功...为什么开启Mybatis-X插件开发时老是爆红呢?当关闭之后,一切正常,而且当代码运行成功后,再次开启mybatis-x插件又不报错了?很奇怪~~通过查询后发现,有很多人学习时也遇到了这样的问题,当启用Mybatis-X插件开发时报错。当关闭之后,开发便不再报错。是不是开发没有遵守该插件的规范呢?学习总结由于本科阶段有Java学习基础,于是该周进度快了一些。本周主要学习了Git、Spring框架、Mybatis框架,在学习期间也遇到了两个小问题,比如:使用Mybatis-X插件爆红、Spring的课程缺少了关于SpringMVC的课程。问题一,已经通过查询解决。问题二,当我在学习mybatis时,最后的课程是SSM整合,但是我突然发现关于SpringMVC的课程在百度网盘中并没有。不过已经通过哔哩哔哩查找到了相关的SpringMVC课程:https://www.bilibili.com/video/BV1WZ4y1P7Bp?p=37&vd_source=105d7c6f614459406c1e6948579b6dc1。
2022年07月31日
21 阅读
0 评论
1 点赞
2022-07-31
Git
1 Git基本配置当安装Git后首先要做的事情是设置用户名称和email地址。这是非常重要的,因为每次Git提交都会使用该用户信息。打开Git Bash设置用户信息git confifig --global user.name “itcast” git confifig --global user.email “hello@itcast.cn”查看配置信息git confifig --global user.name git confifig --global user.email2 创建本地仓库要使用Git对我们的代码进行版本控制,首先需要获得本地仓库:1)在电脑的任意位置创建一个空目录(例如test)作为我们的本地Git仓库。2)进入这个目录中,点击右键打开Git bash窗口。3)执行命令git init。4)如果创建成功后可在文件夹下看到隐藏的.git目录。git init3 基础git操作命令77339@LAPTOP-59ARH7U0 MINGW64 ~/Desktop/test/.git (GIT_DIR!) $ cd .. 77339@LAPTOP-59ARH7U0 MINGW64 ~/Desktop/test (master) $ touch file.txt 77339@LAPTOP-59ARH7U0 MINGW64 ~/Desktop/test (master) $ git status On branch master No commits yet Untracked files: (use "git add <file>..." to include in what will be committed) file.txt nothing added to commit but untracked files present (use "git add" to track) 77339@LAPTOP-59ARH7U0 MINGW64 ~/Desktop/test (master) $ git add . 77339@LAPTOP-59ARH7U0 MINGW64 ~/Desktop/test (master) $ git status On branch master No commits yet Changes to be committed: (use "git rm --cached <file>..." to unstage) new file: file.txt 77339@LAPTOP-59ARH7U0 MINGW64 ~/Desktop/test (master) $ git commot -m "add file" git: 'commot' is not a git command. See 'git --help'. The most similar command is commit 77339@LAPTOP-59ARH7U0 MINGW64 ~/Desktop/test (master) $ git commit -m "add file" [master (root-commit) 36806c9] add file 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 file.txt 77339@LAPTOP-59ARH7U0 MINGW64 ~/Desktop/test (master) $ git status On branch master nothing to commit, working tree clean 77339@LAPTOP-59ARH7U0 MINGW64 ~/Desktop/test (master) $ git log commit 36806c9633d85f2200f9277c9a6b0b196780b866 (HEAD -> master) Author: studentliuchang <773395726@qq.com> Date: Fri Aug 12 16:11:31 2022 +0800 add file 77339@LAPTOP-59ARH7U0 MINGW64 ~/Desktop/test (master) $ vi file.txt 77339@LAPTOP-59ARH7U0 MINGW64 ~/Desktop/test (master) $ cat file.txt updata count01 =1 77339@LAPTOP-59ARH7U0 MINGW64 ~/Desktop/test (master) $ git status On branch master Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git restore <file>..." to discard changes in working directory) modified: file.txt no changes added to commit (use "git add" and/or "git commit -a") 77339@LAPTOP-59ARH7U0 MINGW64 ~/Desktop/test (master) $ git add . warning: LF will be replaced by CRLF in file.txt. The file will have its original line endings in your working directory 77339@LAPTOP-59ARH7U0 MINGW64 ~/Desktop/test (master) $ git status On branch master Changes to be committed: (use "git restore --staged <file>..." to unstage) modified: file.txt 77339@LAPTOP-59ARH7U0 MINGW64 ~/Desktop/test (master) $ git commit -m "updata file01" [master e70b970] updata file01 1 file changed, 2 insertions(+) 77339@LAPTOP-59ARH7U0 MINGW64 ~/Desktop/test (master) $ git log commit e70b970b3b95354337b57fe8fb9b3eebe62ce293 (HEAD -> master) Author: studentliuchang <773395726@qq.com> Date: Fri Aug 12 16:15:01 2022 +0800 updata file01 commit 36806c9633d85f2200f9277c9a6b0b196780b866 Author: studentliuchang <773395726@qq.com> Date: Fri Aug 12 16:11:31 2022 +0800 add file 4 Git远程仓库在码云上创建仓库:验证密钥是否配置成功:ssh -T git@gitee.com # 添加远程仓库 git remote add <远端名称> <仓库路径> # 查看远程仓库 git remote # 推送到远程仓库 git push [-f] [--set-upstream] [远端名称 [本地分支名][:远端分支名] ]5 IDEA使用Git5.1 配置Git5.2 初始化本地仓库配置.gitignore文件:# IntelliJ project files .idea *.iml out gen /.apt_generated/ /.classpath /.factorypath /.project /.settings/ /.springBeans /target/ /.gitignore /user-profile-analysis.iml /.idea /README.md .DS_Store5.3 创建远程仓库5.4 更改提交5.5 克隆到本地修改后再次上传:创建分支:6 使用游戏学习Git地址:https://oschina.gitee.io/
2022年07月31日
34 阅读
0 评论
2 点赞
2022-07-25
Java Web
学习记录 11 Mysql1.1 mysql的安装与配置在本科阶段已经完成了关于mysql的安装以及环境变量的配置等等。1.2 Sql语言DDL(Data Definition Language) : 数据定义语言,用来定义数据库对象:数据库,表,列等。用来操作数据库,表等。DML(Data Manipulation Language) 数据操作语言,用来对数据库中表的数据进行增删改。用来对表中数据进行增删改。DQL(Data Query Language) 数据查询语言,用来查询数据库中表的记录(数据)。用来对数据进行查询操作。DCL(Data Control Language) 数据控制语言,用来定义数据库的访问权限和安全级别,及创建用户。对数据库进行权限控制。1.3 DDL查询所有的数据库SHOW DATABASES;创建数据库:CREATE DATABASE 数据库名称;创建数据库(判断,如果不存在则创建)CREATE DATABASE IF NOT EXISTS 数据库名称;删除数据库DROP DATABASE 数据库名称;删除数据库(判断,如果存在则删除)DROP DATABASE IF EXISTS 数据库名称;使用数据库USE 数据库名称;查看当前使用的数据库SELECT DATABASE();查询当前数据库下所有表名称SHOW TABLES;查询表结构DESC 表名称;创建表CREATE TABLE 表名 ( 字段名1 数据类型1, 字段名2 数据类型2, … 字段名n 数据类型n );案例:需求:设计一张学生表,请注重数据类型、长度的合理性 1. 编号 2. 姓名,姓名最长不超过10个汉字 3. 性别,因为取值只有两种可能,因此最多一个汉字 4. 生日,取值为年月日 5. 入学成绩,小数点后保留两位 6. 邮件地址,最大长度不超过 64 7. 家庭联系电话,不一定是手机号码,可能会出现 - 等字符 8. 学生状态(用数字表示,正常、休学、毕业...)删除表DROP TABLE 表名;删除表时判断表是否存在DROP TABLE IF EXISTS 表名;修改表名ALTER TABLE 表名 RENAME TO 新的表名; -- 将表名student修改为stu alter table student rename to stu;添加一列ALTER TABLE 表名 ADD 列名 数据类型; -- 给stu表添加一列address,该字段类型是varchar(50) alter table stu add address varchar(50);修改数据类型ALTER TABLE 表名 MODIFY 列名 新数据类型; -- 将stu表中的address字段的类型改为 char(50) alter table stu modify address char(50);修改列名和数据类型ALTER TABLE 表名 CHANGE 列名 新列名 新数据类型; -- 将stu表中的address字段名改为 addr,类型改为varchar(50) alter table stu change address addr varchar(50);删除列ALTER TABLE 表名 DROP 列名; -- 将stu表中的addr字段 删除 alter table stu drop addr;1.4 IDEA连接mysql现在版本的IDEA非常强大,可以直接用IDEA连接数据库进行增删改查操作。填写相关信息后,进行连接测试。如图即连接成功。注意:IDEA默认连接8.版本的MySQL,若是5.版本的MySQL就需要更换JDBC的jar包。1.5 DML给指定列添加数据INSERT INTO 表名(列名1,列名2,…) VALUES(值1,值2,…);给全部列添加数据INSERT INTO 表名 VALUES(值1,值2,…);批量添加数据INSERT INTO 表名(列名1,列名2,…) VALUES(值1,值2,…),(值1,值2,…),(值1,值2,…)…; INSERT INTO 表名 VALUES(值1,值2,…),(值1,值2,…),(值1,值2,…)…;修改表数据UPDATE 表名 SET 列名1=值1,列名2=值2,… [WHERE 条件] ;练习将云龙的性别改为女update stu set gender = '女' where name = '云龙';将云龙的生日改为 1999-12-12 分数改为99.99update stu set birthday = '1999-12-12',score = 99.99 where name = '云龙';注意:如果update语句没有加where条件,则会将表中所有数据全部修改!update stu set email = '773395726@qq.com';没有加where条件时会IDEA会拒绝执行:删除数据DELETE FROM 表名 [WHERE 条件] ;练习-- 删除云龙记录 delete from stu where name = '云龙'; -- 删除stu表中所有的数据 delete from stu;为了数据库的安全,IDEA拒绝执行删除stu表中的所有数据。1.6 DQL数据准备-- 删除stu表 drop table if exists stu; -- 创建stu表 CREATE TABLE stu ( id int, -- 编号 name varchar(20), -- 姓名 age int, -- 年龄 sex varchar(5), -- 性别 address varchar(100), -- 地址 math double(5,2), -- 数学成绩 english double(5,2), -- 英语成绩 hire_date date -- 入学时间 ); -- 添加数据 INSERT INTO stu(id,NAME,age,sex,address,math,english,hire_date) VALUES (1,'马运',55,'男','杭州',66,78,'1995-09-01'), (2,'马花疼',45,'女','深圳',98,87,'1998-09-01'), (3,'马斯克',55,'男','香港',56,77,'1999-09-02'), (4,'柳白',20,'女','湖南',76,65,'1997-09-05'), (5,'柳青',20,'男','湖南',86,NULL,'1998-09-01'), (6,'刘德花',57,'男','香港',99,99,'1998-09-01'), (7,'张学右',22,'女','香港',99,99,'1998-09-01'), (8,'德玛西亚',18,'男','南京',56,65,'1994-09-02');查询多个字段SELECT 字段列表 FROM 表名; SELECT * FROM 表名; -- 查询所有数据去除重复记录SELECT DISTINCT 字段列表 FROM 表名;起别名AS: AS 也可以省略练习查询name、age两列select name,age from stu;查询所有列的数据,列名的列表可以使用*替代select * from stu;查询地址信息select address from stu;去除重复记录select distinct address from stu;查询姓名、数学成绩、英语成绩。并通过as给math和english起别名(as关键字可以省略)select name,math as 数学成绩,english as 英文成绩 from stu; select name,math 数学成绩,english 英文成绩 from stu;条件查询:SELECT 字段列表 FROM 表名 WHERE 条件列表;查询年龄大于20岁的学员信息select * from stu where age > 20;查询年龄大于等于20岁的学员信息select * from stu where age >= 20;查询年龄大于等于20岁 并且 年龄 小于等于 30岁 的学员信息select * from stu where age >= 20 && age <= 30; select * from stu where age >= 20 and age <= 30;上面语句中 && 和 and 都表示并且的意思。建议使用 and 。也可以使用 between ... and 来实现上面需求select * from stu where age BETWEEN 20 and 30;查询入学日期在'1998-09-01' 到 '1999-09-01' 之间的学员信息select * from stu where hire_date BETWEEN '1998-09-01' and '1999-09-01';查询年龄等于18岁的学员信息select * from stu where age = 18;查询年龄不等于18岁的学员信息select * from stu where age != 18; select * from stu where age <> 18;查询年龄等于18岁 或者 年龄等于20岁 或者 年龄等于22岁的学员信息select * from stu where age = 18 or age = 20 or age = 22; select * from stu where age in (18,20 ,22);查询英语成绩为 null的学员信息null值的比较不能使用 = 或者 != 。需要使用 is 或者 is notselect * from stu where english is null; select * from stu where english is not null;模糊查询:使用like关键字,可以使用通配符进行占位:(1)_ : 代表单个任意字符。(2)% : 代表任意个数字符。查询姓'马'的学员信息select * from stu where name like '马%';查询第二个字是'花'的学员信息select * from stu where name like '_花%';查询名字中包含 '德' 的学员信息select * from stu where name like '%德%';排序查询:SELECT 字段列表 FROM 表名 ORDER BY 排序字段名1 [排序方式1],排序字段名2 [排序方式2] …;上述语句中的排序方式有两种,分别是:ASC : 升序排列 (默认值)DESC : 降序排列查询学生信息,按照年龄升序排列select * from stu order by age ;查询学生信息,按照数学成绩降序排列select * from stu order by math desc ;查询学生信息,按照数学成绩降序排列,如果数学成绩一样,再按照英语成绩升序排列select * from stu order by math desc , english asc ;聚合函数:函数名功能count(列名)统计数量(一般选用不为null的列)max(列名)最大值min(列名)最小值sum(列名)求和avg(列名)平均值语法SELECT 聚合函数名(列名) FROM 表;统计班级一共有多少个学生select count(id) from stu; select count(english) from stu;上面语句根据某个字段进行统计,如果该字段某一行的值为null的话,将不会被统计。所以可以在count(*) 来实现。* 表示所有字段数据,一行中也不可能所有的数据都为null,所以建议使用 count(*)select count(*) from stu;查询数学成绩的最高分select max(math) from stu;查询数学成绩的最低分select min(math) from stu;查询数学成绩的总分select sum(math) from stu;查询数学成绩的平均分select avg(math) from stu;查询英语成绩的最低分select min(english) from stu;分组查询:语法SELECT 字段列表 FROM 表名 [WHERE 分组前条件限定] GROUP BY 分组字段名 [HAVING 分组后条件过滤];查询男同学和女同学各自的数学平均分select sex, avg(math) from stu group by sex;注意:分组之后,查询的字段为聚合函数和分组字段,查询其他字段无任何意义select name, sex, avg(math) from stu group by sex; -- 这里查询name字段就没有任何意义查询男同学和女同学各自的数学平均分,以及各自人数select sex, avg(math),count(*) from stu group by sex;查询男同学和女同学各自的数学平均分,以及各自人数,要求:分数低于70分的不参与分组select sex, avg(math),count(*) from stu where math > 70 group by sex;查询男同学和女同学各自的数学平均分,以及各自人数,要求:分数低于70分的不参与分组,分组之后人数大于2个的select sex, avg(math),count(*) from stu where math > 70 group by sex having count(*) > 2;where 和 having 区别:执行时机不一样:where 是分组之前进行限定,不满足where条件,则不参与分组,而having是分组之后对结果进行过滤。可判断的条件不一样:where 不能对聚合函数进行判断,having 可以。分页查询:语法SELECT 字段列表 FROM 表名 LIMIT 起始索引 , 查询条目数;注意: 上述语句中的起始索引是从0开始。从0开始查询,查询3条数据select * from stu limit 0 , 3;每页显示3条数据,查询第1页数据select * from stu limit 1 , 3;每页显示3条数据,查询第2页数据select * from stu limit 3 , 3;每页显示3条数据,查询第3页数据select * from stu limit 6 , 3;娱乐学习:搭建Hexo+Gitee个人博客在本科阶段,通过哔哩哔哩学习了用Hexo框架、以及使用Gitee部署、Git上传,成功搭建了一个个人博客,用于记录自己的学习历程。但是由于大三下学期考研,开始停止使用个人博客。直到现在想起,准备重新启用个人博客,但是由于大四下学期进行毕业设计,由于电脑配置太低运行较卡,于是进行重装系统,从而导致了本地的个人博客文件、与Gitee密钥的连接、Git Node.js软件等等都自动删除了。于是准备重新搭建部署一下个人博客。1 博客现状之前搭建的个人博客:可以发现,有些图片不显示了。在本科阶段写的博客,里面的图片我都进行上传到了自己搭建的Gitee图床之中。但是Gitee搭建的图床仓库无法进行公开,所以图片无法进行正常显示。这里需要进行对图片的整改,把Gitee图床的图片都转移到阿里云的OSS对象存储空间里。2 重新搭建2.1 安装相关软件:通过运行版本命令可以看到node.js和Git都已经成功安装。安装hexo在cmd命令行中输入以下命令npm install hexo-cli -g2.2 初始化文件夹hexo init blog # 初始化创建,会再桌面创建blog文件夹 cd blog # 进入blog目录 npm install # 进一步安装hexo所需文件初始化之后的目录:hexo clean # 清除所有记录 /hexo c hexo generate # 生成静态网页 /hexo g hexo server # 启动服务 /hexo s在浏览器进入http://localhost:4000/2.3 生成密钥首先在Gitee创建一个仓库,这里我依旧使用我原来的仓库。生成密钥:配置ssh账户和邮箱,邮箱不必与 GitHub、Gitee 账号相同,个人邮箱即可查看账户邮箱git config --global user.name git config --global user.email配置账户邮箱git config --global user.email abc@qq.com # 设置邮箱 git config --global user.name 'abc' # 设置用户名本地生成ssh公钥命令行执行如下命令,邮箱是刚才配置的邮箱abc@qq.comssh-keygen -t rsa -C "xxxxx@xxxxx.com"按照提示完成三次回车,即可生成 ssh key默认生成目录是 C:\Users\PMB.ssh (pmb各自电脑的用户名)生成的密钥文件:gitee创建公钥首先用户登录之后,打开设置页面,安全设置里面ssh公钥打开,下面就是公钥的模板,标题随便起,知道表达意思即可,公钥就是id_rsa.pub文件内容。测试连接是否成功:ssh -T git@gitee.com2.4 部署安装自动部署发布工具:npm install hexo-deployer-git --save使用下面命令进行上传部署:hexo clean hexo g hexo d在hexo d的时候会让你填写用户名和密码,最好不要填写错误,需要更改注册表。打开gitee可以看到已经推送到了gitee的仓库中:进入仓库,选择服务中的Gitee Pages进行部署:部署成功可以看到已经开启Gitee Pages服务:登入网站,发现报错。查一下到底是为什么报错?成功找到解决办法:原来是由于我把过去的配置文件直接粘贴到了现在生成的文件,由于过去的配置文件和现在的配置文件有一些差异,于是安装一下相关插件即可。安装成功后,再进行部署:OK,部署成功了,接下来就是修改本地的一些文件,把页面的图片等等都进行更改即可。3 博客现状博客地址:https://studentliuchang.gitee.io/以后就可以把自己的学习记录上传到博客上了,不仅可以防止学习记录丢失,而且可以方面查看复习自己学过的东西。学习记录 31 JDBC概述官方(sun公司)定义的一套操作所有关系型数据库的规则,即接口。各个数据库厂商去实现这套接口,提供数据库驱动jar包。我们可以使用这套接口(JDBC)编程,真正执行的代码是驱动jar包中的实现类。2 JDBC入门实现通过Maven引入MySQL驱动jar包:<dependencies> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.29</version> </dependency> </dependencies>JDBC案例一:使用JDBC连接Mysql,把stu表中的7号性别改为男性。package com.liu.JDBC; import java.sql.Connection; import java.sql.DriverAction; import java.sql.DriverManager; import java.sql.Statement; public class JDBCDemo01 { public static void main(String[] args) throws Exception { //注册驱动:通过反射把实现类加载到内存 Class.forName("com.mysql.cj.jdbc.Driver"); //获取连接 String url = "jdbc:mysql://127.0.0.1:3306/test"; String name= "root"; String password = "131411"; Connection connection = DriverManager.getConnection(url, name, password); //定义执行SQL String sql = "update stu set sex='男' where id = 7"; //通过连接对象获取执行sql的对象 Statement Statement statement = connection.createStatement(); //通过执行sql对象执行sql语句 int i = statement.executeUpdate(sql); System.out.println(i); //关闭连接对象 statement.close(); connection.close(); } }注意:视频中的数据库是5.版本的,但由于我的数据库是8.版本的,在项目中引入的jar包也是8.版本的。而且,在通过反射获取实现类时,要多一个cj包!刚开始学习数据库时,遇到这个问题时,我一度以为我的MySQL安装有问题,最后也是花了一些时间才知道该如何解决版本不一样的问题。版本不一致问题很容易遇到,要警惕,代码无错,版本有差异就会报错。3 JDBC相关API3.1 DriverManager类变量和类型方法描述static ConnectiongetConnection(String url)尝试建立与给定数据库URL的连接。static ConnectiongetConnection(String url, String user, String password)尝试建立与给定数据库URL的连接。static voidregisterDriver(Driver driver)使用 DriverManager注册给定的驱动程序。static voidregisterDriver(Driver driver, DriverAction da)使用 DriverManager注册给定的驱动程序。registerDriver()方法registerDriver()方法是用于注册驱动的,但是入门案例却是通过反射加载Driver类进内存。在该类中的静态代码块中已经执行了 DriverManager 对象的 registerDriver() 方法进行驱动的注册了,那么我们只需要加载 Driver 类,该静态代码块就会执行。而 Class.forName("com.mysql.cj.jdbc.Driver"); 就可以加载 Driver 类。注意:MySQL 5之后的驱动包,可以省略注册驱动的步骤自动加载jar包中META-INF/services/java.sql.Driver文件中的驱动类getConnection(String url, String user, String password)方法参数说明:url : 连接路径语法:jdbc:mysql://ip地址(域名):端口号/数据库名称?参数键值对1&参数键值对2…示例:jdbc:mysql://127.0.0.1:3306/stu如果连接的是本机mysql服务器,并且mysql服务默认端口是3306,则url可以简写为:jdbc:mysql:///数据库名称?参数键值对配置 useSSL=false 参数,禁用安全连接方式,解决警告提示user :用户名poassword :密码简化后的JDBC案例:package com.liu.JDBC; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import java.sql.Statement; public class JDBCDemo02 { public static void main(String[] args) throws Exception { //获取连接 String url = "jdbc:mysql:///test"; String name= "root"; String password = "131411"; Connection connection = DriverManager.getConnection(url, name, password); //定义执行SQL String sql = "update stu set sex='女' where id = 7"; //通过连接对象获取执行sql的对象 Statement Statement statement = connection.createStatement(); //通过执行sql对象执行sql语句 int i = statement.executeUpdate(sql); System.out.println(i); //关闭连接对象 statement.close(); connection.close(); } }3.2 Connection类变量和类型方法描述StatementcreateStatement()创建一个 Statement对象,用于将SQL语句发送到数据库。PreparedStatementprepareStatement(String sql)创建一个 PreparedStatement对象,用于将参数化SQL语句发送到数据库。voidsetAutoCommit(boolean autoCommit)将此连接的自动提交模式设置为给定状态。voidcommit()使自上次提交/回滚以来所做的所有更改成为永久更改,并释放此 Connection对象当前持有的所有数据库锁。voidrollback()撤消当前事务中所做的所有更改,并释放此 Connection对象当前持有的所有数据库锁。案例:使用Connection类中的方法开启事务、回滚事务、提交事务。package com.liu.JDBC; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import java.sql.Statement; public class JDBCDemo03 { public static void main(String[] args) throws Exception { //获取连接 String url = "jdbc:mysql:///test"; String name= "root"; String password = "131411"; Connection connection = DriverManager.getConnection(url, name, password); //定义执行SQL String sql1 = "update stu set age=23 where id = 7"; String sql2 = "update stu set age=20 where id = 8"; //通过连接对象获取执行sql的对象 Statement Statement statement = connection.createStatement(); //开启事务 connection.setAutoCommit(false); try { //通过执行sql对象执行sql语句 int i = statement.executeUpdate(sql1); System.out.println(i); int i1 = statement.executeUpdate(sql2); System.out.println(i1); //提交事务 connection.commit(); } catch (Exception e) { //回滚事务 connection.rollback(); e.printStackTrace(); } //关闭连接对象 statement.close(); connection.close(); } }3.3 Statement类变量和类型方法描述intexecuteUpdate(String sql)执行给定的SQL语句,这可能是 INSERT , UPDATE ,或 DELETE语句,或者不返回任何内容,如SQL DDL语句的SQL语句。 执行完DML语句返回受影响的行数。ResultSetexecuteQuery(String sql)执行给定的SQL语句,该语句返回单个 ResultSet对象。executeUpdate(String sql)方法执行DML语句:执行DML语句时,该方法应该返回受影像的行数。我们采用单元测试的方式进行测试,首先在Maven依赖中添加juint地址。<dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.13.1</version> <scope>test</scope> </dependency>@Test public void Test1() throws Exception { //获取连接 String url = "jdbc:mysql:///test"; String name= "root"; String password = "131411"; Connection connection = DriverManager.getConnection(url, name, password); //定义执行SQL:DML语句 String sql = "update stu set sex='女' where id = 7"; String sql2 = "insert into stu values (9,'刘畅',23,'男','河南',90,70,'1999-06-14')," + "(10,'成龙',22,'男','河南',100,80,'2000-06-14')"; //通过连接对象获取执行sql的对象 Statement Statement statement = connection.createStatement(); //通过执行sql对象执行sql语句 int i = statement.executeUpdate(sql); System.out.println(i);//只更改了一条数据,返回值应该是1 if (i>0){ System.out.println("修改成功"); }else { System.out.println("修改失败"); } int i1 = statement.executeUpdate(sql2); System.out.println(i1); //修改两条数据,返回值应该是2 if (i1>0){ System.out.println("修改成功"); }else { System.out.println("修改失败"); } //关闭连接对象 statement.close(); connection.close(); } /** 运行结果: 1 修改成功 2 修改成功 */执行DDL语句时,执行成功也可能返回0:@Test public void Test2() throws Exception { //获取连接 String url = "jdbc:mysql:///test"; String name= "root"; String password = "131411"; Connection connection = DriverManager.getConnection(url, name, password); //定义执行SQL:DML语句 String sql = "create database if not exists stu1"; String sql2 = "drop database stu1"; //通过连接对象获取执行sql的对象 Statement Statement statement = connection.createStatement(); //通过执行sql对象执行sql语句 int i = statement.executeUpdate(sql); System.out.println(i);//返回值为1 int i1 = statement.executeUpdate(sql2); System.out.println(i1);//执行成功,但是返回值为0 //关闭连接对象 statement.close(); connection.close(); }executeQuery(String sql)方法:执行DQLResultSet类:变量和类型方法描述booleannext()将光标从当前位置向前移动一行。 判断当前行是否为有效行。xxxgetXxx(参数)xxx : 数据类型;如: int getInt(参数) ;String getString(参数) ;int类型的参数:列的编号,从1开始 String类型的参数: 列的名称案例:查询stu表中的所有内容,并且封装成Student对象,并存入一个list集合之中。package com.liu.pojo; import java.util.Date; public class Student { private int id; private String name; private int age; private String sex; private String address; private Double math; private Double english; private Date hire_date; public Student() { } public Student(int id, String name, int age, String sex, String address, Double math, Double english, Date hire_date) { this.id = id; this.name = name; this.age = age; this.sex = sex; this.address = address; this.math = math; this.english = english; this.hire_date = hire_date; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; } public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } public Double getMath() { return math; } public void setMath(Double math) { this.math = math; } public Double getEnglish() { return english; } public void setEnglish(Double english) { this.english = english; } public Date getHire_date() { return hire_date; } public void setHire_date(Date hire_date) { this.hire_date = hire_date; } @Override public String toString() { return "Student{" + "id=" + id + ", name='" + name + '\'' + ", age=" + age + ", sex='" + sex + '\'' + ", address='" + address + '\'' + ", math=" + math + ", english=" + english + ", hire_date=" + hire_date + '}'; } }@Test public void Test3() throws Exception{ //获取连接 String url = "jdbc:mysql:///test"; String name= "root"; String password = "131411"; Connection connection = DriverManager.getConnection(url, name, password); //定义执行SQL:DQL语句 String sql = "select * from stu"; //通过连接对象获取执行sql的对象 Statement Statement statement = connection.createStatement(); //通过执行sql对象执行sql语句 ResultSet resultSet = statement.executeQuery(sql); List<Student> list = new ArrayList<>(); while (resultSet.next()){ Student student = new Student(); student.setId(resultSet.getInt(1)); student.setName(resultSet.getString(2)); student.setAge(resultSet.getInt(3)); student.setSex(resultSet.getString("sex")); student.setAddress(resultSet.getString("address")); student.setMath(resultSet.getDouble("math")); student.setEnglish(resultSet.getDouble(7)); student.setHire_date(resultSet.getDate("hire_date")); list.add(student); } System.out.println(list); //关闭连接对象 resultSet.close(); statement.close(); connection.close(); }3.4 PreparedStatement类PreparedStatement作用:预编译SQL语句并执行:预防SQL注入问题案例一:模拟SQL注入问题 @Test public void Test4() throws Exception { //获取连接 String url = "jdbc:mysql:///test"; String name= "root"; String password = "131411"; Connection connection = DriverManager.getConnection(url, name, password); //定义执行SQL:DML语句 String name1 = "马运"; String age = "' or '1' = '1"; String sql = "select *from stu where name ='"+name1+"' and age = '"+age+"'"; //通过连接对象获取执行sql的对象 Statement Statement statement = connection.createStatement(); //通过执行sql对象执行sql语句 ResultSet resultSet = statement.executeQuery(sql); if (resultSet.next()){ System.out.println("登录成功"); }else { System.out.println("登录失败"); } //关闭连接对象 statement.close(); connection.close(); } /** 运行结果: select *from stu where name ='马运' and age = '' or '1' = '1' 登录成功 */从上面语句可以看出条件 username = '马运' and password = '' 不管是否满足,而 or 后面的 '1' = '1' 是始终满足的,最终条件是成立的,就可以正常的进行登陆了。案例二:解决SQL注入问题使用Connection类方法创建PreparedStatement变量和类型方法描述PreparedStatementprepareStatement(String sql)创建一个 PreparedStatement对象,用于将参数化SQL语句发送到数据库。PreparedStatement使用时SQL语句中的参数值,使用?占位符替代。变量和类型方法描述voidsetXxx(参数1,参数2)Xxx:数据类型 ; 如 setInt (参数1,参数2)<br/> 参数: 参数1: ?的位置编号,从1 开始 参数2: ?的值@Test public void Test5() throws Exception{ //获取连接 String url = "jdbc:mysql:///test"; String name= "root"; String password = "131411"; Connection connection = DriverManager.getConnection(url, name, password); //定义执行SQL:DML语句 String name1 = "马运"; int age = 55; String sql = "select * from stu where name = ? and age = ?"; //通过连接对象获取执行sql的对象 Statement PreparedStatement preparedStatement = connection.prepareStatement(sql); // 设置?的值 preparedStatement.setString(1,name1); preparedStatement.setInt(2,age); ResultSet resultSet = preparedStatement.executeQuery(); if (resultSet.next()){ System.out.println("登录成功"); }else { System.out.println("登录失败"); } //关闭连接对象 resultSet.close(); preparedStatement.close(); connection.close(); }4 数据库连接池4.1 数据库连接池概述数据库连接池是个容器,负责分配、管理数据库连接(Connection)它允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个;释放空闲时间超过最大空闲时间的数据库连接来避免因为没有释放数据库连接而引起的数据库连接遗漏好处资源重用提升系统响应速度避免数据库连接遗漏4.2 Driud连接池Druid连接池是阿里巴巴开源的数据库连接池项目功能强大,性能优秀,是Java语言最好的数据库连接池之一使用过程:导入jar包 druid-1.1.12.jar<dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.24</version> </dependency>定义配置文件driverClassName=com.mysql.cj.jdbc.Driver url=jdbc:mysql:///test?useSSL=false&useServerPrepStmts=true username=root password=131411 # 初始化连接数量 initialSize=5 # 最大连接数 maxActive=10 # 最大等待时间 maxWait=3000加载配置文件获取数据库连接池对象获取连接@Test public void Test1() throws Exception { //加载配置文件 Properties properties = new Properties(); properties.load(new FileInputStream("src/main/resources/properties.properties")); //获取数据库连接池对象 DataSource dataSource = DruidDataSourceFactory.createDataSource(properties); //获取连接 Connection connection = dataSource.getConnection(); System.out.println(connection); }4.3 需求案例完成商品品牌数据的增删改查操作查询:查询所有数据添加:添加品牌修改:根据id修改删除:根据id删除环境准备数据库表 tb_brand-- 删除tb_brand表 drop table if exists tb_brand; -- 创建tb_brand表 create table tb_brand ( -- id 主键 id int primary key auto_increment, -- 品牌名称 brand_name varchar(20), -- 企业名称 company_name varchar(20), -- 排序字段 ordered int, -- 描述信息 description varchar(100), -- 状态:0:禁用 1:启用 status int ); -- 添加数据 insert into tb_brand (brand_name, company_name, ordered, description, status) values ('三只松鼠', '三只松鼠股份有限公司', 5, '好吃不上火', 0), ('华为', '华为技术有限公司', 100, '华为致力于把数字世界带入每个人、每个家庭、每个组织,构建万物互联的智能世界', 1), ('小米', '小米科技有限公司', 50, 'are you ok', 1);在pojo包下实体类 Brandpublic class Brand { // id 主键 private Integer id; // 品牌名称 private String brandName; // 企业名称 private String companyName; // 排序字段 private Integer ordered; // 描述信息 private String description; // 状态:0:禁用 1:启用 private Integer status; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getBrandName() { return brandName; } public void setBrandName(String brandName) { this.brandName = brandName; } public String getCompanyName() { return companyName; } public void setCompanyName(String companyName) { this.companyName = companyName; } public Integer getOrdered() { return ordered; } public void setOrdered(Integer ordered) { this.ordered = ordered; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } public Integer getStatus() { return status; } public void setStatus(Integer status) { this.status = status; } @Override public String toString() { return "Brand{" + "id=" + id + ", brandName='" + brandName + '\'' + ", companyName='" + companyName + '\'' + ", ordered=" + ordered + ", description='" + description + '\'' + ", status=" + status + '}'; } }查询所有 @Test public void Test2() throws Exception{ //加载配置文件 Properties properties = new Properties(); properties.load(new FileInputStream("src/main/resources/properties.properties")); DataSource dataSource = DruidDataSourceFactory.createDataSource(properties); Connection connection = dataSource.getConnection(); //编写SQL String sql = "select *from tb_brand"; Statement statement = connection.createStatement(); ResultSet resultSet = statement.executeQuery(sql); ArrayList<Brand> arrayList = new ArrayList<>(); while (resultSet.next()){ Brand brand = new Brand(); brand.setId(resultSet.getInt("id")); brand.setBrandName(resultSet.getString("brand_name")); brand.setCompanyName(resultSet.getString("company_name")); brand.setOrdered(resultSet.getInt("ordered")); brand.setDescription(resultSet.getString("description")); brand.setStatus(resultSet.getInt("status")); arrayList.add(brand); } System.out.println(arrayList); resultSet.close(); statement.close(); connection.close(); }添加数据@Test public void Test3() throws Exception{ //加载配置文件 Properties properties = new Properties(); properties.load(new FileInputStream("src/main/resources/properties.properties")); DataSource dataSource = DruidDataSourceFactory.createDataSource(properties); Connection connection = dataSource.getConnection(); String sql = "insert into tb_brand (brand_name, company_name, ordered, description, status) values(?,?,?,?,?)"; PreparedStatement preparedStatement = connection.prepareStatement(sql); String brandName = "香飘飘"; String companyName = "香飘飘"; int ordered = 1; String description = "绕地球一圈"; int status = 1; preparedStatement.setString(1,brandName); preparedStatement.setString(2,companyName); preparedStatement.setInt(3,ordered); preparedStatement.setString(4,description); preparedStatement.setInt(5,status); int i = preparedStatement.executeUpdate(); if (i>0){ System.out.println("插入成功"); }else { System.out.println("插入失败"); } preparedStatement.close(); connection.close(); }修改数据@Test public void Test4() throws Exception{ //加载配置文件 Properties properties = new Properties(); properties.load(new FileInputStream("src/main/resources/properties.properties")); DataSource dataSource = DruidDataSourceFactory.createDataSource(properties); Connection connection = dataSource.getConnection(); String sql = "update tb_brand set description = ? where id = ?"; PreparedStatement preparedStatement = connection.prepareStatement(sql); String description = "绕地球三圈"; int id = 4; preparedStatement.setString(1,description); preparedStatement.setInt(2,id); int i = preparedStatement.executeUpdate(); if (i>0){ System.out.println("更新成功"); }else { System.out.println("更新失败"); } preparedStatement.close(); connection.close(); }删除数据@Test public void Test5() throws Exception{ //加载配置文件 Properties properties = new Properties(); properties.load(new FileInputStream("src/main/resources/properties.properties")); DataSource dataSource = DruidDataSourceFactory.createDataSource(properties); Connection connection = dataSource.getConnection(); String sql = "delete from tb_brand where id = ?"; PreparedStatement preparedStatement = connection.prepareStatement(sql); int id = 4; preparedStatement.setInt(1,id); int i = preparedStatement.executeUpdate(); if (i>0){ System.out.println("删除成功"); }else { System.out.println("删除失败"); } preparedStatement.close(); connection.close(); }学习记录 31 MyBatis入门需求:查询user表中所有的数据创建user表,添加数据create table tb_user( id int primary key auto_increment, username varchar(20), password varchar(20), gender char(1), addr varchar(30) ); INSERT INTO tb_user VALUES (1, 'zhangsan', '123', '男', '北京'); INSERT INTO tb_user VALUES (2, '李四', '234', '女', '天津'); INSERT INTO tb_user VALUES (3, '王五', '11', '男', '西安');创建模块,导入坐标在创建好的模块中的 pom.xml 配置文件中添加依赖的坐标<dependencies> <!--mybatis 依赖--> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.9</version> </dependency> <!--mysql 驱动--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.29</version> </dependency> <!-- 添加slf4j日志api --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.36</version> </dependency> <!-- 添加logback-classic依赖 --> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.2.3</version> </dependency> <!-- 添加logback-core依赖 --> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-core</artifactId> <version>1.2.3</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.13.1</version> <scope>test</scope> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.24</version> </dependency> </dependencies>注意:需要在项目的 resources 目录下创建logback的配置文件<?xml version="1.0" encoding="UTF-8"?> <configuration> <!-- CONSOLE :表示当前的日志信息是可以输出到控制台的。 --> <appender name="Console" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <pattern>[%level] %blue(%d{HH:mm:ss.SSS}) %cyan([%thread]) %boldGreen(%logger{15}) - %msg %n</pattern> </encoder> </appender> <logger name="com.itheima" level="DEBUG" additivity="false"> <appender-ref ref="Console"/> </logger> <!-- level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF , 默认debug <root>可以包含零个或多个<appender-ref>元素,标识这个输出位置将会被本日志级别控制。 --> <root level="DEBUG"> <appender-ref ref="Console"/> </root> </configuration>编写 MyBatis 核心配置文件 -- > 替换连接信息 解决硬编码问题在模块下的 resources 目录下创建mybatis的配置文件 mybatis-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> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="com.mysql.cj.jdbc.Driver"/> <property name="url" value="jdbc:mysql:///test?useSSL=false"/> <property name="username" value="root"/> <property name="password" value="131411"/> </dataSource> </environment> </environments> <mappers> <!--加载SQL的映射文件--> <mapper resource="UserMapper.xml"/> </mappers> </configuration>编写 SQL 映射文件 --> 统一管理sql语句,解决硬编码问题在模块的 resources 目录下创建映射配置文件 UserMapper.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="test"> <select id="selectAll" resultType="com.itheima.pojo.User"> select * from tb_user; </select> </mapper>在 com.liu.pojo 包下创建 User类public class User { private int id; private String username; private String password; private String gender; private String addr; //省略了 setter 和 getter }在 com.liu.mybatisDemo 包下编写 MybatisDemo 测试类package com.liu.mybatisDemo; import com.liu.pojo.User; import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import java.io.IOException; import java.io.InputStream; import java.util.List; public class MybatisDemo { public static void main(String[] args) throws IOException { //获取sqlSessionFactory String resource = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); //获取sqlSession SqlSession sqlSession = sqlSessionFactory.openSession(); //执行sql语句 List<User> list = sqlSession.selectList("test.selectAll"); System.out.println(list); } }运行结果[DEBUG] 18:19:38.125 [main] o.a.i.l.LogFactory - Logging initialized using 'class org.apache.ibatis.logging.slf4j.Slf4jImpl' adapter. [DEBUG] 18:19:38.145 [main] o.a.i.d.p.PooledDataSource - PooledDataSource forcefully closed/removed all connections. [DEBUG] 18:19:38.145 [main] o.a.i.d.p.PooledDataSource - PooledDataSource forcefully closed/removed all connections. [DEBUG] 18:19:38.145 [main] o.a.i.d.p.PooledDataSource - PooledDataSource forcefully closed/removed all connections. [DEBUG] 18:19:38.145 [main] o.a.i.d.p.PooledDataSource - PooledDataSource forcefully closed/removed all connections. [DEBUG] 18:19:38.255 [main] o.a.i.t.j.JdbcTransaction - Opening JDBC Connection [DEBUG] 18:19:38.545 [main] o.a.i.d.p.PooledDataSource - Created connection 1118078504. [DEBUG] 18:19:38.545 [main] o.a.i.t.j.JdbcTransaction - Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@42a48628] [DEBUG] 18:19:38.555 [main] test.selectAll - ==> Preparing: select * from tb_user; [DEBUG] 18:19:38.595 [main] test.selectAll - ==> Parameters: [DEBUG] 18:19:38.635 [main] test.selectAll - <== Total: 3 [User{id=1, username='zhangsan', password='123', gender='男', addr='北京'}, User{id=2, username='李四', password='234', gender='女', addr='天津'}, User{id=3, username='王五', password='11', gender='男', addr='西安'}]2 Mapper代理开发使用Mapper代理方式,必须满足以下要求:定义与SQL映射文件同名的Mapper接口,并且将Mapper接口和SQL映射文件放置在同一目录下。设置SQL映射文件的namespace属性为Mapper接口全限定名在 Mapper 接口中定义方法,方法名就是SQL映射文件中sql语句的id,并保持参数类型和返回值类型一致在 com.liu.Mapper 包下创建 UserMapper接口,代码如下:package com.liu.Mapper; import com.liu.pojo.User; import java.util.List; public interface UserMapper { List<User> selectAll(); }在 resources 下创建 com/liu/Mapper 目录,并在该目录下创建 UserMapper.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.liu.Mapper.UserMapper"> <select id="selectAll" resultType="com.liu.pojo.User"> select * from tb_user; </select> </mapper>在 com.liu.mybatisDemo 包下创建 MybatisDemo2 测试类,代码如下:package com.liu.mybatisDemo; import com.liu.Mapper.UserMapper; import com.liu.pojo.User; import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import java.io.IOException; import java.io.InputStream; import java.util.List; public class MybatisDemo01 { public static void main(String[] args) throws IOException { //获取sqlSessionFactory String resource = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); //获取sqlSession SqlSession sqlSession = sqlSessionFactory.openSession(); //获取userMapper接口的代理对象 UserMapper userMapper = sqlSession.getMapper(UserMapper.class); List<User> users = userMapper.selectAll(); System.out.println(users); sqlSession.close(); } }注意:如果Mapper接口名称和SQL映射文件名称相同,并在同一目录下,则可以使用包扫描的方式简化SQL映射文件的加载。也就是将核心配置文件的加载映射配置文件的配置修改为:<mappers> <!--加载SQL的映射文件--> <!--<mapper resource="com/liu/Mapper/UserMapper.xml"/>--> <!--Mapper处理方式,扫描文件--> <package name="com.liu.Mapper"/> </mappers>在映射配置文件中的 resultType 属性需要配置数据封装的类型(类的全限定名)。而每次这样写是特别麻烦的,Mybatis 提供了 类型别名(typeAliases) 可以简化这部分的书写。首先需要现在核心配置文件中配置类型别名,也就意味着给pojo包下所有的类起了别名(别名就是类名),不区分大小写。内容如下:<!--取别名--> <typeAliases> <package name="com.liu.pojo"/> </typeAliases>通过上述的配置,我们就可以简化映射配置文件中 resultType 属性值的编写<mapper namespace="com.liu.Mapper.UserMapper"> <select id="selectAll" resultType="User"> select * from tb_user; </select> </mapper>3 MyBatis增删改查3.1 查询操作编写接口方法在 BrandMapper 接口中定义查询数据的方法 :Mybatis针对多参数有多种实现:使用 @Param("参数名称") 标记每一个参数,在映射配置文件中就需要使用 #{参数名称} 进行占位将多个参数封装成一个 实体对象 ,将该实体对象作为接口的方法参数。该方式要求在映射配置文件的SQL中使用 #{内容} 时,里面的内容必须和实体类属性名保持一致。将多个参数封装到map集合中,将map集合作为接口的方法参数。该方式要求在映射配置文件的SQL中使用 #{内容} 时,里面的内容必须和map集合中键的名称一致。//查询操作 List<Brand> selectAll(); Brand selectById(int id); //散装参数 List<Brand> selectByBrand(@Param("status") int status,@Param("companyName") String companyName, @Param("brandName") String brandName); //实体参数 List<Brand> selectByStatusAndName(Brand brand); //map参数 List<Brand> selectByBrandByMap(Map map); //查询单个 List<Brand> selectSingle(Brand brand);编写SQL语句在 BrandMapper.xml 映射配置文件中编写 statement,使用 resultMap 而不是使用 resultType。mybatis提供了两种参数占位符:{} :执行SQL时,会将 #{} 占位符替换为?,将来自动设置参数值。从上述例子可以看出使用#{} 底层使用的是 PreparedStatement${} :拼接SQL。底层使用的是 Statement,会存在SQL注入问题。Mybatis对动态SQL有很强大的支撑:ifchoose (when, otherwise)trim (where, set)foreachif 标签:if 标签:条件判断test 属性:逻辑表达式where 标签:作用:替换where关键字会动态的去掉第一个条件前的 and如果所有的参数没有值则不加where关键字choose标签:choose 标签类似于Java 中的switch语句。when 标签类似于Java 中的case语句。<!--数据库表中的字段与编写的实体类中的属性名字段不一致--> <resultMap id="brandResultMap" type="brand"> <!--id:唯一标识;type:映射的类型--> <!--id:主键字段映射--> <!--result:非主键字段映射--> <result column="brand_name" property="brandName"/> <result column="company_name" property="companyName"/> </resultMap> <select id="selectAll" resultMap="brandResultMap"> select * from tb_brand; </select> <select id="selectById" resultMap="brandResultMap"> select * from tb_brand where id = #{id}; </select> <select id="selectByBrand" resultMap="brandResultMap"> select * from tb_brand where status = #{status} and company_name like #{companyName} and brand_name like #{brandName}; </select> <select id="selectByStatusAndName" resultMap="brandResultMap"> select * from tb_brand where status = #{status} and company_name like #{companyName} and brand_name like #{brandName}; </select> <select id="selectByBrandByMap" resultMap="brandResultMap"> select *from tb_brand <where> <if test="status!=null"> status = #{status} </if> <if test="companyName!=null and companyName!=''"> and company_name like #{companyName} </if> <if test="brandName!=null and brandName!=''"> and brand_name like #{brandName}; </if> </where> </select> <select id="selectSingle" resultMap="brandResultMap"> select *from tb_brand <where> <choose> <when test="status!=null"> status = #{status} </when> <when test="companyName!=null and companyName!=''"> company_name like #{companyName} </when> <when test="brandName!=null and brandName!=''"> brand_name like #{brandName} </when> </choose> </where> </select>编写测试方法在 test/java 下的 com.liu.Mapper 包下的 MybatisTest类中 定义测试方法@Test public void Test1(){ List<Brand> brands = brandMapper.selectAll(); for (Brand brand : brands) { System.out.println(brand); } } @Test public void Test2(){ Brand brand = brandMapper.selectById(1); System.out.println(brand); } @Test public void Test3(){ List<Brand> brands = brandMapper.selectByBrand(1, "%华为%", "%华为%"); for (Brand brand : brands) { System.out.println(brand); } } @Test public void Test4(){ Brand brand = new Brand(); brand.setStatus(1); brand.setCompanyName("%华为%"); brand.setBrandName("%华为%"); List<Brand> brands = brandMapper.selectByStatusAndName(brand); for (Brand brand1 : brands) { System.out.println(brand1); } } @Test public void Test5(){ Map map = new HashMap(); map.put("status",1); map.put("companyName","%华为%"); map.put("brandName","%华为%"); List<Brand> brands = brandMapper.selectByBrandByMap(map); for (Brand brand : brands) { System.out.println(brand); } } @Test public void Test6(){ Brand brand = new Brand(); //brand.setStatus(1); //brand.setCompanyName("%华为%"); brand.setBrandName("%华为%"); List<Brand> brands = brandMapper.selectSingle(brand); for (Brand brand1 : brands) { System.out.println(brand1); } }3.2 添加操作编写接口方法在 BrandMapper 接口中定义查询数据的方法 ://插入操作 void insert(Brand brand);编写SQL语句在 BrandMapper.xml 映射配置文件中编写添加数据的 statement在数据添加成功后,有时候需要获取插入数据库数据的主键(主键是自增长):在 insert 标签上添加如下属性:useGeneratedKeys:是够获取自动增长的主键值。true表示获取keyProperty :指定将获取到的主键值封装到哪儿个属性里<insert id="insert" useGeneratedKeys="true" keyProperty="id"> insert into tb_brand(brand_name, company_name, ordered, description, status) VALUES (#{brandName}, #{companyName}, #{ordered}, #{description}, #{status}); </insert>编写测试方法在 test/java 下的 com.liu.Mapper 包下的 MybatisTest类中 定义测试方法:@Test public void Test7(){ Brand brand = new Brand("锤子手机", "锤子的专卖店", 100, "手机中的战斗机", 1); brandMapper.insert(brand); //主键返回 Integer id = brand.getId(); System.out.println(id); //提交事务,如果不提交事务则会自动回滚 sqlSession.commit(); }3.3 修改操作编写接口方法在 BrandMapper 接口中定义查询数据的方法 ://修改功能 int update(Brand brand);编写SQL语句在 BrandMapper.xml 映射配置文件中编写添加数据的 statementset 标签可以用于动态包含需要更新的列,忽略其它不更新的列。<update id="update"> update tb_brand <set> <if test="brandName != null and brandName != ''"> brand_name = #{brandName}, </if> <if test="companyName != null and companyName != ''"> company_name = #{companyName}, </if> <if test="ordered != null"> ordered = #{ordered}, </if> <if test="description != null and description != ''"> description = #{description}, </if> <if test="status != null"> status = #{status} </if> </set> where id = #{id}; </update>编写测试方法在 test/java 下的 com.liu.Mapper 包下的 MybatisTest类中 定义测试方法:@Test public void Test8(){ Brand brand = new Brand(6,"锤子手机", "锤子的专卖店", 200, "性价比之王,手机中的战斗机", 1); int update = brandMapper.update(brand); System.out.println(update); sqlSession.commit(); }3.4 删除操作编写接口方法在 BrandMapper 接口中定义查询数据的方法 ://删除操作 void deleteById(int id); //删除多个 void deleteByIdIs(int[] ids);编写SQL语句在 BrandMapper.xml 映射配置文件中编写添加数据的 statement编写SQL时需要遍历数组来拼接SQL语句。Mybatis 提供了 foreach 标签供我们使用foreach 标签用来迭代任何可迭代的对象(如数组,集合)。collection 属性:mybatis会将数组参数,封装为一个Map集合。默认:array = 数组使用@Param注解改变map集合的默认key的名称item 属性:本次迭代获取到的元素。separator 属性:集合项迭代之间的分隔符。foreach 标签不会错误地添加多余的分隔符。也就是最后一次迭代不会加分隔符。open 属性:该属性值是在拼接SQL语句之前拼接的语句,只会拼接一次close 属性:该属性值是在拼接SQL语句拼接后拼接的语句,只会拼接一次<delete id="deleteById"> delete from tb_brand where id = #{id} </delete> <delete id="deleteByIdIs"> delete from tb_brand where id in <foreach collection="array" item="id" separator="," open="(" close=")"> #{id} </foreach> </delete>编写测试方法在 test/java 下的 com.liu.Mapper 包下的 MybatisTest类中 定义测试方法:@Test public void Test9(){ brandMapper.deleteById(6); List<Brand> brands = brandMapper.selectAll(); for (Brand brand : brands) { System.out.println(brand); } sqlSession.commit(); } @Test public void Test10(){ int[] ids = {1,5,6}; brandMapper.deleteByIdIs(ids); List<Brand> brands = brandMapper.selectAll(); for (Brand brand : brands) { System.out.println(brand); } sqlSession.commit(); }4 MyBatis注解开发注解一般开发较为简单的语句,对于复杂的SQL语句一定要用配置文件来开发。查询 :@Select添加 :@Insert修改 :@Update删除 :@Delete对User类进行注解的增删改查开发编写接口方法public interface UserMapper { @Select("select * from tb_user") List<User> selectAll(); @Insert("insert into tb_user(username, password, gender, addr) values (#{username},#{password},#{gender},#{addr})") void insert(User user); @Update("update tb_user set username = #{username} where id=#{id}") int update(@Param("username") String username,@Param("id") int id); @Delete("delete from tb_user where id=#{id}") void delete(int id); }编写测试方法public class UserTest { //获取sqlSessionFactory String resource = "mybatis-config.xml"; InputStream inputStream; { try { inputStream = Resources.getResourceAsStream(resource); } catch (IOException e) { e.printStackTrace(); } } SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); //获取sqlSession SqlSession sqlSession = sqlSessionFactory.openSession(); UserMapper userMapper = sqlSession.getMapper(UserMapper.class); @Test public void Test1(){ List<User> users = userMapper.selectAll(); for (User user : users) { System.out.println(user); } } @Test public void Test2(){ User user = new User(); user.setUsername("刘畅"); user.setPassword("1314"); user.setGender("男"); user.setAddr("河南"); userMapper.insert(user); sqlSession.commit(); } @Test public void Test3(){ int update = userMapper.update("六珍惜", 1); sqlSession.commit(); } @Test public void Test4(){ userMapper.delete(5); sqlSession.commit(); } }学习记录 41 Servlet1.1 Servlet概述Servlet是JavaWeb最为核心的内容,它是Java提供的一门==动态==web资源开发技术。使用Servlet就可以实现,根据不同的登录用户在页面上动态显示不同内容。Servlet是JavaEE规范之一,其实就是一个接口,将来我们需要定义Servlet类实现Servlet接口,并由web服务器运行Servlet。1.2 Servlet练习需求分析: 编写一个Servlet类,并使用IDEA中Tomcat插件进行部署,最终通过浏览器访问所编写的Servlet程序。==具体的实现步骤为:创建Web项目web-demo,导入Servlet依赖坐标<dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> <!-- 此处为什么需要添加该标签? provided指的是在编译和测试过程中有效,最后生成的war包时不会加入 因为Tomcat的lib目录中已经有servlet-api这个jar包,如果在生成war包的时候生效就会和Tomcat中的jar包冲突,导致报错 --> <scope>provided</scope> </dependency> </dependencies>创建:定义一个类,实现Servlet接口,并重写接口中所有方法,并在service方法中输入一句话public class ServletDemo1 implements Servlet { public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException { System.out.println("servlet hello world~"); } public void init(ServletConfig servletConfig) throws ServletException { } public ServletConfig getServletConfig() { return null; } public String getServletInfo() { return null; } public void destroy() { } }配置:在类上使用@WebServlet注解,配置该Servlet的访问路径@WebServlet("/demo1")访问:启动Tomcat,浏览器中输入URL地址访问该Servlethttp://localhost:8080/web-demo/demo1器访问后,在控制台会打印servlet hello world~ 说明servlet程序已经成功运行。1.3 执行流程浏览器发出http://localhost:8080/web-demo/demo1请求,从请求中可以解析出三部分内容,分别是localhost:8080、web-demo、demo1根据localhost:8080可以找到要访问的Tomcat Web服务器根据web-demo可以找到部署在Tomcat服务器上的web-demo项目根据demo1可以找到要访问的是项目中的哪个Servlet类,根据@WebServlet后面的值进行匹配找到ServletDemo1这个类后,Tomcat Web服务器就会为ServletDemo1这个类创建一个对象,然后调用对象中的service方法ServletDemo1实现了Servlet接口,所以类中必然会重写service方法供Tomcat Web服务器进行调用service方法中有ServletRequest和ServletResponse两个参数,ServletRequest封装的是请求数据,ServletResponse封装的是响应数据,后期我们可以通过这两个参数实现前后端的数据交互。小结Servlet由谁创建?Servlet方法由谁调用?Servlet由web服务器创建,Servlet方法由web服务器调用服务器怎么知道Servlet中一定有service方法?因为我们自定义的Servlet,必须实现Servlet接口并复写其方法,而Servlet接口中有service方法1.4 servlet生命周期Servlet运行在Servlet容器(web服务器)中,其生命周期由容器来管理,分为4个阶段:加载和实例化:默认情况下,当Servlet第一次被访问时,由容器创建Servlet对象。初始化:在Servlet实例化之后,容器将调用Servlet的init()方法初始化这个对象,完成一些如加载配置文件、创建连接等初始化的工作。该方法只调用一次。请求处理:每次请求Servlet时,Servlet容器都会调用Servlet的service()方法对请求进行处理。服务终止:当需要释放内存或者容器关闭时,容器就会调用Servlet实例的destroy()方法完成资源的释放。在destroy()方法调用之后,容器会释放这个Servlet实例,该实例随后会被Java的垃圾收集器所回收。4个阶段的中所使用的方法:初始化方法,在Servlet被创建时执行,只执行一次void init(ServletConfig config) 提供服务方法, 每次Servlet被访问,都会调用该方法void service(ServletRequest req, ServletResponse res)销毁方法,当Servlet被销毁时,调用该方法。在内存释放或服务器关闭时销毁Servletvoid destroy() 剩下的两个方法是:获取Servlet信息String getServletInfo() //该方法用来返回Servlet的相关信息,没有什么太大的用处,一般我们返回一个空字符串即可 public String getServletInfo() { return ""; }获取ServletConfig对象ServletConfig getServletConfig()ServletConfig对象,在init方法的参数中有,而Tomcat Web服务器在创建Servlet对象的时候会调用init方法,必定会传入一个ServletConfig对象,我们只需要将服务器传过来的ServletConfig进行返回即可。2 Request类2.1 Request概述request:获取请求数据浏览器会发送HTTP请求到后台服务器:Tomcat。HTTP的请求中会包含很多请求数据:请求行+请求头+请求体。后台服务器Tomcat会对HTTP请求中的数据进行解析并把解析结果存入到一个对象中。所存入的对象即为request对象,所以我们可以从request对象中获取请求的相关参数。获取到数据后就可以继续后续的业务,比如获取用户名和密码就可以实现登录操作的相关业务。Request的继承体系为ServletRequest-->HttpServletRequest-->RequestFacade2.2 HttpServletRequest获取请求数据:请求行返回值类型方法声明描述StringgetMethod()该方法用于获取 HTTP 请求方式(如 GET、POST 等)。StringgetRequestURI()该方法用于获取请求行中的资源名称部分,即位于 URL 的主机和端口之后,参数部分之前的部分。StringgetQueryString()该方法用于获取请求行中的参数部分,也就是 URL 中“?”以后的所有内容。StringgetContextPath()返回当前 Servlet 所在的应用的名字(上下文)。对于默认(ROOT)上下文中的 Servlet,此方法返回空字符串""。StringgetServletPath()该方法用于获取 Servlet 所映射的路径。StringgetRemoteAddr()该方法用于获取客户端的 IP 地址。StringgetRemoteHost()该方法用于获取客户端的完整主机名,如果无法解析出客户机的完整主机名,则该方法将会返回客户端的 IP 地址。测试使用:@WebServlet("/ServletDemo01") public class ServletDemo01 extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //请求行 //返回发出此请求的 HTTP 方法的名称,例如 GET、POST 或 PUT. String method = request.getMethod(); System.out.println(method); //返回请求 URI 中指示请求上下文的部分。即项目访问路径 String contextPath = request.getContextPath(); System.out.println(contextPath); //返回的 URL 包含协议、服务器名称、端口号和服务器路径,但不包含查询字符串参数。 StringBuffer requestURL = request.getRequestURL(); System.out.println(requestURL); //返回此请求的 URL 从协议名称到 HTTP 请求第一行中的查询字符串的部分 String requestURI = request.getRequestURI(); System.out.println(requestURI); //返回路径后面的请求 URL 中包含的查询字符串 String queryString = request.getQueryString(); System.out.println(queryString); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } } 使用PostMan发起一个GET请求:获取的请求行数据:2.3 HttpServletRequest获取请求数据:请求头返回值类型方法声明描述StringgetHeader(String name)该方法用于获取一个指定头字段的值。 如果请求消息中包含多个指定名称的头字段,则该方法返回其中第一个头字段的值。EnumerationgetHeaders(String name)该方法返回指定头字段的所有值的枚举集合, 在多数情况下,一个头字段名在请求消息中只出现一次,但有时可能会出现多次。EnumerationgetHeaderNames()该方法返回请求头中所有头字段的枚举集合。StringgetContentType()该方法用于获取 Content-Type 头字段的值。intgetContentLength()该方法用于获取 Content-Length 头字段的值 。StringgetCharacterEncoding()该方法用于返回请求消息的字符集编码 。API测试:@WebServlet("/ServletDemo02") public class ServletDemo02 extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String header = request.getHeader("User-Agent"); System.out.println(header); //获得所有请求头字段的枚举集合 Enumeration<String> headers = request.getHeaderNames(); while (headers.hasMoreElements()) { //获得请求头字段的值 String value = request.getHeader(headers.nextElement()); System.out.println(value); } String contentType = request.getContentType(); System.out.println(contentType); String characterEncoding = request.getCharacterEncoding(); System.out.println(characterEncoding); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } }使用PostMan发起一个GET请求:获取的请求头数据:2.4 HttpServletRequest获取请求数据:请求体返回值类型方法声明功能描述StringgetParameter(String name)返回指定参数名的参数值。String [ ]getParameterValues (String name)以字符串数组的形式返回指定参数名的所有参数值(HTTP 请求中可以有多个相同参数名的参数)。EnumerationgetParameterNames()以枚举集合的形式返回请求中所有参数名。MapgetParameterMap()用于将请求中的所有参数名和参数值装入一个 Map 对象中返回。API测试:@WebServlet("/ServletDemo03") public class ServletDemo03 extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doPost(request, response); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String user = request.getParameter("user"); System.out.println(user); String[] users = request.getParameterValues("user"); for (String s : users) { System.out.println(s); } Map<String, String[]> map = request.getParameterMap(); map.forEach(((s, strings) -> System.out.println(s+"="+request.getParameter(s)))); } }使用PostMan发起一个POST请求:获取的请求体数据:2.5 Request请求乱码问题POST请求和GET请求的参数中如果有中文,后台接收数据就会出现中文乱码问题GET请求在Tomcat8.0以后的版本就不会出现了POST请求解决方案是:设置输入流的编码request.setCharacterEncoding("UTF-8");2.6 Request请求转发RequestDispatcher 接口javax.servlet 包中定义了一个 RequestDispatcher 接口,RequestDispatcher 对象由 Servlet 容器创建,用于封装由路径所标识的 Web 资源。利用 RequestDispatcher 对象可以把请求转发给其他的 Web 资源。Servlet 可以通过 2 种方式获得 RequestDispatcher 对象:调用 ServletContext 的 getRequestDispatcher(String path) 方法,参数 path 指定目标资源的路径,必须为绝对路径;调用 ServletRequest 的 getRequestDispatcher(String path) 方法,参数 path 指定目标资源的路径,可以为绝对路径,也可以为相对路径。RequestDispatcher 接口中提供了以下方法。返回值类型方法功能描述voidforward(ServletRequest request,ServletResponse response)用于将请求转发给另一个 Web 资源。该方法必须在响应提交给客户端之前被调用,否则将抛出 IllegalStateException 异常voidinclude(ServletRequest request,ServletResponse response)用于将其他的资源作为当前响应内容包含进来request 域对象request 是 Servlet 的三大域对象之一,它需要与请求转发配合使用,才可以实现动态资源间的数据传递。返回值类型方法描述voidsetAttribute(String name, Object o)将 Java 对象与属性名绑定,并将它作为一个属性存放到 request 对象中。参数 name 为属性名,参数 object 为属性值。ObjectgetAttribute(String name)根据属性名 name,返回 request 中对应的属性值。voidremoveAttribute(String name)用于移除 request 对象中指定的属性。EnumerationgetAttributeNames()用于返回 request 对象中的所有属性名的枚举集合。测试:@WebServlet("/ServletDemo04") public class ServletDemo04 extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("UTF-8"); request.setAttribute("user","刘畅"); request.getRequestDispatcher("/ServletDemo05").forward(request,response); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } }@WebServlet("/ServletDemo05") public class ServletDemo05 extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("UTF-8"); String user = (String) request.getAttribute("user"); System.out.println(user); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } }3 Response类3.1 Response概述response:设置响应数据业务处理完后,后台就需要给前端返回业务处理的结果即响应数据。把响应数据封装到response对象中。后台服务器[Tomcat]会解析response对象,按照[响应行+响应头+响应体]格式拼接结果。浏览器最终解析结果,把内容展示在浏览器给用户浏览。3.2 HttpServletResponse的API响应行API返回值类型方法描述voidsetStatus(int status)用于设置 HTTP 响应消息的状态码,并生成响应状态行。voidsendError(int sc)用于发送表示错误信息的状态码。响应头API返回值类型方法描述voidaddHeader(String name,String value)用于增加响应头字段,其中,参数 name 用于指定响应头字段的名称,参数 value 用于指定响应头字段的值。voidsetHeader (String name,String value)用于设置响应头字段,其中,参数 name 用于指定响应头字段的名称,参数 value 用于指定响应头字段的值。voidaddIntHeader(String name,int value)用于增加值为 int 类型的响应头字段,其中,参数 name 用于指定响应头字段的名称,参数 value 用于指定响应头字段的值,类型为 int。voidsetIntHeader(String name, int value)用于设置值为 int 类型的响应头字段,其中,参数 name 用于指定响应头字段的名称,参数 value 用于指定响应头字段的值,类型为 int。voidsetContentType(String type)用于设置 Servlet 输出内容的 MIME 类型以及编码格式。voidsetCharacterEncoding(String charset)用于设置输出内容使用的字符编码。响应体API返回值类型方法描述ServletOutputStreamgetOutputStream()用于获取字节输出流对象。PrintWritergetWriter()用于获取字符输出流对象。3.3 Respones请求重定向(1)浏览器发送请求给服务器,服务器中对应的资源A接收到请求。(2)资源A现在无法处理该请求,就会给浏览器响应一个302的状态码+location的一个访问资源B的路径。(3)浏览器接收到响应状态码为302就会重新发送请求到location对应的访问地址去访问资源B。(4)资源B接收到请求后进行处理并最终给浏览器响应结果,这整个过程就叫重定向。转发和重定向的区别转发和重定向都能实现页面的跳转,但是两者也存在以下区别。区别转发重定向浏览器地址栏 URL 是否发生改变否是是否支持跨域跳转否是请求与响应的次数一次请求和一次响应两次请求和两次响应是否共享 request 对象和 response 对象是否是否能通过 request 域对象传递数据是否速度相对要快相对要慢行为类型服务器行为客户端行为response.sendRedirect()HttpServletResponse 接口中的 sendRedirect() 方法用于实现重定向。返回值类型方法描述voidsendRedirect(String location)向浏览器返回状态码为 302 的响应结果,让浏览器访问新的 URL。若指定的 URL 是相对路径,Servlet 容器会将相对路径转换为绝对路径。参数 location 表示重定向的URL。@WebServlet("/ServletDemo06") public class ServletDemo06 extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String contextPath = request.getContextPath(); System.out.println("ServletDemo06访问"); response.sendRedirect(contextPath+"/ServletDemo07"); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } }@WebServlet("/ServletDemo07") public class ServletDemo07 extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println("ServletDemo07访问"); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } } 4 综合案例-登录案例实现登录:Maven导入相关依赖<dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> <scope>provided</scope> </dependency> <!--mybatis 依赖--> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.9</version> </dependency> <!--mysql 驱动--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.29</version> </dependency> <!-- 添加slf4j日志api --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.36</version> </dependency> <!-- 添加logback-classic依赖 --> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.2.3</version> </dependency> <!-- 添加logback-core依赖 --> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-core</artifactId> <version>1.2.3</version> </dependency> </dependencies>在resources下导入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.liu.pojo"/> </typeAliases> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="com.mysql.cj.jdbc.Driver"/> <property name="url" value="jdbc:mysql:///test?useSSL=false"/> <property name="username" value="root"/> <property name="password" value="131411"/> </dataSource> </environment> </environments> <mappers> <package name="com.liu.dao"/> </mappers> </configuration>编写实体类Userpackage com.liu.pojo; public class User { private int id; private String username; private String password; private String gender; private String addr; //省略了 setter 和 getter toString编写Dao层package com.liu.dao; import com.liu.pojo.User; import org.apache.ibatis.annotations.*; import java.util.List; public interface UserDao { @Select("select * from tb_user where username = #{username} and password = #{password}") User selectLogin(@Param("username") String username, @Param("password") String password); }编写Mybatis工具类,简化重复代码package com.liu.utils; import com.liu.dao.UserDao; import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import java.io.IOException; import java.io.InputStream; public class MybatisUtils { public static UserDao getUserDao(){ //获取sqlSessionFactory String resource = "mybatis-config.xml"; InputStream inputStream = null; { try { inputStream = Resources.getResourceAsStream(resource); } catch (IOException e) { e.printStackTrace(); } } SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); //获取sqlSession SqlSession sqlSession = sqlSessionFactory.openSession(); return sqlSession.getMapper(UserDao.class); } }编写Service层package com.liu.service; import com.liu.dao.UserDao; import com.liu.pojo.User; import com.liu.utils.MybatisUtils; public class UserService { public boolean login(String username,String password){ UserDao userDao = MybatisUtils.getUserDao(); User user = userDao.selectLogin(username, password); if (user!=null){ return true; }else { return false; } } }进行测试Dao层import com.liu.dao.UserDao; import com.liu.pojo.User; import com.liu.service.UserService; import com.liu.utils.MybatisUtils; import org.junit.Test; public class Test01 { @Test public void Test1(){ UserDao userDao = MybatisUtils.getUserDao(); User user = userDao.selectLogin("六珍惜", "123"); System.out.println(user); } @Test public void Test2(){ UserService userService = new UserService(); boolean b = userService.login("六珍惜", "123"); System.out.println(b); boolean b1 = userService.login("liuchang", "123"); System.out.println(b1); } }编写Web层package com.liu.web; import com.liu.service.UserService; import javax.servlet.*; import javax.servlet.http.*; import javax.servlet.annotation.*; import java.io.IOException; @WebServlet("/LoginServlet") public class LoginServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("utf-8"); String username = request.getParameter("username"); String password = request.getParameter("password"); System.out.println(username); System.out.println(password); UserService userService = new UserService(); boolean b = userService.login(username, password); String contextPath = request.getContextPath(); if (b){ request.setAttribute("username",username); request.setAttribute("password",password); request.getRequestDispatcher("/LoginSuccessfulServlet").forward(request,response); response.sendRedirect(contextPath+"/LoginSuccessfulServlet"); }else { response.sendRedirect(contextPath+"/LoginFailedServlet"); } } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } }登录成功跳转package com.liu.web; import javax.servlet.*; import javax.servlet.http.*; import javax.servlet.annotation.*; import java.io.IOException; import java.io.PrintWriter; @WebServlet("/LoginSuccessfulServlet") public class LoginSuccessfulServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html; charset=UTF-8"); PrintWriter writer = response.getWriter(); String username = (String) request.getAttribute("username"); String password = (String) request.getAttribute("password"); writer.write("<h1>登录成功了!</h1>"+"欢迎您:"+username); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } }登录失败跳转package com.liu.web; import javax.servlet.*; import javax.servlet.http.*; import javax.servlet.annotation.*; import java.io.IOException; import java.io.PrintWriter; @WebServlet("/LoginFailedServlet") public class LoginFailedServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html; charset=UTF-8"); PrintWriter writer = response.getWriter(); writer.write("<h1>登录失败了!</h1>"); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } }编写前端:index.jsp(从网上复制的)<%@ page contentType="text/html;charset=UTF-8" language="java"%> <!DOCTYPE html> <html> <body> <div class="main"> <div class="title"> <span>密码登录</span> </div> <div class="title-msg"> <span>请输入登录账户和密码</span> </div> <form class="login-form" action="/LoginServlet" method="post" novalidate > <!--输入框--> <div class="input-content"> <!--autoFocus--> <div> <input type="username" autocomplete="off" placeholder="用户名" name="username" required/> </div> <div style="margin-top: 16px"> <input type="password" autocomplete="off" placeholder="登录密码" name="password" required maxlength="32"/> </div> </div> <!--登入按钮--> <div style="text-align: center"> <button type="submit" class="enter-btn" >登录</button> </div> <div class="foor"> <div class="left"><span>忘记密码</span></div> <div class="right"><span>注册账户</span></div> </div> </form> </div> </body> </html> <style> body{ background: #353f42; } *{ padding: 0; margin: 0; } .main { margin: 0 auto; padding-left: 25px; padding-right: 25px; padding-top: 15px; width: 350px; height: 350px; background: #FFFFFF; /*以下css用于让登录表单垂直居中在界面,可删除*/ position: absolute; top: 50%; left: 50%; margin-top: -175px; margin-left: -175px; } .title { width: 100%; height: 40px; line-height: 40px; } .title span { font-size: 18px; color: #353f42; } .title-msg { width: 100%; height: 64px; line-height: 64px; } .title:hover{ cursor: default ; } .title-msg:hover{ cursor: default ; } .title-msg span { font-size: 12px; color: #707472; } .input-content { width: 100%; height: 120px; } .input-content input { width: 330px; height: 40px; border: 1px solid #dad9d6; background: #ffffff; padding-left: 10px; padding-right: 10px; } .enter-btn { width: 350px; height: 40px; color: #fff; background: #0bc5de; line-height: 40px; text-align: center; border: 0px; } .foor{ width: 100%; height: auto; color: #9b9c98; font-size: 12px; margin-top: 20px; } .enter-btn:hover { cursor:pointer; background: #1db5c9; } .foor div:hover { cursor:pointer; color: #484847; font-weight: 600; } .left{ float: left; } .right{ float: right; } </style>运行测试:学习记录 51 会话技术从打开浏览器访问某个网站,到关闭浏览器的过程,称为一次会话。会话技术是指在会话中,帮助服务器记录用户状态和数据的技术。常用的会话技术分为两种:Cookie :客户端会话技术Session :服务端会话技术2 Cookie技术Cookie 属于客户端会话技术,它是服务器发送给浏览器的小段文本信息,存储在客户端浏览器的内存中或硬盘上。当浏览器保存了 Cookie 后,每次访问服务器,都会在 HTTP 请求头中将这个 Cookie 回传给服务器。2.1 Cookie的分类Cookie分为两种:会话级别 Cookie(默认):Cookie 保存到浏览器的内存中,浏览器关闭则 Cookie 失效。持久的 Cookie:Cookie 以文本文件的形式保存到硬盘上。2.2 Cookie的工作流程Cookie 是基于 HTTP 协议实现的,工作流程如下。客户端浏览器访问服务器时,服务器通过在 HTTP 响应中增加 Set-Cookie 字段,将数据信息发送给浏览器。浏览器将 Cookie 保存在内存中或硬盘上。再次请求该服务器时,浏览器通过在 HTTP 请求消息中增加 Cookie 请求头字段,将 Cookie 回传给 Web 服务器。服务器根据 Cookie 信息跟踪客户端的状态。2.3 Cookie APIHttpServletResponse 接口和 HttpServletRequest 接口也都定义了与 Cookie 相关的方法,如下表所示。方法描述所属接口void addCookie(Cookie cookie)用于在响应头中增加一个相应的 Set-Cookie 头字段。javax.servlet.http.HttpServletResponseCookie[] getCookies()用于获取客户端提交的 Cookie。javax.servlet.http.HttpServletRequestjavax.servlet.http.Cookie 类中提供了一系列获取或者设置 Cookie 的方法,如下表。返回值类型方法描述intgetMaxAge()用于获取指定 Cookie 的最大有效时间,以秒为单位。 默认情况下取值为 -1,表示该 Cookie 保留到浏览器关闭为止。StringgetName()用于获取 Cookie 的名称。StringgetPath()用于获取 Cookie 的有效路径。booleangetSecure()如果浏览器只通过安全协议发送 Cookie,则返回 true;如果浏览器可以使用任何协议发送 Cookie,则返回 false。StringgetValue()用于获取 Cookie 的值。intgetVersion()用于获取 Cookie 遵守的协议版本。voidsetMaxAge(int expiry)用于设置 Cookie 的最大有效时间,以秒为单位。 取值为正值时,表示 Cookie 在经过指定时间后过期。取值为负值时,表示 Cookie 不会被持久存储,在 Web 浏览器退出时删除。取值为 0 时,表示删除该 Cookie。voidsetPath(String uri)用于指定 Cookie 的路径。voidsetSecure(boolean flag)用于设置浏览器是否只能使用安全协议(如 HTTPS 或 SSL)发送 Cookie。voidsetValue(String newValue)用于设置 Cookie 的值。@WebServlet("/CookieServlet") public class CookieServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { Cookie cookie = new Cookie("username","liuchang"); //设置其存在5000秒 cookie.setMaxAge(5000); response.addCookie(cookie); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } }@WebServlet("/CookieServlet02") public class CookieServlet02 extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { Cookie[] cookies = request.getCookies(); for (Cookie cookie : cookies) { String name = cookie.getName(); String value = cookie.getValue(); System.out.println(name+":"+value); } } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } }2.4 Cookie的使用细节使用 Cookie 开发时需要注意以下细节:一个 Cookie 只能标识一种信息,它至少包含一个名称(NAME)和一个值(VALUE)。如果创建了一个 Cookie,并发送到浏览器,默认情况下它是一个会话级别的 Cookie。用户退出浏览器就被删除。如果希望将 Cookie 存到磁盘上,则需要调用 setMaxAge(int maxAge) 方法设置最大有效时间,以秒为单位。使用 setMaxAge(0) 手动删除 Cookie时,需要使用 setPath 方法指定 Cookie 的路径,且该路径必须与创建 Cookie 时的路径保持一致。3 Session技术Session 是服务器端会话技术。当浏览器访问 Web 服务器的资源时,服务器可以为每个用户浏览器创建一个 Session 对象,每个浏览器独占一个 Session 对象。由于每个浏览器独占一个 Session,所以用户在访问服务器的资源时,可以把数据保存在各自的 Session 中。当用户再次访问该服务器中的其它资源时,其它资源可以从 Session 中取出数据,为用户服务。3.1 Session 的工作原理当客户端第一次请求会话对象时,服务器会创建一个 Session 对象,并为该 Session 对象分配一个唯一的 SessionID(用来标识这个 Session 对象);服务器将 SessionID 以 Cookie(Cookie 名称为:“JSESSIONID”,值为 SessionID 的值)的形式发送给客户端浏览器;客户端浏览器再次发送 HTTP 请求时,会将携带 SessionID 的 Cookie 随请求一起发送给服务器;服务器从请求中读取 SessionID,然后根据 SessionID 找到对应的 Session 对象。3.2 Session 与 Cookie 对比Session 和 Cookie 都属于会话技术,都能帮助服务器保存和跟踪用户状态,但两者也存在差异,如下表。不同点CookieSession存储位置不同Cookie 将数据存放在客户端浏览器内存中或硬盘上。Session 将数据存储在服务器端。大小和数量限制不同浏览器对 Cookie 的大小和数量有限制。Session 的大小和数量一般不受限制。存放数据类型不同Cookie 中保存的是字符串。Session 中保存的是对象。安全性不同Cookie 明文传递,安全性低,他人可以分析存放在本地的 Cookie 并进行 Cookie 欺骗。Session 存在服务器端,安全性较高。对服务器造成的压力不同Cookie 保存在客户端,不占用服务器资源。Session 保存在服务端,每一个用户独占一个 Session。若并发访问的用户十分多,就会占用大量服务端资源。跨域支持上不同Cookie 支持跨域名访问。Session 不支持跨域名访问。3.3 Session APISession 对象由服务器创建,通过 HttpServletRequest.getSession() 方法可以获得 HttpSession 对象。HttpSession 接口定义了一系列对 Session 对象操作的方法,如下表。返回值类型方法描述longgetCreationTime()返回创建 Session 的时间。StringgetId()返回获取 Seesion 的唯一的 ID。longgetLastAccessedTime()返回客户端上一次发送与此 Session 关联的请求的时间。intgetMaxInactiveInterval()返回在无任何操作的情况下,Session 失效的时间,以秒为单位。ServletContextgetServletContext()返回 Session 所属的 ServletContext 对象。voidinvalidate()使 Session 失效。voidsetMaxInactiveInterval(int interval)指定在无任何操作的情况下,Session 失效的时间,以秒为单位。负数表示 Session 永远不会失效。3.4 Session 域对象Session 对象也是一种域对象,它可以对属性进行操作,进而实现会话中请求之间的数据通讯和数据共享。在 javax.servlet.http.HttpSession 接口中定义了一系列操作属性的方法,如下表。返回值类型方法描述voidsetAttribute(String name, Object o)把一个 Java 对象与一个属性名绑定,并将它作为一个属性存放到 Session 对象中。 参数 name 为属性名,参数 object 为属性值。ObjectgetAttribute(String name)根据指定的属性名 name,返回 Session 对象中对应的属性值。voidremoveAttribute(String name)从 Session 对象中移除属性名为 name 的属性。EnumerationgetAttributeNames()用于返回 Session 对象中的所有属性名的枚举集合。Session 、request 以及 ServletContext 合称为 Servlet 的三大域对象,它们都能保存和传递数据,但是三者也存在许多差异,如下表。不同requestSessionServletContext类型javax.servlet.http.HttpServletRequestjavax.servlet.http.HttpSessionjavax.servlet.ServletContext创建客户端向容器发送请求时创建。容器第一次调用 getSession() 方法时创建。Servlet 容器启动时创建。销毁容器对这次请求做出响应后销毁。Session 销毁的时机: 关闭服务器或应用被卸载。Session 过期,默认为 30 分钟。手动调用 session.invalidate() 方法进行销毁。容器关闭或者 Web 应用被移除时销毁。有效范围只对当前请求涉及的 Servlet 有效。Session 对本次会话期间的所有 Servlet 都有效。对整个 Web 应用内的所有 Servlet 有效。数量Web 应用中的所有 Servlet 实例都可以有多个 request 对象。Web 应用中可以有多个 Session,多个 Servet 实例可以共享同一 Session 对象。在整个 Web 应用中只有一个 Context 对象。数据共享每一次请求都是一个新的 request 对象。 通过和请求转发的配合使用可以实现一次请求中 Web 组件之间共享的数据。每一次会话都是一个新的 Session 对象。 通过 Session 域对象可以实现一次会话中的多个请求之间共享数据。在一个应用中有且只有一个 Context 对象,作用于整个 Web 应用,可以实现多次会话之间的数据共享。学习笔记6Filter、Listener、Ajax1 Filter1.1 Filter概述Servlet Filter 又称 Servlet 过滤器,它是在 Servlet 2.3 规范中定义的,能够对 Servlet 容器传给 Web 资源的 request 对象和 response 对象进行检查和修改。Filter 不是 Servlet,不能直接访问,它本身也不能生成 request 对象和 response 对象,它只能为 Web 资源提供以下过滤功能:在 Web 资源被访问前,检查 request 对象,修改请求头和请求正文,或对请求进行预处理操作。将请求传递到下一个过滤器或目标资源。在 Web 资源被访问后,检查 response 对象,修改响应头和响应正文。注意:过滤器并不是必须要将请求传递到下一个过滤器或目标资源,它可以自行对请求进行处理,并发送响应给客户端,也可以将请求转发或重定向到其他的 Web 资源。Filter 是 Servlet 规范中最实用的技术,通过它可以对服务器管理的所有 Web 资源(例如 JSP、Servlet、静态 HTML 文件、静态图片等)进行拦截,从而实现一些特殊的功能,例如用户的权限控制、过滤敏感词、设置统一编码格式等。1.2 Filter 接口API与开发 Servlet 需要实现 javax.servlet.Servlet 接口类似,开发过滤器要实现 javax.servlet.Filter 接口,并提供一个公开的不带参的构造方法。在 Filter 接口中,定义了 3 个方法,如下表所示。返回值类型方法功能描述voidinit (FilterConfig filterConfig)该方法用于初始化过滤器。voiddoFilter(ServletRequest request,SeivletResponse response, FilterChain chain)该方法完成实际的过滤操作,当客户端请求的 URL 与过滤器映射的 URL 匹配时,容器会先调用该方法对请求进行拦截。 参数 request 和 response 表示请求和响应对象。 参数 chain 代表当前 Filter 链对象,在该方法内部,调用 chain.doFilter() 方法,才能把请求交付给 Filter 链中的下一个 Filter 或者 Web 资源。voiddestroy()该方法在销毁 Filter 对象之前被调用,用于释放被 Filter 对象占用的资源。1.3 Filter 的工作流程客户端请求访问容器内的 Web 资源。Servlet 容器接收请求,并针对本次请求分别创建一个 request 对象和 response 对象。请求到达 Web 资源之前,先调用 Filter 的 doFilter() 方法,检查 request 对象,修改请求头和请求正文,或对请求进行预处理操作。在 Filter 的 doFilter() 方法内,调用 FilterChain.doFilter() 方法,将请求传递给下一个过滤器或目标资源。目标资源生成响应信息返回客户端之前,处理控制权会再次回到 Filter 的 doFilter() 方法,执行 FilterChain.doFilter() 后的语句,检查 response 对象,修改响应头和响应正文。响应信息返回客户端。1.4 Filter的生命周期Filter 的生命周期分为 3 个阶段:初始化阶段拦截和过滤阶段销毁阶段初始化阶段Servlet 容器负责加载和实例化 Filter。容器启动时,读取 web.xml 或 @WebFilter 的配置信息对所有的过滤器进行加载和实例化。加载和实例化完成后,Servlet 容器调用 init() 方法初始化 Filter 实例。在 Filter 的生命周期内, init() 方法只执行一次。拦截和过滤阶段该阶段是 Filter 生命周期中最重要的阶段。当客户端请求访问 Web 资源时,Servlet 容器会根据 web.xml 或 @WebFilter 的过滤规则进行检查。当客户端请求的 URL 与过滤器映射匹配时,容器将该请求的 request 对象、response 对象以及 FilterChain 对象以参数的形式传递给 Filter 的 doFilter() 方法,并调用该方法对请求/响应进行拦截和过滤。销毁阶段Filter 对象创建后会驻留在内存中,直到容器关闭或应用被移除时销毁。销毁 Filter 对象之前,容器会先调用 destory() 方法,释放过滤器占用的资源。在 Filter 的生命周期内,destory() 只执行一次。1.5 Filter案例实现对登录的验证:创建Filter:在IDEA中可以直接使用模板进行创建package com.liu.web.filterDemo; import javax.servlet.*; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServletRequest; import java.io.IOException; @WebServlet("/*") public class FilterDemo01 implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { System.out.println("filter执行"); //进行转换 HttpServletRequest req = (HttpServletRequest) request; //判断访问资源路径是否和登录注册相关 //1,在数组中存储登陆和注册相关的资源路径 String[] urls = {"/","/LoginServlet"}; //2,获取当前访问的资源路径 String url = req.getRequestURL().toString(); //3,遍历数组,获取到每一个需要放行的资源路径 for (String u : urls) { //4,判断当前访问的资源路径字符串是否包含要放行的的资源路径字符串 if(url.contains(u)){ //找到了,放行 chain.doFilter(request, response); //break; return; } } Object username = req.getSession().getAttribute("username"); if (username != null){ chain.doFilter(request, response); }else { System.out.println("请进行登录"); req.getRequestDispatcher("/LoginServlet").forward(request, response); } } @Override public void init(FilterConfig filterConfig) throws ServletException { System.out.println("filter执行"); } @Override public void destroy() { } }2 Listener2.1 Listener概述监听器 Listener 是一个实现特定接口的 Java 程序,这个程序专门用于监听另一个 Java 对象的方法调用或属性改变,当被监听对象发生上述事件后,监听器某个方法将立即自动执行。监听器的相关概念:事件:方法调用、属性改变、状态改变等。事件源:被监听的对象( 例如:request、session、servletContext)。监听器:用于监听事件源对象 ,事件源对象状态的变化都会触发监听器。注册监听器:将监听器与事件源进行绑定。2.2 Listener的分类Servlet 规范中定义了 8 个监听器接口,可以用于监听 ServletContext、HttpSession 和 ServletRequest 对象的生命周期和属性变化事件。开发 Servlet 监听器需要实现相应的监听器接口并重写接口中的方法。监听器 Listener 按照监听的事件划分,可以分为 3 类:监听对象创建和销毁的监听器监听对象中属性变更的监听器监听 HttpSession 中的对象状态改变的监听器这里面只有 ServletContextListener 这个监听器后期我们会接触到,ServletContextListener 是用来监听 ServletContext 对象的创建和销毁。ServletContextListener 接口中有以下两个方法void contextInitialized(ServletContextEvent sce):ServletContext 对象被创建了会自动执行的方法void contextDestroyed(ServletContextEvent sce):ServletContext 对象被销毁时会自动执行的方法2.3 Listener的创建创建Listener:在IDEA中可以直接使用模板进行创建package com.liu.web.filterDemo; import javax.servlet.*; import javax.servlet.http.*; import javax.servlet.annotation.*; @WebListener public class ListenerDemo01 implements ServletContextListener, HttpSessionListener, HttpSessionAttributeListener { public ListenerDemo01() { } @Override public void contextInitialized(ServletContextEvent sce) { /* This method is called when the servlet context is initialized(when the Web application is deployed). */ } @Override public void contextDestroyed(ServletContextEvent sce) { /* This method is called when the servlet Context is undeployed or Application Server shuts down. */ } @Override public void sessionCreated(HttpSessionEvent se) { /* Session is created. */ } @Override public void sessionDestroyed(HttpSessionEvent se) { /* Session is destroyed. */ } @Override public void attributeAdded(HttpSessionBindingEvent sbe) { /* This method is called when an attribute is added to a session. */ } @Override public void attributeRemoved(HttpSessionBindingEvent sbe) { /* This method is called when an attribute is removed from a session. */ } @Override public void attributeReplaced(HttpSessionBindingEvent sbe) { /* This method is called when an attribute is replaced in a session. */ } }学习总结在本周中,主要复习了关于MySql数据库SQL语言的基本语法、底层JDBC的实现、Mybatis框架的基本使用、Servlet、Request、Response、会话技术等等,本周学习进度正常,不过跳过了一些课程,例如:Maven的配置、HTML/CSS/JS前端语言。对于Maven的配置在本科阶段已经会简单的使用,等到看Maven的高级教程时再学习;而对于前端语言的基本语法和基本使用,我也有大致的了解,前端语言入门易学精难,于是就把这两个课程给跳过了。在接下来的时间里,离开学还剩下三周时间,下周计划是将相关的JavaWeb底层中的类复习完毕,先学习Spring(Spring有四天的课程),接着学习Git版本控制工具(1天课程)。在下下周学习Mybatis框架的高级使用、SSM结合、以及SpringBoot框架。最后一周继续学习SpringBoot框架。继续努力~~~
2022年07月25日
22 阅读
0 评论
2 点赞
2022-07-15
Java SE
学习记录11 static关键字1.1 static定义static可以修饰成员变量和成员方法。static修饰成员变量表示该成员变量只在内存中存储一份,可以被共享访问,修改。1.2 static的访问方法类名.静态成员变量(常用)对象.静态成员变量(不常用)1.3 实例成员变量访问方式对象.实例成员变量package com.liu.day01; public class User { //创建静态成员变量 public static int onlineNumber = 100; //实例成员变量,属于每个对象,必须要用对象名访问 private String name; private int age; public static void main(String[] args) { //1.通过 类名.静态成员变量 来访问静态成员变量 System.out.println(User.onlineNumber); //2.通过 对象.静态成员变量 来访问静态成员变量 User user = new User(); System.out.println(++user.onlineNumber); //同一个类中的静态成员变量的访问可以省略类名 user.onlineNumber++; System.out.println(onlineNumber); //实例成员变量访问 user.name = "zhenxi"; user.age = 23; System.out.println(user.age); System.out.println(user.name); } }1.4 两种成员变量在什么情况定义静态成员变量:对象需要共享的信息实例成员变量:属于每个对象,且每个对象的信息不同时1.5 static成员变量的内存机制静态成员变量会在创建方法区时就在堆内存中产生。1.6 static成员方法访问方式与静态成员变量访问方法一致:类.静态成员方法实例成员方法访问方式:对象.实例成员方法package com.liu.day01; public class Student { private String studentName; private String studentId; //静态成员方法:属于该类 public static int getMax(int age1,int age2){ return age1>age2 ? age1:age2; } //实例方法:属于对象 public void study(){ System.out.println(studentName+"在好好学习。"); } public static void main(String[] args) { //通过类直接访问静态成员方法 System.out.println(getMax(10, 20)); //通过对象来访问实例方法 Student student = new Student(); student.studentName="zhenxi"; student.study(); Student student1 = new Student(); student1.studentName="努力"; student1.study(); } }1.7 static成员方法内存机制1.8 static访问注意事项静态方法只能访问静态成员,不可以直接访问实例成员。实例方法可以访问静态成员,也可以访问实例成员。静态方法中是不可以出现this关键字的。1.9 static应用知识:工具类工具类的内部都是一些静态的方法,每个静态方法完成一个功能。一次编写,处处可用,提高代码的重用性,避免重复造轮子。工具类的构造器要进行私有化处理。例如1:编写一个创造随机验证码的工具类LoginUtilspackage com.liu.day01; import java.util.Random; //工具类 public class LoginUtils { //工具类无需创建对象,故把工具类的构造器进行私有化。 private LoginUtils(){} //创建一个随机验证码 public static String createVerifyCode(int n){ String code = ""; String data = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; Random random = new Random(); for (int i = 0; i < n; i++) { int index = random.nextInt(data.length()); code += data.charAt(index); } return code; } }当其它类需要生成验证码时,直接使用:类.静态成员方法,即可调用。例如2:编写定义一个数组工具类package com.liu.day01; public class ArraysUtils { //首先,私有化构造器,使外部不能创建对象 private ArraysUtils(){} //返回整数数组中的内容方法 public static String toString(int[] array){ //首先对传进来的数组进行校验 if (array == null){ return null; } //对数组中的内容进行拼接 String result = "["; for (int i = 0; i < array.length; i++) { result += (i == array.length-1?array[i]:array[i]+","); } result += "]"; return result; } //统计平均值 只考虑浮点型数组 public static float getAerage(float[] array){ //首先对传进来的数组进行校验 if (array == null){ return 0; } float aerAge = 0; float max=array[0];//最大值 int maxIndex=0;//最大值索引 float min=array[0];//最小值 int minIndex=0;//最小值索引 //通过循环找出最大值最小值 for (int i = 0; i < array.length; i++) { if (max<array[i]){ max = array[i]; maxIndex = i; } if (min>array[i]){ min = array[i]; minIndex = i; } } //定义一个新数组,将除去最大值最小值的数据放入新数组中 float[] newArray= new float[array.length-2]; int j = 0; for (int i= 0; i < array.length; i++) { if (!(i == maxIndex || i == minIndex)){ newArray[j]=array[i]; j++; } } //求得新数组中的总数 float sum=0; for (int i = 0; i < newArray.length; i++) { sum += newArray[i]; } //获得平均值 aerAge=sum/array.length; return aerAge; } }数组工具类的调用测试:1.10 static应用知识:代码块代码块是类的5大成分。格式:static{}。特点:随着类的加载而加载,并且自动触发只执行一次。使用场景:在类加载的时候做一些静态数据初始化的操作。package com.liu.day01; import java.util.ArrayList; public class StaticDemo2 { /* * 静态资源*/ public static String schoolName; /* * 实例资源*/ private String name; //静态代码块 可以用于初始化静态设置 static { System.out.println("---静态代码块被执行---"); schoolName = "zhenxi"; } //实例代码块 不常用了解即可 { System.out.println("---实例代码块执行---"); name="努力"; } //无参构造 public StaticDemo2(){ System.out.println("---无参构造执行---"); } public static void main(String[] args) { System.out.println("---main方法执行---"); System.out.println(schoolName); StaticDemo2 staticDemo2 = new StaticDemo2(); System.out.println(staticDemo2.name); } }运行结果:---静态代码块被执行--- ---main方法执行--- zhenxi ---实例代码块执行--- ---无参构造执行--- 努力 Process finished with exit code 0例二:静态代码块初始化牌组package com.liu.day01; import java.util.ArrayList; public class StaticDemo3 { public static ArrayList<String> cards = new ArrayList<>(); static { String[] sizes = {"2","3","4","5","6","7","8","9","10","J","Q","K","A"}; String[] colors = {"♥","♦","♣","桃花"}; for (int i = 0; i < sizes.length; i++) { for (int j = 0; j < colors.length; j++) { String card = sizes[i] + colors[j]; cards.add(card); } } cards.add("小🃏"); cards.add("大🃏"); } public static void main(String[] args) { System.out.println(cards); } }1.11 单例设计模式饿汉单例模式定义一个类,把构造器私有。定义一个静态变量存储一个对象。package com.liu.day01; public class SingleInstance { //第一步:私有化构造器 private SingleInstance(){} //第二步:创建一个静态对象 public static SingleInstance singleInstance = new SingleInstance(); }package com.liu.day01; public class Test2 { public static void main(String[] args) { //通过类. 来获得对象 SingleInstance singleInstance = SingleInstance.singleInstance; } }懒汉单例设计模式:在真正需要该对象时,才会去创建一个对象。定义一个类,私有化构造器。定义一个静态成员变量存储一个对象。提供一个返回单例对象的方法。package com.liu.day01; public class SingleInstance2 { //1.私有化构造器 private SingleInstance2(){} //2.创建一个静态变量 这里私有化是为了防止通过类名.静态成员变量获得instance private static SingleInstance2 instance; //3.提供一个方法,对外返回单例对象 public static SingleInstance2 getInstance(){ //首先判断instance是否第一次创建,若是则创建对象,若不是则直接返回对象。 if (instance == null){ instance =new SingleInstance2(); } return instance; } }package com.liu.day01; public class Test2 { public static void main(String[] args) { //通过类.静态成员方法 来获得对象 SingleInstance2 instance = SingleInstance2.getInstance(); } }2 继承2.1 继承的定义和好处继承就是Java允许我们用extends关键字,让一个类和另一个类建立一种父子关系。优点:提高代码的复用性,减少了代码冗余,增强类的功能扩展性。子类继承父类,子类可以得到父类的属性和行为,子类比父类更强大例如:父类Peoplepackage com.liu.day01.extends_test; public class People { //子类都有的属性 private String name; private int age; //子类都有的方法 public void lookCourse(){ System.out.println("查看课表"); } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } }子类:Teacherpackage com.liu.day01.extends_test; public class Teacher extends People{ //子类特有的属性 private String department; //子类特有的方法 public void releaseIssues(){ System.out.println("发布问题~"); } public String getDepartment() { return department; } public void setDepartment(String department) { this.department = department; } }子类:Studentpackage com.liu.day01.extends_test; public class Student extends People{ //子类特有的属性 private String className; //子类特有的方法 public void learning(){ System.out.println("听课"); } public String getClassName() { return className; } public void setClassName(String className) { this.className = className; } }测试:package com.liu.day01.extends_test; public class Test1 { public static void main(String[] args) { Student student = new Student(); student.lookCourse(); student.learning(); student.setName("zhenxi"); System.out.println(student.getName()); student.setClassName("电子信息"); System.out.println(student.getClassName()); Teacher teacher = new Teacher(); teacher.lookCourse(); teacher.releaseIssues(); teacher.setName("努力"); System.out.println(teacher.getName()); teacher.setDepartment("计控学院"); System.out.println(teacher.getDepartment()); } }2.2 继承的特点子类可以继承父类的属性和行为,但是子类不能继承父类的构造器。Java是单继承模式,一个类只能继承一个直接父类。Java不支持多继承、但支持多层继承。Java的所有类,要么直接继承Object类,要么间接继承Object类。子类可以直接使用父类的静态成员,但不是子类继承了父类的静态成员,而是父类共享给子类。子类可以继承父类的私有成员,但是不能直接访问。2.3 继承后访问成员遵循就近原则先在子类局部范围查找。然后在子类成员范围查找。然后在父类成员范围查找,如果在父类成员中没有找到则报错。package com.liu.day01.extends_test; import java.util.Calendar; public class Test2 { public static void main(String[] args) { Cat cat = new Cat(); cat.eat(); cat.showId(); } } class Animal{ //父类范围 public int id = 10; public void eat(){ System.out.println("吃东西~"); } } class Cat extends Animal{ //子类范围 public int id = 11; public void eat(){ System.out.println("吃猫粮"); } public void showId(){ //局部 int id = 12; System.out.println(id); } }2.4 方法重写在继承中,子类出现了和父类中一模一样的方法声明,我们就称子类这个方法是重写的方法。重写注意事项:重写方法都加上@Override注解。重写方法的名称、形参列表必须与被重写方法的名称和参数列表一致。父类的私有方法、静态方法不能被重写。子类重写父类方法时,访问权限必须大于或者等于父类被重写的方法的权限。package com.liu.day01; public class Override_Test { public static void main(String[] args) { NewPhone newPhone = new NewPhone(); newPhone.call(); newPhone.setMessage(); } } class OldPhone{ public void call(){ System.out.println("打电话"); } public void setMessage(){ System.out.println("发送消息"); } } class NewPhone extends OldPhone{ @Override public void call(){ System.out.println("打电话"); System.out.println("视频通话"); } @Override public void setMessage(){ System.out.println("发送消息"); System.out.println("发送图片"); } }2.5 子类构造器特点子类中的所有构造器默认都会先访问父类中的无参构造,然后再执行自己的构造器。package com.liu.day01.constructor; public class Test1 { public static void main(String[] args) { Student student = new Student(); Student student1 = new Student(1); } } class People{ private String name; public People(){ System.out.println("父类无参构造执行"); } } class Student extends People{ private int id; public Student() { System.out.println("子类无参构造执行"); } public Student(int id){ this.id = id; System.out.println("子类有参构造执行"); } }运行结果: 父类无参构造执行 子类无参构造执行 父类无参构造执行 子类有参构造执行子类构造器如何访问父类有参构造器?通过使用super来调用父类构造器来初始化继承自父类的数据。package com.liu.day01.constructor; public class Test2 { public static void main(String[] args) { Zi zi = new Zi("zhenxi", 23); } } class Fu{ private String name; private int id; public Fu() { } //父类的有参构造 public Fu(String name, int id) { this.name = name; this.id = id; } } class Zi extends Fu{ public Zi() { } //使用super()初始化父类有参构造中的参数 public Zi(String name, int id) { super(name, id); } }2.6 this和super关键字访问成员变量访问成员方法访问构造方法thisthis.成员变量 访问本类成员变量this.成员方法 访问本类成员方法this(...) 访问本类构造器supersuper.成员变量 访问父类成员变量super.成员方法 访问父类成员方法super(...) 访问父类构造器注意:this(...)、super(...)使用注意点子类通过this(...)去调用本类的其它构造器,本类其它构造器会通过super手动调用父类构造器,最终还是会调用父类的构造器。this(...) super(...)都只能放在构造器的第一行,二者不能共存在同一构造器中。学习记录21 导包相同包下的类可以直接访问,不同包下的类必须导包,才可以使用,导包格式: import 包名.类名;假如一个类中需要用到不同类,而这个两个类的名称是一样的, 那么默认只能导入一个类,另一个类要带包名访问。2 权限修饰符主要是用来控制一个成员能够被访问的范围。可以修饰成员变量,方法,构造器,内部类,不同权限修饰符修饰的成员能够被访问的范围将受到限制。权限修饰符:有四种作用范围由小到大(private ->缺省-> protected -> public )在同一类下,四种权限修饰符修饰的变量和方法都可以在另一个方法中被调用。package com.liu.day02.authority_Test; public class Fu { private void privatePrint(){ System.out.println("私有权限"); } void print(){ System.out.println("缺省权限"); } protected void protectedPrint(){ System.out.println("受保护权限"); } public void publicPrint(){ System.out.println("公共权限"); } //同一个类中可以全部访问 public static void main(String[] args) { Fu fu = new Fu(); fu.privatePrint(); fu.print(); fu.protectedPrint(); fu.publicPrint(); } }在同一包下(邻居关系,无继承),public、protected、default修饰的变量和方法都可以在同一包下另一类中被调用。private修饰的不可以。package com.liu.day02.authority_Test; public class Student { public static void main(String[] args) { //同一个包中的其它类:不能访问私有 Fu fu = new Fu(); fu.protectedPrint(); fu.print(); fu.publicPrint(); } }不同包下,但是有继承关系。public、protected修饰的变量和方法都可以在同一包下另一类中被调用。default、private修饰的不可以。package com.liu.day02.authority_Test2; import com.liu.day02.authority_Test.Fu; public class Zi extends Fu{ public static void main(String[] args) { //不同包下的子类能够访问受保护的以及公有的 Zi zi = new Zi(); zi.protectedPrint(); zi.publicPrint(); } }不同包,且非子类public修饰的变量和方法都可以在同一包下另一类中被调用。protected、default、private修饰的不可以。package com.liu.day02.authority_Test2; import com.liu.day02.authority_Test.Fu; public class Teacher { public static void main(String[] args) { //不同包,且无关系类,只能使用公有权限。 Fu fu = new Fu(); fu.publicPrint(); } }3 final关键字final的作用final关键字是最终的意思,可以修饰(类、方法、变量)。修饰类: 表明该类是最终类,不能被继承。修饰方法: 表明该方法是最终方法,不能被重写。修饰变量: 表示该变量第一次赋值后,不能再次被赋值(有且仅能被赋值一次)。final修饰变量的注意final修饰的变量是基本类型:那么变量存储的数据值不能发生改变。final修饰的变量是引用类型:那么变量存储的地址值不能发生改变,但是地址指向的对象内容是可以发生变化的。4 常量常量是使用了public static final修饰的成员变量,必须有初始化值,而且执行的过程中其值不能被改变。常量的作用和好处:可以用于做系统的配置信息,方便程序的维护,同时也能提高可读性。常量命名规范:英文单词全部大写,多个单词下划线连接起来。常量的执行原理在编译阶段会进行“宏替换",把使用常量的地方全部替换成真实的字面量。这样做的好处是让使用常量的程序的执行性能与直接使用字面量是一样的。5 枚举类型枚举是Java中的一 种特殊类型。枚举的作用: "是为了做信息的标志和信息的分类"。public final class Season extends java.lang.Enum<Season> { public static final Season SPRING; public static final Season SUMMER; public static final Season AUTUMN; public static final Season WINTER; public static Season[] values(); public static Season valueOf(java.lang.String); static {}; }枚举的特征:枚举类都是继承了枚举类型: java.lang.Enum枚举都是最终类,不可以被继承。构造器的构造器都是私有的,枚举对外不能创建对象。枚举类的第一行默认都是罗列枚举对象的名称的。枚举类相当于是多例模式。6 抽象类6.1 抽象类的定义在Java中abstract是抽象的意思,可以修饰类、成员方法。abstract修饰类,这个类就是抽象类;修饰方法,这个方法就是抽象方法。抽象方法只有方法签名,不能声明方法体。一个类中如果定义了抽象方法,这个类必须声明成抽象类,否则报错。6.2 抽象类使用场景抽象类可以理解成不完整的设计图,一般作为父类,让子类来继承。当父类知道子类一定要完成某些行为,但是每个子类该行为的实现又不同,于是该父类就把该行为定义成抽象方法的形式,具体实现交给子类去完成。此时这个类就可以声明成抽象类。一个类如果继承了抽象类,那么这个类必须重写完抽象类的全部抽象方法,否则这个类也必须定义成抽象类。案例:Card类package com.liu.day02.abstract_Test; public abstract class Card { private String userName; private Double money; public abstract void pay(double payMoney); public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public Double getMoney() { return money; } public void setMoney(Double money) { this.money = money; } }ColdenCard类package com.liu.day02.abstract_Test; public class GoldenCard extends Card{ @Override public void pay(double payMoney) { payMoney = 0.8 * payMoney; System.out.println("应支付"+payMoney); super.setMoney(super.getMoney() - payMoney); System.out.println("余额"+ super.getMoney()); } }SilverCard类package com.liu.day02.abstract_Test; public class SilverCard extends Card{ @Override public void pay(double payMoney) { payMoney = 0.85 * payMoney; System.out.println("应支付"+payMoney); super.setMoney(super.getMoney() - payMoney); System.out.println("余额"+ super.getMoney()); } }测试:package com.liu.day02.abstract_Test; public class Test1 { public static void main(String[] args) { GoldenCard goldenCard = new GoldenCard(); goldenCard.setMoney(1000.0); goldenCard.pay(200); SilverCard silverCard = new SilverCard(); silverCard.setMoney(1000.0); silverCard.pay(200); } }输出结果应支付160.0 余额840.0 应支付170.0 余额830.06.3 抽象类的特征和注意事项类有的成员(成员变量、方法、构造器)抽象类都具备。抽象类中不一定有抽象方法,有抽象方法的类一定是抽象类。一个类继承了抽象类必须重写完抽象类的全部抽象方法,否则这个类也必须定义成抽象类。不能用abstract修饰变量、代码块、构造器。最重要的特征:得到了抽象方法,失去了创建对象的能力(有得有失)6.4 模板方法模式使用场景说明:当系统中出现同一个功能多处在开发,而该功能中大部分代码是一样的,只有其中部分可能不同的时候。模板方法模式实现步骤把功能定义成一个所谓的模板方法,放在抽象类中,模板方法中只定义通用且能确定的代码。模板方法中不能决定的功能定义成抽象方法让具体子类去实现。案例需求:Student类package com.liu.day02.abstract_Test1; public abstract class Student { //将该方法加上final关键字,使其变成最终方法 public final void write(){ System.out.println("题目:《我的爸爸》"); System.out.println("我的爸爸是一个普通的平凡人。"); System.out.println(text()); System.out.println("我爱我的爸爸。"); } //编写抽象方法 public abstract String text(); }StudentPupil类package com.liu.day02.abstract_Test1; public class StudentPupil extends Student{ @Override public String text() { return "谢谢你,父亲。"; } }StudentMiddle类package com.liu.day02.abstract_Test1; public class StudentMiddle extends Student{ @Override public String text() { return "愿您一生健康。"; } }Test1类package com.liu.day02.abstract_Test1; public class Test1 { public static void main(String[] args) { StudentMiddle studentMiddle = new StudentMiddle(); studentMiddle.write(); StudentPupil studentPupil = new StudentPupil(); studentPupil.write(); } }模板方法模式解决了什么问题?提高了代码的复用性。模板方法已经定义了通用结构,模板方法把不能确定的部分定义成抽象方法,交给子类实现,因此,使用者只需要关心自己需要实现的功能即可。7 接口7.1 接口定义7.2 接口的用法接口是用来被类实现(implements) 的,实现接口的类称为实现类。实现类可以理解成所谓的子类。一个类实现接口,必须重写完全部接口的全部抽象方法,否则这个类需要定义成抽象类。7.3 接口小结类和类的关系: 单继承。类和接口的关系: 多实现。接口和接口的关系:多继承,一个接口可以同时继承多个接口。接口多继承的作用:规范合并,整合多个接口为同一个接口,便于子类实现。学习记录31 多态1.1 多态的定义同类型的对象,执行同一个行为,会表现出不同的行为特征。1.2 多态的前提有继承/实现关系;有父类引用指向子类对象;有方法重写。1.3 多态的常见形式父类类型 对象名称 = new 子类构造器;接口 对象名称 = new 实现类构造器;1.4 多态中成员访问特点方法调用:编译看左边,运行看右边。变量调用:编译看左边,运行也看左边。( 多态侧重行为多态)1.5 多态的优劣势优势:在多态形式下,右边对象可以实现解耦合,便于扩展和维护。定义方法的时候,使用父类型作为参数,该方法就可以接收这父类的一切子类对象,体现出多态的扩展性与便利。劣势:多态下不能使用子类的独有功能。1.6 多态类型转换自动类型转换(从子到父):子类对象赋值给父类类型的变量指向。强制类型转换(从父到子):此时必须进行强制类型转换: 子类对象变量= (子类)父类类型的变量。作用:可以解决多态下的劣势,可以实现调用子类独有的功能。注意:如果转型后的类型和对象真实类型不是同一种类型,那么在转换的时候就会出现ClassCastExceptionJava建议强转转换前使用instanceof判断当前对象的真实类型,再进行强制转换。1.7 案例需求:使用面向对象编程模拟:设计一个电脑对象,可以安装2个USB设备。鼠标:被安装时可以完成接入、调用点击功能、拔出功能。键盘:被安装时可以完成接入、调用打字功能、拔出功能。USB接口实现:package com.liu.day03.polymorphic; public interface USB { void connect(); void unConnect(); }电脑类实现:package com.liu.day03.polymorphic; public class Computer { public void installUsb(USB usb){ usb.connect(); if (usb instanceof Mouse){ Mouse mouse = (Mouse) usb; mouse.click(); }else if (usb instanceof Keyboard){ Keyboard keyboard = (Keyboard) usb; keyboard.typing(); } usb.unConnect(); } }鼠标类实现:package com.liu.day03.polymorphic; public class Mouse implements USB{ @Override public void connect() { System.out.println("鼠标连接成功"); } public void click(){ System.out.println("鼠标点击功能"); } @Override public void unConnect() { System.out.println("鼠标断开连接"); } }键盘类实现:package com.liu.day03.polymorphic; public class Keyboard implements USB{ @Override public void connect() { System.out.println("键盘连接成功"); } public void typing(){ System.out.println("键盘打字功能"); } @Override public void unConnect() { System.out.println("键盘断开连接"); } }测试:package com.liu.day03.polymorphic; public class Test2 { public static void main(String[] args) { USB usb1 = new Mouse(); USB usb2 = new Keyboard(); Computer computer = new Computer(); computer.installUsb(usb1); computer.installUsb(usb2); } } 运行结果: 鼠标连接成功 鼠标点击功能 鼠标断开连接 键盘连接成功 键盘打字功能 键盘断开连接 Process finished with exit code 02 内部类2.1 内部类定义内部类就是定义在一个类里面的类,里面的类可以理解成(寄生),外部类可以理解成(宿主)。2.2 内部类的使用场景、作用当一个事物的内部,还有一个部分需要一个完整的结构进行描述,而这个内部的完整的结构又只为外部事物提供服务,那么整个内部的完整结构可以选择使用内部类来设计。内部类通常可以方便访问外部类的成员,包括私有的成员。内部类提供了更好的封装性,内部类本身就可以用private protectecd等修饰,封装性可以做更多控制。2.3 静态内部类 了解即可有static修饰,属于外部类本身。它的特点和使用与普通类是完全一样的, 类有的成分它都有,只是位置在别人里面而已。可以直接访问外部类的静态成员,不能直接访问外部类的实例成员。package com.liu.day03.innerClass; public class StaticInnerClass { private String className; public static int classId; public final int CLASS_ID = 2; //静态内部类 public static class InClass{ //实例成员 private String name; public InClass() { } public InClass(String name) { this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } //静态成员 public static int id; //常量 public final int USER_ID = 12; //静态内部类能调用主类 public void getInnerClass(){ //只能调用静态成员~ classId = 10; } } public static void main(String[] args) { //创建静态内部类对象的格式 InClass aClass = new StaticInnerClass.InClass(); } }总结:静态内部类可以把其当作一个静态成员,静态成员只能访问静态的成员不能访问实例成员。2.4 实例内部类 了解即可无static修饰,属于外部类的对象。可以直接访问外部类的静态成员,实例方法中可以直接访问外部类的实例成员。成员内部类创建对象:外部类名.内部类名对象名= new外部类构造器.new内部类枸造器();package com.liu.day03.innerClass; public class OnClass { private String name; private static int id; public static void setId(){ id = 12; } //实例内部类 public class InnerClass{ private String inName; //这里使用的是8版本 所以不能在实例内部类中创建静态成员 //public static int inId; public void getOnClass(){ //可以调用实例成员 name = "zhenxi"; //可以调用静态成员 id = 13; setId(); } } public static void main(String[] args) { //实例内部类创建对象的格式 InnerClass innerClass = new OnClass().new InnerClass(); innerClass.getOnClass(); } }注意:在成员内部类中访问所在外部类对象,格式:外部类名.this。package com.liu.day03.innerClass; public class People { private int heartbeat = 90; public class Heart{ private int heartbeat = 100; public void show(){ int heartbeat = 120; System.out.println(heartbeat); //调用局部变量 System.out.println(this.heartbeat);//调用内部类的实例对象 System.out.println(People.this.heartbeat);//调用外部类的实例对象 } } public static void main(String[] args) { Heart heart = new People().new Heart(); heart.show(); } } /*运行结果 120 100 90 */2.5 局部内部类 了解即可局部内部类放在方法、代码块、构造器等执行体中。package com.liu.day03.innerClass; public class Test { static { //静态代码块中的局部内部类 class Dog{ private String dogName; public Dog() { } public Dog(String dogName) { this.dogName = dogName; } } } public static void main(String[] args) { //局部内部类 创建于main方法中 class Cat{ private String name; private int id; public Cat() { } public Cat(String name, int id) { this.name = name; this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getId() { return id; } public void setId(int id) { this.id = id; } } Cat cat = new Cat("橘猫",2); } }局部内部类的类文件名为:外部类$N内部类.class。2.6 匿名内部类 重点本质上是一个没有名字的局部内部类,定义在方法中、代码块中等。作用:方便创建子类对象,最终目的为了简化代码编写。匿名内部类是一个没有名字的内部类。匿名内部类写出来就会产生一个匿名内部类的对象。匿名内部类的对象类型相当于是当前new的那个的类型的子类类型。匿名内部类可以作为方法的实际参数进行传输。例一:package com.liu.day03.innerClass; public class Test2 { public static void main(String[] args) { Swimming s = new Swimming() { @Override public void swim() { System.out.println("学生快乐的自由泳"); } }; go(s); Swimming s1 = new Swimming() { @Override public void swim() { System.out.println("老师贼快~~~~~"); } }; go(s1); System.out.println("--------------"); go(new Swimming() { @Override public void swim() { System.out.println("运动员🏊的贼快啊~~~~~"); } }); } public static void go(Swimming s){ System.out.println("开始。。。"); s.swim(); System.out.println("结束。。。"); } } interface Swimming{ void swim(); }例二:swing编程中的应用package com.liu.day03.innerClass; import javax.swing.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; public class Test3 { public static void main(String[] args) { // 1、创建窗口 JFrame win = new JFrame("登录界面"); JPanel panel = new JPanel(); win.add(panel); // 2、创建一个按钮对象 JButton btn = new JButton("登录"); // 注意:讲解匿名内部类的使用 btn.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { JOptionPane.showMessageDialog(win, "点击事件"); } }); // btn.addActionListener( e -> JOptionPane.showMessageDialog(win, "简略的匿名内部类") ); // 3、把按钮对象添加到桌布上展示 panel.add(btn); // 4、展示窗口 win.setSize(400, 300); win.setLocationRelativeTo(null); win.setVisible(true); } }3 Object类Object类的作用:一个类要么默认继承了0bject类,要么间接继承了0bject类,0bject类是Java中的祖宗类。Object类的方法是一切子类都可以直接使用的。Object类中的常用方法:方法名说明pubilc String toString()Returns a string representation of the object.默认是返回当前对象在堆内存中的地址信息:类的全限名@内存地址public Boolean equals(Object obj)Indicates whether some other object is "equal to" this one.默认是比较当前对象与另一个对象的地址是否相同,相同返回true,不同返回false当未重写toString以及equals方法时:Student类:package com.liu.day03.object_Test; public class Student { private String name; private int id; public Student(String name, int id) { this.name = name; this.id = id; } public Student() { } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getId() { return id; } public void setId(int id) { this.id = id; } }测试类:Test:package com.liu.day03.object_Test; public class Test { public static void main(String[] args) { Student student = new Student("zhenxi",2022935775); Student student1 = new Student("成龙",2022935776); System.out.println(student.toString()); System.out.println(student.equals(student1)); } } /** 运行结果: com.liu.day03.object_Test.Student@1540e19d false */当重写toString方法和equals方法: @Override public boolean equals(Object o) { //首先判断传进来的对象是不是原对象 if (this == o) return true; //判断传进来的对象是否为空,以及判断是否与本对象是不是同一个类型 if (o == null || getClass() != o.getClass()) return false; //将传进来的对象转成Student类型对象 Student student = (Student) o; //判断对象中的内容是否相同 return id == student.id && Objects.equals(name, student.name); } //将对象的内容输出出去 @Override public String toString() { return "Student{" + "name='" + name + '\'' + ", id=" + id + '}'; }再次进行测试:package com.liu.day03.object_Test; public class Test { public static void main(String[] args) { Student student = new Student("zhenxi",2022935777); Student student1 = new Student("成龙",2022935776); System.out.println(student.toString()); System.out.println(student1.toString()); System.out.println(student.equals(student)); System.out.println(student.equals(student1)); } } /** 运行结果: Student{name='zhenxi', id=2022935775} Student{name='成龙', id=2022935776} true false */4 Objects类Objects仍然继承Object类。该类最关键的就是它的equals方法,官方在进行字符串比较时,没有对象自己的equals方法,而是选择了Objects的equals方法来比较两个对象。在使用Objects的equals方法比较两个对象时,底层会先进行非空判断,从而可以避免空指针异常,再进行equals比较。方法名说明public static boolean equals(Object a,Object b)Returns true if the arguments are equal to each other and false otherwise.这是Objects类的equals方法: public static boolean equals(Object a, Object b) { return (a == b) || (a != null && a.equals(b)); }主要用于解决两个对象进行比较时出现NullPointerException这个问题:可以看到阿里的Java开发手册里的强制要求:public class Test3 { public static void main(String[] args) { String s1 = null; String s2 = "liu"; s1.equals(s2); } } /** 运行结果 Exception in thread "main" java.lang.NullPointerException at com.liu.day03.object_Test.Test3.main(Test3.java:7) */当使用Objects的静态equals方法时:public class Test3 { public static void main(String[] args) { String s1 = null; String s2 = "liu"; System.out.println(Objects.equals(s1, s2)); } } /** 运行结果: false */5 StringBuilder类StringBuilder是一个可变的字符串类,我们可以把它看成是一个对象容器。作用:提高字符串的操作效率,如拼接、修改等。StringBuilder构造器说明public StringBuilder()Constructs a string builder with no characters in it and an initial capacity of 16 characters.构造一个其中没有字符且初始容量为 16 个字符的字符串构建器。public StringBuilder(String str)Constructs a string builder initialized to the contents of the specified string.构造一个初始化为指定字符串内容的字符串构建器。StringBuilder常用方法说明public StringBuilder append(任意类型)Appends the string representation of the xxxx argument to the sequence.将 xxxx 参数的字符串表示形式附加到序列中。public StringBuilder reverse()Causes this character sequence to be replaced by the reverse of the sequence.导致此字符序列被相反的序列替换。public int length()Returns the length (character count).返回长度public String toString()Returns a string representing the data in this sequence.返回表示此序列中数据的字符串。方法使用:public class Test { public static void main(String[] args) { StringBuilder stringBuilder = new StringBuilder(); StringBuilder str = new StringBuilder("zhenxi"); str.append(2022).append(935).append(775).append("珍惜大学"); //注意最终还是需要通过toString转换成String类型 System.out.println(str.toString()); System.out.println(str.reverse()); System.out.println(str.length()); } } /** 运行结果: zhenxi2022935775珍惜大学 学大尔哈齐齐5775392202畅刘 18 */与String类比较图:总结:String:内容是不可变的、拼接字符串性能差。StringBuilder:内容是可变的、拼接字符串性能好、代码优雅。需求:设计一个方法用于输出任意整型数组的内容,要求输出成如下格式:”该数组内容为: [11, 22, 33, 44,55]”package com.liu.day03.object_Test; public class StringBuilderTest2 { public static void main(String[] args) { int[] arr1 = null; System.out.println(toString(arr1)); int[] arr2 = {10, 22, 19}; System.out.println(toString(arr2)); int[] arr3 = {}; System.out.println(toString(arr3)); } // 1、定义方法接收任意整型数组,返回数组内容格式 public static String toString(int[] arr){ if(arr != null){ // 2、开始拼接内容。 StringBuilder sb = new StringBuilder("["); for (int i = 0; i < arr.length; i++) { sb.append(arr[i] ).append(i == arr.length - 1 ? "" : ", "); } sb.append("]"); return sb.toString(); }else { return null; } } }6 Math类Math类的常用方法说明public static int abs(int a)Returns the absolute value of an int value.返回 int 值的绝对值。public static double ceil(double a)Returns the smallest (closest to negative infinity) double value that is greater than or equal to the argument and is equal to a mathematical integer.返回大于或等于参数且等于数学整数的最小(最接近负无穷大)double 值。public static double floor(double a)Returns the largest (closest to positive infinity) double value that is less than or equal to the argument and is equal to a mathematical integer.Returns the length (character count). 返回小于或等于参数且等于数学整数的最大(最接近正无穷大)double 值。返回长度(字符数)。public static int round(float a)Returns the closest int to the argument, with ties rounding to positive infinity.返回最接近参数的 int,并舍入为正无穷大。public static int max(int a, int b)Returns the greater of two int values.返回两个 int 值中的较大者。public static int min(int a, int b)Returns the smaller of two int values.返回两个 int 值中较小的一个。static double pow(double a, double b)Returns the value of the first argument raised to the power of the second argument.返回第一个参数的第二个参数次幂的值。static double random()Returns a double value with a positive sign, greater than or equal to 0.0 and less than 1.0.返回一个带正号的“double”值,大于或等于“0.0”且小于“1.0”package com.liu.day03.math; public class Test { public static void main(String[] args) { System.out.println(Math.abs(-1)); System.out.println(Math.ceil(1.01)); System.out.println(Math.ceil(1.54)); System.out.println(Math.floor(3.01)); System.out.println(Math.floor(3.89)); System.out.println(Math.max(12, 10)); System.out.println(Math.min(12, 10)); System.out.println(Math.pow(2, 5)); int a = (int)(Math.random()*100)+1; System.out.println(a); } } /** 运行结果: 1 2.0 2.0 3.0 3.0 12 10 32.0 29 Process finished with exit code 0 */7 System类和Math类一样,不能实例化,直接用类名调用即可。System常用方法说明public static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length)Copies an array from the specified source array, beginning at the specified position, to the specified position of the destination array.将指定源数组中的数组从指定位置开始复制到目标数组的指定位置。public static long currentTimeMillis()Returns the current time in milliseconds.以毫秒为单位返回当前时间。public class Test { public static void main(String[] args) { long startTime = System.currentTimeMillis(); System.out.println(startTime); for (int i = 0; i < 100000; i++) { System.out.println("i:"+i); } long endTime = System.currentTimeMillis(); System.out.println("运行时间为:"+(endTime-startTime)/1000.0+"s"); } } /** i:99999 运行时间为:1.141s */package com.liu.day03.System; import java.util.Arrays; public class SystemDemo { public static void main(String[] args) { /** arraycopy(Object src, int srcPos, Object dest, int destPos, int length) 参数一:被拷贝的数组 参数二:从哪个索引位置开始拷贝 参数三:复制的目标数组 参数四:粘贴位置 参数五:拷贝元素的个数 */ int[] arr1 = {10, 20, 30, 40, 50, 60, 70}; int[] arr2 = new int[6]; // [0, 0, 0, 0, 0, 0] ==> [0, 0, 40, 50, 60, 0] System.arraycopy(arr1, 3, arr2, 2, 3); System.out.println(Arrays.toString(arr2)); } } /** 运行结果: [0, 0, 40, 50, 60, 0] */8 BigDecima类浮点型运算的时候直接加减乘除时可能会出现数据失真(精度问题)。用来对超过16位有效位的数进行精确的计算。此类可以完成大的小数操作,而且也可以使用此类进行精确的四舍五入。从而解决浮点型运算数据失真的问题。BigDecima类的常用方法说明public BigDecimal add(BigDecimal augend)加法public BigDecimal subtract(BigDecimal subtrahend)减法public BigDecimal multiply(BigDecimal multiplicand)乘法public BigDecimal divide(BigDecimal divisor)除法关于BigDecima类的一些注意事项:(图片中的内容来自阿里Java开发规范)package com.liu.day03.System; import java.math.BigDecimal; import java.math.RoundingMode; import java.text.NumberFormat; public class BigDecimalDemo { public static void main(String[] args) { // 浮点型运算的时候直接+ * / 可能会出现数据失真(精度问题)。 System.out.println(0.09 + 0.01); System.out.println(1.0 - 0.32); System.out.println(1.015 * 100); System.out.println(1.301 / 100); System.out.println("-------------------------"); double a = 0.1; double b = 0.2; double c = a + b; System.out.println(c); System.out.println("--------------------------"); // 包装浮点型数据成为大数据对象 BigDeciaml BigDecimal a1 = BigDecimal.valueOf(a); BigDecimal b1 = BigDecimal.valueOf(b); BigDecimal c1 = a1.add(b1); // BigDecimal c1 = a1.subtract(b1); // BigDecimal c1 = a1.multiply(b1); // BigDecimal c1 = a1.divide(b1); System.out.println(c1); // 目的:double double rs = c1.doubleValue(); System.out.println(rs); // 注意事项:BigDecimal是一定要精度运算的 BigDecimal a11 = BigDecimal.valueOf(10.0); BigDecimal b11 = BigDecimal.valueOf(3.0); /** 参数一:除数 参数二:保留小数位数 参数三:舍入模式 */ BigDecimal c11 = a11.divide(b11, 2, RoundingMode.HALF_UP); // 3.3333333333 System.out.println(c11); } }学习记录41 Date类Date类的对象在Java中代表的是当前所在系统的此刻日期时间。案例:请计算出当前时间往后走1小时121秒之后的时间是多少。package com.liu.day04.dateDemo; import java.util.Date; public class DateDemo01 { public static void main(String[] args) { //Date的无参构造 Date date = new Date(); System.out.println(date); long time = date.getTime(); System.out.println(time); System.out.println(System.currentTimeMillis()); //有参构造器 System.out.println(new Date(time)); //请计算出当前时间往后走1小时121秒之后的时间是多少。 long time2 = System.currentTimeMillis(); time2 += (60*60+121)*1000; //通过Date的有参构造进行转换成日期对象 Date date1 = new Date(time2); System.out.println(date1); //或者使用setTime函数 Date date2 = new Date(); date2.setTime(time2); System.out.println(date2); } }2 SimpleDateFormat类可以对Date对象或时间毫秒值格式化成我们喜欢的时间形式。也可以把字符串的时间形式解析成日期对象。案例一:SimpleDateFormat类的基本方法。package com.liu.day04.simpleDateFormat; import java.text.SimpleDateFormat; import java.util.Date; public class SimpleDateFormatDemo { public static void main(String[] args) { //日期对象 Date date = new Date(); System.out.println(date); //格式化日期对象 SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss EEE a"); String s = simpleDateFormat.format(date); System.out.println(s); //求121秒后的格式日期 long l = System.currentTimeMillis() + 121*1000; System.out.println(simpleDateFormat.format(l)); } }需求二:使用上述方法,计算出2021年08月06日11点11分11秒,往后走2天14小时49分06秒后的时间是多少。package com.liu.day04.simpleDateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; public class SimpleDateFormatDemo2 { public static void main(String[] args) throws ParseException { //请计算出2021年08月06日11点11分11秒,往后走2天14小时49分06秒后的时间是多少。 String dateStr = "2021年08月06日 11:11:11"; //将字符串解析为日期对象 SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss"); Date date = simpleDateFormat.parse(dateStr); long time = date.getTime()+(2L*24*60*60+14*60*60+49*60+6)*1000; System.out.println(simpleDateFormat.format(time)); } }需求三:小贾下单并付款的时间为: 2020年11月11日0:03:47小皮下单并付款的时间为: 2020年11月11日0:10:11用代码说明这两位同学有没有参加上秒杀活动?package com.liu.day04.simpleDateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; public class SimpleDateFormatDemo3 { public static void main(String[] args) throws ParseException { //开始时间和结束时间 String startTime="2020年11月11日 00:00:00"; String endTime="2020年11月11日 00:10:00"; //小贾 小皮 String xiaoJia="2020年11月11日 00:03:47"; String xiaoPi="2020年11月11日 00:10:11"; //解析时间 SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss"); Date date1 = simpleDateFormat.parse(startTime); Date date2 = simpleDateFormat.parse(endTime); Date date3 = simpleDateFormat.parse(xiaoJia); Date date4 = simpleDateFormat.parse(xiaoPi); //时间判断函数 if (date3.after(date1) && date3.before(date2)){ System.out.println("小贾发货成功"); }else { System.out.println("小贾抢购失败"); } if (date4.after(date1) && date4.before(date2)){ System.out.println("小皮发货成功"); }else { System.out.println("小皮抢购失败"); } } }注意点:阿里开发手册的规范要遵循3 Calendar类Calendar代表了系统此刻日期对应的日历对象。Calendar是一个抽象类,不能直接创建对象。案例:Calendar类的常用方法使用。package com.liu.day04.simpleDateFormat; import java.util.Calendar; import java.util.Date; public class CalendarDemo { public static void main(String[] args) { //获取日历对象 Calendar calendar = Calendar.getInstance(); System.out.println(calendar); //获取日历信息 System.out.println(calendar.get(Calendar.YEAR)); System.out.println(calendar.get(Calendar.MONTH)+1); System.out.println(calendar.get(Calendar.DAY_OF_MONTH)); //修改日历的信息 calendar.set(Calendar.YEAR,2023); System.out.println(calendar.get(Calendar.YEAR)); //64天2分后的日历 calendar.add(Calendar.DAY_OF_MONTH,64); calendar.add(Calendar.MINUTE,2); Date time = calendar.getTime(); System.out.println(time); } }4 新增日期API4.1 LocalTime/LocalDate/LocalDateTimeLocalDate常用方法:package com.liu.day04.dateDemo; import java.time.LocalDate; import java.time.Month; public class Demo01LocalDate { public static void main(String[] args) { // 1、获取本地日期对象。 LocalDate nowDate = LocalDate.now(); System.out.println("今天的日期:" + nowDate);//今天的日期: int year = nowDate.getYear(); System.out.println("year:" + year); int month = nowDate.getMonthValue(); System.out.println("month:" + month); int day = nowDate.getDayOfMonth(); System.out.println("day:" + day); //当年的第几天 int dayOfYear = nowDate.getDayOfYear(); System.out.println("dayOfYear:" + dayOfYear); //星期 System.out.println(nowDate.getDayOfWeek()); System.out.println(nowDate.getDayOfWeek().getValue()); //月份 System.out.println(nowDate.getMonth());//AUGUST System.out.println(nowDate.getMonth().getValue());//8 LocalDate bt = LocalDate.of(1991, 11, 11); System.out.println(bt);//直接传入对应的年月日 System.out.println(LocalDate.of(1991, Month.NOVEMBER, 11));//相对上面只是把月换成了枚举 } }LocalTime常用方法:package com.liu.day04.dateDemo; import java.time.LocalDateTime; import java.time.LocalTime; import java.time.Month; public class Demo02LocalTime { public static void main(String[] args) { // 1、获取本地时间对象。 LocalTime nowTime = LocalTime.now(); System.out.println("今天的时间:" + nowTime);//今天的时间: int hour = nowTime.getHour();//时 System.out.println("hour:" + hour);//hour: int minute = nowTime.getMinute();//分 System.out.println("minute:" + minute);//minute: int second = nowTime.getSecond();//秒 System.out.println("second:" + second);//second: int nano = nowTime.getNano();//纳秒 System.out.println("nano:" + nano);//nano: System.out.println(LocalTime.of(8, 20));//时分 System.out.println(LocalTime.of(8, 20, 30));//时分秒 System.out.println(LocalTime.of(8, 20, 30, 150));//时分秒纳秒 LocalTime mTime = LocalTime.of(8, 20, 30, 150); System.out.println(LocalDateTime.of(1991, 11, 11, 8, 20)); System.out.println(LocalDateTime.of(1991, Month.NOVEMBER, 11, 8, 20)); System.out.println(LocalDateTime.of(1991, 11, 11, 8, 20, 30)); System.out.println(LocalDateTime.of(1991, Month.NOVEMBER, 11, 8, 20, 30)); System.out.println(LocalDateTime.of(1991, 11, 11, 8, 20, 30, 150)); System.out.println(LocalDateTime.of(1991, Month.NOVEMBER, 11, 8, 20, 30, 150)); } }LocalDateTime常用方法:package com.liu.day04.dateDemo; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; public class Demo03LocalDateTime { public static void main(String[] args) { // 日期 时间 LocalDateTime nowDateTime = LocalDateTime.now(); System.out.println("今天是:" + nowDateTime);//今天是: System.out.println(nowDateTime.getYear());//年 System.out.println(nowDateTime.getMonthValue());//月 System.out.println(nowDateTime.getDayOfMonth());//日 System.out.println(nowDateTime.getHour());//时 System.out.println(nowDateTime.getMinute());//分 System.out.println(nowDateTime.getSecond());//秒 System.out.println(nowDateTime.getNano());//纳秒 //日:当年的第几天 System.out.println("dayOfYear:" + nowDateTime.getDayOfYear());//dayOfYear:249 //星期 System.out.println(nowDateTime.getDayOfWeek());//THURSDAY System.out.println(nowDateTime.getDayOfWeek().getValue());//4 //月份 System.out.println(nowDateTime.getMonth());//SEPTEMBER System.out.println(nowDateTime.getMonth().getValue());//9 LocalDate ld = nowDateTime.toLocalDate(); System.out.println(ld); LocalTime lt = nowDateTime.toLocalTime(); System.out.println(lt.getHour()); System.out.println(lt.getMinute()); System.out.println(lt.getSecond()); } }4.2 Instantpackage com.liu.day04.dateDemo; import java.time.Instant; import java.time.LocalDate; import java.time.ZoneId; import java.util.Date; public class Demo05Instant { public static void main(String[] args) { // 1、得到一个Instant时间戳对象 Instant instant = Instant.now(); System.out.println(instant); // 2、系统此刻的时间戳怎么办? Instant instant1 = Instant.now(); System.out.println(instant1.atZone(ZoneId.systemDefault())); // 3、如何去返回Date对象 Date date = Date.from(instant); System.out.println(date); Instant i2 = date.toInstant(); System.out.println(i2); } }4.3 DateTimeFormatterpackage com.liu.day04.dateDemo; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; public class Demo06DateTimeFormat { public static void main(String[] args) { // 本地此刻 日期时间 对象 LocalDateTime ldt = LocalDateTime.now(); System.out.println(ldt); // 解析/格式化器 DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss EEE a"); // 正向格式化 System.out.println(dtf.format(ldt)); // 逆向格式化 System.out.println(ldt.format(dtf)); // 解析字符串时间 DateTimeFormatter dtf1 = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); // 解析当前字符串时间成为本地日期时间对象 LocalDateTime ldt1 = LocalDateTime.parse("2019-11-11 11:11:11" , dtf1); System.out.println(ldt1); System.out.println(ldt1.getDayOfYear()); } }4.4 Periodpackage com.liu.day04.dateDemo; import java.time.LocalDate; import java.time.Period; public class Demo07Period { public static void main(String[] args) { // 当前本地的年月日 LocalDate today = LocalDate.now(); System.out.println(today);// // 生日的的年月日 LocalDate birthDate = LocalDate.of(1998, 10, 13); System.out.println(birthDate); Period period = Period.between(birthDate, today);//第二个参数减第一个参数 System.out.println(period.getYears()); System.out.println(period.getMonths()); System.out.println(period.getDays()); } }4.5 Durationpackage com.liu.day04.dateDemo; import java.time.Duration; import java.time.LocalDateTime; public class Demo08Duration { public static void main(String[] args) { // 本地日期时间对象。 LocalDateTime today = LocalDateTime.now(); System.out.println(today); // 出生的日期时间对象 LocalDateTime birthDate = LocalDateTime.of(2022,7,23,01,00,00); System.out.println(birthDate); Duration duration = Duration.between(today,birthDate);//第二个参数减第一个参数 System.out.println(duration.toDays());//两个时间差的天数 System.out.println(duration.toHours());//两个时间差的小时数 System.out.println(duration.toMinutes());//两个时间差的分钟数 System.out.println(duration.toMillis());//两个时间差的毫秒数 System.out.println(duration.toNanos());//两个时间差的纳秒数 } }4.6 ChronoUnitpackage com.liu.day04.dateDemo; import java.time.LocalDateTime; import java.time.temporal.ChronoUnit; public class Demo09ChronoUnit { public static void main(String[] args) { // 本地日期时间对象:此刻的 LocalDateTime today = LocalDateTime.now(); System.out.println(today); // 生日时间 LocalDateTime birthDate = LocalDateTime.of(1990,10,1, 10,50,59); System.out.println(birthDate); System.out.println("相差的年数:" + ChronoUnit.YEARS.between(birthDate, today)); System.out.println("相差的月数:" + ChronoUnit.MONTHS.between(birthDate, today)); System.out.println("相差的周数:" + ChronoUnit.WEEKS.between(birthDate, today)); System.out.println("相差的天数:" + ChronoUnit.DAYS.between(birthDate, today)); System.out.println("相差的时数:" + ChronoUnit.HOURS.between(birthDate, today)); System.out.println("相差的分数:" + ChronoUnit.MINUTES.between(birthDate, today)); System.out.println("相差的秒数:" + ChronoUnit.SECONDS.between(birthDate, today)); System.out.println("相差的毫秒数:" + ChronoUnit.MILLIS.between(birthDate, today)); System.out.println("相差的微秒数:" + ChronoUnit.MICROS.between(birthDate, today)); System.out.println("相差的纳秒数:" + ChronoUnit.NANOS.between(birthDate, today)); System.out.println("相差的半天数:" + ChronoUnit.HALF_DAYS.between(birthDate, today)); System.out.println("相差的十年数:" + ChronoUnit.DECADES.between(birthDate, today)); System.out.println("相差的世纪(百年)数:" + ChronoUnit.CENTURIES.between(birthDate, today)); System.out.println("相差的千年数:" + ChronoUnit.MILLENNIA.between(birthDate, today)); System.out.println("相差的纪元数:" + ChronoUnit.ERAS.between(birthDate, today)); } }5 包装类包装类的功能:package com.liu.day04.packingClass; public class PackingDemo { public static void main(String[] args) { int a = 10;//基本类型 Integer a1 = 11;//引用类型 System.out.println(a1); Integer a2 = 10;//自动装箱 System.out.println(a2); int a3 = a2; System.out.println(a3);//自动拆箱 //包装类最有用的:将字符串转化成数字类型 int i = Integer.parseInt("12"); double v = Double.parseDouble("12.31"); System.out.println(i+20); System.out.println(v+0.11); //可以不使用上述方法,可以直接只使用valueOf方法 int i1 = Integer.valueOf("12"); double v1 = Double.valueOf("12.31"); System.out.println(i1+20); System.out.println(v1+0.11); } }6 正则表达式正则表达式可以用一些规定的字符来制定规则,并用来校验数据格式的合法性。案例一:校验qq号码的正确性。package com.liu.day04.regex; public class RegexDemo { public static void main(String[] args) { //需求:校验qq号码 必须全部数字6 -20位 System.out.println(checkQQ("773395")); System.out.println(checkQQ("773395726111")); System.out.println(checkQQ2("773395")); System.out.println(checkQQ2("7733")); } //不使用正则表达式方法 public static boolean checkQQ(String qq){ if (qq == null || qq.length()<6 || qq.length()>20){ return false; } //判断qq中是否全是数字 for (int i = 0; i < qq.length(); i++) { char c = qq.charAt(i); if (c<'0' || c>'9'){ return false; } } return true; } //使用正则表达式 public static boolean checkQQ2(String qq){ return qq!= null && qq.matches("\\d{6,20}"); } }案例二:正则表达式一些基本规定的使用package com.liu.day04.regex; public class RegexDemo02 { public static void main(String[] args) { //public boolean matches(String regex):判断是否与正则表达式匹配,匹配返回true System.out.println("a".matches("[abc]")); // true System.out.println("z".matches("[abc]")); // false // 不能出现a b c ^表示不存在 System.out.println("a".matches("[^abc]")); // false System.out.println("z".matches("[^abc]")); // true System.out.println("a".matches("\\d")); // false System.out.println("3".matches("\\d")); // true //默认匹配一个字符 System.out.println("111".matches("\\d")); // false System.out.println("z".matches("\\w")); // true System.out.println("2".matches("\\w")); // true //默认匹配一个字符 System.out.println("30".matches("\\w")); // false //\w判断是否是一个数字 System.out.println("你".matches("\\w")); //false //\W判断一个非单词字符 System.out.println("你".matches("\\W")); // true System.out.println("---------------------------------"); // 以上正则匹配只能校验单个字符。 // 校验密码 // 必须是数字 字母 下划线 至少 6位 System.out.println("23456sqxS".matches("\\w{6,}")); System.out.println("a557".matches("\\w{6,}")); // 验证码 必须是数字和字符 必须是4位 System.out.println("135s".matches("[a-zA-Z0-9]{4}")); System.out.println("553_".matches("[a-zA-Z0-9]{4}")); System.out.println("886s".matches("[\\w&&[^_]]{4}")); System.out.println("996_".matches("[\\w&&[^_]]{4}")); } }案例三:请编写程序模拟用户输入手机号码、验证格式正确,并给出提示,直到格式输入正确为止。请编写程序模拟用户输入邮箱号码、验证格式正确,并给出提示,直到格式输入正确为止。请编写程序模拟用户输入电话号码、验证格式正确,并给出提示,直到格式输入正确为止。package com.liu.day04.regex; import java.util.Arrays; import java.util.Scanner; public class RegexTest3 { public static void main(String[] args) { checkPhone(); checkEmail(); checkTel(); } public static void checkTel(){ Scanner sc = new Scanner(System.in); while (true) { System.out.println("请您输入您的电话号码:"); String tel = sc.next(); if(tel.matches("0\\d{2,6}-?\\d{5,20}")){ System.out.println("格式正确,注册完成!"); break; }else { System.out.println("格式有误!"); } } } public static void checkEmail(){ Scanner sc = new Scanner(System.in); while (true) { System.out.println("请您输入您的注册邮箱:"); String email = sc.next(); if(email.matches("\\w{1,30}@[a-zA-Z0-9]{2,20}(\\.[a-zA-Z0-9]{2,20}){1,2}")){ System.out.println("邮箱格式正确,注册完成!"); break; }else { System.out.println("格式有误!"); } } } public static void checkPhone(){ Scanner sc = new Scanner(System.in); while (true) { System.out.println("请您输入您的注册手机号码:"); String phone = sc.next(); // 判断手机号码的格式是否正确 if(phone.matches("1[3-9]\\d{9}")){ System.out.println("手机号码格式正确,注册完成!"); break; }else { System.out.println("格式有误!"); } } } } /** 运行结果: 请您输入您的注册手机号码: 13461460555 手机号码格式正确,注册完成! 请您输入您的注册邮箱: 773395777@qq.com 邮箱格式正确,注册完成! 请您输入您的电话号码: 0120-41123333 格式正确,注册完成! */案例四:package com.liu.day04.regex; public class RegexDemo04 { public static void main(String[] args) { String names = "你好jdlajdl努力少年jdoisajdo相信自己ldjsaoijdo"; String[] arrs = names.split("\\w+"); for (int i = 0; i < arrs.length; i++) { System.out.println(arrs[i]); } String names2 = names.replaceAll("\\w+", " "); System.out.println(names2); } } /* 运行结果: 你好 努力少年 相信自己 你好 努力少年 相信自己 **/案例五:正则表达式支持对信息的爬取(了解即可)package com.liu.day04.regex; import java.util.regex.Matcher; import java.util.regex.Pattern; public class RegexDemo05 { public static void main(String[] args) { String rs = "来黑马程序学习Java,电话020-43422424,或者联系邮箱" + "itcast@itcast.cn,电话18762832633,0203232323" + "邮箱bozai@itcast.cn,400-100-3233 ,4001003232"; // 需求:从上面的内容中爬取出 电话号码和邮箱。 // 1、定义爬取规则,字符串形式 String regex = "(\\w{1,30}@[a-zA-Z0-9]{2,20}(\\.[a-zA-Z0-9]{2,20}){1,2})|(1[3-9]\\d{9})" + "|(0\\d{2,6}-?\\d{5,20})|(400-?\\d{3,9}-?\\d{3,9})"; // 2、把这个爬取规则编译成匹配对象。 Pattern pattern = Pattern.compile(regex); // 3、得到一个内容匹配器对象 Matcher matcher = pattern.matcher(rs); // 4、开始寻找对象 while (matcher.find()) { String rs1 = matcher.group(); System.out.println(rs1); } } }7 Arrays类package com.liu.day04.ArrayDemo; import java.util.Arrays; public class ArraysDemo1 { public static void main(String[] args) { // 目标:学会使用Arrays类的常用API ,并理解其原理 int[] arr = {10, 21, 50, 23, 24, 100}; System.out.println(arr); // 1、返回数组内容的 toString(数组) System.out.println(Arrays.toString(arr)); // 2、排序的API(默认自动对数组元素进行升序排序) Arrays.sort(arr); System.out.println(Arrays.toString(arr)); // 3、二分搜索技术(前提数组必须排好序才支持,否则出bug) int index = Arrays.binarySearch(arr, 55); System.out.println(index); // 返回不存在元素的规律: - (应该插入的位置索引 + 1) int index2 = Arrays.binarySearch(arr, 22); System.out.println(index2); // 注意:数组如果么有排好序,可能会找不到存在的元素,从而出现bug!! int[] arr2 = {12, 36, 34, 25 , 13, 24, 234, 100}; System.out.println(Arrays.binarySearch(arr2 , 36)); } }package com.liu.day04.ArrayDemo; import java.util.Arrays; import java.util.Comparator; public class ArraysDemo2 { public static void main(String[] args) { // 目标:自定义数组的排序规则:Comparator比较器对象。 // 1、Arrays的sort方法对于有值特性的数组是默认升序排序 int[] ages = {34, 12, 42, 23}; Arrays.sort(ages); System.out.println(Arrays.toString(ages)); // 2、需求:降序排序!(自定义比较器对象,只能支持引用类型的排序!!) Integer[] ages1 = {34, 12, 42, 23}; /** 参数一:被排序的数组 必须是引用类型的元素 参数二:匿名内部类对象,代表了一个比较器对象。 */ Arrays.sort(ages1, new Comparator<Integer>() { @Override public int compare(Integer o1, Integer o2) { // 指定比较规则。 // if(o1 > o2){ // return 1; // }else if(o1 < o2){ // return -1; // } // return 0; // return o1 - o2; // 默认升序 return o2 - o1; // 降序 } }); System.out.println(Arrays.toString(ages1)); Student[] students = new Student[3]; students[0] = new Student("吴磊",23 , 175.5); students[1] = new Student("谢鑫",18 , 185.5); students[2] = new Student("王亮",20 , 195.5); System.out.println(Arrays.toString(students)); // Arrays.sort(students); // 没有重写比较器直接运行奔溃 Arrays.sort(students, new Comparator<Student>() { @Override public int compare(Student o1, Student o2) { // 自己指定比较规则 // return o1.getAge() - o2.getAge(); // 按照年龄升序排序! // return o2.getAge() - o1.getAge(); // 按照年龄降序排序!! // return Double.compare(o1.getHeight(), o2.getHeight()); //比较浮点型可以这样写 升序 return Double.compare(o2.getHeight(), o1.getHeight()); //比较浮点型可以这样写 降序 } }); System.out.println(Arrays.toString(students)); } }8 选择排序、二分查找算法8.1 选择排序每轮选择当前位置,开始找出后面的较小值与该位置交换。确定总共需要选择几轮:数组的长度-1。控制每轮从以前位置为基准,与后面元素选择几次。选择排序代码实现:package com.liu.day04.est; import java.util.Arrays; public class Test1 { public static void main(String[] args) { // 1、定义数组 int[] arr = {5, 1, 3, 2}; // 0 1 2 3 // 2、定义一个循环控制选择几轮: arr.length - 1 for (int i = 0; i < arr.length - 1; i++) { // 3、定义内部循环,控制选择几次 for (int j = i + 1; j < arr.length; j++) { // 当前位:arr[i] // 如果有比当前位数据更小的,则交换 if(arr[i] > arr[j]) { int temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } } } System.out.println(Arrays.toString(arr)); } }8.2 二分查找定义变量记录左边和右边位置。使用while循环控制查询( 条件是左边位置<= 右边位置)。循环内部获取中间元素索引。判断当前要找的元素如果大于中间元素,左边位置=中间索引+1。判断当前要找的元素如果小于中间元素,右边位置=中间索引-1。判断当前要找的元素如果等于中间元素,返回当前中间元素索引。二分查找代码实现:package com.liu.day04.est; public class Test2 { public static void main(String[] args) { // 1、定义数组 int[] arr = {10, 14, 16, 25, 28, 30, 35, 88, 100}; System.out.println(binarySearch(arr , 35)); System.out.println(binarySearch(arr , 350)); } public static int binarySearch(int[] arr, int data){ // 1、定义左边位置 和 右边位置 int left = 0; int right = arr.length - 1; // 2、开始循环,折半查询。 while (left <= right){ // 取中间索引 int middleIndex = (left + right) / 2; // 3、判断当前中间位置的元素和要找的元素的大小情况 if(data > arr[middleIndex]) { // 往右边找,左位置更新为 = 中间索引+1 left = middleIndex + 1; }else if(data < arr[middleIndex]) { // 往左边找,右边位置 = 中间索引 - 1 right = middleIndex - 1; }else { return middleIndex; } } return -1; // 没有找到该元素返回-1 } }9 Lambda表达式Lambda表达式是JDK 8开始后的一种新语法形式。作用:简化匿名内部类的代码写法。格式:(匿名内部类被重写方法的形参列表) -> { 被重写方法的方法体代码。 }注: ->是语法形式,无实际含义注意: Lambda表达式只能简化函数式接口的匿名内部类的写法形式。什么是函数式接口?1.首先必须是接口、其次接口中有且仅有一个抽象方法的形式。2.通常我们会在接口上加上一个@FunctionalInterface注解,标记该接口必须是满足函数式接口。Lambda表达式的使用:package com.liu.day04.LambdaDemo; public class LambdaDemo1 { public static void main(String[] args) { //Lambda的标准格式简化匿名内部类的代码形式 Dog a = new Dog() { @Override public void run() { System.out.println("狗跑的贼快~~~~~"); } }; a.run(); } } abstract class Dog{ public abstract void run(); }Lambda表达式的省略写法(进一步在Lambda表达式的基础上继续简化)参数类型可以省略不写。如果只有一个参数,参数类型可以省略,同时()也可以省略。如果Lambda表达式的方法体代码只有一行代码。可以省略大括号不写,同时要省略分号!如果Lambda表达式的方法体代码只有一行代码。可以省略大括号不写。此时,如果这行代码是return语句,必须省略return不写,同时也必须省略";"不写。package com.liu.day04.LambdaDemo; import javax.swing.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.Arrays; import java.util.Comparator; public class LambdaDemo3 { public static void main(String[] args) { JFrame win = new JFrame("登录界面"); JButton btn = new JButton("按钮"); //最简Lambda表达式 btn.addActionListener( e -> System.out.println("点一下") ); win.add(btn); win.setSize(400, 300); win.setVisible(true); } }学习记录51 集合1.1 集合与数组的对比相同点:集合和数组都是容器。不同点:对于数组来说:数组定义完成并启动后,类型确定、长度固定。在进行增删数据操作的时候,数组是不太合适的,增删数据都需要放弃原有数组或者移位。当业务数据的个数是固定的,且都是同一批数据类型的时候,可以采取定义数组存储。对于集合来说:集合的大小不固定,启动后可以动态变化,类型也可以选择不固定。集合更像气球。集合非常适合做元素的增删操作。集合中只能存储引用类型数据,如果要存储基本类型数据可以选用包装类。当数据的个数不确定,需要进行增删元素的时候,一般使用集合来存储。1.2 集合类的体系结构集合主要分为两类:Collection单列集合,每个元素(数据)只包含一个值。Map双列集合,每个元素包含两个值(键值对)。2 Collection单列集合2.1 Collection集合体系2.2 Collection集合特点List系列集合:添加的元素是有序、可重复、有索引。ArrayList,LinekdList:有序、可重复、有索引。Set系列集合:添加的元素是无序、不重复、无索引。HashSet:无序、不重复、无索引;LinkedHashSet: 有序、不重复、无索引。TreeSet:按照大小默认升序排序、不重复、无索引。集合都是支持泛型的,可以在编译阶段约束集合只能操作某种数据类型。package com.liu.day05.Collection; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; public class CollectionDemo01 { public static void main(String[] args) { //典型的多态写法 使用左边父类的方法。有序、可重复、有索引 Collection list = new ArrayList<>(); list.add("zhenxi"); list.add(23); list.add("珍惜大学"); list.add("计算机与控制工程学院"); list.add(true); list.add(177.1); System.out.println(list); //无序、不重复、无索引 Collection set = new HashSet<>(); set.add("zhenxi"); set.add("zhenxi"); set.add(123); set.add(false); System.out.println(set); //对泛型的支持 Collection<Integer> list1 = new ArrayList<>(); list1.add(11); list1.add(22); //报错:不兼容的类型: java.lang.String无法转换为java.lang.Integer //list1.add("dd"); System.out.println(list1); } }2.3 Collection集合常用APIpackage com.liu.day05.Collection; import java.util.ArrayList; import java.util.Collection; public class CollectionDemo02 { public static void main(String[] args) { //Collection常用API Collection<String> collection = new ArrayList<>(); //add方法 collection.add("zhenxi"); collection.add("珍惜大学"); System.out.println(collection); //remove方法 collection.remove("zhenxi"); System.out.println(collection); //contains包含方法 System.out.println(collection.contains("珍惜大学")); //isEmpty方法 System.out.println(collection.isEmpty()); //size方法 System.out.println(collection.size()); //toArray方法 Object[] array = collection.toArray(); System.out.println(array); for (Object o : array) { System.out.println(o); } //clear方法 collection.clear(); System.out.println(collection.isEmpty()); } }2.4 Collection集合遍历方式遍历方式一:迭代器方式遍历就是一个一个的把容器中的元素访问一遍。迭代器在Java中的代表是Iterator,迭代器是集合的专用遍历方式。package com.liu.day05.Collection; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; public class CollectionDemo3 { public static void main(String[] args) { //遍历方式一:迭代器遍历 Collection<Integer> list = new ArrayList<>(); list.add(1); list.add(2); list.add(3); list.add(4); list.add(5); System.out.println(list); //获取迭代器对象 Iterator<Integer> iterator = list.iterator(); while (iterator.hasNext()){ //判断是否有元素存在,存在继续循环 Integer next = iterator.next(); System.out.println(next); // 自动拆箱 } } }遍历方式二:增强for循环增强for循环:既可以遍历集合也可以遍历数组。它是JDK5之后出现的,其内部原理是一个lterator迭代器 ,遍历集合相当于是迭代器的简化写法。package com.liu.day05.Collection; import java.util.ArrayList; import java.util.Collection; public class CollectionDemo04 { public static void main(String[] args) { //遍历方式二:For each遍历 Collection<Integer> list = new ArrayList<>(); list.add(1); list.add(2); list.add(3); list.add(4); list.add(5); System.out.println(list); //for each for (Integer integer : list) { System.out.println(integer); } } }遍历方式三:Lambda表达式package com.liu.day05.Collection; import java.util.ArrayList; import java.util.Collection; import java.util.function.Consumer; public class CollectionDemo04 { public static void main(String[] args) { //遍历方式三:lambda遍历 Collection<Integer> list = new ArrayList<>(); list.add(1); list.add(2); list.add(3); list.add(4); list.add(5); System.out.println(list); //lambda表达式 list.forEach(new Consumer<Integer>() { @Override public void accept(Integer integer) { System.out.println(integer); } }); //简化 list.forEach(integer -> System.out.println(integer)); } }2.5 Collection集合存储自定义类型自定义学生类:package com.liu.day05.Collection; public class Student { private String name; private int id; public Student() { } public Student(String name, int id) { this.name = name; this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getId() { return id; } public void setId(int id) { this.id = id; } @Override public String toString() { return "Student{" + "name='" + name + '\'' + ", id=" + id + '}'; } }集合存储并遍历:package com.liu.day05.Collection; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; public class CollectionDemo05 { public static void main(String[] args) { //存储自定义类型 Collection<Student> list = new ArrayList<>(); list.add(new Student("zhenxi",2022956775)); list.add(new Student("海珠",2022956776)); list.add(new Student("贾哥",2022956777)); System.out.println(list); //对自定义类型进行遍历 for (Student student : list) { System.out.println(student); } Iterator<Student> iterator = list.iterator(); while (iterator.hasNext()){ Student student = iterator.next(); System.out.println(student); } list.forEach(student -> System.out.println(student)); } }3 List集合3.1 List集合特点ArrayList、LinekdList:有序,可重复,有索引。有序:存储和取出的元素顺序一致。有索引:可以通过索引操作元素。可重复:存储的元素可以重复。3.2 List底层原理ArrayList底层是基于数组实现的:根据索引定位元素快,增删相对慢。LinkedList底层基于双链表实现的:查询元素慢,增删首尾元素是非常快的。3.3 List集合的API1、List集合继承了Collection集合的全部功能,"同时因为List系列集合有索引"。2、因为List集合多了索引,所以多了很多按照索引操作元素的功能。3、ArrayList实现类集合底层基于数组存储数据的,查询快,增删慢。public void add(int index, E element): 将指定的元素,添加到该集合中的指定位置上。public E get(int index):返回集合中指定位置的元素。public E remove(int index): 移除列表中指定位置的元素, 返回的是被移除的元素。public E set(int index, E element):用指定元素替换集合中指定位置的元素,返回更新前的元素值。package com.liu.day05.Collection; import java.util.ArrayList; import java.util.List; public class ListDemo01 { public static void main(String[] args) { // 1.创建一个ArrayList集合对象: // List:有序,可重复,有索引的。 List<String> list = new ArrayList<>(); // 一行经典代码! list.add("zhenxi"); list.add("珍惜大学"); list.add("计算机与控制工程学院"); list.add("2022"); list.add("电子信息"); // 2.在某个索引位置插入元素。 list.add(2, "2022967775"); System.out.println(list); // 3.根据索引删除元素,返回被删除元素 System.out.println(list.remove(1)); System.out.println(list); // 4.根据索引获取元素:public E get(int index):返回集合中指定位置的元素。 System.out.println(list.get(1)); // 5.修改索引位置处的元素: public E set(int index, E element) System.out.println(list.set(0, "努力!")); System.out.println(list); } } /** 运行结果: [zhenxi, 珍惜大学, 2022967775, 计算机与控制工程学院, 2022, 电子信息] 珍惜大学 [zhenxi, 2022967775, 计算机与控制工程学院, 2022, 电子信息] 2022967775 zhenxi [努力!, 2022967775, 计算机与控制工程学院, 2022, 电子信息] Process finished with exit code 0 */3.4 List集合遍历方式(1)for循环。(独有的,因为List有索引)。(2)迭代器。(3)foreach。(4)Lambda表达式package com.liu.day05.Collection; import java.util.ArrayList; import java.util.Iterator; import java.util.List; public class ListDemo02 { public static void main(String[] args) { List<String> list = new ArrayList<>(); list.add("zhenxi"); list.add("珍惜大学"); list.add("计算机与控制工程学院"); list.add("2022"); list.add("电子信息"); //for循环 for (int i = 0; i < list.size(); i++) { String s = list.get(i); System.out.println(s); } //迭代器方式 Iterator<String> it = list.iterator(); while (it.hasNext()){ String ele = it.next(); System.out.println(ele); } //for each for (String ele : list) { System.out.println(ele); } //Lambda表达式 list.forEach(s -> { System.out.println(s); }); } }3.5 LinkedList集合特有功能底层数据结构是双链表,查询慢,首尾操作的速度是极快的,所以多了很多首尾操作的特有API。案例:使用LinkedList模仿栈和队列package com.liu.day05.Collection; import java.util.LinkedList; import java.util.List; public class ListDemo03 { public static void main(String[] args) { // LinkedList可以完成队列结构,和栈结构 (双链表) // 1、做一个队列: LinkedList<String> queue = new LinkedList<>(); // 入队 queue.addLast("1号"); queue.addLast("2号"); queue.addLast("3号"); System.out.println(queue); // 出队 System.out.println(queue.getFirst()); System.out.println(queue.removeFirst()); System.out.println(queue.removeFirst()); System.out.println(queue); // 2、做一个栈 LinkedList<String> stack = new LinkedList<>(); // 入栈 压栈 (push) stack.push("第1颗子弹"); stack.push("第2颗子弹"); stack.push("第3颗子弹"); stack.push("第4颗子弹"); System.out.println(stack); // 出栈 弹栈 pop System.out.println(stack.pop()); System.out.println(stack.pop()); System.out.println(stack.pop()); System.out.println(stack); } }3.6 List集合修改异常问题迭代器遍历集合且直接用集合删除元素的时候可能出现。增强for循环遍历集合且直接用集合删除元素的时候可能出现。package com.liu.day05.Collection; import java.util.ArrayList; import java.util.Iterator; import java.util.List; public class ListDemo04 { public static void main(String[] args) { List<String> list = new ArrayList<>(); list.add("zhenxi"); list.add("珍惜大学"); list.add("计算机与控制工程学院"); list.add("2022"); list.add("电子信息"); //迭代器遍历集合且直接用集合删除元素的时候可能出现。 Iterator<String> it = list.iterator(); while (it.hasNext()){ String ele = it.next(); if ("zhenxi".equals(ele)){ //报错ConcurrentModificationException 并发修改异常 //list.remove(ele); it.remove(); //要用迭代器的删除方法 } } System.out.println(list); //for each for (String ele : list) { //报错ConcurrentModificationException 并发修改异常 //list.remove(ele); //该问题无法解决,因为foreach调用的是迭代器,但是无法直接调用迭代器的remove方法。 System.out.println(ele); } } }4 泛型4.1 泛型概述与特点泛型:是JDK5中引入的特性,可以在编译阶段约束操作的数据类型,并进行检查。泛型的格式: <数据类型>;注意:泛型只能支持引用数据类型。集合体系的全部接口和实现类都是支持泛型的使用的。统一数据类型。把运行时期的问题提前到了编译期间,避免了强制类型转换可能出现的异常,因为编译阶段类型就能确定下来。4.2 自定义泛型类定义类时同时定义了泛型的类就是泛型类。泛型类的格式:修饰符class类名<泛型变量>{ }。此处泛型变量T可以随便写为任意标识,常见的如E、T、K、V等。作用:编译阶段可以指定数据类型,类似于集合的作用。需求:模拟ArrayList定义一个MyArrayList ,关注泛型设计package com.liu.day05.genericity; public class Test { public static void main(String[] args) { MyArrayList<String> list = new MyArrayList<>(); list.add("zhenxi"); list.add("珍惜大学"); list.add("计算机与控制工程学院"); list.add("2022"); list.add("电子信息"); System.out.println(list); MyArrayList<Integer> list2 = new MyArrayList<>(); list2.add(1); list2.add(2); list2.add(3); list2.remove(4); System.out.println(list2); } }package com.liu.day05.genericity; import java.util.ArrayList; public class MyArrayList<E> { private ArrayList lists = new ArrayList(); public void add(E e){ lists.add(e); } public void remove(E e){ lists.remove(e); } @Override public String toString() { return lists.toString(); } }4.3 自定义泛型方法定义方法时同时定义了泛型的方法就是泛型方法。泛型方法的格式:修饰符 <泛型变量> 方法返回值 方法名称(形参列表){}作用:方法中可以使用泛型接收一切实际类型的参数,方法更具备通用性。案例:给你任何一个类型的数组,都能返回它的内容。也就是实现Arrays.toString(数组)的功能!package com.liu.day05.genericity; public class GenericDemo { public static void main(String[] args) { String[] names = {"zhenxi", "成龙", "帅博"}; printArray(names); Integer[] ages = {23, 22, 21}; printArray(ages); Integer[] ages2 = getArr(ages); String[] names2 = getArr(names); } public static <T> T[] getArr(T[] arr){ return arr; } public static <T> void printArray(T[] arr){ if(arr != null){ StringBuilder sb = new StringBuilder("["); for (int i = 0; i < arr.length; i++) { sb.append(arr[i]).append(i == arr.length - 1 ? "" : ", "); } sb.append("]"); System.out.println(sb); }else { System.out.println(arr); } } }4.4 自定义泛型接口使用了泛型定义的接口就是泛型接口。泛型接口的格式:修饰符interface接口名称<泛型变量>{}作用:泛型接口可以让实现类选择当前功能需要操作的数据类型案例:教务系统,提供一个接口可约束一定要完成数据(学生,老师)的增删改查操作package com.liu.day05.genericity_Impl; public interface Data<E> { void add(E e); void delete(int id); void update(E e); E queryById(int id); }package com.liu.day05.genericity_Impl; public class StudentData implements Data<Student>{ @Override public void add(Student student) { } @Override public void delete(int id) { } @Override public void update(Student student) { } @Override public Student queryById(int id) { return null; } }package com.liu.day05.genericity_Impl; public class TeacherData implements Data<Teacher>{ @Override public void add(Teacher teacher) { } @Override public void delete(int id) { } @Override public void update(Teacher teacher) { } @Override public Teacher queryById(int id) { return null; } }4.5 泛型通配符?可以在使用泛型的时候代表一切类型。ETKV是在定义泛型的时候使用的。案例:开发一个极品飞车的游戏,所有的汽车都能一起参与比赛。package com.liu.day05.genericity_Impl; import java.util.ArrayList; public class GenericDemo { public static void main(String[] args) { ArrayList<BMW> bmws = new ArrayList<>(); bmws.add(new BMW()); bmws.add(new BMW()); bmws.add(new BMW()); go(bmws); ArrayList<BENZ> benzs = new ArrayList<>(); benzs.add(new BENZ()); benzs.add(new BENZ()); benzs.add(new BENZ()); go(benzs); ArrayList<Dog> dogs = new ArrayList<>(); dogs.add(new Dog()); dogs.add(new Dog()); dogs.add(new Dog()); // go(dogs); } /** 所有车比赛 但该静态方法传进来的必须是继承Car的子类 */ public static void go(ArrayList<? extends Car> cars){ } } class Dog{ } class BENZ extends Car{ } class BMW extends Car{ } // 父类 class Car{ }学习记录61 Set集合无序:存取顺序不一致。不重复:可以去除重复。无索引:没有带索引的方法,所以不能使用普通for循环遍历,也不能通过索引来获取元素。HashSet:无序、不重复、无索引。LinkedHashSet:有序、不重复、无索引。TreeSet:排序、不重复、无索引。package com.liu.day06.Map; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.Set; import java.util.TreeSet; public class SetDemo { public static void main(String[] args) { Set<String> set = new HashSet<>(); set.add("zhenxi"); set.add("珍惜大学"); set.add("计算机与控制工程学院"); set.add("2022"); set.add("2022"); set.add("电子信息"); System.out.println(set); Set<String> linkedHashSet = new LinkedHashSet<>(); linkedHashSet.add("zhenxi"); linkedHashSet.add("珍惜大学"); linkedHashSet.add("计算机与控制工程学院"); linkedHashSet.add("2022"); linkedHashSet.add("2022"); linkedHashSet.add("电子信息"); System.out.println(linkedHashSet); Set<String> treeSet = new TreeSet<>(); treeSet.add("zhenxi"); treeSet.add("珍惜大学"); treeSet.add("计算机与控制工程学院"); treeSet.add("2022"); treeSet.add("2022"); treeSet.add("电子信息"); System.out.println(treeSet); } } /** 运行结果: [电子信息, 计算机与控制工程学院, 珍惜大学, zhenxi, 2022] [zhenxi, 珍惜大学, 计算机与控制工程学院, 2022, 电子信息] [2022, zhenxi, 电子信息, 计算机与控制工程学院, 珍惜大学] */2 HashSet集合HashSet集合底层采取哈希表存储的数据。哈希表是一种对于增删改查数据性能都较好的结构。2.1 哈希值是JDK根据对象的地址,按照某种规则算出来的int类型的数值。0bject类的APIpublic int hashCode():返回对象的哈希值。对象的哈希值特点同一个对象多次调用hashCode()方法返回的哈希值是相同的。默认情况下,不同对象的哈希值是不同的。package com.liu.day06.HashDemo; import java.util.Objects; public class Student { private String name; private int id; public Student() { } public Student(String name, int id) { this.name = name; this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getId() { return id; } public void setId(int id) { this.id = id; } @Override public String toString() { return "Student{" + "name='" + name + '\'' + ", id=" + id + '}'; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Student student = (Student) o; return id == student.id && Objects.equals(name, student.name); } //重写的hashCode方法 @Override public int hashCode() { return Objects.hash(name, id); } }package com.liu.day06.HashDemo; public class HashSetDemo01 { public static void main(String[] args) { Student student = new Student(); int hashCode = student.hashCode(); System.out.println(hashCode); Student student1 = new Student("zhenxi",23); Student student2 = new Student("zhenxi",23); Student student3 = new Student("成龙",23); System.out.println(student1.hashCode()); System.out.println(student2.hashCode()); System.out.println(student3.hashCode()); /* * 当在Student类中未重写hashCode方法时的运行结果: * 1735600054 21685669 2133927002 * */ /* * 当重写了hashCode方法后运行结果为: * 21128011 21128011 25392495 * * */ } }2.2 底层原理JDK8之前的,哈希表:底层使用数组+链表组成JDK8开始后,哈希表:底层采用数组+链表+红黑树组成。哈希表的详细流程创建一个默认长度16,默认加载因为0.75的数组,数组名table。根据元素的哈希值跟数组的长度计算出应存入的位置。判断当前位置是否为null,如果是null直接存入,如果位置不为null,表示有元素,则调用equals方法比较属性值,如果一样,则不存,如果不一样,则存入数组。当数组存满到16*0.75=12时,就自动扩容,每次扩容原先的两倍。HashSet去重复原理创建一个默认长度16的数组,数组名table。根据元素的哈希值跟数组的长度求余计算出应存入的位置(哈希算法)。判断当前位置是否为null,如果是null直接存入。如果位置不为null,表示有元素,则调用equals方法比较。如果一样,则不存,如果不一样,则存入数组。需求创建一个存储学生对象的集合,存储多个学生对象,使用程序实现在控制台遍历该集合。要求:学生对象的成员变量值相同,我们就认为是同一个对象。package com.liu.day06.HashDemo; import java.util.HashSet; import java.util.Set; public class HashSetDemo02 { public static void main(String[] args) { Set<Student> set = new HashSet<>(); set.add(new Student("zhenxi",23)); set.add(new Student("成龙",23)); set.add(new Student("zhenxi",23)); //遍历 for (Student student : set){ System.out.println(student); } /*若是没有重写hashCode方法:运行结果为: * Student{name='成龙', id=23} Student{name='zhenxi', id=23} Student{name='zhenxi', id=23}*/ /* * 若是重写了hashCode方法:运行结果为: *Student{name='zhenxi', id=23} Student{name='成龙', id=23} * */ } }3 LinkedHashSet集合是HashSet的子类。有序、不重复、无索引。这里的有序指的是保证存储和取出的元素顺序一致。原理:底层数据结构是依然哈希表,只是每个元素又额外的多了一个双链表的机制记录存储的顺序。package com.liu.day06.HashDemo; import java.util.LinkedHashSet; import java.util.Set; public class LinkedHashSetDemo { public static void main(String[] args) { Set<String> set = new LinkedHashSet<>(); set.add("zhenxi"); set.add("珍惜大学"); set.add("计算机与控制工程学院"); set.add("2022"); set.add("2022"); set.add("电子信息"); System.out.println(set); for (String s:set){ System.out.println(s); } } } /** 运行结果: [zhenxi, 珍惜大学, 计算机与控制工程学院, 2022, 电子信息] zhenxi 珍惜大学 计算机与控制工程学院 2022 电子信息 */4 TreeSet集合不重复、无索引、可排序。可排序:按照元素的大小默认升序(有小到大)排序。TreeSet集合底层是基于红黑树的数据结构实现排序的,增删改查性能都较好。对于数值类型: Integer ,Double,官方默认按照大小进行升序排序。对于字符串类型:默认按照首字符的编号升序排序。对于自定义类型如Student对象,TreeSet无法直接排序。需要制定排序规则。方式一:让自定义的类(如学生类)实现Comparable接口重写里面的compareTo方法来定制比较规则。方式二:TreeSet集合有参数构造器,可以设置Comparator接口对应的比较器对象,来定制比较规则。package com.liu.day06.HashDemo; import java.util.Comparator; import java.util.Set; import java.util.TreeSet; public class TreeSetDemo { public static void main(String[] args) { Set<Integer> set = new TreeSet<>(); set.add(2); set.add(3); set.add(1); set.add(4); System.out.println(set);//[1, 2, 3, 4] Set<Double> doubleSet = new TreeSet<>(); doubleSet.add(3.1); doubleSet.add(4.1); doubleSet.add(6.6); doubleSet.add(0.1); System.out.println(doubleSet);//[0.1, 3.1, 4.1, 6.6] Set<String> stringSet = new TreeSet<>(); stringSet.add("a"); stringSet.add("A"); stringSet.add("/"); stringSet.add("你"); System.out.println(stringSet);//[/, A, a, 你] //第一种方式 Set<Student> studentSet = new TreeSet<>(); studentSet.add(new Student("zhenxi",23)); studentSet.add(new Student("成龙",24)); studentSet.add(new Student("zhenxi",23)); System.out.println(studentSet); //第二种方式 Set<Apple> appleSet = new TreeSet<>(new Comparator<Apple>() { @Override public int compare(Apple o1, Apple o2) { return o1.getWeight() - o2.getWeight(); } }); appleSet.add(new Apple("富士康",50)); appleSet.add(new Apple("富士康",50)); appleSet.add(new Apple("星空灰",30)); appleSet.add(new Apple("高调白",40)); System.out.println(appleSet); } } /** 运行结果: [1, 2, 3, 4] [0.1, 3.1, 4.1, 6.6] [/, A, a, 你] [Student{name='zhenxi', id=23}, Student{name='成龙', id=24}] [Apple{name='星空灰', weight=30}, Apple{name='高调白', weight=40}, Apple{name='富士康', weight=50}] */Student类:package com.liu.day06.HashDemo; import java.util.Objects; public class Student implements Comparable<Student>{ private String name; private int id; public Student() { } public Student(String name, int id) { this.name = name; this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getId() { return id; } public void setId(int id) { this.id = id; } @Override public String toString() { return "Student{" + "name='" + name + '\'' + ", id=" + id + '}'; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Student student = (Student) o; return id == student.id && Objects.equals(name, student.name); } @Override public int hashCode() { return Objects.hash(name, id); } @Override public int compareTo(Student o) { return this.id - o.id; } }Apple类:package com.liu.day06.HashDemo; import java.util.Objects; public class Apple { private String name; private int weight; public Apple() { } public Apple(String name, int weight) { this.name = name; this.weight = weight; } @Override public String toString() { return "Apple{" + "name='" + name + '\'' + ", weight=" + weight + '}'; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Apple apple = (Apple) o; return weight == apple.weight && Objects.equals(name, apple.name); } @Override public int hashCode() { return Objects.hash(name, weight); } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getWeight() { return weight; } public void setWeight(int weight) { this.weight = weight; } }5 关于Collection集合如何选用如果希望元素可以重复,又有索引,索引查询要快?用ArrayList集合, 基于数组的。(用的最多)如果希望元素可以重复,又有索引,增删首尾操作快?用LinkedList集合, 基于链表的。如果希望增删改查都快,但是元素不重复、无序、无索引。用HashSet集合, 基于哈希表的。如果希望增删改查都快,但是元素不重复、有序、无索引。用LinkedHashSet集合, 基于哈希表和双链表。、如果要对对象进行排序。用TreeSet集合, 基于红黑树。后续也可以用Lit集合实现排序。6 可变参数可变参数用在形参中可以接收多个数据。可变参数的格式: 数据类型...参数名称。传输参数非常灵活,方便。可以不传输参数,可以传输1个或者多个,也可以传输一个数组。可变参数在方法内部本质上就是一个数组。一个形参列表中可变参数只能有一个。可变参数必须放在形参列表的最后面。package com.liu.day06.HashDemo; import java.util.Arrays; public class MethodDemo { public static void main(String[] args) { sum(); sum(1); sum(1,2); sum(1,2,3); avg(1,12); avg(2,12,13); avg(3,12,15); avg(4,12,19,20); } public static void sum(int...nums){ System.out.println(nums.length); System.out.println(Arrays.toString(nums)); } public static void avg(int sum,int...nums){ System.out.println(nums.length); System.out.println(Arrays.toString(nums)); } }7 Collections工具类java.utils.Collections:是集合工具类。Collections并不属于集合,是用来操作集合的工具类。Collections有几个常用的API: public static <T> boolean addAll(Collection<? super T> c, T... elements):给集合对象批量添加元素! public static void shuffle(List<?> list) :打乱集合顺序。 public static <T> void sort(List<T> list):将集合中元素按照默认规则排序。 public static <T> void sort(List<T> list,Comparator<? super T> c):将集合中元素按照指定规则排序。package com.liu.day06.CollectionsDemo; import java.util.*; public class CollectionsDemo01 { public static void main(String[] args) { List<String> names = new ArrayList<>(); Collections.addAll(names, "zhenxi","珍惜大学","计算机与控制工程学院","2022","电子信息"); System.out.println(names); // 2、public static void shuffle(List<?> list) :打乱集合顺序。 Collections.shuffle(names); System.out.println(names); // 3、 public static <T> void sort(List<T> list):将集合中元素按照默认规则排序。 (排值特性的元素) List<Integer> list = new ArrayList<>(); Collections.addAll(list, 23,41,21,31); System.out.println(list); Collections.sort(list); System.out.println(list); //4. public static <T> void sort(List<T> list,Comparator<? super T> c):将集合中元素按照指定规则排序。 List<Apple> apples = new ArrayList<>(); // 可以重复! apples.add(new Apple("红富士", "红色", 9.9, 500)); apples.add(new Apple("星空绿", "绿色", 19.9, 300)); apples.add(new Apple("橄榄青", "青色", 29.9, 400)); apples.add(new Apple("格调黄", "黄色", 0.9, 500)); //方式一:Apple类重写比较规则与上个案例student类重写相同 /* @Override public int compareTo(Apple o) { return this.price- o.price; } **/ // 方式二:sort方法自带比较器对象 // Collections.sort(apples, new Comparator<Apple>() { // @Override // public int compare(Apple o1, Apple o2) { // return Double.compare(o1.getPrice() , o2.getPrice()); // 按照价格排序!! // } // }); Collections.sort(apples, ( o1, o2) -> Double.compare(o1.getPrice() , o2.getPrice()) ); System.out.println(apples); } } /** 运行结果: [zhenxi, 珍惜大学, 计算机与控制工程学院, 2022, 电子信息] [珍惜大学, 电子信息, zhenxi, 2022, 计算机与控制工程学院] [23, 41, 21, 31] [21, 23, 31, 41] [Apple{name='格调黄', color='黄色', price=0.9, weight=500}, Apple{name='红富士', color='红色', price=9.9, weight=500}, Apple{name='星空绿', color='绿色', price=19.9, weight=300}, Apple{name='橄榄青', color='青色', price=29.9, weight=400}] */8 综合案例:斗地主游戏package com.liu.day06.HashDemo; public class Card { private String size; private String color; private int index; // 牌的真正大小 public Card(){ } public Card(String size, String color, int index) { this.size = size; this.color = color; this.index = index; } public String getSize() { return size; } public void setSize(String size) { this.size = size; } public String getColor() { return color; } public void setColor(String color) { this.color = color; } public int getIndex() { return index; } public void setIndex(int index) { this.index = index; } @Override public String toString() { return size + color; } }package com.liu.day06.HashDemo; import java.util.*; public class GameDemo { //定义一个静态的集合存储54张牌对象 public static List<Card> allCards = new ArrayList<>(); //做牌:定义静态代码块初始化牌数据 static { //定义点数:个数确定,类型确定,使用数组 String[] sizes = {"3","4","5","6","7","8","9","10","J","Q","K","A","2"}; //定义花色:个数确定,类型确定,使用数组 String[] colors = {"♠", "♥", "♣", "♦"}; //组合点数和花色 int index = 0; // 记录牌的大小 for (String size : sizes) { index++; for (String color : colors) { //封装成一个牌对象。 Card c = new Card(size, color, index); //存入到集合容器中去 allCards.add(c); } } //大小王存入到集合对象中去 "👲" , "🃏" Card c1 = new Card("" , "🃏", ++index); Card c2 = new Card("" , "👲",++index); Collections.addAll(allCards , c1 , c2); System.out.println("新牌:" + allCards); } public static void main(String[] args) { //洗牌 Collections.shuffle(allCards); System.out.println("洗牌后:" + allCards); // 发牌(定义三个玩家,每个玩家的牌也是一个集合容器) List<Card> liuchang = new ArrayList<>(); List<Card> shuang = new ArrayList<>(); List<Card> chenglong = new ArrayList<>(); //开始发牌(从牌集合中发出51张牌给三个玩家,剩余3张作为底牌) // allCards = [🃏, A♠, 5♥, 2♠, 2♣, Q♣, 👲, Q♠ ... // i 0 1 2 3 4 5 6 7 % 3 for (int i = 0; i < allCards.size() - 3; i++) { // 先拿到当前牌对象 Card c = allCards.get(i); if(i % 3 == 0) { // 请阿冲接牌 liuchang.add(c); }else if(i % 3 == 1){ // 请阿鸠 shuang.add(c); }else if(i % 3 == 2){ // 请盈盈接牌 chenglong.add(c); } } //拿到最后三张底牌(把最后三张牌截取成一个子集合) List<Card> lastThreeCards = allCards.subList(allCards.size() - 3 , allCards.size()); System.out.println("三张底牌:" + lastThreeCards); //给玩家的牌排序(从大到小 可以自己先试试怎么实现) sortCards(liuchang); sortCards(shuang); sortCards(chenglong); //输出玩家的牌: System.out.println("zhenxi:" + liuchang); System.out.println("爽:" + shuang); System.out.println("成龙:" + chenglong); } private static void sortCards(List<Card> cards) { // cards = [J♥, A♦, 3♥, 🃏, 5♦, Q♥, 2♥ Collections.sort(cards, new Comparator<Card>() { @Override public int compare(Card o1, Card o2) { // o1 = J♥ // o2 = A♦ // 知道牌的大小,才可以指定规则 return o2.getIndex() - o1.getIndex(); } }); } }9 Map集合9.1 Map集合的概述Map集合是一种双列集合,每个元素包含两个数据。Map集合的每个元素的格式: key=value(键 值对元素)。Map集合也被称为“键值对集合”。Map集合的完整格式: {key1=value1,key2=value2 ,key3=value3,...}9.2 Map集合体系特点Map集合的特点都是由键决定的。Map集合的键是无序,不重复的,无索引的,值不做要求(可以重复)。Map集合后面重复的键对应的值会覆盖前面重复键的值。Map集合的键值对都可以为null。HashMap:元素按照键是无序,不重复,无索引,值不做要求。( 与Map体系一致)LinkedHashMap:元素按照键是有序,不重复,无索引,值不做要求。TreeMap:元素按照建是排序,不重复,无索引的,值不做要求。9.3 Map集合常用APIMap是双列集合的祖宗接口,它的功能是全部双列集合都可以继承使用的。package com.liu.day06.Map; package com.liu.day06.Map; import java.util.Collection; import java.util.HashMap; import java.util.Map; import java.util.Set; public class MapDemo01 { public static void main(String[] args) { Map<String, Integer> map = new HashMap<>(); //put方法 map.put("zhenxi",23); map.put("成龙",22); map.put("爽",23); map.put("帅博",21); System.out.println(map); //get方法 Integer integer = map.get("zhenxi"); System.out.println(integer); //keySet方法:获取全部键的集合 Set<String> set = map.keySet(); for (String s : set) { System.out.println(s); } //values方法:获取全部值的集合 Collection<Integer> values = map.values(); for (Integer value : values) { System.out.println(value); } //remove方法 map.remove("zhenxi"); System.out.println(map); //containsKey方法 System.out.println(map.containsKey("爽")); //containsValue方法 System.out.println(map.containsValue(21)); //isEmpty方法 System.out.println(map.isEmpty()); //size方法 System.out.println(map.size()); //clear方法 map.clear(); System.out.println(map.isEmpty()); } } /**运行结果: {成龙=22, zhenxi=23, 爽=23, 帅博=21} 23 成龙 zhenxi 爽 帅博 22 23 23 21 {成龙=22, 爽=23, 帅博=21} true true false 3 true Process finished with exit code 0 */9.4 Map集合的遍历方式第一种:键找值先获取Map集合的全部键的Set集合。遍历键的Set集合,然后通过键提取对应值。package com.liu.day06.Map; import java.util.HashMap; import java.util.Map; import java.util.Set; public class MapDemo02 { public static void main(String[] args) { Map<String, Integer> map = new HashMap<>(); //put方法 map.put("zhenxi",23); map.put("成龙",22); map.put("爽",23); map.put("帅博",21); System.out.println(map); Set<String> set = map.keySet(); for (String key : set) { Integer value = map.get(key); System.out.println(key + "=" + value); } } } /** {成龙=22, zhenxi=23, 爽=23, 帅博=21} 成龙=22 zhenxi=23 爽=23 帅博=21 */第二种:键值对先把Map集合转换成Set集合,Set集合中每个元素都是键值对实体类型了。遍历Set集合,然后提取键以及提取值。package com.liu.day06.Map; import java.util.HashMap; import java.util.Map; import java.util.Set; public class MapDemo03 { public static void main(String[] args) { Map<String, Integer> map = new HashMap<>(); //put方法 map.put("zhenxi",23); map.put("成龙",22); map.put("爽",23); map.put("帅博",21); System.out.println(map); Set<Map.Entry<String, Integer>> entrySet = map.entrySet(); for (Map.Entry<String, Integer> entry : entrySet) { String key = entry.getKey(); Integer value = entry.getValue(); System.out.println(key+"="+value); } } }第三种:Lambda表达式得益于JDK 8开始的新技术Lambda表达式,提供了一种更简单、更直接的遍历集合的方式。package com.liu.day06.Map; import java.util.HashMap; import java.util.Map; import java.util.function.BiConsumer; public class MapDemo04 { public static void main(String[] args) { Map<String, Integer> map = new HashMap<>(); //put方法 map.put("zhenxi",23); map.put("成龙",22); map.put("爽",23); map.put("帅博",21); System.out.println(map); //匿名内部类 map.forEach(new BiConsumer<String, Integer>() { @Override public void accept(String s, Integer integer) { System.out.println(s+"="+integer); } }); //lambda表达式 map.forEach(((s, integer) -> System.out.println(s+"="+integer))); } }9.5 Map集合案例需求:某个班级80名学生,现在需要组成秋游活动,班长提供了四个最点依次是(A、B、C. D) ,每个学生只能选择一个景点,请统计出最终哪个景点想去的人数最多。分析:将80个学生选择的数据拿到程序中去。定义Map集合用于存储最终统计的结果。遍历80个学生选择的数据,看Map集合中是否存在,不存在存入“数据=1“,存在则其对应值+1。package com.liu.day06.Map; import java.util.*; public class MapTest1 { public static void main(String[] args) { // 1、把80个学生选择的数据拿进来。 String[] selects = {"A" , "B", "C", "D"}; StringBuilder sb = new StringBuilder(); Random r = new Random(); for (int i = 0; i < 80; i++) { sb.append(selects[r.nextInt(selects.length)]); } System.out.println(sb); // 2、定义一个Map集合记录最终统计的结果: A=30 B=20 C=20 D=10 键是景点 值是选择的数量 Map<Character, Integer> infos = new HashMap<>(); // // 3、遍历80个学生选择的数据 for (int i = 0; i < sb.length(); i++) { // 4、提取当前选择景点字符 char ch = sb.charAt(i); // 5、判断Map集合中是否存在这个键 if(infos.containsKey(ch)){ // 让其值 + 1 infos.put(ch , infos.get(ch) + 1); }else { // 说明此景点是第一次被选 infos.put(ch , 1); } } // 4、输出集合 System.out.println(infos); } }9.6 Map集合的实现类HashMapHashMap是Map里面的一个实现类。特点都是由键决定的:无序、不重复、无索引。没有额外需要学习的特有方法,直接使用Map里面的方法就可以了。HashMap跟HashSet底层原理是一模一样的, 都是哈希表结构,只是HashMap的每个元素包含两个值而已。Set系列集合的底层就是Map实现的,只是Set集合中的元素只要键数据,不要值数据而已。依赖hashCode方法和equals方法保证键的唯一。如果键要存储的是自定义对象,需要重写hashCode和equals方法。package com.liu.day06.Map; import com.liu.day06.HashDemo.Student; import java.util.HashMap; import java.util.Map; public class HashMapDemo { public static void main(String[] args) { Map<String, Integer> map = new HashMap<>(); //put方法 map.put("zhenxi",23); map.put("成龙",22); map.put("爽",23); map.put("帅博",21); System.out.println(map); Map<Student,Integer> map1 = new HashMap<>(); map1.put(new Student("zhenxi",02),23); map1.put(new Student("成龙",11),22); map1.put(new Student("辰乐",07),22); System.out.println(map1); /** student需要重写hashCode和equals方法 @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Student student = (Student) o; return id == student.id && Objects.equals(name, student.name); } @Override public int hashCode() { return Objects.hash(name, id); } */ } } /** {成龙=22, zhenxi=23, 爽=23, 帅博=21} {Student{name='成龙', id=11}=22, Student{name='zhenxi', id=2}=23, Student{name='辰乐', id=7}=22} */9.7 Map集合的实现类LinkedHashMap由键决定:有序、不重复、无索引。这里的有序指的是保证存储和取出的元素顺序一致。原理:底层数据结构是依然哈希表,只是每个键值对元素又额外的多了一个双链表的机制记录存储的顺序。package com.liu.day06.Map; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; public class LinkedHashMapDemo { public static void main(String[] args) { Map<String, Integer> map = new LinkedHashMap<>(); //put方法 map.put("zhenxi",23); map.put("zhenxi",23); map.put("成龙",22); map.put("爽",23); map.put("帅博",21); System.out.println(map); //{zhenxi=23, 成龙=22, 爽=23, 帅博=21} } }9.8 Map集合的实现类TreeMap由键决定特性:不重复、无索引、可排序。可排序:按照键数据的大小默认升序(有小到大)排序。只能对键排序。注意: TreeMap集合是一定要排序的,可以默认排序,也可以将键按照指定的规则进行排序。TreeMap集合自定义排序规则有2种:类实现Comparable接口, 重写比较规则。集合自定义Comparator比较器对象, 重写比较规则。package com.liu.day06.Map; import com.liu.day06.HashDemo.Apple; import com.liu.day06.HashDemo.Student; import java.util.Comparator; import java.util.HashMap; import java.util.Map; import java.util.TreeMap; public class TreeMapDemo { public static void main(String[] args) { Map<String, Integer> map = new TreeMap<>(); //put方法 map.put("zhenxi",23); map.put("成龙",22); map.put("爽",23); map.put("帅博",21); System.out.println(map); //方式一:实现Comparable接口 /* * @Override public int compareTo(Student o) { return this.id - o.id; } * */ Map<Student,Integer> map1 = new TreeMap<>(); map1.put(new Student("zhenxi",02),23); map1.put(new Student("成龙",11),22); map1.put(new Student("辰乐",07),22); System.out.println(map1); //方式二:集合自定义 Map<Apple,Integer> map2 = new TreeMap<>(new Comparator<Apple>() { @Override public int compare(Apple o1, Apple o2) { return o1.getWeight() - o2.getWeight(); } }); map2.put(new Apple("星空灰",20),5000); map2.put(new Apple("格调紫",40),4000); map2.put(new Apple("青绿",30),4500); System.out.println(map2); } }学习记录71 不可变集合不可变集合,就是不可被修改的集合。集合的数据项在创建的时候提供,并且在整个生命周期中都不可改变。否则报错。如果某个数据不能被修改,把它防御性地拷贝到不可变集合中是个很好的实践。或者当集合对象被不可信的库调用时,不可变形式是安全的。package com.liu.day7.Collection; import java.util.List; import java.util.Map; import java.util.Set; public class CollectionDemo01 { public static void main(String[] args) { //注意JDK8版本会报错的 不可变集合是Java9的新特征 List<Double> list = List.of(300.3,335.0,329.0,329.0); //list.add(22.2);//报错:UnsupportedOperationException 不支持的操作异常 //list.set(1,334.3);//报错:UnsupportedOperationException 不支持的操作异常 System.out.println(list.get(1)); System.out.println(list); //不可更改、不可添加、能够查询 //set集合 //Set<Integer> set = Set.of(2,4,6,4);//报错:IllegalArgumentException Set<Integer> set = Set.of(2,3,6,4); //set.add(9);//报错:UnsupportedOperationException 不支持的操作异常 //set.clear();//报错:UnsupportedOperationException 不支持的操作异常 System.out.println(set); //map集合 Map<String, Integer> map = Map.of("zhenxi",23,"成龙",24); //map.put("努力",666);//报错:UnsupportedOperationException 不支持的操作异常 System.out.println(map.get("zhenxi")); System.out.println(set); } }2 Stream流2.1 Stream流概述在Java 8中,得益于Lambda所带来的函数式编程,引入了一个全新的Stream流概念。目的:用于简化集合和数组操作的API。Stream流思想的核心:先得到集合或者数组的Stream流(就是一根传送带)。把元素放上去。然后就用这个Stream流简化的API来方便的操作元素。package com.liu.day7.Stream; import java.util.ArrayList; import java.util.Collections; import java.util.List; public class StreamDemo { public static void main(String[] args) { List<String> arrayList = new ArrayList<>(); Collections.addAll(arrayList,"zhenxi","刘珍惜","张宇","张乐雨","李金磊","李景隆"); System.out.println(arrayList); //从集合中找到姓张的并存到新集合中 List<String> zhangList = new ArrayList<>(); for (String name : arrayList){ if (name.startsWith("张")){ zhangList.add(name); } } System.out.println(zhangList); //从集合中找到姓张并且是三个字的并存到新集合中 List<String> zhangThreeList = new ArrayList<>(); for (String name : zhangList){ if (name.length()==3){ zhangThreeList.add(name); } } System.out.println(zhangThreeList); //使用Stream流来获取 arrayList.stream().filter(s -> s.startsWith("张")).filter(s -> s.length() == 3).forEach(s -> System.out.println(s)); } }2.2 Stream流的获取package com.liu.day7.Stream; import java.util.*; import java.util.stream.Stream; public class StreamDemo01 { public static void main(String[] args) { //Collection集合获取流 Collection<String> list =new ArrayList<>(); Stream<String> stream = list.stream(); //Map集合获取流 Map<String, Integer> map=new HashMap<>(); //键流 Stream<String> stream1 = map.keySet().stream(); //值流 Stream<Integer> stream2 = map.values().stream(); //键值对流 Stream<Map.Entry<String, Integer>> entryStream = map.entrySet().stream(); //数组获取流 String[] namesArray = {"","zhenxi","珍惜大学","计算机与控制工程学院"}; Stream<String> stringStream = Arrays.stream(namesArray); } }2.3 Stream流的API获取Stream流方法:创建一条流水线,并把数据放到流水线上准备进行操作。中间方法:流水线上的操作。一次操作完毕之后,还可以继续进行其他操作。中间方法也称为非终结方法,调用完成后返回新的Stream流可以继续使用,支持链式编程。终结方法:一个Stream流只能有一个终结方法,是流水线上的最后一个操作。终结操作方法,调用完成后流就无法继续使用了,原因是不会返回Stream了在Stream流中无法直接修改集合、数组中的数据。package com.liu.day7.Stream; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.function.Consumer; import java.util.function.Predicate; import java.util.stream.Stream; public class StreamDemo02 { public static void main(String[] args) { //Stream流API List<String> arrayList = new ArrayList<>(); Collections.addAll(arrayList,"zhenxi","刘珍惜","张宇","张乐雨","李金磊","李景隆"); //filter的匿名内部类写法:以及使用终止方法forEach() arrayList.stream().filter(new Predicate<String>() { @Override public boolean test(String s) { return s.startsWith("刘"); } }).forEach(new Consumer<String>() { @Override public void accept(String s) { System.out.println(s); } });//运行结果:zhenxi 刘珍惜 //filter使用lambda表达式 以及使用终止方法count() long l = arrayList.stream().filter(s -> s.startsWith("刘")).count(); System.out.println(l);//2 //map加工方法 arrayList.stream().map(s -> "珍惜大学"+s).forEach(s -> System.out.println(s)); /* 珍惜大学zhenxi 珍惜大学刘珍惜 珍惜大学张宇 珍惜大学张乐雨 珍惜大学李金磊 珍惜大学李景隆*/ //加工成一个学生对象 arrayList.stream().map(s -> new Student(s)).forEach(student -> System.out.println(student)); /* Student{name='zhenxi'} Student{name='刘珍惜'} Student{name='张宇'} Student{name='张乐雨'} Student{name='李金磊'} Student{name='李景隆'} */ //limit方法 arrayList.stream().filter(s -> s.startsWith("张")).limit(1).forEach(s -> System.out.println(s));//张宇 //skip arrayList.stream().filter(s -> s.startsWith("张")).skip(1).forEach(s -> System.out.println(s));//张乐雨 //distinct方法 arrayList.add("zhenxi"); arrayList.stream().filter(s -> s.startsWith("刘")).distinct().forEach(s -> System.out.println(s));//zhenxi 刘珍惜 //concat方法 Stream<String> stream = arrayList.stream().filter(s -> s.startsWith("张")); Stream<String> stream1 = arrayList.stream().filter(s -> s.startsWith("刘")); Stream.concat(stream,stream1).forEach(s -> System.out.println(s)); /* 张宇 张乐雨 zhenxi 刘珍惜 zhenxi*/ } }2.4 Stream流案例员工信息至少包含了(名称、性别、工资、奖金、处罚记录)。开发一部有4个员工、开发二部有5名员工。分别筛选出2个部门的最高工资的员工信息,封装成优秀员工对象Topperformer。分别统计出2个部门的平均月收入,要求去掉最高和最低工资。统计2个开发部门整体的平均工资,去掉最低和最高工资的平均值。Employee类:package com.liu.day7.Stream; public class Employee { private String name; private char sex; private double salary; private double bonus; private String punish; // 处罚信息 public Employee(){ } public Employee(String name, char sex, double salary, double bonus, String punish) { this.name = name; this.sex = sex; this.salary = salary; this.bonus = bonus; this.punish = punish; } public String getName() { return name; } public void setName(String name) { this.name = name; } public char getSex() { return sex; } public void setSex(char sex) { this.sex = sex; } public double getSalary() { return salary; } public void setSalary(double salary) { this.salary = salary; } public double getBonus() { return bonus; } public void setBonus(double bonus) { this.bonus = bonus; } public String getPunish() { return punish; } public void setPunish(String punish) { this.punish = punish; } public double getTotalSalay(){ return salary * 12 + bonus; } @Override public String toString() { return "Employee{" + "name='" + name + '\'' + ", sex=" + sex + ", salary=" + salary + ", bonus=" + bonus + ", punish='" + punish + '\'' + '}'+"\n"; } }Topperformer类:package com.liu.day7.Stream; public class Topperformer { private String name; private double money; // 月薪 public Topperformer() { } public Topperformer(String name, double money) { this.name = name; this.money = money; } public String getName() { return name; } public void setName(String name) { this.name = name; } public double getMoney() { return money; } public void setMoney(double money) { this.money = money; } @Override public String toString() { return "Topperformer{" + "name='" + name + '\'' + ", money=" + money + '}'; } }package com.liu.day7.Stream; import java.math.BigDecimal; import java.math.RoundingMode; import java.util.ArrayList; import java.util.List; import java.util.stream.Stream; public class StreamDemo04 { public static double allMoney ; public static double allMoney2 ; // 2个部门去掉最高工资,最低工资的总和 public static void main(String[] args) { List<Employee> one = new ArrayList<>(); one.add(new Employee("猪八戒",'男',30000 , 25000, null)); one.add(new Employee("孙悟空",'男',25000 , 1000, "顶撞上司")); one.add(new Employee("沙僧",'男',20000 , 20000, null)); one.add(new Employee("小白龙",'男',20000 , 25000, null)); List<Employee> two = new ArrayList<>(); two.add(new Employee("武松",'男',15000 , 9000, null)); two.add(new Employee("李逵",'男',20000 , 10000, null)); two.add(new Employee("西门庆",'男',50000 , 100000, "被打")); two.add(new Employee("潘金莲",'女',3500 , 1000, "被打")); two.add(new Employee("武大郎",'女',20000 , 0, "下毒")); // 1、开发一部的最高工资的员工。(API) // 指定大小规则了 Topperformer t = one.stream().max((e1, e2) -> Double.compare(e1.getSalary() + e1.getBonus(), e2.getSalary() + e2.getBonus())) .map(e -> new Topperformer(e.getName(), e.getSalary() + e.getBonus())).get(); System.out.println(t); // 2、统计平均工资,去掉最高工资和最低工资 one.stream().sorted((e1, e2) -> Double.compare(e1.getSalary() + e1.getBonus(), e2.getSalary() + e2.getBonus())) .skip(1).limit(one.size() - 2).forEach(e -> { // 求出总和:剩余员工的工资总和 allMoney += (e.getSalary() + e.getBonus()); }); System.out.println("开发一部的平均工资是:" + allMoney / (one.size() - 2)); // 3、合并2个集合流,再统计 Stream<Employee> s1 = one.stream(); Stream<Employee> s2 = two.stream(); Stream<Employee> s3 = Stream.concat(s1 , s2); s3.sorted((e1, e2) -> Double.compare(e1.getSalary() + e1.getBonus(), e2.getSalary() + e2.getBonus())) .skip(1).limit(one.size() + two.size() - 2).forEach(e -> { // 求出总和:剩余员工的工资总和 allMoney2 += (e.getSalary() + e.getBonus()); }); // BigDecimal BigDecimal a = BigDecimal.valueOf(allMoney2); BigDecimal b = BigDecimal.valueOf(one.size() + two.size() - 2); System.out.println("开发部的平均工资是:" + a.divide(b,2, RoundingMode.HALF_UP)); } }2.5 Stream流的收集收集Stream流的含义:就是把Stream流操作后的结果数据转回到集合或者数组中去。Stream流:方便操作集合/数组的手段。package com.liu.day7.Stream; import java.util.*; import java.util.function.IntFunction; import java.util.stream.Collectors; import java.util.stream.Stream; public class StreamDemo05 { public static void main(String[] args) { List<String> list = new ArrayList<>(); Collections.addAll(list,"zhenxi","刘珍惜","张宇","张乐雨","李金磊","李景隆"); Stream<String> s1 = list.stream().filter(s -> s.startsWith("张")); List<String> zhangList = s1.collect(Collectors.toList()); // 可变集合 zhangList.add("java"); System.out.println(zhangList); // List<String> list1 = s1.toList(); // 得到不可变集合 // list1.add("java"); // System.out.println(list1); // 注意注意注意:“流只能使用一次” Stream<String> s2 = list.stream().filter(s -> s.startsWith("张")); Set<String> zhangSet = s2.collect(Collectors.toSet()); System.out.println(zhangSet); Stream<String> s3 = list.stream().filter(s -> s.startsWith("张")); String[] arrs = s3.toArray(String[]::new); System.out.println("Arrays数组内容:" + Arrays.toString(arrs)); } }3 异常3.1 异常概述、体系异常是什么?异常是程序在"编译"或者"执行"的过程中可能出现的问题。异常是应该尽量提前避免的。异常可能也是无法做到绝对避免的,异常可能有太多情况了,开发中只能提前干预!!异常一旦出现了,如果没有提前处理,程序就会退出JVM虚拟机而终止,开发中异常是需要提前处理的。异常分为几类?编译时异常、 运行时异常。编译时异常: 没有继承RuntimeExcpetion的异常,编译阶段就会出错。运行时异常: 继承自RuntimeException的异常或其子类,编译阶段不报错,运行可能报错。3.2 常见运行时异常继承自RuntimeException的异常或者其子类,编译阶段是不会出错的,它是在运行时阶段可能出现的错误,运行时异常编译阶段可以处理也可以不处理,代码编译都能通过!!运行时异常示例:1.数组索引越界异常: ArrayIndexOutOfBoundsException。2.空指针异常 : NullPointerException。直接输出没有问题。但是调用空指针的变量的功能就会报错!!3.类型转换异常:ClassCastException。4.迭代器遍历没有此元素异常:NoSuchElementException。5.数学操作异常:ArithmeticException。6.数字转换异常: NumberFormatException。小结:运行时异常继承了RuntimeException ,编译阶段不报错,运行时才可能会出现错误!运行时异常:一般是程序员业务没有考虑好或者是编程逻辑不严谨引起的程序错误。public class ExceptionDemo01 { public static void main(String[] args) { /** 1.数组索引越界异常: ArrayIndexOutOfBoundsException。*/ int[] arr = {1, 2, 3}; System.out.println(arr[2]); // System.out.println(arr[3]); // 运行出错,程序终止 /** 2.空指针异常 : NullPointerException。直接输出没有问题。但是调用空指针的变量的功能就会报错!! */ String name = null; System.out.println(name); // null // System.out.println(name.length()); // 运行出错,程序终止 /** 3.类型转换异常:ClassCastException。 */ Object o = 23; // String s = (String) o; // 运行出错,程序终止 /** 5.数学操作异常:ArithmeticException。 */ //int c = 10 / 0; /** 6.数字转换异常: NumberFormatException。 */ //String number = "23"; String number = "23aabbc"; Integer it = Integer.valueOf(number); // 运行出错,程序终止 System.out.println(it + 1); System.out.println("程序结束。。。。。"); } }3.3 常见编译时异常继承自Exception的异常或者其子类,没有继承RuntimeException。编译时异常是编译阶段就会报错。程序员必须编译阶段就处理的。否则代码编译就报错!!编译时异常的作用是什么:是担心程序员的技术不行,在编译阶段就爆出一个错误,目的在于提醒!提醒程序员这里很可能出错,请检查并注意不要出bug。package com.liu.day7.Exception; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; public class ExceptionDemo02 { public static void main(String[] args) throws ParseException { String date = "2015-01-12 10:23:21"; // 创建一个简单日期格式化类: SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM-dd HH:mm:ss"); // 解析字符串时间成为日期对象 Date d = sdf.parse(date); // System.out.println(d); } }3.4 异常的默认处理流程默认会在出现异常的代码那里自动的创建一个异常对象:ArithmeticException。异常会从方法中出现的点这里抛出给调用者,调用者最终抛出给JVM虚拟机。虚拟机接收到异常对象后,先在控制台直接输出异常栈信息数据。直接从当前执行的异常点干掉当前程序。后续代码没有机会执行了,因为程序已经死亡。小结:异常一旦出现,会自动创建异常对象,最终抛出给虚拟机,虚拟机。只要收到异常,就直接输出异常信息,干掉程序!!3.5 编译时异常的处理机制编译时异常:编译阶段就会报错,一定需要程序员处理的,否则代码无法通过!!异常处理方法一:抛出去!抛出异常格式:方法 throws 异常1 , 异常2 , ..{}建议抛出异常的方式:代表可以抛出一切异常,方法 throws Exception{}在出现编译时异常的地方层层把异常抛出去给调用者,调用者最终抛出给JVM虚拟机。JVM虚拟机输出异常信息,直接干掉程序,这种方式与默认方式是一样的。虽然可以解决代码编译时的错误,但是一旦运行时真的出现异常,程序还是会立即死亡!小结:方式一出现异常层层跑出给虚拟机,最终程序如果真的出现异常,程序还是立即死亡!这种方式不好!package com.liu.day7.ExceptionTest; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.InputStream; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; public class ExceptionDemo01 { public static void main(String[] args) throws Exception { System.out.println("程序开始。。。。。"); parseTime("2011-11-11 11:11:11"); System.out.println("程序结束。。。。。"); } public static void parseTime(String date) throws Exception { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); Date d = sdf.parse(date); System.out.println(d); InputStream is = new FileInputStream("E:/meinv.jpg"); } }异常处理方法二:try catch自己捕获异常和处理异常的格式:捕获处理 try{ // 监视可能出现异常的代码! }catch(异常类型1 变量){ // 处理异常 }catch(异常类型2 变量){ // 处理异常 }...监视捕获处理异常企业级写法: try{ // 可能出现异常的代码! }catch (Exception e){ e.printStackTrace(); // 直接打印异常栈信息 } Exception可以捕获处理一切异常类型!小结:第二种方式,可以处理异常,并且出现异常后代码也不会死亡。这种方案还是可以的。但是从理论上来说,这种方式不是最好的,上层调用者不能直接知道底层的执行情况!package com.liu.day7.ExceptionTest; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.InputStream; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; public class ExceptionDemo02 { public static void main(String[] args) { System.out.println("程序开始。。。。"); parseTime("2011-11-11 11:11:11"); System.out.println("程序结束。。。。"); } public static void parseTime(String date) { try { SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM-dd HH:mm:ss"); Date d = sdf.parse(date); System.out.println(d); InputStream is = new FileInputStream("E:/meinv.jpg"); } catch (Exception e) { e.printStackTrace(); // 打印异常栈信息 } } }异常处理方法三:前两者结合在出现异常的地方把异常一层一层的抛出给最外层调用者,最外层调用者集中捕获处理!!(规范做法)小结:编译时异常的处理方式三:底层出现的异常抛出给最外层调用者集中捕获处理。这种方案最外层调用者可以知道底层执行的情况,同时程序在出现异常后也不会立即死亡,这是理论上最好的方案。 package com.liu.day7.ExceptionTest; import java.io.FileInputStream; import java.io.InputStream; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; public class ExceptionDemo03 { public static void main(String[] args) { System.out.println("程序开始。。。。"); try { parseTime("2011-11-11 11:11:11"); System.out.println("功能操作成功~~~"); } catch (Exception e) { e.printStackTrace(); System.out.println("功能操作失败~~~"); } System.out.println("程序结束。。。。"); } public static void parseTime(String date) throws Exception { SimpleDateFormat sdf = new SimpleDateFormat("yyyy、MM-dd HH:mm:ss"); Date d = sdf.parse(date); System.out.println(d); InputStream is = new FileInputStream("D:/meinv.jpg"); } }3.6 运行时异常的处理机制可以不处理,编译阶段又不报错。按照理论规则:建议还是处理,只需要在最外层捕获处理即可。但程序会默认抛出异常。3.7 自定义异常自定义异常的必要?Java无法为这个世界,上全部的问题提供异常类。如果企业想通过异常的方式来管理自己的某个业务问题,就需要自定义异常类了。自定义异常的好处:可以使用异常的机制管理业务问题,如提醒程序员注意。同时一旦出现bug,可以用异常的形式清晰的指出出错的地方。自定义编译时异常:定义一个异常类继承Exception。重写构造器。在出现异常的地方用throw new 自定义对象抛出!编译时异常是编译阶段就报错,提醒更加强烈,一定需要处理!!自定义运行时异常.定义一个异常类继承RuntimeException。重写构造器。在出现异常的地方用throw new 自定义对象抛出!提醒不强烈,编译阶段不报错!!运行时才可能出现!!package com.liu.day7.ExceptionTest; /** 自定义的编译时异常 1、继承Exception 2、重写构造器 */ public class ItheimaAgeIlleagalException extends Exception{ public ItheimaAgeIlleagalException() { } public ItheimaAgeIlleagalException(String message) { super(message); } }package com.liu.day7.ExceptionTest; /** 自定义的编译时异常 1、继承RuntimeException 2、重写构造器 */ public class ItheimaAgeIlleagalRuntimeException extends RuntimeException{ public ItheimaAgeIlleagalRuntimeException() { } public ItheimaAgeIlleagalRuntimeException(String message) { super(message); } }package com.liu.day7.ExceptionTest; public class ExceptionDemo { public static void main(String[] args) { try { checkAge2(-23); } catch (Exception e) { e.printStackTrace(); } } public static void checkAge2(int age) { if(age < 0 || age > 200){ // 抛出去一个异常对象给调用者 // throw :在方法内部直接创建一个异常对象,并从此点抛出 // throws : 用在方法申明上的,抛出方法内部的异常 throw new ItheimaAgeIlleagalRuntimeException(age + " is illeagal!"); }else { System.out.println("年龄合法:推荐商品给其购买~~"); } } public static void checkAge(int age) throws ItheimaAgeIlleagalException { if(age < 0 || age > 200){ // 抛出去一个异常对象给调用者 // throw :在方法内部直接创建一个异常对象,并从此点抛出 // throws : 用在方法申明上的,抛出方法内部的异常 throw new ItheimaAgeIlleagalException(age + " is illeagal!"); }else { System.out.println("年龄合法:推荐商品给其购买~~"); } } }学习总结1这一周主要学习了大量的常用API,这些类的方法在后面编程中是非常重要的,所以需要经常复习。虽然在本科期间已经学习过Java,但是如今再次学习,依旧发现了以前学习的弊端和疏漏,对于SE的知识理解的更加深刻,并从资料中学习到了很多Java的新特性,以及一些常用类的常用API。本周学习劲头适中,不慢不快,不骄不躁,稳扎稳打,继续坚持~学习记录81 日志框架1.1 日志概述和优势用来记录程序运行过程中的信息,并可以永久存储。可以将系统执行的信息选择性的记录到指定的位置(控制台、文件中、数据库中)。可以随时以开关的形式控制是否记录日志,无需修改源代码。 输出语句日志技术输出位置只能输出到控制台可以将日志信息写入到文件或者数据库中取消日志需要修改代码,灵活性比较差不需要修改代码,灵活性比较好多线程性能较差性能较好1.2 日志常见形式日志规范大多是一些接口,提供给实现框架去设计的。常见的规范是:Commons Logging和Simple Logging Facade for Java常见的实现框架:Log4J、Logback。1.3 logback日志框架介绍logback是由log4j创始人设计的另一个开源日志组件,性能比log4j要好。logback是基于slf4j的日志规范实现的框架。logback-core: logback-core 模块为其他两个模块奠定了基础,必须有。logback-classic:它是log4j的一个改良版本,同时它完整实现了slf4j API。logback-access模块与Tomcat和Jetty等Servlet容器集成,以提供HTTP访向日志功能。1.4 logback的使用Maven需要引入的依赖:<dependencies> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-core</artifactId> <version>1.2.9</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.36</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.2.9</version> </dependency> </dependencies>logback的配置文件:<?xml version="1.0" encoding="UTF-8"?> <configuration> <!-- CONSOLE :表示当前的日志信息是可以输出到控制台的。 --> <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"> <!--输出流对象 默认 System.out 改为 System.err--> <target>System.out</target> <encoder> <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度 %msg:日志消息,%n是换行符--> <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%-5level] %c [%thread] : %msg%n</pattern> </encoder> </appender> <!-- File是输出的方向通向文件的 --> <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <encoder> <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern> <charset>utf-8</charset> </encoder> <!--日志输出路径--> <file>C:/code/log-data.log</file> <!--指定日志文件拆分和压缩规则--> <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"> <!--通过指定压缩文件名称,来确定分割文件方式--> <fileNamePattern>C:/code/log-data2-%d{yyyy-MMdd}.log%i.gz</fileNamePattern> <!--文件拆分大小--> <maxFileSize>1MB</maxFileSize> </rollingPolicy> </appender> <!-- level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF , 默认debug <root>可以包含零个或多个<appender-ref>元素,标识这个输出位置将会被本日志级别控制。 --> <root level="ALL"> <appender-ref ref="CONSOLE"/> <appender-ref ref="FILE" /> </root> </configuration>Logback测试代码:package com.day08; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class LogbackDemo { public static final Logger LOGGER = LoggerFactory.getLogger("LogbackDemo.class"); public static void main(String[] args) { LOGGER.debug("main方法执行了"); LOGGER.info("开始记录第二条日志"); int a = 10; int b = 0; LOGGER.trace("a="+a); LOGGER.trace("b="+b); } } /** 运行结果: 2022-07-29 11:59:09.845 [DEBUG] LogbackDemo.class [main] : main方法执行了 2022-07-29 11:59:09.849 [INFO ] LogbackDemo.class [main] : 开始记录第二条日志 2022-07-29 11:59:09.850 [TRACE] LogbackDemo.class [main] : a=10 2022-07-29 11:59:09.850 [TRACE] LogbackDemo.class [main] : b=0 */生成的日志文件:2 阶段项目项目准备:集成日志框架、用于后期记录日志信息。定义一个电影类Movie类,Movie类包含:片名、主演、评分、时长、票价、余票。系统包含2个用户角色:客户、商家。存在大量相同属性信息。定义User类作为父类,属性:登录名称、密码、真实名称、性别、电话、账户金额。定义Business类代表商家角色,属性:店铺名称、地址。定义Customer类代表客户角色,属性。定义集合List用户存放系统注册的用户对象信息。定义集合Map<Business, List>存放商家和其排片信息。Bean包:User类:package com.day08.movies.bean; /** 用户类(客户和商家的父类 ) */ public class User { private String loginName; // 假名 不能重复 private String userName; // 真名 private String passWord; private char sex; private String phone; private double money; public User(){ } public User(String loginName, String userName, String passWord, char sex, String phone, double money) { this.loginName = loginName; this.userName = userName; this.passWord = passWord; this.sex = sex; this.phone = phone; this.money = money; } public String getLoginName() { return loginName; } public void setLoginName(String loginName) { this.loginName = loginName; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public String getPassWord() { return passWord; } public void setPassWord(String passWord) { this.passWord = passWord; } public char getSex() { return sex; } public void setSex(char sex) { this.sex = sex; } public String getPhone() { return phone; } public void setPhone(String phone) { this.phone = phone; } public double getMoney() { return money; } public void setMoney(double money) { this.money = money; } }Business类:package com.day08.movies.bean; public class Business extends User{ // 店铺名称 private String shopName; // 店铺地址 private String address; public String getShopName() { return shopName; } public void setShopName(String shopName) { this.shopName = shopName; } public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } }Customer类:package com.day08.movies.bean; import java.util.HashMap; import java.util.Map; /** 客户角色 */ public class Customer extends User{ // 定义一个属性存储购买记录。 private Map<String, Boolean> buyMovies = new HashMap<>(); public Map<String, Boolean> getBuyMovies() { return buyMovies; } public void setBuyMovies(Map<String, Boolean> buyMovies) { this.buyMovies = buyMovies; } }Movie类:package com.day08.movies.bean; import com.day08.movies.run.MovieSystem; import java.math.BigDecimal; import java.math.RoundingMode; import java.util.Date; import java.util.List; public class Movie { private String name; private String actor; private double time; private double price; private int number; // 余票 private Date startTime; // 放映时间 public Movie() { } public Movie(String name, String actor, double time, double price, int number, Date startTime) { this.name = name; this.actor = actor; this.time = time; this.price = price; this.number = number; this.startTime = startTime; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getActor() { return actor; } public void setActor(String actor) { this.actor = actor; } public double getScore() { List<Double> scores = MovieSystem.MOVIES_SCORE.get(name); if(scores!=null && scores.size() > 0){ double sum = 0; for (Double score : scores) { sum += score; } return BigDecimal.valueOf(sum).divide(BigDecimal.valueOf(scores.size()), 2 , RoundingMode.UP).doubleValue(); }else { return 0; } } public double getTime() { return time; } public void setTime(double time) { this.time = time; } public double getPrice() { return price; } public void setPrice(double price) { this.price = price; } public int getNumber() { return number; } public void setNumber(int number) { this.number = number; } public Date getStartTime() { return startTime; } public void setStartTime(Date startTime) { this.startTime = startTime; } }主程序:package com.day08.movies.run; import com.day08.movies.bean.Business; import com.day08.movies.bean.Customer; import com.day08.movies.bean.Movie; import com.day08.movies.bean.User; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.math.BigDecimal; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.*; public class MovieSystem { /** 定义系统的数据容器用户存储数据 1、存储很多用户(客户对象,商家对象) */ public static final List<User> ALL_USERS = new ArrayList<>(); /** 2、存储系统全部商家和其排片信息 。 商家1 = [p1,p2,p3,...] 商家2 = [p2,p3,...] ... */ public static final Map<Business, List<Movie>> ALL_MOVIES = new HashMap<>(); public static final Scanner SYS_SC = new Scanner(System.in); // 定义一个静态的User类型的变量记住当前登录成功的用户对象 public static User loginUser; public static SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss"); public static final Logger LOGGER = LoggerFactory.getLogger("MovieSystem.class"); /** 3、准备一些测试数据 */ static { Customer c = new Customer(); c.setLoginName("zyf888"); c.setPassWord("123456"); c.setUserName("黑马刘德华"); c.setSex('男'); c.setMoney(10000); c.setPhone("110110"); ALL_USERS.add(c); Customer c1 = new Customer(); c1.setLoginName("gzl888"); c1.setPassWord("123456"); c1.setUserName("黑马关之琳"); c1.setSex('女'); c1.setMoney(2000); c1.setPhone("111111"); ALL_USERS.add(c1); Business b = new Business(); b.setLoginName("baozugong888"); b.setPassWord("123456"); b.setUserName("黑马包租公"); b.setMoney(0); b.setSex('男'); b.setPhone("110110"); b.setAddress("火星6号2B二层"); b.setShopName("甜甜圈国际影城"); ALL_USERS.add(b); // 注意,商家一定需要加入到店铺排片信息中去 List<Movie> movies = new ArrayList<>(); ALL_MOVIES.put(b , movies); // b = [] Business b2 = new Business(); b2.setLoginName("baozupo888"); b2.setPassWord("123456"); b2.setUserName("黑马包租婆"); b2.setMoney(0); b2.setSex('女'); b2.setPhone("110110"); b2.setAddress("火星8号8B八层"); b2.setShopName("巧克力国际影城"); ALL_USERS.add(b2); // 注意,商家一定需要加入到店铺排片信息中去 List<Movie> movies3 = new ArrayList<>(); ALL_MOVIES.put(b2 , movies3); // b2 = [] } public static void main(String[] args) { showMain(); } /** 首页展示 */ private static void showMain() { while (true) { System.out.println("===============黑马电影首页================="); System.out.println("1、登录"); System.out.println("2、用户注册"); System.out.println("3、商家注册"); System.out.println("请输入操作命令:"); String command = SYS_SC.nextLine(); switch (command) { case "1": // 登录了 login(); break; case "2": // break; case "3": break; default: System.out.println("命令有误,请确认!"); } } } /** 登录功能 */ private static void login() { while (true) { System.out.println("请您输入登录名称:"); String loginName = SYS_SC.nextLine(); System.out.println("请您输入登录密码:"); String passWord = SYS_SC.nextLine(); // 1、根据登录名称查询用户对象。 User u = getUserByLoginName(loginName); // 2、判断用户对象是否存在,存在说明登录名称正确了 if(u != null){ // 3、比对密码是否正确 if(u.getPassWord().equals(passWord)){ // 登录成功了:... loginUser = u; // 记住登录成功的用户 LOGGER.info(u.getUserName() +"登录了系统~~~"); // 判断是用户登录的,还是商家登录的。 if(u instanceof Customer) { // 当前登录的是普通用户 showCustomerMain(); }else { // 当前登录的肯定是商家用户 showBusinessMain(); } return; }else { System.out.println("密码有毛病~~"); } }else { System.out.println("登录名称错误,请确认"); } } } /** 商家的后台操作界面 */ private static void showBusinessMain() { while (true) { System.out.println("============黑马电影商家界面==================="); System.out.println(loginUser.getUserName() + (loginUser.getSex()=='男'? "先生":"女士" + "欢迎您进入系统")); System.out.println("1、展示详情:"); System.out.println("2、上架电影:"); System.out.println("3、下架电影:"); System.out.println("4、修改电影:"); System.out.println("5、退出:"); System.out.println("请输入您要操作的命令:"); String command = SYS_SC.nextLine(); switch (command){ case "1": // 展示全部排片信息 showBusinessInfos(); break; case "2": // 上架电影信息 addMovie(); break; case "3": // 下架电影信息 deleteMovie(); break; case "4": // 修改电影信息 updateMovie(); break; case "5": System.out.println(loginUser.getUserName() +"请您下次再来啊~~~"); return; // 干掉方法 default: System.out.println("不存在该命令!!"); break; } } } /** 影片修改功能 */ private static void updateMovie() { System.out.println("================修改电影===================="); Business business = (Business) loginUser; List<Movie> movies = ALL_MOVIES.get(business); if(movies.size() == 0) { System.out.println("当期无片可以修改~~"); return; } // 2、让用户选择需要下架的电影名称 while (true) { System.out.println("请您输入需要修改的电影名称:"); String movieName = SYS_SC.nextLine(); // 3、去查询有没有这个影片对象。 Movie movie = getMovieByName(movieName); if(movie != null){ // 修改它 System.out.println("请您输入修改后的片名:"); String name = SYS_SC.nextLine(); System.out.println("请您输入修改后主演:"); String actor = SYS_SC.nextLine(); System.out.println("请您输入修改后时长:"); String time = SYS_SC.nextLine(); System.out.println("请您输入修改后票价:"); String price = SYS_SC.nextLine(); System.out.println("请您输入修改后票数:"); String totalNumber = SYS_SC.nextLine(); // 200\n while (true) { try { System.out.println("请您输入修改后的影片放映时间:"); String stime = SYS_SC.nextLine(); movie.setName(name); movie.setActor(actor); movie.setPrice(Double.valueOf(price)); movie.setTime(Double.valueOf(time)); movie.setNumber(Integer.valueOf(totalNumber)); movie.setStartTime(sdf.parse(stime)); System.out.println("恭喜您,您成功修改了该影片了!!!"); showBusinessInfos(); return; // 直接退出去 } catch (Exception e) { e.printStackTrace(); LOGGER.error("时间解析出了毛病"); } } }else { System.out.println("您的店铺没有上架该影片!"); System.out.println("请问继续修改吗?y/n"); String command = SYS_SC.nextLine(); switch (command) { case "y": break; default: System.out.println("好的!"); return; } } } } /** 影片下架功能 */ private static void deleteMovie() { System.out.println("================下架电影===================="); Business business = (Business) loginUser; List<Movie> movies = ALL_MOVIES.get(business); if(movies.size() == 0) { System.out.println("当期无片可以下架~~"); return; } // 2、让用户选择需要下架的电影名称 while (true) { System.out.println("请您输入需要下架的电影名称:"); String movieName = SYS_SC.nextLine(); // 3、去查询有没有这个影片对象。 Movie movie = getMovieByName(movieName); if(movie != null){ // 下架它 movies.remove(movie); System.out.println("您当前店铺已经成功下架了:" + movie.getName()); showBusinessInfos(); return; }else { System.out.println("您的店铺没有上架该影片!"); System.out.println("请问继续下架吗?y/n"); String command = SYS_SC.nextLine(); switch (command) { case "y": break; default: System.out.println("好的!"); return; } } } } /** 去查询当前商家下的排片 */ public static Movie getMovieByName(String movieName){ Business business = (Business) loginUser; List<Movie> movies = ALL_MOVIES.get(business); for (Movie movie : movies) { if(movie.getName().contains(movieName)) { return movie; } } return null; } /** 商家进行电影上架 Map<Business , List<Movie>> ALL_MOVIES u1 = [p1,p2,p3] u2 = [p1,p2,p3] */ private static void addMovie() { System.out.println("================上架电影===================="); // 根据商家对象(就是登录的用户loginUser),作为Map集合的键 提取对应的值就是其排片信息 :Map<Business , List<Movie>> ALL_MOVIES Business business = (Business) loginUser; List<Movie> movies = ALL_MOVIES.get(business); System.out.println("请您输入新片名:"); String name = SYS_SC.nextLine(); System.out.println("请您输入主演:"); String actor = SYS_SC.nextLine(); System.out.println("请您输入时长:"); String time = SYS_SC.nextLine(); System.out.println("请您输入票价:"); String price = SYS_SC.nextLine(); System.out.println("请您输入票数:"); String totalNumber = SYS_SC.nextLine(); // 200\n while (true) { try { System.out.println("请您输入影片放映时间:"); String stime = SYS_SC.nextLine(); // public Movie(String name, String actor, double time, double price, int number, Date startTime) // 封装成电影对象 ,加入集合movices中去 Movie movie = new Movie(name, actor ,Double.valueOf(time) , Double.valueOf(price) , Integer.valueOf(totalNumber) , sdf.parse(stime)); movies.add(movie); System.out.println("您已经成功上架了:《" + movie.getName() + "》"); return; // 直接退出去 } catch (ParseException e) { e.printStackTrace(); LOGGER.error("时间解析出了毛病"); } } } /** 定义一个静态的Map集合存储电影的评分 */ public static final Map<String , List<Double>> MOVIES_SCORE = new HashMap<>(); /** 展示商家的详细:展示当前商家的信息。 */ private static void showBusinessInfos() { System.out.println("================商家详情界面================="); LOGGER.info(loginUser.getUserName() +"商家,正在看自己的详情~~~"); // 根据商家对象(就是登录的用户loginUser),作为Map集合的键 提取对应的值就是其排片信息 :Map<Business , List<Movie>> ALL_MOVIES Business business = (Business) loginUser; System.out.println(business.getShopName() + "\t\t电话:" + business.getPhone() + "\t\t地址:" + business.getAddress() + "\t\t余额:" + business.getMoney()); List<Movie> movies = ALL_MOVIES.get(business); if(movies.size() > 0) { System.out.println("片名\t\t\t主演\t\t时长\t\t评分\t\t票价\t\t余票数量\t\t放映时间"); for (Movie movie : movies) { System.out.println(movie.getName()+"\t\t\t" + movie.getActor()+ "\t\t" + movie.getTime() + "\t\t" + movie.getScore() + "\t\t" + movie.getPrice() + "\t\t" + movie.getNumber() + "\t\t" + sdf.format(movie.getStartTime())); } }else { System.out.println("您的店铺当前无片在放映~~~~"); } } /** 客户操作界面 */ private static void showCustomerMain() { while (true) { System.out.println("============黑马电影客户界面==================="); System.out.println(loginUser.getUserName() + (loginUser.getSex()=='男'? "先生":"女士" + "欢迎您进入系统" + "\t余额:" + loginUser.getMoney())); System.out.println("请您选择要操作的功能:"); System.out.println("1、展示全部影片信息功能:"); System.out.println("2、根据电影名称查询电影信息:"); System.out.println("3、评分功能:"); System.out.println("4、购票功能:"); System.out.println("5、退出系统:"); System.out.println("请输入您要操作的命令:"); String command = SYS_SC.nextLine(); switch (command){ case "1": // 展示全部排片信息 showAllMovies(); break; case "2": break; case "3": // 评分功能 scoreMovie(); showAllMovies(); break; case "4": // 购票功能 buyMovie(); break; case "5": return; // 干掉方法 default: System.out.println("不存在该命令!!"); break; } } } private static void scoreMovie() { // 1、查询当前登录成功的用户历史购买记录,看哪些电影是它可以评分的。 Customer c = (Customer) loginUser; Map<String, Boolean> movies = c.getBuyMovies(); if(movies.size() == 0 ){ System.out.println("当前您没有看过电影,不能评价!"); return; } // 买过了 ,看哪些电影是它可以评分的。 movies.forEach((name, flag) -> { if(flag){ System.out.println(name +"此电影已评价"); }else { System.out.println("请您对:" + name +"进行打分(0-10):"); double score = Double.valueOf(SYS_SC.nextLine()); // 先根据电影名称拿到评分数据 List<Double> scores = MOVIES_SCORE.get(name); // MOVIES_SCORE = [ 名称=[10] , ... ] if(scores == null){ // 说明此电影是第一次评价 scores = new ArrayList<>(); scores.add(score); MOVIES_SCORE.put(name , scores); }else { scores.add(score); } movies.put(name, true); } }); } /** 用户购票功能 ALL_MOVIES = {b1=[p1,p2,p3,..] , b2=[p2,p3,...]} */ private static void buyMovie() { showAllMovies(); System.out.println("=============用户购票功能================="); while (true) { System.out.println("请您输入需要买票的门店:"); String shopName = SYS_SC.nextLine(); // 1、查询是否存在该商家。 Business business = getBusinessByShopName(shopName); if(business == null){ System.out.println("对不起,没有该店铺!请确认"); }else { // 2、此商家全部的排片 List<Movie> movies = ALL_MOVIES.get(business); // 3、判断是否存在上映的电影 if(movies.size() > 0) { // 4、开始进行选片购买 while (true) { System.out.println("请您输入需要购买电影名称:"); String movieName = SYS_SC.nextLine(); // 去当前商家下,查询该电影对象。 Movie movie = getMovieByShopAndName(business, movieName); if(movie != null){ // 开始购买 while (true) { System.out.println("请您输入要购买的电影票数:"); String number = SYS_SC.nextLine(); int buyNumber = Integer.valueOf(number); // 判断电影是否购票 if(movie.getNumber() >= buyNumber){ // 可以购买了 // 当前需要花费的金额 double money = BigDecimal.valueOf(movie.getPrice()).multiply(BigDecimal.valueOf(buyNumber)) .doubleValue(); if(loginUser.getMoney() >= money){ // 终于可以买票了 System.out.println("您成功购买了"+ movie.getName() + buyNumber + "张票!总金额是:" + money); // 更新自己的金额 更新商家的金额 loginUser.setMoney(loginUser.getMoney() - money); business.setMoney(business.getMoney() + money); movie.setNumber(movie.getNumber() - buyNumber); Customer c = (Customer) loginUser; // 记录购买电影的信息 // 第一个参数是购买的电影,第二个参数是没有评价的标记! c.getBuyMovies().put(movie.getName(), false); return;// 结束方法 }else { // 钱不够! System.out.println("是否继续~~"); System.out.println("是否继续买票?y/n"); String command = SYS_SC.nextLine(); switch (command) { case "y": break; default: System.out.println("好的!"); return; } } }else { // 票数不够 System.out.println("您当前最多可以购买:" + movie.getNumber()); System.out.println("是否继续买票?y/n"); String command = SYS_SC.nextLine(); switch (command) { case "y": break; default: System.out.println("好的!"); return; } } } }else { System.out.println("电影名称有毛病~~"); } } }else { System.out.println("该电影院关门了~~~"); System.out.println("是否继续买票?y/n"); String command = SYS_SC.nextLine(); switch (command) { case "y": break; default: System.out.println("好的!"); return; } } } } } public static Movie getMovieByShopAndName(Business business , String name){ List<Movie> movies = ALL_MOVIES.get(business); for (Movie movie : movies) { if(movie.getName().contains(name)){ return movie; } } return null; } /** 根据商家店铺名称查询商家对象 * @return */ public static Business getBusinessByShopName(String shopName){ Set<Business> businesses = ALL_MOVIES.keySet(); for (Business business : businesses) { if(business.getShopName().equals(shopName)){ return business; } } return null; } /** 用户功能:展示全部商家和其排片信息 */ private static void showAllMovies() { System.out.println("=============展示全部商家排片信息================="); ALL_MOVIES.forEach((business, movies) -> { System.out.println(business.getShopName() + "\t\t电话:" + business.getPhone() + "\t\t地址:" + business.getAddress()); System.out.println("\t\t\t片名\t\t\t主演\t\t时长\t\t评分\t\t票价\t\t余票数量\t\t放映时间"); for (Movie movie : movies) { System.out.println("\t\t\t" + movie.getName()+"\t\t\t" + movie.getActor()+ "\t\t" + movie.getTime() + "\t\t" + movie.getScore() + "\t\t" + movie.getPrice() + "\t\t" + movie.getNumber() + "\t\t" + sdf.format(movie.getStartTime())); } }); } public static User getUserByLoginName(String loginName){ for (User user : ALL_USERS) { // 判断这个用户的登录名称是否是我们想要的 if(user.getLoginName().equals(loginName)){ return user; } } return null; // 查询此用户登录名称 } }学习记录91 File1.1 File类概述File类在包java.io.File下,代表操作系统的文件对象(文件、文件夹)。File类提供了诸如:定位文件,获取文件本身的信息、删除文件、创建文件(文件夹)等功能。绝对路径和相对路径在Window中:绝对路径:从盘符开始。相对路径:不带盘符,默认直接到当前工程下的目录寻找文件。在Linux中,这是一个非常重要的概念:绝对路径是从目录树的树根“/”目录开始往下直至到达文件所经过的所有节点目录。下级目录接在上级目录后面用“/”隔开。注意:绝对路径都是从“/”开始的,所以第一个字符一定是“/”。相对路径是指目标目录相对于当前目录的位置。如果不在当前目录下,则需要使用两个特殊目录“.”和“../”了。目录“.”指向当前目录,而目录“../”指向上级目录。package com.day09.FileDemo; import java.io.File; public class FileDemo01 { public static void main(String[] args) { //创建一个file对象 //三种以绝对路径创建的文件对象 File file = new File("F:\\Learing\\The_third_week\\xxx"); File file1 = new File("F:/Learing/The_third_week/xxx"); File file2 = new File("F:" + File.separator + "Learing" + File.separator + "The_third_week" + File.separator + "xxx"); //获取file文件字节大小 long l = file.length(); System.out.println(l);//55 System.out.println(file1.length());//55 System.out.println(file2.length());//55 //使用相对路径定位文件,相对于在同一个工程下 File file3 = new File("xxx"); System.out.println(file3.length());//55 //File创建对象可以是文件也可以是文件夹 File file4 = new File("F:/Learing"); System.out.println(file4.length());//8192 System.out.println(file4.exists());//true } }1.2 File类常用APIFile类判断文件类型、获取文件信息功能。package com.day09.FileDemo; import java.io.File; import java.text.SimpleDateFormat; public class FileDemo02 { public static void main(String[] args) { File file = new File("F:\\Learing\\The_third_week\\xxx"); File file1 = new File("F:/Learing"); //判断是文件还是文件夹 System.out.println(file.isDirectory()); System.out.println(file1.isDirectory()); System.out.println(file.isFile()); System.out.println(file1.isFile()); //判断文件是否存在 System.out.println(file.exists()); System.out.println(file1.exists()); //得到文件的绝对路径 String fileAbsolutePath = file.getAbsolutePath(); System.out.println(fileAbsolutePath); //获取路径名 String path = file.getPath(); System.out.println(path); //获取文件名 String name = file.getName(); System.out.println(name); //最后修改时间 long l = file.lastModified(); System.out.println(new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").format(l)); } }package com.day09.FileDemo; import java.io.File; import java.io.IOException; public class FileDemo03 { public static void main(String[] args) throws IOException { //创建一个新文件 File file = new File("F:\\Learing\\The_third_week\\yyy"); System.out.println(file.createNewFile()); //创建一级目录 File file1 = new File("F:\\Learing\\The_third_week1"); System.out.println(file1.mkdir()); //创建多级文件夹 File file2 = new File("F:\\Learing\\The_third_week\\Test"); System.out.println(file2.mkdirs()); //删除文件 只能删除非空文件夹 System.out.println(file1.delete()); System.out.println(file2.delete()); System.out.println(file.delete()); } }1.3 File类的遍历方法listFiles方法注意事项:当调用者不存在时,返回null。当调用者是一个文件时,返回null。当调用者是一个空文件夹时,返回一个长度为0的数组。当调用者是一个有内容的文件夹时,将里面所有文件和文件夹的路径放在File数组中返回。当调用者是一个有隐藏文件的文件夹时,将里面所有文件和文件夹的路径放在File数组中返回,包含隐藏内容。package com.day09.FileDemo; import java.io.File; public class FileDemo04 { public static void main(String[] args) { //返回file名称数组 File file = new File("F:\\Learing\\The_third_week"); String[] lists = file.list(); for (String list : lists) { System.out.println(list); } /*. idea pom.xml src target xxx*/ //返回文件对象 File[] files = file.listFiles(); for (File file1 : files) { System.out.println(file1); } /* * F:\Learing\The_third_week\.idea F:\Learing\The_third_week\pom.xml F:\Learing\The_third_week\src F:\Learing\The_third_week\target F:\Learing\The_third_week\xxx * */ //当调用者不存在返回Null File file2 = new File("F:\\Learing\\The_third_week1"); File[] files1 = file2.listFiles(); System.out.println(files1);//null //当调用者是文件时,返回null File file3 = new File("F:\\Learing\\The_third_week\\xxx"); File[] files2 = file3.listFiles(); System.out.println(files2);//null //当调用者时空文件,返回空数组 File file1 = new File("F:\\Learing\\The_third_week\\src\\main\\java\\com\\day10"); File[] files3 = file1.listFiles(); for (File file4 : files3) { System.out.println(file4); } } }2 方法递归2.1 规律化递归方法直接调用自己或者间接调用自己的形式称为方法递归( recursion)。递归做为一种算法在程序设计语言中广泛应用。递归解决问题的思路:把一个复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解。递归算法三要素大体可以总结为:递归的公式: f(n)= f(n-1)* n。递归的终结点: f(1)。递归的方向必须走向终结点。需求一:求阶乘!首先求出数学公式:$$ f(1) = 1; f(2) = 1*2; f(3)=1*2*3; f(4)=1*2*3*4;...... $$故可推出公式:$$ f(x)=f(x-1)*x $$递归的终结点:$$ f(1) =1 $$package com.day09.recursionDemo; public class recursionDemo01 { public static void main(String[] args) { int i = factorial(3); System.out.println(i); } //求某个数的阶乘 public static int factorial(int num){ if (num==1){ return 1; }else { return factorial(num-1)*num; } } }使用debug运行情况:debug中的方法调用是通过下面该图1,2,3,4步进行调用的。需求二:求1~n的和首先求出数学公式:$$ f(1) = 1; f(2) = 1+2; f(3)=1+2+3; f(4)=1+2+3+4;...... $$故可推出公式:$$ f(x)=f(x-1)+x $$递归的终结点:$$ f(1) =1 $$package com.day09.recursionDemo; public class recursionDemo01 { public static void main(String[] args) { int i = factorial(6); System.out.println(i); } //求1~n的和 public static int factorial(int num){ if (num==1){ return 1; }else { return factorial(num-1)+num; } } } //21需求三:猴子吃桃问题首先需要判断出公式:$$ f(x) - f(x)/2 -1 = f(x+1) $$公式两边同乘2,化简即可得到:$$ f(x) = 2f(x+1) + 2 $$递归的终结点:$$ f(10) = 1 $$package com.day09.recursionDemo; public class Monkey_eating_peach { public static void main(String[] args) { System.out.println("猴子第一天摘了"+num(1)+"个桃。"); } public static int num(int i){ if (i == 10){ return 1; }else return 2 * num(i+1) + 2; } } //猴子第一天摘了1534个桃。2.2 非规律化递归需求一:在D盘寻找:copytranslator.exe 文件package com.day09.recursionDemo; import java.io.File; public class Find_files_recursively { //在D盘寻找:copytranslator.exe public static void main(String[] args) { searchFiles(new File("D:/"),"copytranslator.exe"); } public static void searchFiles(File dir,String filename){ //判断传进来的目录是否是一级目录 if (dir != null && dir.isDirectory()){ File[] files = dir.listFiles(); if (files != null && files.length>0){ for (File file : files) { //看是否是文件 if (file.isFile()){ if (file.getName().contains(filename)){ System.out.println("找到目标文件"+file.getAbsolutePath()); } }else { searchFiles(file,filename); } } } }else { System.out.println("对不起,传入的文件地址不正确。"); } } }需求二:啤酒2元1瓶,4个盖子可以换一瓶,2个空瓶可以换一瓶,请问10元钱可以喝多少瓶酒,剩余多少空瓶和盖子。package com.day09.recursionDemo; import java.util.Arrays; public class RecursionDemo02 { // 定义一个静态的成员变量用于存储可以买的酒数量 public static int totalNumber; // 总数量 public static int lastBottleNumber; // 记录每次剩余的瓶子个数 public static int lastCoverNumber; // 记录每次剩余的盖子个数 public static void main(String[] args) { // 1、拿钱买酒 buy(10); System.out.println("总数:" + totalNumber); System.out.println("剩余盖子数:" + lastCoverNumber); System.out.println("剩余瓶子数:" + lastBottleNumber); } public static void buy(int money){ // 2、看可以立马买多少瓶 int buyNumber = money / 2; // 5 totalNumber += buyNumber; // 3、把盖子 和瓶子换算成钱 // 统计本轮总的盖子数 和 瓶子数 int coverNumber = lastCoverNumber + buyNumber; int bottleNumber = lastBottleNumber + buyNumber; // 统计可以换算的钱 int allMoney = 0; if(coverNumber >= 4){ allMoney += (coverNumber / 4) * 2; } lastCoverNumber = coverNumber % 4; if(bottleNumber >= 2){ allMoney += (bottleNumber / 2) * 2; } lastBottleNumber = bottleNumber % 2; if(allMoney >= 2){ buy(allMoney); } Integer[] arr2 = new Integer[]{11, 22, 33}; Arrays.sort(arr2); } }3 字符集ASCII字符集:ASCII(American Standard Code for Information Interchange,美国信息交换标准代码):包括了数字、英文、符号。ASCII使用1个字节存储一 个字符,一个字节是8位,总共可以表示128个字符信息,对于英文,数字来说是够用的。GBK:window系统默认的码表。 兼容ASCII码表,也包含了几万个汉字,并支持繁体汉字以及部分日韩文字。注意: GBK是中国的码表,一个中文以两个字节的形式存储。但不包含世界上所有国家的文字。Unicode码表:unicode (又称统一码、万国码、单一码)是计算机科学领域里的一项业界字符编码标准。容纳世界上大多数国家的所有常见文字和符号。由于Unicode会先通过UTF-8, UTF-16, 以及UTF-32的编码成二进制后再存储到计算机,其中最为常见的就是UTF-8。Unicode是万国码,以UTF-8编码后一个中文一般以三个字节的形式存储。UTF-8也要兼容ASCII编码表。技术人员都应该使用UTF-8的字符集编码。编码前和编码后的字符集需要一致, 否则会出现中文乱码。package com.day09.character_set; import java.io.UnsupportedEncodingException; import java.nio.charset.StandardCharsets; import java.util.Arrays; public class Test { public static void main(String[] args) throws UnsupportedEncodingException { //编码:把文字转成字节 String name = "努力少年!123"; byte[] bytes = name.getBytes(); System.out.println(bytes.length); String string = Arrays.toString(bytes); System.out.println(string); //18 //[-27, -118, -86, -27, -118, -101, -27, -80, -111, -27, -71, -76, -17, -68, -127, 49, 50, 51] byte[] bytes1 = name.getBytes("GBK"); System.out.println(bytes1.length); String string1 = Arrays.toString(bytes1); System.out.println(string1); //13 //[-59, -84, -63, -90, -55, -39, -60, -22, -93, -95, 49, 50, 51] //解码:把字节转换成对应的中文 String s = new String(bytes); System.out.println(s); String s1 = new String(bytes1, "GBK"); System.out.println(s1); } }总结:字符串常见的字符底层组成是什么样的?英文和数字等在任何国家的字符集中都占1个字节。GBK字符中一个中文字符占2个字节。UTF-8编码中一个中文一般占3个字节。4 字节流IO流概述:I表示intput,是数据从硬盘文件读入到内存的过程,称之输入,负责读。O表示output,是内存程序的数据从内存到写出到硬盘文件的过程,称之输出,负责写。IO流分类:字节输入流:以内存为基准,来自磁盘文件/网络中的数据以字节的形式读入到内存中去的流称为字节输入流。字节输出流:以内存为基准,把内存中的数据以字节写出到磁盘文件或者网络中去的流称为字节输出流。字符输入流:以内存为基准,来自磁盘文件/网络中的数据以字符的形式读入到内存中去的流称为字符输入流。字符输出流:以内存为基准,把内存中的数据以字符写出到磁盘文件或者网络介质中去的流称为字符输出流。IO流体系:4.1 FileInputStream字节输入流构造器描述FileInputStream(File file)通过打开与实际文件的连接来创建 FileInputStream ,该文件由文件系统中的 File对象 file命名。FileInputStream(String name)通过打开与实际文件的连接来创建 FileInputStream ,该文件由文件系统中的路径名 name命名。变量和类型方法描述voidclose()关闭此文件输入流并释放与该流关联的所有系统资源。intread()从此输入流中读取一个字节的数据。intread(byte[] b)从此输入流 b.length最多 b.length字节的数据读 b.length字节数组。intread(byte[] b, int off, int len)从此输入流 len最多 len字节的数据读入一个字节数组。package com.day09.IODemo; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; public class FileInputStreamDemo { public static void main(String[] args) throws IOException { //创建一个字节输入流 经典多态写法 InputStream inputStream = new FileInputStream("yyy"); //读取一个字节并返回 int read = inputStream.read(); System.out.println((char) read);//a System.out.println((char) inputStream.read());//b System.out.println((char) inputStream.read());//c System.out.println((char) inputStream.read());//d System.out.println((char) inputStream.read());//1 System.out.println(inputStream.read());//-1 inputStream.close(); //即-1代表读取结束 //使用循环读取文件中的所有字节 InputStream inputStream1 = new FileInputStream("yyy"); int b; while ((b=inputStream1.read())!= -1){ System.out.println((char) b); } inputStream1.close(); } }总结:每次读取一个字节存在什么问题?性能较慢。读取中文字符输出无法避免乱码问题。package com.day09.IODemo; import java.io.*; public class FileInputStreamDemo03 { public static void main(String[] args) throws IOException { InputStream inputStream = new FileInputStream(new File("yyy")); //使用字节数组来接收 每三个字节一块进行接收 byte[] bytes = new byte[3]; int read = inputStream.read(bytes); String s = new String(bytes); System.out.println(s);//abc //使用字节数组来接收 byte[] bytes1 = new byte[3]; int read1 = inputStream.read(bytes1); String s1 = new String(bytes1); System.out.println(s1);//d1 这里出现了乱码现象 inputStream.close(); //使用循环进行读取 InputStream inputStream1 = new FileInputStream(new File("yyy")); byte[] bytes2 = new byte[3]; int led; // 记录每次读取的字节数 while ((led = inputStream1.read(bytes2))!= -1){ System.out.print(new String(bytes2,0,led)); } //abcd1 inputStream.close(); } }总结:每次读取一个字节数组存在什么问题?读取的性能得到了提升。读取中文字符输出无法避免乱码问题。package com.day09.IODemo; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.InputStream; public class FileInputStreamDemo04 { //使用字节输入流一次性读取完全部字节。从而解决乱码问题 public static void main(String[] args) throws Exception { File file = new File("xxx"); InputStream inputStream = new FileInputStream(file); //2.定义一个字节数组于文件的大小一致 byte[] bytes = new byte[(int) file.length()]; int read = inputStream.read(bytes); System.out.println(new String(bytes)); inputStream.close(); //JDK9之后出现的新API InputStream inputStream1 = new FileInputStream(file); byte[] bytes1 = inputStream1.readAllBytes(); System.out.println(new String(bytes1)); inputStream.close(); } }总结:如何使用字节输入流读取中文内容输出不乱码呢?一次性读取完全部字节。可以定义与文件一样大的字节数组读取,也可以使用官方API。4.2 FileOutputStream字节输出流构造器描述FileOutputStream(File file)创建文件输出流以写入由指定的 File对象表示的文件。FileOutputStream(File file, boolean append)创建文件输出流以写入由指定的 File对象表示的文件。FileOutputStream(String name)创建文件输出流以写入具有指定名称的文件。FileOutputStream(String name, boolean append)创建文件输出流以写入具有指定名称的文件。变量和类型方法描述voidclose()关闭此文件输出流并释放与此流关联的所有系统资源。voidwrite(byte[] b)将指定字节数组中的 b.length字节写入此文件输出流。voidwrite(byte[] b, int off, int len)将从偏移量 off开始的指定字节数组中的 len字节写入此文件输出流。voidwrite(int b)将指定的字节写入此文件输出流。package com.day09.IODemo; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.OutputStream; public class FileOutputStreamDemo { public static void main(String[] args) throws Exception { //经典多态写法 OutputStream outputStream = new FileOutputStream("1.txt",true);//写true实现管道追加,即文件不进行覆盖,而是进行添加字节 //写入一个字节数组 byte[] bytes = "你好啊。我叫zhenxi,很高兴认识你。".getBytes(); outputStream.write(bytes); //写入一个字节进去 outputStream.write('a'); outputStream.write('b'); //写数据必须要刷新数据 outputStream.flush(); //关闭流自动刷新数据 outputStream.close(); } }4.3 经典案例:文件拷贝package com.day09.IODemo; import java.io.*; public class File_copy { public static void main(String[] args) throws Exception { InputStream inputStream = new FileInputStream(new File("C:\\Users\\77339\\Desktop\\最近任务\\javakfscss_jb51\\java开发手册-嵩山版\\java开发手册-嵩山版.pdf")); byte[] bytes = inputStream.readAllBytes(); OutputStream outputStream = new FileOutputStream(new File("xx.pdf")); outputStream.write(bytes); outputStream.close(); inputStream.close(); } }5 资源释放try-catch-finallyfinally:在异常处理时提供finally块来执行所有清除操作,比如说IO流中的释放资源。特点:被finally控制的语句最终一定会执行,除非JVM退出。异常处理标准格式: try...catch...finallypackage com.day09.IODemo; import java.io.*; public class Try_finally { public static void main(String[] args) { InputStream is = null; OutputStream os = null; try { is = new FileInputStream(new File("D:\\杂物\\本科杂物\\毕设\\系统演示视频.mp4")); os = new FileOutputStream("111.mp4"); byte[] bytes = is.readAllBytes(); os.write(bytes); } catch (Exception e) { e.printStackTrace(); }finally { try { os.close(); } catch (IOException e) { e.printStackTrace(); } try { is.close(); } catch (IOException e) { e.printStackTrace(); } } } }6 字符流6.1 FileReader字符输入流构造器描述FileReader(File file)使用平台 FileReader ,在 File读取时创建一个新的 FileReaderFileReader(File file, Charset charset)创建一个新的FileReader ,给出File读取和charset 。FileReader(String fileName)使用平台 default charset创建一个新的 FileReader ,给定要读取的文件的名称 。FileReader(String fileName, Charset charset)给定要读取的文件的名称和FileReader ,创建一个新的FileReader。变量和类型方法描述StringgetEncoding()返回此流使用的字符编码的名称。intread()读一个字符。intread(char[] cbuf, int offset, int length)将字符读入数组的一部分。booleanready()判断此流是否可以读取。package com.day09.IODemo; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; import java.io.Reader; public class FileReaderDemo01 { public static void main(String[] args) throws IOException { Reader fr = new FileReader("xxx"); //读取一个字符 int read = fr.read(); System.out.println((char) read); //你 //使用循环进行读取字符 int led; while ((led = fr.read())!=-1){ System.out.print((char) led); } fr.close(); //使用字符数组进行读取字符 Reader fr1 = new FileReader("1.txt"); char[] chars = new char[1024]; int len; while ((len =fr1.read(chars))!=-1){ String s = new String(chars, 0, len); System.out.println(s); } fr1.close(); } }6.2 FileWriter字符输出流构造器描述FileWriter(File file)给 File写一个 FileWriter ,使用平台的 default charsetFileWriter(File file, boolean append)在给出要写入的 FileWriter下构造 File ,并使用平台的 default charset构造一个布尔值,指示是否附加写入的数据。FileWriter(File file, Charset charset)构造一个FileWriter给予File编写和charset。FileWriter(File file, Charset charset, boolean append)构造FileWriter给出File写入, charset和一个布尔值,指示是否附加写入的数据。FileWriter(String fileName)构造一个 FileWriter给出文件名,使用平台的 default charsetFileWriter(String fileName, boolean append)使用平台的 default charset构造一个 FileWriter给定一个文件名和一个布尔值,指示是否附加写入的数据。FileWriter(String fileName, Charset charset)构造一个FileWriter给出文件名和charset。FileWriter(String fileName, Charset charset, boolean append)构造一个FileWriter给定一个文件名, charset和一个布尔值,指示是否附加写入的数据。变量和类型方法描述voidflush()刷新流。StringgetEncoding()返回此流使用的字符编码的名称。voidwrite(char[] cbuf, int off, int len)写一个字符数组的一部分。voidwrite(int c)写一个字符。voidwrite(String str, int off, int len)写一个字符串的一部分。package com.day09.IODemo; import java.io.FileWriter; import java.io.IOException; import java.io.Writer; public class FileWriterDemo { public static void main(String[] args) throws Exception { Writer fw = new FileWriter("222.txt",true); //写入一个字符 fw.write(11); fw.write('a'); fw.write("你"); //写入一个字符数组进去 char[] chars = "你好,我是zhenxi。".toCharArray(); fw.write(chars); //直接写入字符串 fw.write("加油!继续努力!"); fw.close(); } }学习记录101 缓冲流缓冲流概述缓冲流也称为高效流、或者高级流。之前学习的字节流可以称为原始流。作用:缓冲流自带缓冲区、可以提高原始字节流、字符流读写数据的性能。缓冲流的作用缓冲流自带缓冲区、可以提高原始字节流、字符流读写数据的性能。缓冲流分类字节缓冲流字节缓冲输入流:BufferedInputStream字节缓冲输出流:BufferedOutputStream字符缓冲流字符缓冲输入流:BufferedReader字符缓冲输出流:BufferedWriter1.1 字节缓冲流构造器描述BufferedInputStream(InputStream in)创建一个 BufferedInputStream并保存其参数,即输入流 in ,供以后使用。BufferedInputStream(InputStream in, int size)创建具有指定缓冲区大小的 BufferedInputStream ,并保存其参数(输入流 in )供以后使用。构造器描述BufferedOutputStream(OutputStream out)创建新的缓冲输出流以将数据写入指定的基础输出流。BufferedOutputStream(OutputStream out, int size)创建新的缓冲输出流,以使用指定的缓冲区大小将数据写入指定的基础输出流。需求一:使用字节缓冲流实现文件复制。package com.day10.Buffered; import java.io.*; public class BufferedDemo { public static void main(String[] args) throws Exception { InputStream inputStream = new FileInputStream(new File("D:\\Java\\Java文档\\java开发手册-嵩山版\\java开发手册-嵩山版.pdf")); BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream); byte[] bytes = bufferedInputStream.readAllBytes(); OutputStream outputStream = new FileOutputStream(new File("xx.pdf")); BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(outputStream); bufferedOutputStream.write(bytes); bufferedOutputStream.close(); inputStream.close(); } }需求二:分别使用低级字节流和高级字节缓冲流拷贝大视频,记录耗时。分析:①使用低级的字节流按照一个一个字节的形式复制文件。②使用低级的字节流按照一个一个字节数组的形式复制文件。③使用高级的缓冲字节流按照一个一个字节的形式复制文件。④使用高级的缓冲字节流按照一个一个字节数组的形式复制文件。package com.day10.Buffered; import java.io.*; public class BufferedDemo02 { public static final String FILE_PATH = "D:\\杂物\\本科杂物\\毕设\\系统演示视频.mp4"; public static void main(String[] args) { // copy1(); //几乎没动静 copy2(); //1s左右 copy3();//6s左右 copy4();//1s以下 } //使用高级缓冲流采用字符数组进行读取 private static void copy4() { long l = System.currentTimeMillis(); BufferedInputStream bufferedInputStream = null; BufferedOutputStream bufferedOutputStream = null; try { InputStream inputStream = new FileInputStream(FILE_PATH); bufferedInputStream = new BufferedInputStream(inputStream); OutputStream outputStream = new FileOutputStream("copy1.mp4"); bufferedOutputStream = new BufferedOutputStream(outputStream); byte[] bytes = bufferedInputStream.readAllBytes(); bufferedOutputStream.write(bytes); } catch (IOException e) { e.printStackTrace(); }finally { try { bufferedInputStream.close(); } catch (IOException e) { e.printStackTrace(); } try { bufferedOutputStream.close(); } catch (IOException e) { e.printStackTrace(); } } long l1 = System.currentTimeMillis(); System.out.println("copy4所消耗的时间为:"+(l1-l)/1000); } //使用高级缓冲流一个字节一个读取 private static void copy3() { long l = System.currentTimeMillis(); BufferedInputStream bufferedInputStream = null; BufferedOutputStream bufferedOutputStream = null; try { InputStream inputStream = new FileInputStream(FILE_PATH); bufferedInputStream = new BufferedInputStream(inputStream); OutputStream outputStream = new FileOutputStream("copy1.mp4"); bufferedOutputStream = new BufferedOutputStream(outputStream); int len; while ((len = bufferedInputStream.read())!= -1){ try { bufferedOutputStream.write(len); } catch (IOException e) { e.printStackTrace(); } } } catch (IOException e) { e.printStackTrace(); }finally { try { bufferedInputStream.close(); } catch (IOException e) { e.printStackTrace(); } try { bufferedOutputStream.close(); } catch (IOException e) { e.printStackTrace(); } } long l1 = System.currentTimeMillis(); System.out.println("copy3所消耗的时间为:"+(l1-l)/1000); } //使用低级流的字节数组 private static void copy2() { long l = System.currentTimeMillis(); InputStream inputStream = null; OutputStream outputStream = null; try { inputStream = new FileInputStream(FILE_PATH); outputStream = new FileOutputStream("copy02.mp4"); byte[] bytes = inputStream.readAllBytes(); outputStream.write(bytes); } catch (IOException e) { e.printStackTrace(); }finally { try { outputStream.close(); } catch (IOException e) { e.printStackTrace(); } try { inputStream.close(); } catch (IOException e) { e.printStackTrace(); } } long l1 = System.currentTimeMillis(); System.out.println("copy2所消耗的时间为:"+(l1-l)/1000); } //使用低阶字节流 一个一个读 private static void copy1() { long l = System.currentTimeMillis(); InputStream inputStream = null; OutputStream outputStream = null; try { inputStream = new FileInputStream(FILE_PATH); outputStream = new FileOutputStream("copy1.mp4"); int len; while ((len = inputStream.read())!= -1){ try { outputStream.write(len); } catch (IOException e) { e.printStackTrace(); } } } catch (IOException e) { e.printStackTrace(); }finally { try { outputStream.close(); } catch (IOException e) { e.printStackTrace(); } try { inputStream.close(); } catch (IOException e) { e.printStackTrace(); } } long l1 = System.currentTimeMillis(); System.out.println("copy1所消耗的时间为:"+(l1-l)/1000); } }1.2 字符缓冲流构造器描述BufferedReader(Reader in)创建使用默认大小的输入缓冲区的缓冲字符输入流。BufferedReader(Reader in, int sz)创建使用指定大小的输入缓冲区的缓冲字符输入流。变量和类型方法描述Stream<String>lines()返回 Stream ,其元素是从此 BufferedReader读取的行。intread()读一个字符。intread(char[] cbuf, int off, int len)将字符读入数组的一部分。StringreadLine()读一行文字。booleanready()判断此流是否可以读取。voidreset()将流重置为最新标记。longskip(long n)跳过字符。package com.day10.Buffered; import java.io.*; public class BufferedReaderDemo { public static void main(String[] args) { try { Reader reader = new FileReader("222.txt"); BufferedReader bufferedReader = new BufferedReader(reader); //FileReader经典方法 /*char[] chars = new char[1024]; int len; while ((len = bufferedReader.read(chars))!=-1){ System.out.println(new String(chars, 0, len)); }*/ //readLine方法 String line; while ((line = bufferedReader.readLine())!=null){ System.out.println(line); } } catch (IOException e) { e.printStackTrace(); } } }构造器描述BufferedWriter(Writer out)创建使用默认大小的输出缓冲区的缓冲字符输出流。BufferedWriter(Writer out, int sz)创建一个使用给定大小的输出缓冲区的新缓冲字符输出流。变量和类型方法描述voidflush()刷新流。voidnewLine()写一个行分隔符。voidwrite(char[] cbuf, int off, int len)写一个字符数组的一部分。voidwrite(int c)写一个字符。voidwrite(String s, int off, int len)写一个字符串的一部分。package com.day10.Buffered; import java.io.BufferedWriter; import java.io.FileWriter; import java.io.IOException; import java.io.Writer; public class BufferedWriterDemo { public static void main(String[] args) throws IOException { Writer fw = new FileWriter("222.txt",true); BufferedWriter bw = new BufferedWriter(fw); //写入一个字符 bw.write(98); bw.write('a'); bw.write("你"); bw.newLine(); //写入一个字符数组进去 char[] chars = "你好,我是zhenxi。".toCharArray(); bw.write(chars); bw.newLine(); //直接写入字符串 bw.write("加油!继续努力!"); bw.close(); bw.newLine(); } }需求:把《出师表》的文章顺序进行恢复到一个新文件中。分析:①定义一个缓存字符输入流管道与源文件接通。②定义一个List集合存储读取的每行数据。③定义一个循环按照行读取数据,存入到List集合中去。④对List集合中的每行数据按照首字符编号升序排序。⑤定义一个缓存字符输出管道与目标文件接通。package com.day10.Buffered; import java.io.*; import java.util.ArrayList; import java.util.Collections; import java.util.List; public class BufferTest { public static void main(String[] args) { BufferedReader br = null; BufferedWriter bw = null; try { Reader fileReader = new FileReader("csb.txt"); br = new BufferedReader(fileReader); bw = new BufferedWriter(new FileWriter("new_csb.txt")); List<String> list = new ArrayList<>(); String line; while ((line = br.readLine())!= null){ list.add(line); } System.out.println(list); Collections.sort(list); System.out.println(list); for (String s : list) { bw.write(s); bw.newLine(); } } catch (IOException e) { e.printStackTrace(); }finally { try { bw.close(); } catch (IOException e) { e.printStackTrace(); } try { br.close(); } catch (IOException e) { e.printStackTrace(); } } } }2 转换流问题引出:当使用输入流读取文件时默认读取UTF-8的编码。若读取一个GBK的编码则会乱码。package com.day10.transform_stream; import java.io.BufferedReader; import java.io.FileReader; import java.io.IOException; import java.io.Reader; public class CharSetDemo { public static void main(String[] args) { try { Reader reader = new FileReader("C:\\Users\\77339\\Desktop\\111.txt"); BufferedReader bufferedReader = new BufferedReader(reader); String line; while ((line = bufferedReader.readLine())!=null){ System.out.println(line); } } catch (IOException e) { e.printStackTrace(); } } } /*** 运行结果: ����������������GBK�ı��롣 / //文件内容:啊啊啊啊啊,这是GBK的编码。InputStreamReader输入转换流:构造器描述InputStreamReader(InputStream in, String charsetName)创建一个使用指定charset的InputStreamReader。变量和类型方法描述StringgetEncoding()返回此流使用的字符编码的名称。intread()读一个字符。intread(char[] cbuf, int offset, int length)将字符读入数组的一部分。booleanready()判断此流是否可以读取。package com.day10.transform_stream; import java.io.*; public class CharSetDemo02 { public static void main(String[] args) { try { FileInputStream fi = new FileInputStream("C:\\Users\\77339\\Desktop\\111.txt"); //使用转换流 InputStreamReader inputStreamReader = new InputStreamReader(fi,"GBK"); BufferedReader bufferedReader = new BufferedReader(inputStreamReader); String line; while ((line = bufferedReader.readLine())!=null){ System.out.println(line); } } catch (IOException e) { e.printStackTrace(); } } }OutputStreamWriter输出转换流:构造器描述OutputStreamWriter(OutputStream out)创建使用默认字符编码的OutputStreamWriter。OutputStreamWriter(OutputStream out, String charsetName)创建使用指定charset的OutputStreamWriter。OutputStreamWriter(OutputStream out, CharsetEncoder enc)创建使用给定charset编码器的OutputStreamWriter。变量和类型方法描述voidflush()刷新流。StringgetEncoding()返回此流使用的字符编码的名称。voidwrite(char[] cbuf, int off, int len)写一个字符数组的一部分。voidwrite(int c)写一个字符。voidwrite(String str, int off, int len)写一个字符串的一部分。package com.day10.transform_stream; import java.io.*; public class CharSetDemo03 { public static void main(String[] args) throws IOException { OutputStream outputStream = new FileOutputStream("charsetdemo03.txt"); Writer writer = new OutputStreamWriter(outputStream, "GBK"); BufferedWriter bw = new BufferedWriter(writer); //写入一个字符 bw.write(98); bw.write('a'); bw.write("你"); bw.newLine(); //写入一个字符数组进去 char[] chars = "你好,我是zhenxi。".toCharArray(); bw.write(chars); bw.newLine(); //直接写入字符串 bw.write("加油!继续努力!"); bw.close(); } }3 序列化3.1 对象序列化作用:以内存为基准,把内存中的对象存储到磁盘文件中去,称为对象序列化。使用到的流是对象字节输出流: ObjectOutputStream。变量构造器描述publicObjectOutputStream(OutputStream out)创建一个写入指定OutputStream的ObjectOutputStream。变量和类型方法描述voidclose()关闭流。voidflush()刷新流。voidwrite(byte[] buf)写一个字节数组。voidwrite(byte[] buf, int off, int len)写一个子字节数组。voidwriteObject(Object obj)将指定的对象写入ObjectOutputStream。案例:序列下面student类对象。package com.day10.Serialization; import java.io.Serializable; //对象要进行序列化,必须要实现该接口 public class Student implements Serializable { private String name; private String password; private int id; private char sex; public Student() { } public Student(String name, String password, int id, char sex) { this.name = name; this.password = password; this.id = id; this.sex = sex; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public int getId() { return id; } public void setId(int id) { this.id = id; } public char getSex() { return sex; } public void setSex(char sex) { this.sex = sex; } @Override public String toString() { return "Student{" + "name='" + name + '\'' + ", password='" + password + '\'' + ", id=" + id + ", sex=" + sex + '}'; } }package com.day10.Serialization; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.ObjectOutputStream; public class SerializationDemo { public static void main(String[] args) throws Exception { Student student = new Student("zhenxi", "2022956775", 775, '男'); ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("Serialization.txt")); oos.writeObject(student); oos.close(); } }3.2 对象反序列化使用到的流是对象字节输入流: ObjectInputStream。作用:以内存为基准,把存储到磁盘文件中去的对象数据恢复成内存中的对象,称为对象反序列化。变量构造器描述publicObjectInputStream(InputStream in)创建一个从指定的InputStream读取的ObjectInputStream。变量和类型方法描述voidclose()关闭输入流。intread()读取一个字节的数据。intread(byte[] buf, int off, int len)读入一个字节数组。ObjectreadObject()从ObjectInputStream中读取一个对象。package com.day10.Serialization; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.ObjectInputStream; public class SerializationDemo02 { public static void main(String[] args) throws Exception { //反序列化 ObjectInputStream oit = new ObjectInputStream(new FileInputStream("Serialization.txt")); Student student = (Student)oit.readObject(); System.out.println(student); } } /** Student{name='zhenxi', password='2022956775', id=775, sex=男} */4 打印流作用:打印流可以实现方便、高效的打印数据到文件中去。打印流一般是指: PrintStream, PrintWriter两个类。构造器描述PrintStream(File file)使用指定的文件创建没有自动行刷新的新打印流。PrintStream(File file, String csn)使用指定的文件和字符集创建一个没有自动行刷新的新打印流。PrintStream(OutputStream out)创建新的打印流。PrintStream(String fileName)使用指定的文件名创建没有自动行刷新的新打印流。PrintStream(String fileName, String csn)使用指定的文件名和字符集创建一个没有自动行刷新的新打印流。package com.day10.PrintStream; import java.io.FileNotFoundException; import java.io.PrintStream; public class PrintStreamDemo { public static void main(String[] args) throws FileNotFoundException { PrintStream printStream = new PrintStream("printStream.txt"); printStream.println("打印流"); printStream.println(111); printStream.println('a'); printStream.println("包含了缓冲流"); printStream.close(); } }PrintWriter类:构造器描述PrintWriter(File file)使用指定的文件创建一个没有自动行刷新的新PrintWriter。PrintWriter(File file, String csn)使用指定的文件和字符集创建一个没有自动行刷新的新PrintWriter。PrintWriter(OutputStream out)从现有的OutputStream创建一个没有自动行刷新的新PrintWriter。PrintWriter(Writer out)创建一个新的PrintWriter,没有自动行刷新。PrintWriter(String fileName)使用指定的文件名创建一个没有自动行刷新的新PrintWriter。PrintWriter(String fileName, String csn)使用指定的文件名和字符集创建一个没有自动行刷新的新PrintWriter。package com.day10.PrintStream; import java.io.FileNotFoundException; import java.io.PrintStream; import java.io.PrintWriter; public class PrintWriterDemo { public static void main(String[] args) throws FileNotFoundException { PrintWriter printWriter = new PrintWriter("printWriter.txt"); printWriter.println("printWriter打印流"); printWriter.println(111); printWriter.println('a'); printWriter.println("包含了缓冲流"); printWriter.close(); } }PrintStream和PrintWriter的区别:打印数据功能上是一模一样的, 都是使用方便,性能高效(核心优势)。PrintStream继承自字节输出流OutputStream,支持写字节数据的方法。PrintWriter继承自字符输出流Writer,支持写字符数据出去。5 Properties结合IO流Properties代表的是一个属性文件, 可以把自己对象中的键值对信息存入到一个属性文件中去。属性文件:后缀是.properties结尾的文件,里面的内容都是key=value,后续做系统配置信息的。Properties类API变量和类型方法描述StringgetProperty(String key)在此属性列表中搜索具有指定键的属性。StringgetProperty(String key, String defaultValue)在此属性列表中搜索具有指定键的属性。voidlist(PrintStream out)将此属性列表打印到指定的输出流。voidlist(PrintWriter out)将此属性列表打印到指定的输出流。voidload(InputStream inStream)从输入字节流中读取属性列表(键和元素对)。voidload(Reader reader)以简单的面向行的格式从输入字符流中读取属性列表(键和元素对)。Enumeration<?>propertyNames()返回此属性列表中所有键的枚举,如果尚未从主属性列表中找到相同名称的键,则包括默认属性列表中的不同键。ObjectsetProperty(String key, String value)调用 Hashtable方法 put 。voidstore(OutputStream out, String comments)将此 Properties表中的此属性列表(键和元素对)以适合使用 load(InputStream)方法加载到 Properties表的格式写入输出流。voidstore(Writer writer, String comments)将此 Properties表中的此属性列表(键和元素对)以适合使用 load(Reader)方法的格式写入输出字符流。Set<String>stringPropertyNames()从此属性列表返回一组不可修改的键,其中键及其对应的值是字符串,如果尚未从主属性列表中找到相同名称的键,则包括默认属性列表中的不同键。需求:使用Properties把键值对信息存入到属性文件之中。如何读取Properties文件。package com.day10.propertiesDemo; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.util.Properties; public class PropertiesDemo01 { public static void main(String[] args) throws IOException { Properties properties = new Properties(); properties.setProperty("admin","1234"); properties.setProperty("liuchang","141"); properties.setProperty("save","11234"); properties.setProperty("admin2","123412"); properties.setProperty("admin3","123431"); System.out.println(properties); //保存properties properties.store(new FileWriter("properties.properties"),"测试properties的保存功能。"); //如何读取Properties文件信息 Properties properties1 = new Properties(); properties1.load(new FileReader("properties.properties")); System.out.println(properties1); //取数据 String admin = properties1.getProperty("admin"); System.out.println(admin); } }生成的properties文件#\u6D4B\u8BD5properties\u7684\u4FDD\u5B58\u529F\u80FD\u3002 #Mon Aug 01 15:32:06 CST 2022 liuchang=141 save=11234 admin=1234 admin2=123412 admin3=1234316 IO框架commons-io是apache开源基金组织提供的一组有关I0操作的类库,可以提高I0功能开发的效率。commons-io工具包提供了很多有关io操作的类。有两个主要的类FileUtils, l0Utils。引入jar包: <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.6</version> </dependency>package com.day10.party; import org.apache.commons.io.FileUtils; import java.io.File; import java.io.IOException; public class Test1 { public static void main(String[] args) throws IOException { //工具类,直接用静态方法 String s = FileUtils.readFileToString(new File("222.txt"), "UTF-8"); System.out.println(s); //复制文件 FileUtils.copyFile(new File("222.txt"),new File("221.txt")); } }学习记录111 多线程的创建方式Java是通过java.lang.Thread类来代表线程的。按照面向对象的思想,Thread类应该提供了实现多线程的方式。1.1 多线程的实现方案一:继承Thread类①定义一个子类MyThread继承线程类java.lang.Thread,重写run()方法。②创建MyThread类的对象。③调用线程对象的start()方法启动线程(启动后还是执行run方法的)。package com.day11.ThreadDemo; public class ThreadDemo01 { public static void main(String[] args) { Thread thread = new MyThread(); thread.start(); for (int i = 0; i < 100; i++) { System.out.println("主线程:"+i); } } } class MyThread extends Thread{ @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println("子线程:"+i); } } }方式一:优缺点:优点:编码简单。缺点:线程类已经继承Thread,无法继承其他类,不利于扩展。总结:为什么不直接调用了run方法,而是调用start启动线程。直接调用run方法会当成普通方法执行,此时相当于还是单线程执行。只有调用start方法才是启动一个新的线程执行。要把主线程任务放在子线程之前了。这样主线程一直是先跑完的,相当于是一个单线程的效果了。1.2 多线程的实现方案二:实现Runnable接口①定义一个线程任务类MyRunnable实现Runnable接口,重写run()方法。②创建MyRunnable任务对象。③把MyRunnable任务对象交给Thread处理。package com.day11.ThreadDemo; public class ThreadDemo02 { public static void main(String[] args) { Runnable runnable = new MyRunnable(); Thread thread = new Thread(runnable); thread.start(); for (int i = 0; i < 100; i++) { System.out.println("主线程"+i); } } } class MyRunnable implements Runnable{ @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println("子线程"+i); } } }①可以创建Runnable的匿名内部类对象。②交给Thread处理 。③调用线程对象的start()启动线程。package com.day11.ThreadDemo; public class ThreadDemo2 { public static void main(String[] args) { Runnable runnable = new Runnable() { @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println("子线程"+i); } } }; Thread thread = new Thread(runnable); thread.start(); new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println("子线程2"+i); } } }).start(); for (int i = 0; i < 10; i++) { System.out.println("主线程"+i); } } }方式二:优缺点优点:线程任务类只是实现接口,可以继续继承类和实现接口,扩展性强。缺点:编程多一层对象包装,如果线程有执行结果是不可以直接返回的。总结:前2种线程创建方式都存在一个问题他们重写的run方法均不能直接返回结果。不适合需要返回线程执行结果的业务场景。1.3 多线程的实现方案三:利用Callable、FutureTask接口实现。①得到任务对象:定义类实现Callable接口,重写call方法,封装要做的事情。用FutureTask把Callable对象封装成线程任务对象。②把线程任务对象交给Thread处理。③调用Thread的start方法启动线程,执行任务。Callable重要API:接口,没有构造方法,需要实现call方法变量和类型方法描述Vcall()计算结果,如果无法执行,则抛出异常FutureTask重要API:构造器描述FutureTask(Callable<V> callable)创建一个 FutureTask ,在运行时将执行给定的 Callable 。变量和类型方法描述Vget()如果需要等待计算完成,然后检索其结果。package com.day11.ThreadDemo; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; public class ThreadDemo03 { public static void main(String[] args) { Callable<String> callable = new MyCallable(100); FutureTask<String> futureTask = new FutureTask<>(callable); Thread thread = new Thread(futureTask); thread.start(); Callable<String> callable1 = new MyCallable(200); FutureTask<String> futureTask1 = new FutureTask<>(callable1); Thread thread1 = new Thread(futureTask1); thread1.start(); try { String s = futureTask.get(); System.out.println("第一个子线程结果"+s); } catch (Exception e) { e.printStackTrace(); } try { String s1 = futureTask1.get(); System.out.println("第二个子线程结果"+s1); } catch (Exception e) { e.printStackTrace(); } } } class MyCallable implements Callable<String>{ private int n; public MyCallable(int n) { this.n = n; } @Override public String call() throws Exception { int sum =0; for (int i = 0; i < n; i++) { sum += i; } return "子线程运行结果"+sum; } } /** 第一个子线程结果子线程运行结果4950 第二个子线程结果子线程运行结果19900 */方式三优缺点:优点:线程任务类只是实现接口,可以继续继承类和实现接口,扩展性强。可以在线程执行完毕后去获取线程执行的结果。缺点:编码复杂一点。1.4 三种方式对比2 Thread类常用方法构造器描述Thread()分配新的 Thread对象。Thread(Runnable target)分配新的 Thread对象。Thread(Runnable target, String name)分配新的 Thread对象。Thread(String name)分配新的 Thread对象。变量和类型方法描述static ThreadcurrentThread()返回对当前正在执行的线程对象的引用。longgetId()返回此Thread的标识符。StringgetName()返回此线程的名称。intgetPriority()返回此线程的优先级。Thread.StategetState()返回此线程的状态。ThreadGroupgetThreadGroup()返回此线程所属的线程组。voidinterrupt()中断此线程。static booleaninterrupted()测试当前线程是否已被中断。booleanisAlive()测试此线程是否存活。booleanisDaemon()测试此线程是否为守护程序线程。voidjoin()等待这个线程死亡。voidjoin(long millis)此线程最多等待 millis毫秒。voidjoin(long millis, int nanos)此线程最多等待 millis毫秒加上 nanos纳秒。voidrun()如果此线程是使用单独的Runnable运行对象构造的,则调用该Runnable对象的run方法; 否则,此方法不执行任何操作并返回。voidsetName(String name)将此线程的名称更改为等于参数 name 。voidsetPriority(int newPriority)更改此线程的优先级。static voidsleep(long millis)导致当前正在执行的线程休眠(暂时停止执行)指定的毫秒数,具体取决于系统计时器和调度程序的精度和准确性。static voidsleep(long millis, int nanos)导致当前正在执行的线程休眠(暂时停止执行)指定的毫秒数加上指定的纳秒数,具体取决于系统定时器和调度程序的精度和准确性。voidstart()导致此线程开始执行; Java虚拟机调用此线程的run方法。StringtoString()返回此线程的字符串表示形式,包括线程的名称,优先级和线程组。package com.day11.ThreadDemo; public class ThreadDemo04 { public static void main(String[] args) throws InterruptedException { Thread thread = new MyThread1(); thread.setName("子线程1"); thread.start(); System.out.println(thread.getId()); System.out.println(thread.getName()); Thread thread1 = new MyThread1("子线程2"); thread1.start(); System.out.println(thread1.getName()); Thread.sleep(2); Thread thread2 = Thread.currentThread(); System.out.println(thread2.getName()); } } class MyThread1 extends Thread{ public MyThread1(String name) { super(name); } public MyThread1() { } @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println("子线程启动"+i); } } }3 线程安全线程安全问题出现的原因?存在多线程并发。同时访问共享资源。存在修改共享资源。需求:小明和小红是一对夫妻,他们有一个共同的账户,余额是10万元,模拟2人同时去取钱10万。分析:①需要提供一个账户类,创建一账户对象代表2个人的共享账户。②需要定义一个线程类,线程类可以处理账户对象。③创建2个线程对象,传入同一个账户对象。④启动2个线程,去同一个账户对象中取钱10万。package com.day11.ThreadDemo; public class Account { private String cardId; private double money; // 账户的余额 public Account(){ } public Account(String cardId, double money) { this.cardId = cardId; this.money = money; } /** 小明 小红 */ public void drawMoney(double money) { // 0、先获取是谁来取钱,线程的名字就是人名 String name = Thread.currentThread().getName(); // 1、判断账户是否够钱 if(this.money >= money){ // 2、取钱 System.out.println(name + "来取钱成功,吐出:" + money); // 3、更新余额 this.money -= money; System.out.println(name + "取钱后剩余:" + this.money); }else { // 4、余额不足 System.out.println(name +"来取钱,余额不足!"); } } public String getCardId() { return cardId; } public void setCardId(String cardId) { this.cardId = cardId; } public double getMoney() { return money; } public void setMoney(double money) { this.money = money; } }package com.day11.ThreadDemo; public class DrawThread extends Thread{ private Account account; public DrawThread(Account account,String name) { super(name); this.account = account; } @Override public void run() { account.drawMoney(10000); } }package com.day11.ThreadDemo; public class ThreadDemo05 { public static void main(String[] args) { Account account = new Account("66666", 10000); new DrawThread(account,"小明").start(); new DrawThread(account,"小红").start(); } } /** 运行结果: 小明来取钱成功,吐出:10000.0 小红来取钱成功,吐出:10000.0 小明取钱后剩余:0.0 小红取钱后剩余:-10000.0 */4 线程同步为了解决线程安全问题。让多个线程实现先后依次访问共享资源,这样就解决了安全问题。加锁,把共享资源进行上锁,每次只能一个线程进入访问完毕以后解锁,然后其他线程才能进来。4.1 同步代码块作用:把出现线程安全问题的核心代码给上锁。原理:每次只能一个线程进入,执行完毕后自动解锁,其他线程才可以进来执行。建议使用共享资源作为锁对象。对于实例方法建议使用this作为锁对象。对于静态方法建议使用字节码(类名.class) 对象作为锁对象。public void drawMoney(double money) { // 0、先获取是谁来取钱,线程的名字就是人名 String name = Thread.currentThread().getName(); // 1、判断账户是否够钱 //使用synchronized同步代码块,为进程上锁 synchronized (this) { if(this.money >= money){ // 2、取钱 System.out.println(name + "来取钱成功,吐出:" + money); // 3、更新余额 this.money -= money; System.out.println(name + "取钱后剩余:" + this.money); }else { // 4、余额不足 System.out.println(name +"来取钱,余额不足!"); } } } /** 运行结果: 小明来取钱成功,吐出:10000.0 小明取钱后剩余:0.0 小红来取钱,余额不足! */4.2 同步方法作用:把出现线程安全问题的核心方法给上锁。原理:每次只能一个线程进入,执行完毕以后自动解锁,其他线程才可以进来执行。 public synchronized void drawMoney(double money) { // 0、先获取是谁来取钱,线程的名字就是人名 String name = Thread.currentThread().getName(); // 1、判断账户是否够钱 if(this.money >= money){ // 2、取钱 System.out.println(name + "来取钱成功,吐出:" + money); // 3、更新余额 this.money -= money; System.out.println(name + "取钱后剩余:" + this.money); }else { // 4、余额不足 System.out.println(name +"来取钱,余额不足!"); } }同步方法底层原理同步方法其实底层也是有隐式锁对象的,只是锁的范围是整个方法代码。如果方法是实例方法:同步方法默认用this作为的锁对象。但是代码要高度面向对象!如果方法是静态方法:同步方法默认用类名.class作为的锁对象。4.3 Lock锁为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock,更加灵活、方便。Lock实现提供比使用synchronized方法和语句可以获得更广泛的锁定操作。Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来构建Lock锁对象。变量和类型方法描述voidlock()获得锁。voidunlock()释放锁定。package com.day11.ThreadDemo; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class Account { private String cardId; private double money; // 账户的余额 private final Lock lock = new ReentrantLock(); public Account(){ } public Account(String cardId, double money) { this.cardId = cardId; this.money = money; } /** 小明 小红 */ public void drawMoney(double money) { // 0、先获取是谁来取钱,线程的名字就是人名 String name = Thread.currentThread().getName(); //上锁操作 lock.lock(); // 1、判断账户是否够钱 try { if(this.money >= money){ // 2、取钱 System.out.println(name + "来取钱成功,吐出:" + money); // 3、更新余额 this.money -= money; System.out.println(name + "取钱后剩余:" + this.money); }else { // 4、余额不足 System.out.println(name +"来取钱,余额不足!"); } } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } public String getCardId() { return cardId; } public void setCardId(String cardId) { this.cardId = cardId; } public double getMoney() { return money; } public void setMoney(double money) { this.money = money; } }5 线程池5.1 线程池的概述不使用线程池的问题:如果用户每发起一个请求,后台就创建一个新线程来处理,下次新任务来了又要创建新线程,而创建新线程的开销是很大的,这样会严重影响系统的性能。线程池就是一个可以复用线程的技术。JDK 5.0起提供了代表线程池的接口: ExecutorService方式一:使用ExecutorService的实现类ThreadPoolExecutor自创建一个线程池对象。构造器描述ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)使用给定的初始参数创建新的 ThreadPoolExecutor 。参数一:指定线程池的线程数量(核心线程):corePoolSize 不能小于0参数二:指定线程池可支持的最大线程数:maximumPoolSize 最大数量>=核心线程数量参数三:指定临时线程的最大存活时间: keepAliveTime 不能小于0参数四:指定存活时间的单位(秒、分、时、天): unit 时间单位参数五:指定任务队列: workQueue 不能为null参数六:指定用哪个线程工厂创建线程: threadFactory 不能为null参数七:指定线程忙,任务满的时候,新任务来了怎么办: handler 不能为null临时线程什么时候创建啊?新任务提交时发现核心线程都在忙,任务队列也满了,并且还可以创建临时线程,此时才会创建临时线程。什么时候会开始拒绝任务?核心线程和临时线程都在忙,任务队列也满了,新的任务过来的时候才会开始任务拒绝。 ExecutorService pool = new ThreadPoolExecutor(3, 5 , 6, TimeUnit.SECONDS, new ArrayBlockingQueue<>(5) , Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy() );方式二:使用Executors (线程池的工具类)调用方法返回不同特点的线程池对象。变量和类型方法描述static ExecutorServicenewCachedThreadPool()创建一个根据需要创建新线程的线程池,但在它们可用时将重用以前构造的线程。static ExecutorServicenewCachedThreadPool(ThreadFactory threadFactory)创建一个根据需要创建新线程的线程池,但在它们可用时将重用以前构造的线程,并在需要时使用提供的ThreadFactory创建新线程。static ExecutorServicenewFixedThreadPool(int nThreads)创建一个线程池,该池重用在共享的无界队列中运行的固定数量的线程。package com.day11.d8_threadpool; import java.util.concurrent.*; /** 目标:使用Executors的工具方法直接得到一个线程池对象。 */ public class ThreadPoolDemo3 { public static void main(String[] args) throws Exception { // 1、创建固定线程数据的线程池 ExecutorService pool = Executors.newFixedThreadPool(3); pool.execute(new MyRunnable()); pool.execute(new MyRunnable()); pool.execute(new MyRunnable()); pool.execute(new MyRunnable()); // 已经没有多余线程了 } }5.2 线程池处理Runnable任务Execute类方法:变量和类型方法描述voidexecute(Runnable command)在将来的某个时间执行给定的命令。package com.day11.d8_threadpool; public class MyRunnable implements Runnable{ @Override public void run() { for (int i = 0; i < 5; i++) { System.out.println(Thread.currentThread().getName() + "输出了:HelloWorld ==> " + i); } try { System.out.println(Thread.currentThread().getName() + "本任务与线程绑定了,线程进入休眠了~~~"); Thread.sleep(10000000); } catch (Exception e) { e.printStackTrace(); } } }package com.day11.d8_threadpool; import java.util.concurrent.*; /** 目标:自定义一个线程池对象,并测试其特性。 */ public class ThreadPoolDemo1 { public static void main(String[] args) { // 1、创建线程池对象 /** public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) */ ExecutorService pool = new ThreadPoolExecutor(3, 5 , 6, TimeUnit.SECONDS, new ArrayBlockingQueue<>(5) , Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy() ); // 2、给任务线程池处理。 Runnable target = new MyRunnable(); pool.execute(target); pool.execute(target); pool.execute(target); pool.execute(target); pool.execute(target); pool.execute(target); pool.execute(target); pool.execute(target); // 创建临时线程 pool.execute(target); pool.execute(target); // // 不创建,拒绝策略被触发!!! // pool.execute(target); // 关闭线程池(开发中一般不会使用)。 // pool.shutdownNow(); // 立即关闭,即使任务没有完成,会丢失任务的! pool.shutdown(); // 会等待全部任务执行完毕之后再关闭(建议使用的) } }5.3 线程池处理Callable任务变量和类型方法描述<T> Future<T>submit(Callable<T> task)提交值返回任务以执行并返回表示任务的挂起结果的Future。package com.day11.d8_threadpool; import java.util.concurrent.Callable; /** 1、定义一个任务类 实现Callable接口 应该申明线程任务执行完毕后的结果的数据类型 */ public class MyCallable implements Callable<String>{ private int n; public MyCallable(int n) { this.n = n; } /** 2、重写call方法(任务方法) */ @Override public String call() throws Exception { int sum = 0; for (int i = 1; i <= n ; i++) { sum += i; } return Thread.currentThread().getName() + "执行 1-" + n+ "的和,结果是:" + sum; } }package com.day11.d8_threadpool; import java.util.concurrent.*; /** 目标:自定义一个线程池对象,并测试其特性。 */ public class ThreadPoolDemo2 { public static void main(String[] args) throws Exception { // 1、创建线程池对象 /** public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) */ ExecutorService pool = new ThreadPoolExecutor(3, 5 , 6, TimeUnit.SECONDS, new ArrayBlockingQueue<>(5) , Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy() ); // 2、给任务线程池处理。 Future<String> f1 = pool.submit(new MyCallable(100)); Future<String> f2 = pool.submit(new MyCallable(200)); Future<String> f3 = pool.submit(new MyCallable(300)); Future<String> f4 = pool.submit(new MyCallable(400)); Future<String> f5 = pool.submit(new MyCallable(500)); System.out.println(f1.get()); System.out.println(f2.get()); System.out.println(f3.get()); System.out.println(f4.get()); System.out.println(f5.get()); } }6 定时器定时器是一种控制任务延时调用,或者周期调用的技术。作用:闹钟、定时邮件发送。定时器的实现方式:方式一:Timer构造器描述Timer()创建一个新计时器。Timer(String name)创建一个新的计时器,其关联的线程具有指定的名称。变量和类型方法描述voidcancel()终止此计时器,丢弃当前计划的任何任务。intpurge()从此计时器的任务队列中删除所有已取消的任务。voidschedule(TimerTask task, long delay)在指定的延迟后安排指定的任务执行。voidschedule(TimerTask task, long delay, long period)在指定 的延迟之后开始,为重复的 固定延迟执行安排指定的任务。voidschedule(TimerTask task, Date time)计划在指定时间执行指定的任务。Timer定时器的特点和存在的问题Timer是单线程,处理多个任务按照顺序执行,存在延时与设置定时器的时间有出入。可能因为其中的某个任务的异常使Timer线程死掉,从而影响后续任务执行。package com.day11.d9_timer; import java.util.Date; import java.util.Timer; import java.util.TimerTask; /** 目标:Timer定时器的使用和了解。 */ public class TimerDemo1 { public static void main(String[] args) { // 1、创建Timer定时器 Timer timer = new Timer(); // 定时器本身就是一个单线程。 // 2、调用方法,处理定时任务 timer.schedule(new TimerTask() { @Override public void run() { System.out.println(Thread.currentThread().getName() + "执行AAA~~~" + new Date());} }, 0, 2000); //这里会出现报错,从而发生计时器C不能成功的进行。 timer.schedule(new TimerTask() { @Override public void run() { System.out.println(Thread.currentThread().getName() + "执行BB~~~"+ new Date()); System.out.println(10/0); } }, 0, 2000); timer.schedule(new TimerTask() { @Override public void run() { System.out.println(Thread.currentThread().getName() + "执行CCC~~~"+ new Date()); } }, 0, 3000); } }方式二:ScheduledExecutorService变量和类型方法描述ScheduledFuture<?>scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit)提交定期操作,该操作在给定的初始延迟后首先启用,随后在给定的时间段内启用; 也就是说,执行将在initialDelay之后开始,然后是initialDelay + period ,然后是initialDelay + 2 * period ,依此类推。package com.day11.d9_timer; import java.util.Date; import java.util.Timer; import java.util.TimerTask; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; /** 目标:Timer定时器的使用和了解。 */ public class TimerDemo2 { public static void main(String[] args) { // 1、创建ScheduledExecutorService线程池,做定时器 ScheduledExecutorService pool = Executors.newScheduledThreadPool(3); // 2、开启定时任务 pool.scheduleAtFixedRate(new TimerTask() { @Override public void run() { System.out.println(Thread.currentThread().getName() + "执行输出:AAA ==》 " + new Date()); try { Thread.sleep(100000); } catch (InterruptedException e) { e.printStackTrace(); } } }, 0, 2, TimeUnit.SECONDS); pool.scheduleAtFixedRate(new TimerTask() { @Override public void run() { System.out.println(Thread.currentThread().getName() + "执行输出:BBB ==》 " + new Date()); System.out.println(10 / 0); } }, 0, 2, TimeUnit.SECONDS); pool.scheduleAtFixedRate(new TimerTask() { @Override public void run() { System.out.println(Thread.currentThread().getName() + "执行输出:CCC ==》 " + new Date()); } }, 0, 2, TimeUnit.SECONDS); } }7 并发,并行并发与并行:正在运行的程序(软件)就是一个独立的进程,线程是属于进程的,多个线程其实是并发与并行同时进行的。并发的理解:CPU同时处理线程的数量有限。CPU会轮询为系统的每个线程服务,由于CPU切换的速度很快,给我们的感觉这些线程在同时执行,这就是并发。并行的理解:在同一个时刻上,同时有多个线程在被CPU处理并执行。学习记录121 网络通信实现网络编程关键的三要素:IP地址:设备在网络中的地址,是唯一的标识。端口:应用程序在设备中唯一的标识。协议:数据在网络中传输的规则,常见的协议有UDP协议和TCP协议。1.1 IP地址InetAddress类变量和类型方法描述booleanequals(Object obj)将此对象与指定的对象进行比较。byte[]getAddress()返回此 InetAddress对象的原始IP地址。static InetAddress[]getAllByName(String host)根据主机的名称,根据系统上配置的名称服务返回其IP地址数组。static InetAddressgetByAddress(byte[] addr)给定原始IP地址返回 InetAddress对象。static InetAddressgetByName(String host)根据主机名称确定主机的IP地址。StringgetHostAddress()返回文本表示中的IP地址字符串。StringgetHostName()获取此IP地址的主机名。static InetAddressgetLocalHost()返回本地主机的地址。booleanisReachable(int timeout)测试该地址是否可达。package com.day12.d1_inetAddress; import java.net.InetAddress; public class InetAddressDemo01 { public static void main(String[] args) throws Exception { // 1.获取本机地址对象。 InetAddress ip1 = InetAddress.getLocalHost(); System.out.println(ip1.getHostName()); System.out.println(ip1.getHostAddress()); // 2.获取域名ip对象 InetAddress ip2 = InetAddress.getByName("www.baidu.com"); System.out.println(ip2.getHostName()); System.out.println(ip2.getHostAddress()); // 3.获取公网IP对象。 InetAddress ip3 = InetAddress.getByName("112.80.248.76"); System.out.println(ip3.getHostName()); System.out.println(ip3.getHostAddress()); // 4.判断是否能通: ping 5s之前测试是否可通 System.out.println(ip3.isReachable(5000)); } } /** LAPTOP-59ARH7U0 192.168.0.196 www.baidu.com 39.156.66.14 112.80.248.76 112.80.248.76 true */1.2 端口号端口号:标识正在计算机设备上运行的进程(程序),被规定为一个16位的二进制,范围是0~65535。端口类型:周知端口: 0~1023,被预先定义的知名应用占用(如: HTTP占用80,FTP占用21)。注册端口: 1024~49151,分配给用户进程或某些应用程序。( 如: Tomcat占用8080,MySQL占用3306)动态端口: 49152到65535, 之所以称为动态端口,是因为它-般不固定分配某种进程,而是动态分配。注意:我们自己开发的程序选择注册端口,且一个设备中不能出现两个程序的端口号一样,否则出错。1.3 通信协议传输层的2个常见协议:TCP(Transmission Control Protocol):传输控制协议。UDP(User Datagram Protocol):用户数据报协议。TCP协议特点:使用TCP协议,必须双方先建立连接,它是一种面向连接的可靠通信协议。传输前,采用“三次握手”方式建立连接,所以是可靠的。在连接中可进行大数据量的传输。连接、发送数据都需要确认,且传输完毕后,还需释放已建立的连接,通信效率较低。TCP协议通信场景:对信息安全要求较高的场景,例如:文件下载、金融等数据通信。UDP协议:UDP是一种无连接、不可靠传输的协议。将数据源IP、目的地IP和端口封装成数据包,不需要建立连接。每个数据包的大小限制在64KB内。发送不管对方是否准备好,接收方收到也不确认,故是不可靠的。可以广播发送,发送数据结束时无需释放资源,开销小,速度快。UDP协议通信场景:语音通话,视频会话等。2 UDP通信DatagramPacket:数据包对象构造器描述DatagramPacket(byte[] buf, int length)构造 DatagramPacket用于接收长度为 length数据包。DatagramPacket(byte[] buf, int offset, int length, InetAddress address, int port)构造一个数据报包,用于将长度为 length且偏移量为 ioffset的数据包发送到指定主机上的指定端口号。DatagramSocket:发送端和接收端对象构造器描述DatagramSocket()构造一个数据报套接字并将其绑定到本地主机上的任何可用端口。DatagramSocket(int port)构造一个数据报套接字并将其绑定到本地主机上的指定端口。变量和类型方法描述voidreceive(DatagramPacket p)从此套接字接收数据报包。voidsend(DatagramPacket p)从此套接字发送数据报包。案例一:使用UDP实现:一对一发送客户端:package com.day12.d2_udp1; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; import java.net.SocketException; /** 发送端 一发 一收 */ public class ClientDemo1 { public static void main(String[] args) throws Exception { System.out.println("=====客户端启动======"); // 1、创建发送端对象:发送端自带默认的端口号(人) DatagramSocket socket = new DatagramSocket(6666); // 2、创建一个数据包对象封装数据(韭菜盘子) /** public DatagramPacket(byte buf[], int length, InetAddress address, int port) 参数一:封装要发送的数据(韭菜) 参数二:发送数据的大小 参数三:服务端的主机IP地址 参数四:服务端的端口 */ byte[] buffer = "我是一颗快乐的韭菜,你愿意吃吗?".getBytes(); DatagramPacket packet = new DatagramPacket( buffer, buffer.length, InetAddress.getLocalHost() , 8888); // 3、发送数据出去 socket.send(packet); socket.close(); } }服务器端:package com.day12.d2_udp1; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.SocketException; /** 接收端 */ public class ServerDemo2 { public static void main(String[] args) throws Exception { System.out.println("=====服务端启动======"); // 1、创建接收端对象:注册端口(人) DatagramSocket socket = new DatagramSocket(8888); // 2、创建一个数据包对象接收数据(韭菜盘子) byte[] buffer = new byte[1024 * 64]; DatagramPacket packet = new DatagramPacket(buffer, buffer.length); // 3、等待接收数据。 socket.receive(packet); // 4、取出数据即可 // 读取多少倒出多少 int len = packet.getLength(); String rs = new String(buffer,0, len); System.out.println("收到了:" + rs); // 获取发送端的ip和端口 String ip =packet.getSocketAddress().toString(); System.out.println("对方地址:" + ip); int port = packet.getPort(); System.out.println("对方端口:" + port); socket.close(); } }运行结果:注意:先启动服务端后启动客户端!=====服务端启动====== 收到了:我是一颗快乐的韭菜,你愿意吃吗? 对方地址:/192.168.0.196:6666 对方端口:6666案例二:多发多收客户端:package com.day12.d3_udp2; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; import java.net.MulticastSocket; import java.util.Scanner; /** 发送端 多发 多收 */ public class ClientDemo1 { public static void main(String[] args) throws Exception { System.out.println("=====客户端启动======"); // 1、创建发送端对象:发送端自带默认的端口号(人) DatagramSocket socket = new DatagramSocket(7777); Scanner sc = new Scanner(System.in); while (true) { System.out.println("请说:"); String msg = sc.nextLine(); if("exit".equals(msg)){ System.out.println("离线成功!"); socket.close(); break; } // 2、创建一个数据包对象封装数据(韭菜盘子) byte[] buffer = msg.getBytes(); DatagramPacket packet = new DatagramPacket( buffer, buffer.length, InetAddress.getLocalHost() , 8888); // 3、发送数据出去 socket.send(packet); } } }服务器端:package com.day12.d3_udp2; import java.net.DatagramPacket; import java.net.DatagramSocket; /** 接收端 */ public class ServerDemo2 { public static void main(String[] args) throws Exception { System.out.println("=====服务端启动======"); // 1、创建接收端对象:注册端口(人) DatagramSocket socket = new DatagramSocket(8888); // 2、创建一个数据包对象接收数据(韭菜盘子) byte[] buffer = new byte[1024 * 64]; DatagramPacket packet = new DatagramPacket(buffer, buffer.length); while (true) { // 3、等待接收数据。 socket.receive(packet); // 4、取出数据即可 // 读取多少倒出多少 int len = packet.getLength(); String rs = new String(buffer,0, len); System.out.println("收到了来自:" + packet.getAddress() +", 对方端口是" + packet.getPort() +"的消息:" + rs); } } }运行结果:注意:先启动服务端后启动客户端!=====客户端启动====== 请说: 你好 请说: 我是zhenxi 请说: exit 离线成功! Process finished with exit code 0=====客户端启动====== 请说: 你好 请说: 我是流畅 请说: exit 离线成功! Process finished with exit code 0=====服务端启动====== 收到了来自:/192.168.0.196, 对方端口是7777的消息:你好 收到了来自:/192.168.0.196, 对方端口是6666的消息:你好 收到了来自:/192.168.0.196, 对方端口是7777的消息:我是zhenxi 收到了来自:/192.168.0.196, 对方端口是6666的消息:我是流畅案例三:UDP如何实现广播使用广播地址:255.255.255.255具体操作:①发送端发送的数据包的目的地写的是广播地址、且指定端口。(255.255.255.255,9999)②本机所在网段的其他主机的程序只要匹配端口成功即就可以收到消息了。(9999)package com.day12.d4_upd3; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; import java.util.Scanner; public class ClientDemo1 { public static void main(String[] args) throws Exception { System.out.println("=====客户端启动======"); // 1、创建发送端对象:发送端自带默认的端口号(人) DatagramSocket socket = new DatagramSocket(); Scanner sc = new Scanner(System.in); while (true) { System.out.println("请说:"); String msg = sc.nextLine(); if("exit".equals(msg)){ System.out.println("离线成功!"); socket.close(); break; } // 2、创建一个数据包对象封装数据(韭菜盘子) byte[] buffer = msg.getBytes(); // 注意:只要目的地IP是 255.255.255.255 这个消息将以广播的形式对外发送 DatagramPacket packet = new DatagramPacket( buffer, buffer.length, InetAddress.getByName("255.255.255.255") , 8888); // 3、发送数据出去 socket.send(packet); } } }3 TCP通信TCP相关类:Socket类构造器描述Socket()创建一个未连接的套接字,系统默认类型为SocketImpl。Socket(String host, int port)创建流套接字并将其连接到指定主机上的指定端口号。变量和类型方法描述InputStreamgetInputStream()返回此套接字的输入流。OutputStreamgetOutputStream()返回此套接字的输出流。ServerSocket(服务端)构造器描述ServerSocket()创建未绑定的服务器套接字。ServerSocket(int port)创建绑定到指定端口的服务器套接字。变量和类型方法描述Socketaccept()侦听对此套接字的连接并接受它。需求一:①创建客户端的Socket对象,请求与服务端的连接。②使用socket对象调用getOutputStream()方法得到字节输出流。③使用字节输出流完成数据的发送。④释放资源:关闭socket管道。客户端:package com.day12.d5_socket1; import java.io.IOException; import java.io.OutputStream; import java.io.PrintStream; import java.net.Socket; public class ClientDemo1 { public static void main(String[] args) { try { System.out.println("====客户端启动==="); // 1、创建Socket通信管道请求有服务端的连接 // public Socket(String host, int port) // 参数一:服务端的IP地址 // 参数二:服务端的端口 Socket socket = new Socket("127.0.0.1", 7777); // 2、从socket通信管道中得到一个字节输出流 负责发送数据 OutputStream os = socket.getOutputStream(); // 3、把低级的字节流包装成打印流 PrintStream ps = new PrintStream(os); // 4、发送消息 ps.println("TCP的客户端."); ps.flush(); // 关闭资源。 // socket.close(); } catch (Exception e) { e.printStackTrace(); } } }服务端:package com.day12.d5_socket1; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.ServerSocket; import java.net.Socket; public class ServerDemo2 { public static void main(String[] args) { try { System.out.println("===服务端启动成功==="); // 1、注册端口 ServerSocket serverSocket = new ServerSocket(7777); // 2、必须调用accept方法:等待接收客户端的Socket连接请求,建立Socket通信管道 Socket socket = serverSocket.accept(); // 3、从socket通信管道中得到一个字节输入流 InputStream is = socket.getInputStream(); // 4、把字节输入流包装成缓冲字符输入流进行消息的接收 BufferedReader br = new BufferedReader(new InputStreamReader(is)); // 5、按照行读取消息 String msg; if ((msg = br.readLine()) != null){ System.out.println(socket.getRemoteSocketAddress() + "说了:: " + msg); } } catch (Exception e) { e.printStackTrace(); } } }需求二:多发多收消息客户端:package com.day12.d6_socket2; import java.io.OutputStream; import java.io.PrintStream; import java.net.Socket; import java.util.Scanner; /** 实现多发和多收 */ public class ClientDemo1 { public static void main(String[] args) { try { System.out.println("====客户端启动==="); // 1、创建Socket通信管道请求有服务端的连接 // public Socket(String host, int port) // 参数一:服务端的IP地址 // 参数二:服务端的端口 Socket socket = new Socket("127.0.0.1", 7777); // 2、从socket通信管道中得到一个字节输出流 负责发送数据 OutputStream os = socket.getOutputStream(); // 3、把低级的字节流包装成打印流 PrintStream ps = new PrintStream(os); Scanner sc = new Scanner(System.in); while (true) { System.out.println("请说:"); String msg = sc.nextLine(); // 4、发送消息 ps.println(msg); ps.flush(); } // 关闭资源。 // socket.close(); } catch (Exception e) { e.printStackTrace(); } } }服务端:package com.day12.d6_socket2; import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; import java.net.ServerSocket; import java.net.Socket; public class ServerDemo2 { public static void main(String[] args) { try { System.out.println("===服务端启动成功==="); // 1、注册端口 ServerSocket serverSocket = new ServerSocket(7777); while (true) { // 2、必须调用accept方法:等待接收客户端的Socket连接请求,建立Socket通信管道 Socket socket = serverSocket.accept(); // 3、从socket通信管道中得到一个字节输入流 InputStream is = socket.getInputStream(); // 4、把字节输入流包装成缓冲字符输入流进行消息的接收 BufferedReader br = new BufferedReader(new InputStreamReader(is)); // 5、按照行读取消息 String msg; while ((msg = br.readLine()) != null){ System.out.println(socket.getRemoteSocketAddress() + "说了:: " + msg); } } } catch (Exception e) { e.printStackTrace(); } } }注意:本案例实现了多发多收,那么是否可以同时接收多个客户端的消息?不可以的。TCP连接是一对一的!因为服务端现在只有一个线程,只能与一个客户端进行通信。需求三:同时处理多个客户端消息客户端:package com.day12.d7_socket3; import java.io.OutputStream; import java.io.PrintStream; import java.net.Socket; import java.util.Scanner; /** 目标:实现服务端可以同时处理多个客户端的消息。 */ public class ClientDemo1 { public static void main(String[] args) { try { System.out.println("====客户端启动==="); // 1、创建Socket通信管道请求有服务端的连接 // public Socket(String host, int port) // 参数一:服务端的IP地址 // 参数二:服务端的端口 Socket socket = new Socket("127.0.0.1", 7777); // 2、从socket通信管道中得到一个字节输出流 负责发送数据 OutputStream os = socket.getOutputStream(); // 3、把低级的字节流包装成打印流 PrintStream ps = new PrintStream(os); Scanner sc = new Scanner(System.in); while (true) { System.out.println("请说:"); String msg = sc.nextLine(); // 4、发送消息 ps.println(msg); ps.flush(); } // 关闭资源。 // socket.close(); } catch (Exception e) { e.printStackTrace(); } } } 服务端:package com.day12.d7_socket3; import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; import java.net.ServerSocket; import java.net.Socket; /** 目标:实现服务端可以同时处理多个客户端的消息。 */ public class ServerDemo2 { public static void main(String[] args) { try { System.out.println("===服务端启动成功==="); // 1、注册端口 ServerSocket serverSocket = new ServerSocket(7777); // a.定义一个死循环由主线程负责不断的接收客户端的Socket管道连接。 while (true) { // 2、每接收到一个客户端的Socket管道,交给一个独立的子线程负责读取消息 Socket socket = serverSocket.accept(); System.out.println(socket.getRemoteSocketAddress()+ "它来了,上线了!"); // 3、开始创建独立线程处理socket new ServerReaderThread(socket).start(); } } catch (Exception e) { e.printStackTrace(); } } }创建的线程:package com.day12.d7_socket3; import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; import java.net.Socket; public class ServerReaderThread extends Thread{ private Socket socket; public ServerReaderThread(Socket socket){ this.socket = socket; } @Override public void run() { try { // 从socket通信管道中得到一个字节输入流 InputStream is = socket.getInputStream(); // 把字节输入流包装成缓冲字符输入流进行消息的接收 BufferedReader br = new BufferedReader(new InputStreamReader(is)); // 按照行读取消息 String msg; while ((msg = br.readLine()) != null){ System.out.println(socket.getRemoteSocketAddress() + "说了:: " + msg); } } catch (Exception e) { System.out.println(socket.getRemoteSocketAddress() + "下线了!!!"); } } }需求四:引入线程池处理多个客户端消息客户端:package com.day12.d8_socket4; import java.io.OutputStream; import java.io.PrintStream; import java.net.Socket; import java.util.Scanner; /** 使用线程池优化:实现通信。 */ public class ClientDemo1 { public static void main(String[] args) { try { System.out.println("====客户端启动==="); // 1、创建Socket通信管道请求有服务端的连接 // public Socket(String host, int port) // 参数一:服务端的IP地址 // 参数二:服务端的端口 Socket socket = new Socket("127.0.0.1", 6666); // 2、从socket通信管道中得到一个字节输出流 负责发送数据 OutputStream os = socket.getOutputStream(); // 3、把低级的字节流包装成打印流 PrintStream ps = new PrintStream(os); Scanner sc = new Scanner(System.in); while (true) { System.out.println("请说:"); String msg = sc.nextLine(); // 4、发送消息 ps.println(msg); ps.flush(); } // 关闭资源。 // socket.close(); } catch (Exception e) { e.printStackTrace(); } } }服务端:package com.day12.d8_socket4; import java.net.ServerSocket; import java.net.Socket; import java.util.concurrent.*; /** 目标:实现服务端可以同时处理多个客户端的消息。 */ public class ServerDemo2 { // 使用静态变量记住一个线程池对象 private static ExecutorService pool = new ThreadPoolExecutor(300, 1500, 6, TimeUnit.SECONDS, new ArrayBlockingQueue<>(2) , Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy()); public static void main(String[] args) { try { System.out.println("===服务端启动成功==="); // 1、注册端口 ServerSocket serverSocket = new ServerSocket(6666); // a.定义一个死循环由主线程负责不断的接收客户端的Socket管道连接。 while (true) { // 2、每接收到一个客户端的Socket管道, Socket socket = serverSocket.accept(); System.out.println(socket.getRemoteSocketAddress()+ "它来了,上线了!"); // 任务对象负责读取消息。 Runnable target = new ServerReaderRunnable(socket); pool.execute(target); } } catch (Exception e) { e.printStackTrace(); } } }线程任务:package com.day12.d8_socket4; import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; import java.net.Socket; public class ServerReaderRunnable implements Runnable{ private Socket socket; public ServerReaderRunnable(Socket socket){ this.socket = socket; } @Override public void run() { try { // 从socket通信管道中得到一个字节输入流 InputStream is = socket.getInputStream(); // 把字节输入流包装成缓冲字符输入流进行消息的接收 BufferedReader br = new BufferedReader(new InputStreamReader(is)); // 按照行读取消息 String msg; while ((msg = br.readLine()) != null){ System.out.println(socket.getRemoteSocketAddress() + "说了:: " + msg); } } catch (Exception e) { System.out.println(socket.getRemoteSocketAddress() + "下线了!!!"); } } }学习总结2在这一周的学习中,主要学习了logback日志框架的使用、IO流相关知识、线程相关知识、网络通信相关知识等等。在该周的学习过程中没有遇到什么困难,但是对于前几周所学习的知识有些遗忘,于是回头又复习了一遍上两周所记录的笔记,对Java SE的基础知识掌握的越发牢固。该周学习进程有一点慢,下周需要在加快速度的同时也要保证质量。在完成本周课程的基础上,我将继续深入学习Java语言及面向对象技术。希望通过这次学习,能够让自己的编程能力有所提升,继续努力坚持!
2022年07月15日
22 阅读
0 评论
2 点赞