首页
壁纸
留言板
友链
更多
统计归档
Search
1
TensorBoard:训练日志及网络结构可视化工具
12,595 阅读
2
主板开机跳线接线图【F_PANEL接线图】
7,388 阅读
3
Linux使用V2Ray 原生客户端
6,472 阅读
4
移动光猫获取超级密码&开启公网ipv6
5,358 阅读
5
NVIDIA 显卡限制功率
3,184 阅读
好物分享
实用教程
linux使用
wincmd
学习笔记
mysql
java学习
nginx
综合面试题
大数据
网络知识
linux
放码过来
python
javascript
java
opencv
蓝桥杯
leetcode
深度学习
开源模型
相关知识
数据集和工具
模型轻量化
语音识别
计算机视觉
杂七杂八
硬件科普
主机安全
嵌入式设备
其它
bug处理
登录
/
注册
Search
标签搜索
好物分享
学习笔记
linux
MySQL
nvidia
typero
内网穿透
webdav
vps
java
cudann
gcc
cuda
树莓派
CNN
图像去雾
ssh安全
nps
暗通道先验
阿里云
jupiter
累计撰写
358
篇文章
累计收到
72
条评论
首页
栏目
好物分享
实用教程
linux使用
wincmd
学习笔记
mysql
java学习
nginx
综合面试题
大数据
网络知识
linux
放码过来
python
javascript
java
opencv
蓝桥杯
leetcode
深度学习
开源模型
相关知识
数据集和工具
模型轻量化
语音识别
计算机视觉
杂七杂八
硬件科普
主机安全
嵌入式设备
其它
bug处理
页面
壁纸
留言板
友链
统计归档
搜索到
32
篇与
的结果
2023-08-21
java数据随机初始化&bean随机初始化
0.背景最近在开发一个web小demo,其中进行数据测试需要对一些字段特别多的java bean进行初始化,一番百度之后查找到了几个解决方案,记录如下。1.Jpopulator1.1 pom依赖<dependency> <groupId>io.github.benas</groupId> <artifactId>jpopulator</artifactId> <version>1.0.1</version> </dependency>1.2 使用方式及测试实体类import lombok.Data; @Data public class Person { private int id; private String name; private String gender; }测试代码@Test public void testJpopulator(){ Populator populator = new PopulatorBuilder().build(); Person person = (Person) populator.populateBean(Person.class); System.out.println(person); List<Person> persons = populator.populateBeans(Person.class, 2); System.out.println(persons); }Person(id=-1042426959, name=nOBdJpkFbB, gender=bCwLDjOxXb) [Person(id=813095571, name=zxDkvjecWM, gender=SztfWBTZEj), Person(id=1664076867, name=ckMadNdfcX, gender=qmxsXZOiOI)]2.PODAM2.1 pom依赖<dependency> <groupId>uk.co.jemos.podam</groupId> <artifactId>podam</artifactId> <version>7.1.1.RELEASE</version> </dependency>2.2 使用方式及测试实体类import lombok.Data; @Data public class Person { private int id; private String name; private String gender; }基本使用@Test public void testPodam(){ PodamFactory factory = new PodamFactoryImpl(); Person person = factory.manufacturePojo(Person.class); System.out.println(person); }Person(id=1889825458, name=s6tv8LgsPD, gender=zY5put8aZT)2.3 进阶自定义数据生成策略比较复杂,不推荐。参考文档:Java Unit Tests make easy - Random Values with PODAM (onloadcode.com)3.common-random参考文档:https://github.com/yindz/common-random支持的数据类型:数字(int/long/double)汉字(简体)邮箱地址中文人名(简体)英文人名虚拟身份证号码(中国大陆)虚拟信用卡号码(Visa/Mastercard/JCB/银联/AmericanExpress)手机号码(中国大陆)省份和城市(中国大陆)邮编(中国大陆)联系地址(中国大陆)车牌号(中国大陆,包括新能源车型)域名静态URL日期(特定日期之前/特定日期之后)时间(过去/未来)时间戳强密码网络昵称(登录名)拼音网络昵称(登录名)IPv4地址端口号QQ号码非主流QQ网名学历小学名称、年级、班级中学名称、年级、班级高校名称(数据取自教育部网站)公司名称经纬度(中国)中文短句User-Agent(PC/Android/iOS)网卡MAC地址RGB颜色值HEX颜色值股票名称+股票代码开放式基金名称+基金代码缺点:只能逐一生成单个的随机字段,数据、生成对象需要逐一对对象的属性进行填充吗。对于字段较多的对象生成比较麻烦4.Jmockdata(★★★)(推荐)支持类型:Java基本类型字符串枚举日期数组多维数组集合[List|Set|Map]Java对象4.1 pom依赖<dependency> <groupId>com.github.jsonzou</groupId> <artifactId>jmockdata</artifactId> <version>4.2.0</version> </dependency>4.2 Java常用类型的生成@Test public void testJmockdata(){ int randomInt = JMockData.mock(int.class); long randomLong = JMockData.mock(long.class); double randomDouble = JMockData.mock(double.class); float randomFloat = JMockData.mock(float.class); String randomString = JMockData.mock(String.class); BigDecimal randomBigDecimal = JMockData.mock(BigDecimal.class); System.out.println("randomInt:"+randomInt); System.out.println("randomLong:"+randomLong); System.out.println("randomDouble:"+randomDouble); System.out.println("randomFloat:"+randomFloat); System.out.println("randomString:"+randomString); System.out.println("randomBigDecimal:"+randomBigDecimal); }randomInt:1116 randomLong:6722 randomDouble:1303.83 randomFloat:9700.79 randomString:cggMvc randomBigDecimal:3029.84.3 对象的生成实体类@Data public class Student { private int id; private String name; private int age; private LocalDateTime createTime; }生成测试数据@Test public void testJmockdata(){ Student student1 = JMockData.mock(Student.class); System.out.println("默认生成策略生成结果:"+student1); //按照规则定义Student对象里面单个字段生成,没配置的就按默认的生成邪恶了 MockConfig mockConfig = new MockConfig() .subConfig("id").intRange(1,10) .subConfig("name").subConfig("[a-zA-Z_]{1}[a-z0-9_]{5,15}") .subConfig("age").intRange(18,21) .globalConfig(); Student student2 = JMockData.mock(Student.class,mockConfig); System.out.println("自定义生成策略生成结果:"+student2); }执行结果默认生成策略生成结果:Student(id=7105, name=s, age=9755, createTime=2039-09-15T13:53:47.294) 自定义生成策略生成结果:Student(id=1, name=akXrp, age=18, createTime=2050-12-04T07:05:53.299)4.4 根据正则模拟数据配合4.3共同完成对象属性的初始化。/** * 根据正则模拟数据 * 正则优先于其他规则 */ @Test public void testRegexMock() { MockConfig mockConfig = new MockConfig() // 随机段落字符串 .stringRegex("I'am a nice man\\.And I'll just scribble the characters, like:[a-z]{2}-[0-9]{2}-[abc123]{2}-\\w{2}-\\d{2}@\\s{1}-\\S{1}\\.?-.") // 邮箱 .subConfig(RegexTestDataBean.class,"userEmail") .stringRegex("[a-z0-9]{5,15}\\@\\w{3,5}\\.[a-z]{2,3}") // 用户名规则 .subConfig(RegexTestDataBean.class,"userName") .stringRegex("[a-zA-Z_]{1}[a-z0-9_]{5,15}") // 年龄 .subConfig(RegexTestDataBean.class,"userAge") .numberRegex("[1-9]{1}\\d?") // 用户现金 .subConfig(RegexTestDataBean.class,"userMoney") .numberRegex("[1-9]{2}\\.\\d?") // 用户的得分 .subConfig(RegexTestDataBean.class,"userScore") .numberRegex("[1-9]{1}\\d{1}") // 用户身价 .subConfig(RegexTestDataBean.class,"userValue") .numberRegex("[1-9]{1}\\d{3,8}") .globalConfig(); }参考资料Jpopulator测试数据生成工具-腾讯云开发者社区-腾讯云 (tencent.com)Podam 一个Pojo填充随机值利器_这个程序员像只猴的博客-CSDN博客jmockdata: Jmockdta是一款实现模拟JAVA类型或对象的实例化并随机初始化对象的数据的工具框架。单元测试的利器 (gitee.com)GitHub - yindz/common-random: 简单易用的随机数据生成器。生成各种比较真实的假数据。一般用于开发和测试阶段的数据填充模拟。支持各类中国特色本地化的数据格式。An easy-to use random data generator. Generally used for data filling, simulation, demonstration and other scenarios in the development and test phase.JmockData---jfairy 学习记录(生成测试数据)_"小王"的博客-CSDN博客Jmockdta是一款实现模拟JAVA类型或对象的实例化并随机初始化对象的数据的工具框架-面圈网 (mianshigee.com)
2023年08月21日
46 阅读
0 评论
0 点赞
2023-05-10
SpringBoot调用阿里云内容审核API实现文本和图片审核
1.服务开通地址:https://vision.aliyun.com/imageaudit?spm=5176.11065253.1411203.3.7e8153f6mehjzV2.引入公共POM依赖<!--json转换依赖--> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>2.0.25</version> </dependency> <!--文字内容审核依赖及图片审核依赖共用--> <dependency> <groupId>com.aliyun</groupId> <artifactId>imageaudit20191230</artifactId> <version>2.0.6</version> </dependency>3.文本审核3.1 核心代码private static final String accessKeyId = "<your-access-key-id>"; private static final String accessKeySecret = "<your-access-key-secret>"; @PostMapping("/scanText") public String scanText(@RequestBody HashMap<String,String> reqMap) throws Exception { // 获取待检测的文字 String text = reqMap.get("text"); System.out.println("text="+text); // 返回结果的变量 Map<String,String> resMap = new HashMap<>(); //实例化客户端 Config config = new Config() // 必填,您的 AccessKey ID .setAccessKeyId(accessKeyId) // 必填,您的 AccessKey Secret .setAccessKeySecret(accessKeySecret); config.endpoint = "imageaudit.cn-shanghai.aliyuncs.com"; Client client = new Client(config); /** * spam:文字垃圾内容识别 * politics:文字敏感内容识别 * abuse:文字辱骂内容识别 * terrorism:文字暴恐内容识别 * porn:文字鉴黄内容识别 * flood:文字灌水内容识别 * contraband:文字违禁内容识别 * ad:文字广告内容识别 */ // 设置待检测类型 ScanTextRequest.ScanTextRequestLabels labels0 = new ScanTextRequest.ScanTextRequestLabels() .setLabel("politics"); ScanTextRequest.ScanTextRequestLabels labels1 = new ScanTextRequest.ScanTextRequestLabels() .setLabel("contraband"); ScanTextRequest.ScanTextRequestLabels labels2 = new ScanTextRequest.ScanTextRequestLabels() .setLabel("terrorism"); ScanTextRequest.ScanTextRequestLabels labels3 = new ScanTextRequest.ScanTextRequestLabels() .setLabel("abuse"); ScanTextRequest.ScanTextRequestLabels labels4 = new ScanTextRequest.ScanTextRequestLabels() .setLabel("spam"); ScanTextRequest.ScanTextRequestLabels labels5 = new ScanTextRequest.ScanTextRequestLabels() .setLabel("ad"); // 设置待检测内容 ScanTextRequest.ScanTextRequestTasks tasks0 = new ScanTextRequest.ScanTextRequestTasks() .setContent(text); ScanTextRequest scanTextRequest = new ScanTextRequest() .setTasks(java.util.Arrays.asList( tasks0 )) .setLabels(java.util.Arrays.asList( labels0, labels1, labels2, labels3, labels4, labels5 )); RuntimeOptions runtime = new RuntimeOptions(); ScanTextResponse response = null; try { // 复制代码运行请自行打印 API 的返回值 response = client.scanTextWithOptions(scanTextRequest, runtime); resMap.put("data",JSON.toJSONString(response.getBody().getData().getElements().get(0).getResults())); //调用后获取到他的返回对象, 然后判断我们的文字 是什么内容 List<ScanTextResponseBody.ScanTextResponseBodyDataElementsResultsDetails> responseDetails = response.getBody().getData().getElements().get(0).getResults().get(0).getDetails(); if (responseDetails.size()>0){ resMap.put("state","block"); StringBuilder error = new StringBuilder("检测到:"); for (ScanTextResponseBody.ScanTextResponseBodyDataElementsResultsDetails detail : responseDetails) { if ("abuse".equals(detail.getLabel())) error.append("辱骂内容、"); if ("spam".equals(detail.getLabel())) error.append("垃圾内容、"); if ("politics".equals(detail.getLabel())) error.append("敏感内容、"); if ("terrorism".equals(detail.getLabel())) error.append("暴恐内容、"); if ("porn".equals(detail.getLabel())) error.append("黄色内容、"); if ("flood".equals(detail.getLabel())) error.append("灌水内容、"); if ("contraband".equals(detail.getLabel())) error.append("违禁内容、"); if ("ad".equals(detail.getLabel())) error.append("广告内容、"); } resMap.put("msg",error.toString()); return JSON.toJSONString(resMap); }else { resMap.put("state","pass"); resMap.put("msg","未检测出违规!"); return JSON.toJSONString(resMap); } } catch (Exception _error) { resMap.put("state","review"); resMap.put("msg","阿里云无法进行判断,需要人工进行审核,错误详情:"+_error); return JSON.toJSONString(resMap); } }3.2 调用结果req{ "text":"hello word! 卧槽6666" }res{ "state": "block", "msg": "检测到:辱骂内容、", "data": { "details": [{ "contexts": [{ "context": "卧槽" }], "label": "abuse" }], "label": "abuse", "rate": 99.91, "suggestion": "block" } }4.图片审核4.1 核心代码private static final String accessKeyId = "<your-access-key-id>"; private static final String accessKeySecret = "<your-access-key-secret>"; @PostMapping("/scanImage") public String scanImage(@RequestBody HashMap<String,String> reqMap) throws Exception { // 获取待检测的文字 String image = reqMap.get("image"); System.out.println("image="+image); // 返回结果的变量 Map<String,String> resMap = new HashMap<>(); //实例化客户端 Config config = new Config() // 必填,您的 AccessKey ID .setAccessKeyId(accessKeyId) // 必填,您的 AccessKey Secret .setAccessKeySecret(accessKeySecret); config.endpoint = "imageaudit.cn-shanghai.aliyuncs.com"; Client client = new Client(config); // 设置待检测内容 ScanImageRequest.ScanImageRequestTask task0 = new ScanImageRequest.ScanImageRequestTask().setImageURL(image); // 封装检测请求 /** * porn:图片智能鉴黄 * terrorism:图片敏感内容识别、图片风险人物识别 * ad:图片垃圾广告识别 * live:图片不良场景识别 * logo:图片Logo识别 */ ScanImageRequest scanImageRequest = new ScanImageRequest() .setTask(java.util.Arrays.asList( task0 )) .setScene(java.util.Arrays.asList( "porn","terrorism","live" )); RuntimeOptions runtime = new RuntimeOptions(); // 调用API获取检测结果 ScanImageResponse response = client.scanImageWithOptions(scanImageRequest, runtime); resMap.put("data",JSON.toJSONString(response.getBody().getData().getResults().get(0))); // 检测结果解析 try { List<ScanImageResponseBody.ScanImageResponseBodyDataResultsSubResults> responseSubResults = response.getBody().getData().getResults().get(0).getSubResults(); for(ScanImageResponseBody.ScanImageResponseBodyDataResultsSubResults responseSubResult : responseSubResults){ if(responseSubResult.getSuggestion()!="pass"){ resMap.put("state",responseSubResult.getSuggestion()); String msg = ""; switch (responseSubResult.getLabel()){ case "porn": msg = "图片智能鉴黄未通过"; break; case "terrorism": msg = "图片敏感内容识别、图片风险人物识别未通过"; break; case "ad": msg = "图片垃圾广告识别未通过"; break; case "live": msg = "图片不良场景识别未通过"; break; case "logo": msg = "图片Logo识别未通过"; break; } return JSON.toJSONString(resMap); } } } catch (Exception error) { resMap.put("state","review"); resMap.put("msg","发生错误,详情:"+error); return JSON.toJSONString(resMap); } resMap.put("state","pass"); return JSON.toJSONString(resMap); }4.2 调用结果req{ "image":"https://jupite-aliyun.oss-cn-hangzhou.aliyuncs.com/second_hand_shop/client/img/goodImgs/1683068284289.jpg" }res{ "data": { "imageURL": "http://jupite-aliyun.oss-cn-hangzhou.aliyuncs.com/second_hand_shop/client/img/goodImgs/1683068284289.jpg", "subResults": [ { "label": "normal", "rate": 99.9, "scene": "porn", "suggestion": "pass" }, { "label": "normal", "rate": 99.88, "scene": "terrorism", "suggestion": "pass" }, { "label": "normal", "rate": 99.91, "scene": "live", "suggestion": "pass" } ] }, "state": "pass" }参考资料https://next.api.aliyun.com/api/imageaudit/2019-12-30/ScanImage阿里云文本检测 使用教程(Java)https://vision.aliyun.com/imageaudit?spm=5176.11065253.1411203.3.7e8153f6mehjzV
2023年05月10日
515 阅读
0 评论
0 点赞
2023-05-02
SpringBoot整合阿里云短信发送
0.业务场景短信发送验证码实现注册,登录...1.开通阿里云短信服务去到阿里云官方网址:https://www.aliyun.com/ 选择短信服务,在这里能获取到我们需要的4个参数,分别是accessKeyId、accessKeySecret、短信签名、模板code。2.整合进SpringBoot-方法一(推荐方法二)2.1导入依赖<dependency> <groupId>com.aliyun</groupId> <artifactId>aliyun-java-sdk-core</artifactId> <version>4.3.3</version> </dependency>2.2 封装成工具类或者服务类package top.inat.shop.utils; import com.aliyuncs.CommonRequest; import com.aliyuncs.CommonResponse; import com.aliyuncs.DefaultAcsClient; import com.aliyuncs.IAcsClient; import com.aliyuncs.http.MethodType; import com.aliyuncs.profile.DefaultProfile; import org.springframework.stereotype.Component; import java.util.Random; /** * @program: server * @ClassName AliMessageUtil * @description: 验证码工具类 * @author: jupiter * @create: 2023-04-23 10:17 * @Version 1.0 **/ @Component public class AliMessageUtil { /** * 需要配置的参数 */ // 阿里云的id和秘钥 从个人中心进行创建 private static final String accessKeyId="XXXXXXXX"; private static final String secret="XXXXXXXX"; //申请的阿里云的签名名称 private static final String SignName="smile佳"; //申请的阿里云的短信模板code private static final String TemplateCode = "SMS_147439706"; /** * 生成6位数字验证码函数 */ public static String generateVerifiCode() { int n = 6; StringBuilder code = new StringBuilder(); Random ran = new Random(); for (int i = 0; i < n; i++) { code.append(Integer.valueOf(ran.nextInt(10)).toString()); } return code.toString(); } /** * 通过阿里云短信发送验证码 * @param code 验证码 * @param phone 手机号 * @return */ public static boolean sendMsmVerifyCode(String phone,String code) { //default 地域节点,默认就好 后面是 阿里云的 id和秘钥 DefaultProfile profile = DefaultProfile.getProfile("default", accessKeyId, secret); IAcsClient client = new DefaultAcsClient(profile); // 组装请求对象 SendSmsRequest request = new SendSmsRequest(); request.putQueryParameter("PhoneNumbers", phone); request.putQueryParameter("SignName",SignName); request.putQueryParameter("TemplateCode", TemplateCode); request.putQueryParameter("TemplateParam", "{\"code\":\"" + code + "\"}"); try { CommonResponse response = client.getCommonResponse(request); System.out.println(response.getData()); return response.getHttpResponse().isSuccess(); } catch (Exception e) { e.printStackTrace(); } return false; } }进行测试 @Test void aliMessageTest(){ String code = AliMessageUtil.generateVerifiCode(); System.out.println("生成的验证码为:"+code); String phone = "18673918533"; boolean sendRes = AliMessageUtil.sendMsmVerifyCode(phone,code); System.out.println("短信发送结果:"+sendRes); }2.3 运行结果生成的验证码为:196573 {"Message":"OK","RequestId":"97D16831-6EB8-5300-AF5F-25EC86638C26","Code":"OK","BizId":"405312082956966726^0"} 短信发送结果:true2.整合进SpringBoot-方法二2.1导入依赖<dependency> <groupId>com.aliyun</groupId> <artifactId>dysmsapi20170525</artifactId> <version>2.0.9</version> </dependency> <!-- fastjson 打印详细的发送返回的结果用的,只看发送成功失败的话可以去掉 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.35</version> </dependency>2.2 封装成工具类或者服务类package top.inat.shop.utils; import com.alibaba.fastjson.JSON; import com.aliyun.dysmsapi20170525.Client; import com.aliyun.dysmsapi20170525.models.SendSmsRequest; import com.aliyun.dysmsapi20170525.models.SendSmsResponse; import com.aliyun.dysmsapi20170525.models.SendSmsResponseBody; import com.aliyun.teaopenapi.models.Config; import org.springframework.stereotype.Component; import java.util.Random; /** * @program: server * @ClassName AliMessageUtil * @description: 验证码工具类 * @author: jupiter * @create: 2023-04-23 10:17 * @Version 1.0 **/ @Component public class AliMessageUtil { /** * 需要配置的参数 */ private static final String accessKeyId="LTAI5t7Lg3SECa8JSvyYrhoj";//这里修改为个人中心生成的AccessKey ID private static final String accessKeySecret="AXeyeLFKUU8MkgUSnTj2qTLqnZv2rL";//这里修改为个人中心生成的AccessKey Secret private static final String SignName="smile佳"; //申请的阿里云的签名名称 private static final String TemplateCode = "SMS_147439706"; ////申请的阿里云的短信模板code /** * 生成6位数字验证码 */ public static String generateVerifiCode() { int n = 6; StringBuilder code = new StringBuilder(); Random ran = new Random(); for (int i = 0; i < n; i++) { code.append(Integer.valueOf(ran.nextInt(10)).toString()); } return code.toString(); } /** * 通过阿里云短信发送验证码 * @param code 验证码 * @param phone 手机号 * @return */ public static boolean sendMsmVerifyCode(String phone,String code) throws Exception { Config config = new Config().setAccessKeyId(accessKeyId).setAccessKeySecret(accessKeySecret).setEndpoint( "dysmsapi.aliyuncs.com"); Client client = new Client(config); SendSmsRequest request = new SendSmsRequest(); request.setPhoneNumbers(phone); request.setSignName(SignName); request.setTemplateCode(TemplateCode); request.setTemplateParam("{\"code\":\"" + code + "\"}"); SendSmsResponse response = client.sendSms(request); SendSmsResponseBody body = response.getBody(); System.out.println(JSON.toJSONString(body));//不用fastjson打印结果就注释掉这一行 if("OK".equals(body.getCode())){ return true; } return false; } } 进行测试 @Test void aliMessageTest(){ String code = AliMessageUtil.generateVerifiCode(); System.out.println("生成的验证码为:"+code); String phone = "18673918533"; boolean sendRes = AliMessageUtil.sendMsmVerifyCode(phone,code); System.out.println("短信发送结果:"+sendRes); }2.3 运行结果生成的验证码为:196573 {"Message":"OK","RequestId":"97D16831-6EB8-5300-AF5F-25EC86638C26","Code":"OK","BizId":"405312082956966726^0"} 短信发送结果:true参考资料SpringBoot整合阿里云短信服务详细过程(保证初学者也能实现)SpringBoot集成阿里云短信服务发送短信阿里云——Java实现手机短信验证码功能
2023年05月02日
357 阅读
0 评论
0 点赞
2022-09-01
排序算法重梳理
0.复杂度和稳定性汇总版本一名词解释:n:数据规模k:"桶"的个数In-place:占用常数内存,不占用额外内存Out-place:占用额外内存稳定性:排序后 2 个相等键值的顺序和排序之前它们的顺序相同版本二 其中:k表示计数排序中最大值和最小值之间的差值;l表示桶排序中桶的个数;d表示基数排序中最大值的位数,r表示是多少进制;希尔排序的时间复杂度很大程度上取决于增量gap sequence的选择,不同的增量会有不同的时间复杂度。文中使用的“gap=length/2”和“gap=gap/2”是一种常用的方式,也被称为希尔增量,但其并不是最优的。其实希尔排序增量的选择与证明一直都是个数学难题,而下图列出的是迄今为止大部分的gap sequence选择的方案:1.逐一代码实现1.1 冒泡排序每次循环都比较前后两个元素的大小,如果前者大于后者,则将两者进行交换。这样做会将每次循环中最大的元素替换到末尾,逐渐形成有序集合。将每次循环中的最大(小)元素逐渐由队首转移到队尾的过程形似“冒泡”过程,故因此得名。一个优化冒泡排序的方法就是 如果在一次循环的过程中没有发生交换,则可以立即退出当前循环,因为此时已经排好序了(也就是时间复杂度最好情况下是$O(n)$的由来)。import java.util.Arrays; class Solution { // 交换数组元素位置 public static void swap(int[] array,int index1,int index2){ int temp = array[index1]; array[index1] = array[index2]; array[index2] = temp; } public static void bubbleSort(int[] array){ for (int i = 0; i < array.length-1; i++) { boolean flag = false;//记录本轮是否发生冒泡 for (int j = 0; j < array.length-1-i; j++) { if(array[j]>array[j+1]){ swap(array,j,j+1); flag = true; } } if (!flag) { //本轮没有发生冒泡则表示数组已经有序,可以直接break break; } } } public static void main(String[] args) { int[] array = new int[]{1,9,4,8,2,3,0,7,5,6}; bubbleSort(array); System.out.println(Arrays.toString(array)); } }1.2 选择排序每次循环都会找出当前循环中最小(大)的元素,然后和此次循环中的队首元素进行交换。import java.util.Arrays; class Solution { // 交换数组元素位置 public static void swap(int[] array,int index1,int index2){ int temp = array[index1]; array[index1] = array[index2]; array[index2] = temp; } public static void selectSort(int[] array){ for (int i = 0; i < array.length; i++) { int minIndex = i; for (int j = i+1; j < array.length; j++) { minIndex = array[j]<array[minIndex]?j:minIndex; } swap(array,i,minIndex); } } public static void main(String[] args) { int[] array = new int[]{1,9,4,8,2,3,0,7,5,6}; selectSort(array); System.out.println(Arrays.toString(array)); } }1.3 快速排序import java.util.Arrays; class Solution { // 交换数组元素位置 public static void swap(int[] array,int index1,int index2){ int temp = array[index1]; array[index1] = array[index2]; array[index2] = temp; } /** * @brief 快速排序-partition * 大体思想:把比pivot小的换到前面 */ public static int partition(int[] array,int left,int right){ // 取最后一个元素作为中心元素 int pivot = array[right]; // 遍历数组中的所有元素,将比中心元素大的放在右边,比中心元素小的放在左边---该步骤有点类似于选择排序 int i = left; for (int j = left; j < right; j++) { if (array[j] <= pivot) { swap(array,i,j);//比pivot小的,全部换到前面去 i++; } } //此时,i指向的元素一定大于等于pivot,把privot换回到中间 swap(array,i,right); return i; } /** * @brief 快速排序-递归划分 */ public static void quickSort(int[] array){ int mid = partition(array,0, array.length-1); quickSort(array,0,mid-1); quickSort(array,mid+1,right); } public static void main(String[] args) { int[] array = new int[]{1,9,4,8,2,3,0,7,5,6}; quickSort(array); System.out.println(Arrays.toString(array)); } }1.4 插入排序插入排序的精髓在于每次都会在先前排好序的子集合中插入下一个待排序的元素,每次都会判断待排序元素的上一个元素是否大于待排序元素,如果大于,则将元素右移,然后判断再上一个元素与待排序元素...以此类推。直到小于等于比较元素时就是找到了该元素的插入位置。这里的等于条件放在哪里很重要,因为它是决定插入排序稳定与否的关键。import java.util.Arrays; class Solution { // 交换数组元素位置 public static void swap(int[] array,int index1,int index2){ int temp = array[index1]; array[index1] = array[index2]; array[index2] = temp; } public static void insertSort(int[] array){ for (int i = 1; i < array.length; i++) { int curItem = array[i];//缓存下一个待排序的元素 // 把有序集合中的所有比curItem大的元素都往后移一位 int j = i-1; while (j>=0&&array[j]>curItem){ array[j+1] = array[j--]; } // 把待排序元素插入到有序序列中 array[j+1] = curItem; } } public static void main(String[] args) { int[] array = new int[]{1,9,4,8,2,3,0,7,5,6}; insertSort(array); System.out.println(Arrays.toString(array)); } }1.5 希尔排序希尔排序可以认为是插入排序的改进版本。首先按照初始增量来将数组分成多个组,每个组内部使用插入排序。然后缩小增量来重新分组,组内再次使用插入排序...重复以上步骤,直到增量变为1的时候,这个时候整个数组就是一个分组,进行最后一次完整的插入排序即可结束。在排序开始时的增量较大,分组也会较多,但是每个分组中的数据较少,所以插入排序会很快。随着每一轮排序的进行,增量和分组数会逐渐变小,每个分组中的数据会逐渐变多。但因为之前已经经过了多轮的分组排序,而此时的数组会趋近于一个有序的状态,所以这个时候的排序也是很快的。而对于数据较多且趋向于无序的数据来说,如果只是使用插入排序的话效率就并不高。所以总体来说,希尔排序的执行效率是要比插入排序高的。import java.util.Arrays; class Solution { // 交换数组元素位置 public static void swap(int[] array,int index1,int index2){ int temp = array[index1]; array[index1] = array[index2]; array[index2] = temp; } public static void shellSort(int[] array){ int gap = array.length >>> 1; // 希尔排序的初始增量 while (gap>0){ for (int i = 0; i < gap; i++) { //对根据增量划分的组执行插入排序 // 一次排序一个增量组--插入排序 for (int j = i+gap; j < array.length; j+=gap) { int curItem = array[j]; // 待排序元素 int k = j - gap; while (k>=i&&array[k]>curItem){ array[k+gap] = array[k]; k-=gap; } array[k+gap] = curItem; } } gap >>>= 1; } } public static void main(String[] args) { int[] array = new int[]{1,9,4,8,2,3,0,7,5,6}; shellSort(array); System.out.println(Arrays.toString(array)); } }1.6 堆排序堆排序的过程是首先构建一个大(小)顶堆,大顶堆首先是一棵完全二叉树,其次它保证堆中任意节点的值总是不大(小)于其父节点的值。因为大顶堆中的最大元素肯定是根节点,所以每次取出根节点即为当前大顶堆中的最大元素,取出后剩下的节点再重新构建大顶堆,再取出根节点,再重新构建…重复这个过程,直到数据都被取出,最后取出的结果即为排好序的结果。import java.util.Arrays; class Solution { // 交换数组元素位置 public static void swap(int[] array,int index1,int index2){ int temp = array[index1]; array[index1] = array[index2]; array[index2] = temp; } /** * 维护堆的性质 * @param array 存储堆的数组 * @param size 堆的大小 * @param i 待维护节点的下标 */ public static void heapify(int[] array,int size,int i ){ int largest = i; int lson = i*2+1; int rson = i*2+2; if(lson<size&&array[lson]>array[largest]) largest = lson; if(rson<size&&array[rson]>array[largest]) largest = rson; if(largest!=i){ swap(array,largest,i); heapify(array,size,largest); } } public static void heapSort(int[] array,int size){ // 建堆 for (int i = size/2-1; i>=0 ; i--) { // 从最后一个元素的父节点开始维护 heapify(array,size,i); } // 排序 for (int i = size-1; i >=0 ; i--) { swap(array,i,0); // 将堆的最后一个元素和堆顶元素进行交换 heapify(array,i,0); // 将堆顶元素移出堆,并维护堆顶元素的性质 } } public static void main(String[] args) { int[] array = new int[]{1,9,4,8,2,3,0,7,5,6}; heapSort(array, array.length); System.out.println(Arrays.toString(array)); } }1.7 归并排序归并排序使用的是分治的思想,首先将数组不断拆分,直到最后拆分成两个元素的子数组,将这两个元素进行排序合并,再向上递归。不断重复这个拆分和合并的递归过程,最后得到的就是排好序的结果。import java.util.Arrays; class Solution { // 交换数组元素位置 public static void swap(int[] array,int index1,int index2){ int temp = array[index1]; array[index1] = array[index2]; array[index2] = temp; } // 合并 public static void merge(int[] array, int left, int mid, int right) { int[] temp = new int[right - left + 1]; // 临时数组 int p1 = left; // 标记左半区第一个未排序的元素 int p2 = mid + 1; // 标记右半区第一个未排序的元素 int k = 0; //临时数组元素的下标 // 合并两个有序数组 while (p1 <= mid && p2 <= right) { if (array[p1] <= array[p2]) { temp[k++] = array[p1++]; } else { temp[k++] = array[p2++]; } } // 把剩余的数组直接放到temp数组中 while (p1 <= mid) { temp[k++] = array[p1++]; } while (p2 <= right) { temp[k++] = array[p2++]; } // 复制回原数组 for (int i = 0; i < temp.length; i++) { array[i + left] = temp[i]; } } public static void mergeSort(int[] array,int left,int right){ //如果只有一个元素,那么就不需要继续划分 //只有一个元素的区域,本生就是有序的,只需要被归并即可 if(left<right){ //找中间点,这里没有选择“(left + right) / 2”的方式,是为了防止数据溢出 int mid = left + ((right - left) >>> 1); // 递归划分左半区 mergeSort(array, left, mid); // 递归划分右半区 mergeSort(array, mid + 1, right); // 对子数组进行合并 merge(array, left, mid, right); } } public static void main(String[] args) { int[] array = new int[]{1,9,4,8,2,3,0,7,5,6}; mergeSort(array,0, array.length); System.out.println(Arrays.toString(array)); } }1.8 计数排序计数排序会创建一个临时的数组,里面存放每个数出现的次数。比如一个待排序的数组是[2,4,1,2,5,3,4,8,7],那么这个临时数组中记录的数据就是[0,1,2,1,2,1,0,1,1]。那么最后只需要遍历这个临时数组中的计数值就可以了。import java.util.Arrays; class Solution { // 交换数组元素位置 public static void swap(int[] array,int index1,int index2){ int temp = array[index1]; array[index1] = array[index2]; array[index2] = temp; } public static int[] countingSort(int[] array,int left,int right){ //记录待排序数组中的最大值 int max = array[0]; //记录待排序数组中的最小值 int min = array[0]; for (int item : array) { if (item > max) max = item; if (item < min) min = item; } //记录每个数出现的次数 int[] temp = new int[max - min + 1]; for (int item : array) { temp[item - min]++; } // 将结果复制回原数组 int index = 0; for (int i = 0; i < temp.length; i++) { //当输出一个数之后,当前位置的计数就减一,直到减到0为止 while (temp[i]-- > 0) { array[index++] = i + min; } } return array; } public static void main(String[] args) { int[] array = new int[]{1,9,4,8,2,3,0,7,5,6}; countingSort(array,0, array.length); System.out.println(Arrays.toString(array)); } }1.9 桶排序上面的计数排序在数组最大值和最小值之间的差值是多少,就会生成一个多大的临时数组,也就是生成了一个这么多的桶,而每个桶中就只插入一个数据。如果差值比较大的话,会比较浪费空间。那么我能不能在一个桶中插入多个数据呢?当然可以,而这就是桶排序的思路。桶排序类似于哈希表,通过一定的映射规则将数组中的元素映射到不同的桶中,每个桶内进行内部排序,最后将每个桶按顺序输出就行了。桶排序执行的高效与否和是否是稳定的取决于哈希散列的算法以及内部排序的结果。需要注意的是,这个映射算法并不是常规的映射算法,要求是每个桶中的所有数都要比前一个桶中的所有数都要大,这样最后输出的才是一个排好序的结果。比如说第一个桶中存1-30的数字,第二个桶中存31-60的数字,第三个桶中存61-90的数字...以此类推import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.List; class Solution { // 交换数组元素位置 public static void swap(int[] array,int index1,int index2){ int temp = array[index1]; array[index1] = array[index2]; array[index2] = temp; } public static int[] bucketSort(int[] array,int left,int right){ if (array == null || array.length < 2) { return array; } //记录待排序数组中的最大值 int max = array[0]; //记录待排序数组中的最小值 int min = array[0]; for (int i : array) { if (i > max) { max = i; } if (i < min) { min = i; } } //计算桶的数量(可以自定义实现) int bucketNumber = (max - min) / array.length + 1; List<Integer>[] buckets = new ArrayList[bucketNumber]; //计算每个桶存数的范围(可以自定义实现或者不用实现) int bucketRange = (max - min + 1) / bucketNumber; for (int value : array) { //计算应该放到哪个桶中(可以自定义实现) int bucketIndex = (value - min) / (bucketRange + 1); //延迟初始化 if (buckets[bucketIndex] == null) { buckets[bucketIndex] = new ArrayList<>(); } //放入指定的桶 buckets[bucketIndex].add(value); } int index = 0; for (List<Integer> bucket : buckets) { if (bucket == null) { continue; } //对每个桶进行内部排序,我这里使用的是快速排序,也可以使用别的排序算法,当然也可以继续递归去做桶排序 bucket.sort(new Comparator<Integer>() { @Override public int compare(Integer o1, Integer o2) { return o1-o2; } }); //将不为null的桶中的数据按顺序写回到array数组中 for (Integer integer : bucket) { array[index++] = integer; } } return array; } public static void main(String[] args) { int[] array = new int[]{1,9,4,8,2,3,0,7,5,6}; bucketSort(array,0, array.length); System.out.println(Arrays.toString(array)); } }1.10 基数排序基数排序不是根据一个数的整体来进行排序的,而是将数的每一位上的数字进行排序。比如说第一轮排序,我拿到待排序数组中所有数个位上的数字来进行排序;第二轮排序我拿到待排序数组中所有数十位上的数字来进行排序;第三轮排序我拿到待排序数组中所有数百位上的数字来进行排序...以此类推。每一轮的排序都会累加上一轮所有前几位上排序的结果,最终的结果就会是一个有序的数列。基数排序一般是对所有非负整数进行排序的,但是也可以有别的手段来去掉这种限制(比如都加一个固定的数或者都乘一个固定的数,排完序后再恢复等等)。基数排序和桶排序很像,桶排序是按数值的区间进行划分,而基数排序是按数的每一位的值进行划分。同时这两个排序都是需要依靠其他排序算法来实现的(如果不算递归调用桶排序本身的话)。基数排序每一轮的内部排序会使用到计数排序来实现,因为每一位上的数字无非就是0-9,是一个小范围的数,所以使用计数排序很合适。import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.List; class Solution { // 交换数组元素位置 public static void swap(int[] array,int index1,int index2){ int temp = array[index1]; array[index1] = array[index2]; array[index2] = temp; } public static void radixSort(int[] array) { //定义一个二维数组,表示10个桶,每个桶就是一个一维数组 int[][] bucket = new int[10][array.length];//很明显,基数排序使用了空间换时间 //为了记录每个桶中实际存放了多少个数据,定义一个一维数组来记录每次放入数据的个数 //比如bucketElementCounts[0]=3,意思是bucket[0]存放了3个数据 int[] bucketElementCounts = new int[10]; int digitOfElement = 0;//每次取出的元素的位数 //找到数组中最大数的位数 int max = 0; for (int i = 0; i < array.length; i++) { if (max < String.valueOf(array[i]).length()) { max = String.valueOf(array[i]).length(); } } int index = 0; for (int i = 0, n = 1; i < max; i++, n *= 10) { //第i+1轮排序(针对每个元素的位进行排序处理) for (int j = 0; j < array.length; j++) { digitOfElement = array[j] / n % 10;//取出每个元素的位 bucket[digitOfElement][bucketElementCounts[digitOfElement]] = array[j];//放入对应的桶 bucketElementCounts[digitOfElement]++; } //按照桶的顺序(一维数组的下标取出数据),放入原来的数组 index = 0; //遍历每一个桶,并将桶中数据放入原数组 for (int k = 0; k < bucketElementCounts.length; k++) { //如果桶中有数据,我们才放到原数组 if (bucketElementCounts[k] != 0) { //循环第k个桶,放入 for (int l = 0; l < bucketElementCounts[k]; l++) { array[index] = bucket[k][l]; index++; } } bucketElementCounts[k] = 0;//置零!!!!! } } } public static void main(String[] args) { int[] array = new int[]{1,9,4,8,2,3,0,7,5,6}; radixSort(array); System.out.println(Arrays.toString(array)); } }参考资料排序算法:快速排序【图解+代码】排序算法:堆排序【图解+代码】[排序算法:希尔排序【图解+代码】]()排序算法:归并排序【图解+代码】十种经典排序算法总结基数排序(Java)
2022年09月01日
815 阅读
0 评论
0 点赞
2022-06-05
SpringBoot学习笔记
SpringBoot学习笔记1.SpringBoot简介1.1 回顾什么是SpringSpring是一个开源框架,2003 年兴起的一个轻量级的Java 开发框架,作者:Rod Johnson 。Spring是为了解决企业级应用开发的复杂性而创建的,简化开发。1.2 Spring是如何简化Java开发的为了降低Java开发的复杂性,Spring采用了以下4种关键策略:1、基于POJO的轻量级和最小侵入性编程,所有东西都是bean;2、通过IOC,依赖注入(DI)和面向接口实现松耦合;3、基于切面(AOP)和惯例进行声明式编程;4、通过切面和模版减少样式代码,RedisTemplate,xxxTemplate;1.3什么是SpringBoot学过javaweb的同学就知道,开发一个web应用,从最初开始接触Servlet结合Tomcat, 跑出一个Hello Wolrld程序,是要经历特别多的步骤;后来就用了框架Struts,再后来是SpringMVC,到了现在的SpringBoot,过一两年又会有其他web框架出现;你们有经历过框架不断的演进,然后自己开发项目所有的技术也在不断的变化、改造吗?建议都可以去经历一遍;言归正传,什么是SpringBoot呢,就是一个javaweb的开发框架,和SpringMVC类似,对比其他javaweb框架的好处,官方说是简化开发,约定大于配置, you can "just run",能迅速的开发web应用,几行代码开发一个http接口。所有的技术框架的发展似乎都遵循了一条主线规律:从一个复杂应用场景 衍生 一种规范框架,人们只需要进行各种配置而不需要自己去实现它,这时候强大的配置功能成了优点;发展到一定程度之后,人们根据实际生产应用情况,选取其中实用功能和设计精华,重构出一些轻量级的框架;之后为了提高开发效率,嫌弃原先的各类配置过于麻烦,于是开始提倡“约定大于配置”,进而衍生出一些一站式的解决方案。是的这就是Java企业级应用->J2EE->spring->springboot的过程。随着 Spring 不断的发展,涉及的领域越来越多,项目整合开发需要配合各种各样的文件,慢慢变得不那么易用简单,违背了最初的理念,甚至人称配置地狱。Spring Boot 正是在这样的一个背景下被抽象出来的开发框架,目的为了让大家更容易的使用 Spring 、更容易的集成各种常用的中间件、开源软件;Spring Boot 基于 Spring 开发,Spirng Boot 本身并不提供 Spring 框架的核心特性以及扩展功能,只是用于快速、敏捷地开发新一代基于 Spring 框架的应用程序。也就是说,它并不是用来替代 Spring 的解决方案,而是和 Spring 框架紧密结合用于提升 Spring 开发者体验的工具。Spring Boot 以约定大于配置的核心思想,默认帮我们进行了很多设置,多数 Spring Boot 应用只需要很少的 Spring 配置。同时它集成了大量常用的第三方库配置(例如 Redis、MongoDB、Jpa、RabbitMQ、Quartz 等等),Spring Boot 应用中这些第三方库几乎可以零配置的开箱即用。简单来说就是SpringBoot其实不是什么新的框架,它默认配置了很多框架的使用方式,就像maven整合了所有的jar包,spring boot整合了所有的框架 。Spring Boot 出生名门,从一开始就站在一个比较高的起点,又经过这几年的发展,生态足够完善,Spring Boot 已经当之无愧成为 Java 领域最热门的技术。Spring Boot的主要优点:为所有Spring开发者更快的入门开箱即用,提供各种默认配置来简化项目配置内嵌式容器简化Web项目没有冗余代码生成和XML配置的要求真的很爽,我们快速去体验开发个接口的感觉吧!2.HelloSpringBoot2.1 准备工作我们将学习如何快速的创建一个Spring Boot应用,并且实现一个简单的Http请求处理。通过这个例子对Spring Boot有一个初步的了解,并体验其结构简单、开发快速的特性。环境准备:java version "1.8.0_181"Maven-3.6.1SpringBoot 2.x 最新版开发工具:IDEA2.2 创建基础项目说明Spring官方提供了非常方便的工具让我们快速构建应用Spring Initializr:https://start.spring.io/项目创建方式一:使用Spring Initializr 的 Web页面创建项目1、打开 https://start.spring.io/2、填写项目信息3、点击”Generate Project“按钮生成项目;下载此项目4、解压项目包,并用IDEA以Maven项目导入,一路下一步即可,直到项目导入完毕。5、如果是第一次使用,可能速度会比较慢,包比较多、需要耐心等待一切就绪。项目创建方式二:使用 IDEA 直接创建项目1、创建一个新项目2、选择spring initalizr , 可以看到默认就是去官网的快速构建工具那里实现3、填写项目信息4、选择初始化的组件(初学勾选 Web 即可)5、填写项目路径6、等待项目构建成功项目结构分析:通过上面步骤完成了基础项目的创建。就会自动生成以下文件。1、程序的主启动类2、一个 application.properties 配置文件3、一个 测试类4、一个 pom.xml2.3 pom.xml 分析打开pom.xml,看看Spring Boot项目的依赖:<!-- 父依赖 --> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.2.5.RELEASE</version> <relativePath/> </parent> <dependencies> <!-- web场景启动器 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- springboot单元测试 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <!-- 剔除依赖 --> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> </dependencies> <build> <plugins> <!-- 打包插件 --> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>2.4 编写一个http接口1、在主程序的同级目录下,新建一个controller包,一定要在同级目录下,否则识别不到2、在包中新建一个HelloController类@RestController public class HelloController { @RequestMapping("/hello") public String hello() { return "Hello World"; } }3、编写完毕后,从主程序启动项目,浏览器发起请求,看页面返回;控制台输出了 Tomcat 访问的端口号!简单几步,就完成了一个web接口的开发,SpringBoot就是这么简单。所以我们常用它来建立我们的微服务项目!2.5 将项目打成jar包,点击 maven的 package如果遇到以上错误,可以配置打包时 跳过项目运行测试用例<!-- 在工作中,很多情况下我们打包是不想执行测试用例的 可能是测试用例不完事,或是测试用例会影响数据库数据 跳过测试用例执 --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <configuration> <!--跳过项目运行测试用例--> <skipTests>true</skipTests> </configuration> </plugin>如果打包成功,则会在target目录下生成一个 jar 包打成了jar包后,就可以在任何地方运行了!OK3.运行原理探究我们之前写的HelloSpringBoot,到底是怎么运行的呢,Maven项目,我们一般从pom.xml文件探究起;3.1 pom.xml父依赖其中它主要是依赖一个父项目,主要是管理项目的资源过滤及插件!<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.2.5.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent>点进去,发现还有一个父依赖<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>2.2.5.RELEASE</version> <relativePath>../../spring-boot-dependencies</relativePath> </parent>这里才是真正管理SpringBoot应用里面所有依赖版本的地方,SpringBoot的版本控制中心;以后我们导入依赖默认是不需要写版本;但是如果导入的包没有在依赖中管理着就需要手动配置版本了;启动器 spring-boot-starter<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>springboot-boot-starter-xxx:就是spring-boot的场景启动器spring-boot-starter-web:帮我们导入了web模块正常运行所依赖的组件;SpringBoot将所有的功能场景都抽取出来,做成一个个的starter (启动器),只需要在项目中引入这些starter即可,所有相关的依赖都会导入进来 , 我们要用什么功能就导入什么样的场景启动器即可 ;我们未来也可以自己自定义 starter;3.2 主启动类即相关注解默认的主启动类//@SpringBootApplication 来标注一个主程序类 //说明这是一个Spring Boot应用 @SpringBootApplication public class SpringbootApplication { public static void main(String[] args) { //以为是启动了一个方法,没想到启动了一个服务 SpringApplication.run(SpringbootApplication.class, args); } }但是一个简单的启动类并不简单!我们来分析一下这些注解都干了什么@SpringBootApplication作用:标注在某个类上说明这个类是SpringBoot的主配置类 , SpringBoot就应该运行这个类的main方法来启动SpringBoot应用;进入这个注解:可以看到上面还有很多其他注解!//@SpringBootApplication 来标注一个主程序类 //说明这是一个Spring Boot应用 @SpringBootApplication public class SpringbootApplication { public static void main(String[] args) { //以为是启动了一个方法,没想到启动了一个服务 SpringApplication.run(SpringbootApplication.class, args); } }@ComponentScan这个注解在Spring中很重要 ,它对应XML配置中的元素。作用:自动扫描并加载符合条件的组件或者bean , 将这个bean定义加载到IOC容器中@SpringBootConfiguration作用:SpringBoot的配置类 ,标注在某个类上 , 表示这是一个SpringBoot的配置类;我们继续进去这个注解查看// 点进去得到下面的 @Component @Configuration public @interface SpringBootConfiguration {} @Component public @interface Configuration {}这里的 @Configuration,说明这是一个配置类 ,配置类就是对应Spring的xml 配置文件;里面的 @Component 这就说明,启动类本身也是Spring中的一个组件而已,负责启动应用!我们回到 SpringBootApplication 注解中继续看。@EnableAutoConfiguration@EnableAutoConfiguration :开启自动配置功能以前我们需要自己配置的东西,而现在SpringBoot可以自动帮我们配置 ;@EnableAutoConfiguration告诉SpringBoot开启自动配置功能,这样自动配置才能生效;点进注解接续查看:@AutoConfigurationPackage :自动配置包@Import({Registrar.class})public @interface AutoConfigurationPackage {}@import :Spring底层注解@import , 给容器中导入一个组件Registrar.class 作用:将主启动类的所在包及包下面所有子包里面的所有组件扫描到Spring容器 ;这个分析完了,退到上一步,继续看@Import({AutoConfigurationImportSelector.class}) :给容器导入组件 ;AutoConfigurationImportSelector :自动配置导入选择器,那么它会导入哪些组件的选择器呢?我们点击去这个类看源码:1、这个类中有一个这样的方法// 获得候选的配置 protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) { //这里的getSpringFactoriesLoaderFactoryClass()方法 //返回的就是我们最开始看的启动自动导入配置文件的注解类;EnableAutoConfiguration List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader()); Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct."); return configurations; }2、这个方法又调用了 SpringFactoriesLoader 类的静态方法!我们进入SpringFactoriesLoader类loadFactoryNames() 方法public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) { String factoryClassName = factoryClass.getName(); //这里它又调用了 loadSpringFactories 方法 return (List)loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList()); }3、我们继续点击查看 loadSpringFactories 方法private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) { //获得classLoader , 我们返回可以看到这里得到的就是EnableAutoConfiguration标注的类本身 MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader); if (result != null) { return result; } else { try { //去获取一个资源 "META-INF/spring.factories" Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories"); LinkedMultiValueMap result = new LinkedMultiValueMap(); //将读取到的资源遍历,封装成为一个Properties while(urls.hasMoreElements()) { URL url = (URL)urls.nextElement(); UrlResource resource = new UrlResource(url); Properties properties = PropertiesLoaderUtils.loadProperties(resource); Iterator var6 = properties.entrySet().iterator(); while(var6.hasNext()) { Entry<?, ?> entry = (Entry)var6.next(); String factoryClassName = ((String)entry.getKey()).trim(); String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue()); int var10 = var9.length; for(int var11 = 0; var11 < var10; ++var11) { String factoryName = var9[var11]; result.add(factoryClassName, factoryName.trim()); } } } cache.put(classLoader, result); return result; } catch (IOException var13) { throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13); } } }4、发现一个多次出现的文件:spring.factories,全局搜索它spring.factories我们根据源头打开spring.factories , 看到了很多自动配置的文件;这就是自动配置根源所在!WebMvcAutoConfiguration我们在上面的自动配置类随便找一个打开看看,比如 :WebMvcAutoConfiguration可以看到这些一个个的都是JavaConfig配置类,而且都注入了一些Bean,可以找一些自己认识的类,看着熟悉一下!所以,自动配置真正实现是从classpath中搜寻所有的META-INF/spring.factories配置文件 ,并将其中对应的 org.springframework.boot.autoconfigure. 包下的配置项,通过反射实例化为对应标注了 @Configuration的JavaConfig形式的IOC容器配置类 , 然后将这些都汇总成为一个实例并加载到IOC容器中。结论:SpringBoot在启动的时候从类路径下的META-INF/spring.factories中获取EnableAutoConfiguration指定的值将这些值作为自动配置类导入容器 , 自动配置类就生效 , 帮我们进行自动配置工作;整个J2EE的整体解决方案和自动配置都在springboot-autoconfigure的jar包中;它会给容器中导入非常多的自动配置类 (xxxAutoConfiguration), 就是给容器中导入这个场景需要的所有组件 , 并配置好这些组件 ;有了自动配置类 , 免去了我们手动编写配置注入功能组件等的工作;现在大家应该大概的了解了下,SpringBoot的运行原理,后面我们还会深化一次!3.3 SpringApplication不简单的方法我最初以为就是运行了一个main方法,没想到却开启了一个服务;@SpringBootApplication public class SpringbootApplication { public static void main(String[] args) { SpringApplication.run(SpringbootApplication.class, args); } }SpringApplication.run分析分析该方法主要分两部分,一部分是SpringApplication的实例化,二是run方法的执行;SpringApplication这个类主要做了以下四件事情:1、推断应用的类型是普通的项目还是Web项目2、查找并加载所有可用初始化器 , 设置到initializers属性中3、找出所有的应用程序监听器,设置到listeners属性中4、推断并设置main方法的定义类,找到运行的主类查看构造器:public SpringApplication(ResourceLoader resourceLoader, Class... primarySources) { // ...... this.webApplicationType = WebApplicationType.deduceFromClasspath(); this.setInitializers(this.getSpringFactoriesInstances(); this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class)); this.mainApplicationClass = this.deduceMainApplicationClass(); }3.4 run方法流程分析4. yaml语法学习4.1 配置文件SpringBoot使用一个全局的配置文件 , 配置文件名称是固定的application.properties语法结构 :key=valueapplication.yml语法结构 :key:空格 value配置文件的作用 :修改SpringBoot自动配置的默认值,因为SpringBoot在底层都给我们自动配置好了;比如我们可以在配置文件中修改Tomcat 默认启动的端口号!测试一下!server.port=80814.2 yaml概述YAML是 "YAML Ain't a Markup Language" (YAML不是一种标记语言)的递归缩写。在开发的这种语言时,YAML 的意思其实是:"Yet Another Markup Language"(仍是一种标记语言)这种语言以数据作为中心,而不是以标记语言为重点!以前的配置文件,大多数都是使用xml来配置;比如一个简单的端口配置,我们来对比下yaml和xml传统xml配置:<server> <port>8081<port> </server>yaml配置:server: prot: 80804.3 yaml基础语法说明:语法要求严格!1、空格不能省略2、以缩进来控制层级关系,只要是左边对齐的一列数据都是同一个层级的。3、属性和值的大小写都是十分敏感的。字面量:普通的值 [ 数字,布尔值,字符串 ]字面量直接写在后面就可以 , 字符串默认不用加上双引号或者单引号;k: v注意:“ ” 双引号,不会转义字符串里面的特殊字符 , 特殊字符会作为本身想表示的意思;比如 :name: "kuang \n shen" 输出 :kuang 换行 shen'' 单引号,会转义特殊字符 , 特殊字符最终会变成和普通字符一样输出比如 :name: ‘kuang \n shen’ 输出 :kuang \n shen对象、Map(键值对)#对象、Map格式 k: v1: v2:在下一行来写对象的属性和值得关系,注意缩进;比如:student: name: qinjiang age: 3行内写法student: {name: qinjiang,age: 3}数组( List、set )用 - 值表示数组中的一个元素,比如:pets: - cat - dog - pig行内写法pets: [cat,dog,pig]修改SpringBoot的默认端口号配置文件中添加,端口号的参数,就可以切换端口;server: port: 80825.注入配置文件yaml文件更强大的地方在于,他可以给我们的实体类直接注入匹配值!5.1 yaml注入配置文件1、在springboot项目中的resources目录下新建一个文件 application.yml2、编写一个实体类 Dog;package com.kuang.springboot.pojo; @Component //注册bean到容器中 public class Dog { private String name; private Integer age; //有参无参构造、get、set方法、toString()方法 }3、思考,我们原来是如何给bean注入属性值的!@Value,给狗狗类测试一下:@Component //注册bean public class Dog { @Value("阿黄") private String name; @Value("18") private Integer age; }4、在SpringBoot的测试类下注入狗狗输出一下;@SpringBootTest class DemoApplicationTests { @Autowired //将狗狗自动注入进来 Dog dog; @Test public void contextLoads() { System.out.println(dog); //打印看下狗狗对象 } }结果成功输出,@Value注入成功,这是我们原来的办法对吧。5、我们在编写一个复杂一点的实体类:Person 类@Component //注册bean到容器中 public class Person { private String name; private Integer age; private Boolean happy; private Date birth; private Map<String,Object> maps; private List<Object> lists; private Dog dog; //有参无参构造、get、set方法、toString()方法 }6、我们来使用yaml配置的方式进行注入,大家写的时候注意区别和优势,我们编写一个yaml配置!person: name: qinjiang age: 3 happy: false birth: 2000/01/01 maps: {k1: v1,k2: v2} lists: - code - girl - music dog: name: 旺财 age: 17、我们刚才已经把person这个对象的所有值都写好了,我们现在来注入到我们的类中!/* @ConfigurationProperties作用: 将配置文件中配置的每一个属性的值,映射到这个组件中; 告诉SpringBoot将本类中的所有属性和配置文件中相关的配置进行绑定 参数 prefix = “person” : 将配置文件中的person下面的所有属性一一对应 */ @Component //注册bean @ConfigurationProperties(prefix = "person") public class Person { private String name; private Integer age; private Boolean happy; private Date birth; private Map<String,Object> maps; private List<Object> lists; private Dog dog; }8、IDEA 提示,springboot配置注解处理器没有找到,让我们看文档,我们可以查看文档,找到一个依赖!<!-- 导入配置文件处理器,配置文件进行绑定就会有提示,需要重启 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> </dependency>9、确认以上配置都OK之后,我们去测试类中测试一下:@SpringBootTest class DemoApplicationTests { @Autowired Person person; //将person自动注入进来 @Test public void contextLoads() { System.out.println(person); //打印person信息 } }结果:所有值全部注入成功!yaml配置注入到实体类完全OK!课堂测试:1、将配置文件的key 值 和 属性的值设置为不一样,则结果输出为null,注入失败2、在配置一个person2,然后将 @ConfigurationProperties(prefix = "person2") 指向我们的person2;5.2 加载指定的配置文件@PropertySource :加载指定的配置文件;@configurationProperties:默认从全局配置文件中获取值;1、我们去在resources目录下新建一个person.properties文件name=kuangshen2、然后在我们的代码中指定加载person.properties文件@PropertySource(value = "classpath:person.properties") @Component //注册bean public class Person { @Value("${name}") private String name; ...... }3、再次输出测试一下:指定配置文件绑定成功!5.3 配置文件占位符配置文件还可以编写占位符生成随机数person: name: qinjiang${random.uuid} # 随机uuid age: ${random.int} # 随机int happy: false birth: 2000/01/01 maps: {k1: v1,k2: v2} lists: - code - girl - music dog: name: ${person.hello:other}_旺财 age: 15.4 回顾properties配置我们上面采用的yaml方法都是最简单的方式,开发中最常用的;也是springboot所推荐的!那我们来唠唠其他的实现方式,道理都是相同的;写还是那样写;配置文件除了yml还有我们之前常用的properties , 我们没有讲,我们来唠唠!【注意】properties配置文件在写中文的时候,会有乱码 , 我们需要去IDEA中设置编码格式为UTF-8;settings-->FileEncodings 中配置;测试步骤:1、新建一个实体类User@Component //注册bean public class User { private String name; private int age; private String sex; }2、编辑配置文件 user.propertiesuser1.name=kuangshen user1.age=18 user1.sex=男3、我们在User类上使用@Value来进行注入!@Component //注册bean @PropertySource(value = "classpath:user.properties") public class User { //直接使用@value @Value("${user.name}") //从配置文件中取值 private String name; @Value("#{9*2}") // #{SPEL} Spring表达式 private int age; @Value("男") // 字面量 private String sex; }4、Springboot测试@SpringBootTest class DemoApplicationTests { @Autowired User user; @Test public void contextLoads() { System.out.println(user); } }结果正常输出:5.5 对比小结@Value这个使用起来并不友好!我们需要为每个属性单独注解赋值,比较麻烦;我们来看个功能对比图1、@ConfigurationProperties只需要写一次即可 , @Value则需要每个字段都添加2、松散绑定:这个什么意思呢? 比如我的yml中写的last-name,这个和lastName是一样的, - 后面跟着的字母默认是大写的。这就是松散绑定。可以测试一下3、JSR303数据校验 , 这个就是我们可以在字段是增加一层过滤器验证 , 可以保证数据的合法性4、复杂类型封装,yml中可以封装对象 , 使用value就不支持结论:配置yml和配置properties都可以获取到值 , 强烈推荐 yml;如果我们在某个业务中,只需要获取配置文件中的某个值,可以使用一下 @value;如果说,我们专门编写了一个JavaBean来和配置文件进行一一映射,就直接@configurationProperties,不要犹豫!6.JSR303数据校验6.1 先看看如何使用Springboot中可以用@validated来校验数据,如果数据异常则会统一抛出异常,方便异常中心统一处理。我们这里来写个注解让我们的name只能支持Email格式;@Component //注册bean @ConfigurationProperties(prefix = "person") @Validated //数据校验 public class Person { @Email(message="邮箱格式错误") //name必须是邮箱格式 private String name; }运行结果 :default message [不是一个合法的电子邮件地址];使用数据校验,可以保证数据的正确性;6.2 常见参数@NotNull(message="名字不能为空") private String userName; @Max(value=120,message="年龄最大不能查过120") private int age; @Email(message="邮箱格式错误") private String email; 空检查 @Null 验证对象是否为null @NotNull 验证对象是否不为null, 无法查检长度为0的字符串 @NotBlank 检查约束字符串是不是Null还有被Trim的长度是否大于0,只对字符串,且会去掉前后空格. @NotEmpty 检查约束元素是否为NULL或者是EMPTY. Booelan检查 @AssertTrue 验证 Boolean 对象是否为 true @AssertFalse 验证 Boolean 对象是否为 false 长度检查 @Size(min=, max=) 验证对象(Array,Collection,Map,String)长度是否在给定的范围之内 @Length(min=, max=) string is between min and max included. 日期检查 @Past 验证 Date 和 Calendar 对象是否在当前时间之前 @Future 验证 Date 和 Calendar 对象是否在当前时间之后 @Pattern 验证 String 对象是否符合正则表达式的规则 .......等等 除此以外,我们还可以自定义一些数据校验规则7.多环境切换profile是Spring对不同环境提供不同配置功能的支持,可以通过激活不同的环境版本,实现快速切换环境;7.1 多配置文件我们在主配置文件编写的时候,文件名可以是 application-{profile}.properties/yml , 用来指定多个环境版本;例如:application-test.properties 代表测试环境配置application-dev.properties 代表开发环境配置但是Springboot并不会直接启动这些配置文件,它默认使用application.properties主配置文件;我们需要通过一个配置来选择需要激活的环境:#比如在配置文件中指定使用dev环境,我们可以通过设置不同的端口号进行测试; #我们启动SpringBoot,就可以看到已经切换到dev下的配置了; spring.profiles.active=dev7.2 yaml的多文档块和properties配置文件中一样,但是使用yml去实现不需要创建多个配置文件,更加方便了 !server: port: 8081 #选择要激活那个环境块 spring: profiles: active: prod --- server: port: 8083 spring: profiles: dev #配置环境的名称 --- server: port: 8084 spring: profiles: prod #配置环境的名称注意:如果yml和properties同时都配置了端口,并且没有激活其他环境 , 默认会使用properties配置文件的!7.3 配置文件加载位置外部加载配置文件的方式十分多,我们选择最常用的即可,在开发的资源文件中进行配置!官方外部配置文件说明参考文档springboot 启动会扫描以下位置的application.properties或者application.yml文件作为Spring boot的默认配置文件:优先级1:项目路径下的config文件夹配置文件 优先级2:项目路径下配置文件 优先级3:资源路径下的config文件夹配置文件 优先级4:资源路径下配置文件优先级由高到底,高优先级的配置会覆盖低优先级的配置;SpringBoot会从这四个位置全部加载主配置文件;互补配置;我们在最低级的配置文件中设置一个项目访问路径的配置来测试互补问题;#配置项目的访问路径 server.servlet.context-path=/kuang7.4 拓展,运维小技巧指定位置加载配置文件我们还可以通过spring.config.location来改变默认的配置文件位置项目打包好以后,我们可以使用命令行参数的形式,启动项目的时候来指定配置文件的新位置;这种情况,一般是后期运维做的多,相同配置,外部指定的配置文件优先级最高java -jar spring-boot-config.jar --spring.config.location=F:/application.properties8.自动配置原理配置文件到底能写什么?怎么写?SpringBoot官方文档中有大量的配置,我们无法全部记住8.1 分析自动配置原理我们以HttpEncodingAutoConfiguration(Http编码自动配置)为例解释自动配置原理;//表示这是一个配置类,和以前编写的配置文件一样,也可以给容器中添加组件; @Configuration //启动指定类的ConfigurationProperties功能; //进入这个HttpProperties查看,将配置文件中对应的值和HttpProperties绑定起来; //并把HttpProperties加入到ioc容器中 @EnableConfigurationProperties({HttpProperties.class}) //Spring底层@Conditional注解 //根据不同的条件判断,如果满足指定的条件,整个配置类里面的配置就会生效; //这里的意思就是判断当前应用是否是web应用,如果是,当前配置类生效 @ConditionalOnWebApplication( type = Type.SERVLET ) //判断当前项目有没有这个类CharacterEncodingFilter;SpringMVC中进行乱码解决的过滤器; @ConditionalOnClass({CharacterEncodingFilter.class}) //判断配置文件中是否存在某个配置:spring.http.encoding.enabled; //如果不存在,判断也是成立的 //即使我们配置文件中不配置pring.http.encoding.enabled=true,也是默认生效的; @ConditionalOnProperty( prefix = "spring.http.encoding", value = {"enabled"}, matchIfMissing = true ) public class HttpEncodingAutoConfiguration { //他已经和SpringBoot的配置文件映射了 private final Encoding properties; //只有一个有参构造器的情况下,参数的值就会从容器中拿 public HttpEncodingAutoConfiguration(HttpProperties properties) { this.properties = properties.getEncoding(); } //给容器中添加一个组件,这个组件的某些值需要从properties中获取 @Bean @ConditionalOnMissingBean //判断容器没有这个组件? public CharacterEncodingFilter characterEncodingFilter() { CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter(); filter.setEncoding(this.properties.getCharset().name()); filter.setForceRequestEncoding(this.properties.shouldForce(org.springframework.boot.autoconfigure.http.HttpProperties.Encoding.Type.REQUEST)); filter.setForceResponseEncoding(this.properties.shouldForce(org.springframework.boot.autoconfigure.http.HttpProperties.Encoding.Type.RESPONSE)); return filter; } //。。。。。。。 }一句话总结 :根据当前不同的条件判断,决定这个配置类是否生效!一但这个配置类生效;这个配置类就会给容器中添加各种组件;这些组件的属性是从对应的properties类中获取的,这些类里面的每一个属性又是和配置文件绑定的;所有在配置文件中能配置的属性都是在xxxxProperties类中封装着;配置文件能配置什么就可以参照某个功能对应的这个属性类//从配置文件中获取指定的值和bean的属性进行绑定 @ConfigurationProperties(prefix = "spring.http") public class HttpProperties { // ..... }我们去配置文件里面试试前缀,看提示!这就是自动装配的原理!8.2 精髓1、SpringBoot启动会加载大量的自动配置类2、我们看我们需要的功能有没有在SpringBoot默认写好的自动配置类当中;3、我们再来看这个自动配置类中到底配置了哪些组件;(只要我们要用的组件存在在其中,我们就不需要再手动配置了)4、给容器中自动配置类添加组件的时候,会从properties类中获取某些属性。我们只需要在配置文件中指定这些属性的值即可;xxxxAutoConfigurartion:自动配置类;给容器中添加组件xxxxProperties:封装配置文件中相关属性;8.3 了解:@Conditional了解完自动装配的原理后,我们来关注一个细节问题,自动配置类必须在一定的条件下才能生效;@Conditional派生注解(Spring注解版原生的@Conditional作用)作用:必须是@Conditional指定的条件成立,才给容器中添加组件,配置配里面的所有内容才生效;那么多的自动配置类,必须在一定的条件下才能生效;也就是说,我们加载了这么多的配置类,但不是所有的都生效了。我们怎么知道哪些自动配置类生效?我们可以通过启用 debug=true属性;来让控制台打印自动配置报告,这样我们就可以很方便的知道哪些自动配置类生效;#开启springboot的调试类 debug=truePositive matches:(自动配置类启用的:正匹配)Negative matches:(没有启动,没有匹配成功的自动配置类:负匹配)Unconditional classes: (没有条件的类)【演示:查看输出的日志】9.整合JDBC9.1 SpringData简介对于数据访问层,无论是 SQL(关系型数据库) 还是 NOSQL(非关系型数据库),Spring Boot 底层都是采用 Spring Data 的方式进行统一处理。Spring Boot 底层都是采用 Spring Data 的方式进行统一处理各种数据库,Spring Data 也是 Spring 中与 Spring Boot、Spring Cloud 等齐名的知名项目。Sping Data 官网:https://spring.io/projects/spring-data数据库相关的启动器 :可以参考官方文档:https://docs.spring.io/spring-boot/docs/2.2.5.RELEASE/reference/htmlsingle/#using-boot-starter9.2 创建测试项目测试数据源1、我去新建一个项目测试:springboot-data-jdbc ; 引入相应的模块!基础模块2、项目建好之后,发现自动帮我们导入了如下的启动器:<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency>3、编写yaml配置文件连接数据库;spring: datasource: username: root password: 123456 #?serverTimezone=UTC解决时区的报错 url: jdbc:mysql://localhost:3306/springboot?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8 driver-class-name: com.mysql.cj.jdbc.Driver4、配置完这一些东西后,我们就可以直接去使用了,因为SpringBoot已经默认帮我们进行了自动配置;去测试类测试一下@SpringBootTest class SpringbootDataJdbcApplicationTests { //DI注入数据源 @Autowired DataSource dataSource; @Test public void contextLoads() throws SQLException { //看一下默认数据源 System.out.println(dataSource.getClass()); //获得连接 Connection connection = dataSource.getConnection(); System.out.println(connection); //关闭连接 connection.close(); } }结果:我们可以看到他默认给我们配置的数据源为 : class com.zaxxer.hikari.HikariDataSource , 我们并没有手动配置我们来全局搜索一下,找到数据源的所有自动配置都在 :DataSourceAutoConfiguration文件:@Import( {Hikari.class, Tomcat.class, Dbcp2.class, Generic.class, DataSourceJmxConfiguration.class} ) protected static class PooledDataSourceConfiguration { protected PooledDataSourceConfiguration() { } }这里导入的类都在 DataSourceConfiguration 配置类下,可以看出 Spring Boot 2.2.5 默认使用HikariDataSource 数据源,而以前版本,如 Spring Boot 1.5 默认使用 org.apache.tomcat.jdbc.pool.DataSource 作为数据源;HikariDataSource 号称 Java WEB 当前速度最快的数据源,相比于传统的 C3P0 、DBCP、Tomcat jdbc 等连接池更加优秀;可以使用 spring.datasource.type 指定自定义的数据源类型,值为 要使用的连接池实现的完全限定名。9.3 JDBCTemplate1、有了数据源(com.zaxxer.hikari.HikariDataSource),然后可以拿到数据库连接(java.sql.Connection),有了连接,就可以使用原生的 JDBC 语句来操作数据库;2、即使不使用第三方第数据库操作框架,如 MyBatis等,Spring 本身也对原生的JDBC 做了轻量级的封装,即JdbcTemplate。3、数据库操作的所有 CRUD 方法都在 JdbcTemplate 中。4、Spring Boot 不仅提供了默认的数据源,同时默认已经配置好了 JdbcTemplate 放在了容器中,程序员只需自己注入即可使用5、JdbcTemplate 的自动配置是依赖 org.springframework.boot.autoconfigure.jdbc 包下的 JdbcTemplateConfiguration 类JdbcTemplate主要提供以下几类方法:execute方法:可以用于执行任何SQL语句,一般用于执行DDL语句;update方法及batchUpdate方法:update方法用于执行新增、修改、删除等语句;batchUpdate方法用于执行批处理相关语句;query方法及queryForXXX方法:用于执行查询相关语句;call方法:用于执行存储过程、函数相关语句。9.4 测试编写一个Controller,注入 jdbcTemplate,编写测试方法进行访问测试;package com.kuang.controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.Date; import java.util.List; import java.util.Map; @RestController @RequestMapping("/jdbc") public class JdbcController { /** * Spring Boot 默认提供了数据源,默认提供了 org.springframework.jdbc.core.JdbcTemplate * JdbcTemplate 中会自己注入数据源,用于简化 JDBC操作 * 还能避免一些常见的错误,使用起来也不用再自己来关闭数据库连接 */ @Autowired JdbcTemplate jdbcTemplate; //查询employee表中所有数据 //List 中的1个 Map 对应数据库的 1行数据 //Map 中的 key 对应数据库的字段名,value 对应数据库的字段值 @GetMapping("/list") public List<Map<String, Object>> userList(){ String sql = "select * from employee"; List<Map<String, Object>> maps = jdbcTemplate.queryForList(sql); return maps; } //新增一个用户 @GetMapping("/add") public String addUser(){ //插入语句,注意时间问题 String sql = "insert into employee(last_name, email,gender,department,birth)" + " values ('狂神说','24736743@qq.com',1,101,'"+ new Date().toLocaleString() +"')"; jdbcTemplate.update(sql); //查询 return "addOk"; } //修改用户信息 @GetMapping("/update/{id}") public String updateUser(@PathVariable("id") int id){ //插入语句 String sql = "update employee set last_name=?,email=? where id="+id; //数据 Object[] objects = new Object[2]; objects[0] = "秦疆"; objects[1] = "24736743@sina.com"; jdbcTemplate.update(sql,objects); //查询 return "updateOk"; } //删除用户 @GetMapping("/delete/{id}") public String delUser(@PathVariable("id") int id){ //插入语句 String sql = "delete from employee where id=?"; jdbcTemplate.update(sql,id); //查询 return "deleteOk"; } }测试请求,结果正常;10.整合Druid10.1 Druid简介Java程序很大一部分要操作数据库,为了提高性能操作数据库的时候,又不得不使用数据库连接池。Druid 是阿里巴巴开源平台上一个数据库连接池实现,结合了 C3P0、DBCP 等 DB 池的优点,同时加入了日志监控。Druid 可以很好的监控 DB 池连接和 SQL 的执行情况,天生就是针对监控而生的 DB 连接池。Druid已经在阿里巴巴部署了超过600个应用,经过一年多生产环境大规模部署的严苛考验。Spring Boot 2.0 以上默认使用 Hikari 数据源,可以说 Hikari 与 Driud 都是当前 Java Web 上最优秀的数据源,我们来重点介绍 Spring Boot 如何集成 Druid 数据源,如何实现数据库监控。Github地址:https://github.com/alibaba/druid/com.alibaba.druid.pool.DruidDataSource 基本配置参数如下:10.2 配置数据源1、添加上 Druid 数据源依赖。<!-- https://mvnrepository.com/artifact/com.alibaba/druid --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.21</version> </dependency>2、切换数据源;之前已经说过 Spring Boot 2.0 以上默认使用 com.zaxxer.hikari.HikariDataSource 数据源,但可以 通过 spring.datasource.type 指定数据源。spring: datasource: username: root password: 123456 url: jdbc:mysql://localhost:3306/springboot?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8 driver-class-name: com.mysql.cj.jdbc.Driver type: com.alibaba.druid.pool.DruidDataSource # 自定义数据源3、数据源切换之后,在测试类中注入 DataSource,然后获取到它,输出一看便知是否成功切换;4、切换成功!既然切换成功,就可以设置数据源连接初始化大小、最大连接数、等待时间、最小连接数 等设置项;可以查看源码 spring: datasource: username: root password: 123456 #?serverTimezone=UTC解决时区的报错 url: jdbc:mysql://localhost:3306/springboot?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8 driver-class-name: com.mysql.cj.jdbc.Driver type: com.alibaba.druid.pool.DruidDataSource #Spring Boot 默认是不注入这些属性值的,需要自己绑定 #druid 数据源专有配置 initialSize: 5 minIdle: 5 maxActive: 20 maxWait: 60000 timeBetweenEvictionRunsMillis: 60000 minEvictableIdleTimeMillis: 300000 validationQuery: SELECT 1 FROM DUAL testWhileIdle: true testOnBorrow: false testOnReturn: false poolPreparedStatements: true #配置监控统计拦截的filters,stat:监控统计、log4j:日志记录、wall:防御sql注入 #如果允许时报错 java.lang.ClassNotFoundException: org.apache.log4j.Priority #则导入 log4j 依赖即可,Maven 地址:https://mvnrepository.com/artifact/log4j/log4j filters: stat,wall,log4j maxPoolPreparedStatementPerConnectionSize: 20 useGlobalDataSourceStat: true connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5005、导入Log4j 的依赖<!-- https://mvnrepository.com/artifact/log4j/log4j --> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency>6、现在需要程序员自己为 DruidDataSource 绑定全局配置文件中的参数,再添加到容器中,而不再使用 Spring Boot 的自动生成了;我们需要 自己添加 DruidDataSource 组件到容器中,并绑定属性;package com.kuang.config; import com.alibaba.druid.pool.DruidDataSource; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import javax.sql.DataSource; @Configuration public class DruidConfig { /* 将自定义的 Druid数据源添加到容器中,不再让 Spring Boot 自动创建 绑定全局配置文件中的 druid 数据源属性到 com.alibaba.druid.pool.DruidDataSource从而让它们生效 @ConfigurationProperties(prefix = "spring.datasource"):作用就是将 全局配置文件中 前缀为 spring.datasource的属性值注入到 com.alibaba.druid.pool.DruidDataSource 的同名参数中 */ @ConfigurationProperties(prefix = "spring.datasource") @Bean public DataSource druidDataSource() { return new DruidDataSource(); } }7、去测试类中测试一下;看是否成功!@SpringBootTest class SpringbootDataJdbcApplicationTests { //DI注入数据源 @Autowired DataSource dataSource; @Test public void contextLoads() throws SQLException { //看一下默认数据源 System.out.println(dataSource.getClass()); //获得连接 Connection connection = dataSource.getConnection(); System.out.println(connection); DruidDataSource druidDataSource = (DruidDataSource) dataSource; System.out.println("druidDataSource 数据源最大连接数:" + druidDataSource.getMaxActive()); System.out.println("druidDataSource 数据源初始化连接数:" + druidDataSource.getInitialSize()); //关闭连接 connection.close(); } }输出结果 :可见配置参数已经生效!10.3 配置Druid数据源监控Druid 数据源具有监控的功能,并提供了一个 web 界面方便用户查看,类似安装 路由器 时,人家也提供了一个默认的 web 页面。所以第一步需要设置 Druid 的后台管理页面,比如 登录账号、密码 等;配置后台管理; //配置 Druid 监控管理后台的Servlet; //内置 Servlet 容器时没有web.xml文件,所以使用 Spring Boot 的注册 Servlet 方式 @Bean public ServletRegistrationBean statViewServlet() { ServletRegistrationBean bean = new ServletRegistrationBean(new StatViewServlet(), "/druid/*"); // 这些参数可以在 com.alibaba.druid.support.http.StatViewServlet // 的父类 com.alibaba.druid.support.http.ResourceServlet 中找到 Map<String, String> initParams = new HashMap<>(); initParams.put("loginUsername", "admin"); //后台管理界面的登录账号 initParams.put("loginPassword", "123456"); //后台管理界面的登录密码 //后台允许谁可以访问 //initParams.put("allow", "localhost"):表示只有本机可以访问 //initParams.put("allow", ""):为空或者为null时,表示允许所有访问 initParams.put("allow", ""); //deny:Druid 后台拒绝谁访问 //initParams.put("kuangshen", "192.168.1.20");表示禁止此ip访问 //设置初始化参数 bean.setInitParameters(initParams); return bean; }配置完毕后,我们可以选择访问 :http://localhost:8080/druid/login.html进入之后配置 Druid web 监控 filter 过滤器//配置 Druid 监控 之 web 监控的 filter //WebStatFilter:用于配置Web和Druid数据源之间的管理关联监控统计 @Bean public FilterRegistrationBean webStatFilter() { FilterRegistrationBean bean = new FilterRegistrationBean(); bean.setFilter(new WebStatFilter()); //exclusions:设置哪些请求进行过滤排除掉,从而不进行统计 Map<String, String> initParams = new HashMap<>(); initParams.put("exclusions", "*.js,*.css,/druid/*,/jdbc/*"); bean.setInitParameters(initParams); //"/*" 表示过滤所有请求 bean.setUrlPatterns(Arrays.asList("/*")); return bean; }平时在工作中,按需求进行配置即可,主要用作监控!11.整合MyBatis11.1 整合测试1、导入 MyBatis 所需要的依赖<dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.1.1</version> </dependency>2、配置数据库连接信息(不变)spring: datasource: username: root password: 123456 #?serverTimezone=UTC解决时区的报错 url: jdbc:mysql://localhost:3306/springboot?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8 driver-class-name: com.mysql.cj.jdbc.Driver type: com.alibaba.druid.pool.DruidDataSource #Spring Boot 默认是不注入这些属性值的,需要自己绑定 #druid 数据源专有配置 initialSize: 5 minIdle: 5 maxActive: 20 maxWait: 60000 timeBetweenEvictionRunsMillis: 60000 minEvictableIdleTimeMillis: 300000 validationQuery: SELECT 1 FROM DUAL testWhileIdle: true testOnBorrow: false testOnReturn: false poolPreparedStatements: true #配置监控统计拦截的filters,stat:监控统计、log4j:日志记录、wall:防御sql注入 #如果允许时报错 java.lang.ClassNotFoundException: org.apache.log4j.Priority #则导入 log4j 依赖即可,Maven 地址:https://mvnrepository.com/artifact/log4j/log4j filters: stat,wall,log4j maxPoolPreparedStatementPerConnectionSize: 20 useGlobalDataSourceStat: true connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5003、测试数据库是否连接成功!4、创建实体类,导入 Lombok!Department.javapackage com.kuang.pojo; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @Data @NoArgsConstructor @AllArgsConstructor public class Department { private Integer id; private String departmentName; }5、创建mapper目录以及对应的 Mapper 接口DepartmentMapper.java //@Mapper : 表示本类是一个 MyBatis 的 Mapper @Mapper @Repository public interface DepartmentMapper { // 获取所有部门信息 List<Department> getDepartments(); // 通过id获得部门 Department getDepartment(Integer id); }6、对应的Mapper映射文件DepartmentMapper.xml <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.kuang.mapper.DepartmentMapper"> <select id="getDepartments" resultType="Department"> select * from department; </select> <select id="getDepartment" resultType="Department" parameterType="int"> select * from department where id = #{id}; </select> </mapper>7、maven配置资源过滤问题<resources> <resource> <directory>src/main/java</directory> <includes> <include>**/*.xml</include> </includes> <filtering>true</filtering> </resource> </resources>8、编写部门的 DepartmentController 进行测试!@RestController public class DepartmentController { @Autowired DepartmentMapper departmentMapper; // 查询全部部门 @GetMapping("/getDepartments") public List<Department> getDepartments(){ return departmentMapper.getDepartments(); } // 查询全部部门 @GetMapping("/getDepartment/{id}") public Department getDepartment(@PathVariable("id") Integer id){ return departmentMapper.getDepartment(id); } }启动项目访问进行测试!11.2 增加一个员工类再测试下,为之后做准备1、新建一个pojo类 Employee ;@Data @AllArgsConstructor @NoArgsConstructor public class Employee { private Integer id; private String lastName; private String email; //1 male, 0 female private Integer gender; private Integer department; private Date birth; private Department eDepartment; // 冗余设计 }2、新建一个 EmployeeMapper 接口//@Mapper : 表示本类是一个 MyBatis 的 Mapper @Mapper @Repository public interface EmployeeMapper { // 获取所有员工信息 List<Employee> getEmployees(); // 新增一个员工 int save(Employee employee); // 通过id获得员工信息 Employee get(Integer id); // 通过id删除员工 int delete(Integer id); }3、编写 EmployeeMapper.xml 配置文件<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.kuang.mapper.EmployeeMapper"> <resultMap id="EmployeeMap" type="Employee"> <id property="id" column="eid"/> <result property="lastName" column="last_name"/> <result property="email" column="email"/> <result property="gender" column="gender"/> <result property="birth" column="birth"/> <association property="eDepartment" javaType="Department"> <id property="id" column="did"/> <result property="departmentName" column="dname"/> </association> </resultMap> <select id="getEmployees" resultMap="EmployeeMap"> select e.id as eid,last_name,email,gender,birth,d.id as did,d.department_name as dname from department d,employee e where d.id = e.department </select> <insert id="save" parameterType="Employee"> insert into employee (last_name,email,gender,department,birth) values (#{lastName},#{email},#{gender},#{department},#{birth}); </insert> <select id="get" resultType="Employee"> select * from employee where id = #{id} </select> <delete id="delete" parameterType="int"> delete from employee where id = #{id} </delete> </mapper>4、编写EmployeeController类进行测试@RestController public class EmployeeController { @Autowired EmployeeMapper employeeMapper; // 获取所有员工信息 @GetMapping("/getEmployees") public List<Employee> getEmployees(){ return employeeMapper.getEmployees(); } @GetMapping("/save") public int save(){ Employee employee = new Employee(); employee.setLastName("kuangshen"); employee.setEmail("qinjiang@qq.com"); employee.setGender(1); employee.setDepartment(101); employee.setBirth(new Date()); return employeeMapper.save(employee); } // 通过id获得员工信息 @GetMapping("/get/{id}") public Employee get(@PathVariable("id") Integer id){ return employeeMapper.get(id); } // 通过id删除员工 @GetMapping("/delete/{id}") public int delete(@PathVariable("id") Integer id){ return employeeMapper.delete(id); } }12.Web开发静态资源处理12.1 简介1、创建一个SpringBoot应用,选择我们需要的模块,SpringBoot就会默认将我们的需要的模块自动配置好2、手动在配置文件中配置部分配置项目就可以运行起来了3、专注编写业务代码,不需要考虑以前那样一大堆的配置了。要熟悉掌握开发,之前学习的自动配置的原理一定要搞明白!比如SpringBoot到底帮我们配置了什么?我们能不能修改?我们能修改哪些配置?我们能不能扩展?向容器中自动配置组件 : Autoconfiguration*自动配置类,封装配置文件的内容:Properties*没事就找找类,看看自动装配原理!我们之后来进行一个单体项目的小项目测试,让大家能够快速上手开发!12.2 静态资源映射规则首先,我们搭建一个普通的SpringBoot项目,回顾一下HelloWorld程序!写请求非常简单,那我们要引入我们前端资源,我们项目中有许多的静态资源,比如css,js等文件,这个SpringBoot怎么处理呢?如果我们是一个web应用,我们的main下会有一个webapp,我们以前都是将所有的页面导在这里面的,对吧!但是我们现在的pom呢,打包方式是为jar的方式,那么这种方式SpringBoot能不能来给我们写页面呢?当然是可以的,但是SpringBoot对于静态资源放置的位置,是有规定的!我们先来聊聊这个静态资源映射规则:SpringBoot中,SpringMVC的web配置都在 WebMvcAutoConfiguration 这个配置类里面;我们可以去看看 WebMvcAutoConfigurationAdapter 中有很多配置方法;有一个方法:addResourceHandlers 添加资源处理@Override public void addResourceHandlers(ResourceHandlerRegistry registry) { if (!this.resourceProperties.isAddMappings()) { // 已禁用默认资源处理 logger.debug("Default resource handling disabled"); return; } // 缓存控制 Duration cachePeriod = this.resourceProperties.getCache().getPeriod(); CacheControl cacheControl = this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl(); // webjars 配置 if (!registry.hasMappingForPattern("/webjars/**")) { customizeResourceHandlerRegistration(registry.addResourceHandler("/webjars/**") .addResourceLocations("classpath:/META-INF/resources/webjars/") .setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl)); } // 静态资源配置 String staticPathPattern = this.mvcProperties.getStaticPathPattern(); if (!registry.hasMappingForPattern(staticPathPattern)) { customizeResourceHandlerRegistration(registry.addResourceHandler(staticPathPattern) .addResourceLocations(getResourceLocations(this.resourceProperties.getStaticLocations())) .setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl)); } }读一下源代码:比如所有的 /webjars/** , 都需要去 classpath:/META-INF/resources/webjars/ 找对应的资源;12.3 什么是webjars 呢?Webjars本质就是以jar包的方式引入我们的静态资源 , 我们以前要导入一个静态资源文件,直接导入即可。使用SpringBoot需要使用Webjars,我们可以去搜索一下:网站:https://www.webjars.org 要使用jQuery,我们只要要引入jQuery对应版本的pom依赖即可!<dependency> <groupId>org.webjars</groupId> <artifactId>jquery</artifactId> <version>3.4.1</version> </dependency>导入完毕,查看webjars目录结构,并访问Jquery.js文件!访问:只要是静态资源,SpringBoot就会去对应的路径寻找资源,我们这里访问:http://localhost:8080/webjars/jquery/3.4.1/jquery.js12.4 第二种静态资源映射规则那我们项目中要是使用自己的静态资源该怎么导入呢?我们看下一行代码;我们去找staticPathPattern发现第二种映射规则 :/** , 访问当前的项目任意资源,它会去找 resourceProperties 这个类,我们可以点进去看一下分析:// 进入方法 public String[] getStaticLocations() { return this.staticLocations; } // 找到对应的值 private String[] staticLocations = CLASSPATH_RESOURCE_LOCATIONS; // 找到路径 private static final String[] CLASSPATH_RESOURCE_LOCATIONS = { "classpath:/META-INF/resources/", "classpath:/resources/", "classpath:/static/", "classpath:/public/" };ResourceProperties 可以设置和我们静态资源有关的参数;这里面指向了它会去寻找资源的文件夹,即上面数组的内容。所以得出结论,以下四个目录存放的静态资源可以被我们识别:"classpath:/META-INF/resources/" "classpath:/resources/" "classpath:/static/" "classpath:/public/"我们可以在resources根目录下新建对应的文件夹,都可以存放我们的静态文件;比如我们访问 http://localhost:8080/1.js , 他就会去这些文件夹中寻找对应的静态资源文件;12.5 自定义静态资源路径我们也可以自己通过配置文件来指定一下,哪些文件夹是需要我们放静态资源文件的,在application.properties中配置;spring.resources.static-locations=classpath:/coding/,classpath:/kuang/一旦自己定义了静态文件夹的路径,原来的自动配置就都会失效了!12.6 首页处理静态资源文件夹说完后,我们继续向下看源码!可以看到一个欢迎页的映射,就是我们的首页!@Bean public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext applicationContext, FormattingConversionService mvcConversionService, ResourceUrlProvider mvcResourceUrlProvider) { WelcomePageHandlerMapping welcomePageHandlerMapping = new WelcomePageHandlerMapping( new TemplateAvailabilityProviders(applicationContext), applicationContext, getWelcomePage(), // getWelcomePage 获得欢迎页 this.mvcProperties.getStaticPathPattern()); welcomePageHandlerMapping.setInterceptors(getInterceptors(mvcConversionService, mvcResourceUrlProvider)); return welcomePageHandlerMapping; }点进去继续看private Optional<Resource> getWelcomePage() { String[] locations = getResourceLocations(this.resourceProperties.getStaticLocations()); // ::是java8 中新引入的运算符 // Class::function的时候function是属于Class的,应该是静态方法。 // this::function的funtion是属于这个对象的。 // 简而言之,就是一种语法糖而已,是一种简写 return Arrays.stream(locations).map(this::getIndexHtml).filter(this::isReadable).findFirst(); } // 欢迎页就是一个location下的的 index.html 而已 private Resource getIndexHtml(String location) { return this.resourceLoader.getResource(location + "index.html"); }欢迎页,静态资源文件夹下的所有 index.html 页面;被 /** 映射。比如我访问 http://localhost:8080/ ,就会找静态资源文件夹下的 index.html新建一个 index.html ,在我们上面的3个目录中任意一个;然后访问测试 http://localhost:8080/ 看结果!12.7 关于网站图标说明:与其他静态资源一样,Spring Boot在配置的静态内容位置中查找 favicon.ico。如果存在这样的文件,它将自动用作应用程序的favicon。1、关闭SpringBoot默认图标#关闭默认图标 spring.mvc.favicon.enabled=false2、自己放一个图标在静态资源目录下,我放在 public 目录下3、清除浏览器缓存!刷新网页,发现图标已经变成自己的了!13.Thymeleaf模板引擎13.1 模板引擎前端交给我们的页面,是html页面。如果是我们以前开发,我们需要把他们转成jsp页面,jsp好处就是当我们查出一些数据转发到JSP页面以后,我们可以用jsp轻松实现数据的显示,及交互等。jsp支持非常强大的功能,包括能写Java代码,但是呢,我们现在的这种情况,SpringBoot这个项目首先是以jar的方式,不是war,像第二,我们用的还是嵌入式的Tomcat,所以呢,他现在默认是不支持jsp的。那不支持jsp,如果我们直接用纯静态页面的方式,那给我们开发会带来非常大的麻烦,那怎么办呢?SpringBoot推荐你可以来使用模板引擎:模板引擎,我们其实大家听到很多,其实jsp就是一个模板引擎,还有用的比较多的freemarker,包括SpringBoot给我们推荐的Thymeleaf,模板引擎有非常多,但再多的模板引擎,他们的思想都是一样的,什么样一个思想呢我们来看一下这张图:模板引擎的作用就是我们来写一个页面模板,比如有些值呢,是动态的,我们写一些表达式。而这些值,从哪来呢,就是我们在后台封装一些数据。然后把这个模板和这个数据交给我们模板引擎,模板引擎按照我们这个数据帮你把这表达式解析、填充到我们指定的位置,然后把这个数据最终生成一个我们想要的内容给我们写出去,这就是我们这个模板引擎,不管是jsp还是其他模板引擎,都是这个思想。只不过呢,就是说不同模板引擎之间,他们可能这个语法有点不一样。其他的我就不介绍了,我主要来介绍一下SpringBoot给我们推荐的Thymeleaf模板引擎,这模板引擎呢,是一个高级语言的模板引擎,他的这个语法更简单。而且呢,功能更强大。我们呢,就来看一下这个模板引擎,那既然要看这个模板引擎。首先,我们来看SpringBoot里边怎么用。13.2 引入Thymeleaf怎么引入呢,对于springboot来说,什么事情不都是一个start的事情嘛,我们去在项目中引入一下。给大家三个网址:Thymeleaf 官网:https://www.thymeleaf.org/Thymeleaf 在Github 的主页:https://github.com/thymeleaf/thymeleafSpring官方文档:找到我们对应的版本https://docs.spring.io/spring-boot/docs/2.2.5.RELEASE/reference/htmlsingle/#using-boot-starter 找到对应的pom依赖:可以适当点进源码看下本来的包!<!--thymeleaf--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency>Maven会自动下载jar包,我们可以去看下下载的东西;13.3 Thymeleaf分析前面呢,我们已经引入了Thymeleaf,那这个要怎么使用呢?我们首先得按照SpringBoot的自动配置原理看一下我们这个Thymeleaf的自动配置规则,在按照那个规则,我们进行使用。我们去找一下Thymeleaf的自动配置类:ThymeleafProperties@ConfigurationProperties( prefix = "spring.thymeleaf" ) public class ThymeleafProperties { private static final Charset DEFAULT_ENCODING; public static final String DEFAULT_PREFIX = "classpath:/templates/"; public static final String DEFAULT_SUFFIX = ".html"; private boolean checkTemplate = true; private boolean checkTemplateLocation = true; private String prefix = "classpath:/templates/"; private String suffix = ".html"; private String mode = "HTML"; private Charset encoding; }我们可以在其中看到默认的前缀和后缀!我们只需要把我们的html页面放在类路径下的templates下,thymeleaf就可以帮我们自动渲染了。使用thymeleaf什么都不需要配置,只需要将他放在指定的文件夹下即可!13.4 测试1、编写一个TestController@Controller public class TestController { @RequestMapping("/t1") public String test1(){ //classpath:/templates/test.html return "test"; } }2、编写一个测试页面 test.html 放在 templates 目录下<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>测试页面</h1> </body> </html>3、启动项目请求测试13.5 Thymeleaf 语法学习要学习语法,还是参考官网文档最为准确,我们找到对应的版本看一下;Thymeleaf 官网:https://www.thymeleaf.org/ , 简单看一下官网!我们去下载Thymeleaf的官方文档!我们做个最简单的练习 :我们需要查出一些数据,在页面中展示1、修改测试请求,增加数据传输;@RequestMapping("/t1") public String test1(Model model){ //存入数据 model.addAttribute("msg","Hello,Thymeleaf"); //classpath:/templates/test.html return "test"; }2、我们要使用thymeleaf,需要在html文件中导入命名空间的约束,方便提示。我们可以去官方文档的#3中看一下命名空间拿来过来:xmlns:th="http://www.thymeleaf.org"3、我们去编写下前端页面<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>狂神说</title> </head> <body> <h1>测试页面</h1> <!--th:text就是将div中的内容设置为它指定的值,和之前学习的Vue一样--> <div th:text="${msg}"></div> </body> </html>4、启动测试!OK,入门搞定,我们来认真研习一下Thymeleaf的使用语法!1、我们可以使用任意的 th:attr 来替换Html中原生属性的值!2、我们能写哪些表达式呢?Simple expressions:(表达式语法) Variable Expressions: ${...}:获取变量值;OGNL; 1)、获取对象的属性、调用方法 2)、使用内置的基本对象:#18 #ctx : the context object. #vars: the context variables. #locale : the context locale. #request : (only in Web Contexts) the HttpServletRequest object. #response : (only in Web Contexts) the HttpServletResponse object. #session : (only in Web Contexts) the HttpSession object. #servletContext : (only in Web Contexts) the ServletContext object. 3)、内置的一些工具对象: #execInfo : information about the template being processed. #uris : methods for escaping parts of URLs/URIs #conversions : methods for executing the configured conversion service (if any). #dates : methods for java.util.Date objects: formatting, component extraction, etc. #calendars : analogous to #dates , but for java.util.Calendar objects. #numbers : methods for formatting numeric objects. #strings : methods for String objects: contains, startsWith, prepending/appending, etc. #objects : methods for objects in general. #bools : methods for boolean evaluation. #arrays : methods for arrays. #lists : methods for lists. #sets : methods for sets. #maps : methods for maps. #aggregates : methods for creating aggregates on arrays or collections. ================================================================================== Selection Variable Expressions: *{...}:选择表达式:和${}在功能上是一样; Message Expressions: #{...}:获取国际化内容 Link URL Expressions: @{...}:定义URL; Fragment Expressions: ~{...}:片段引用表达式 Literals(字面量) Text literals: 'one text' , 'Another one!' ,… Number literals: 0 , 34 , 3.0 , 12.3 ,… Boolean literals: true , false Null literal: null Literal tokens: one , sometext , main ,… Text operations:(文本操作) String concatenation: + Literal substitutions: |The name is ${name}| Arithmetic operations:(数学运算) Binary operators: + , - , * , / , % Minus sign (unary operator): - Boolean operations:(布尔运算) Binary operators: and , or Boolean negation (unary operator): ! , not Comparisons and equality:(比较运算) Comparators: > , < , >= , <= ( gt , lt , ge , le ) Equality operators: == , != ( eq , ne ) Conditional operators:条件运算(三元运算符) If-then: (if) ? (then) If-then-else: (if) ? (then) : (else) Default: (value) ?: (defaultvalue) Special tokens: No-Operation: _13.5 练习测试1、 我们编写一个Controller,放一些数据@RequestMapping("/t2") public String test2(Map<String,Object> map){ //存入数据 map.put("msg","<h1>Hello</h1>"); map.put("users", Arrays.asList("qinjiang","kuangshen")); //classpath:/templates/test.html return "test"; }2、测试页面取出数据<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>狂神说</title> </head> <body> <h1>测试页面</h1> <div th:text="${msg}"></div> <!--不转义--> <div th:utext="${msg}"></div> <!--遍历数据--> <!--th:each每次遍历都会生成当前这个标签:官网#9--> <h4 th:each="user :${users}" th:text="${user}"></h4> <h4> <!--行内写法:官网#12--> <span th:each="user:${users}">[[${user}]]</span> </h4> </body> </html>3、启动项目测试!我们看完语法,很多样式,我们即使现在学习了,也会忘记,所以我们在学习过程中,需要使用什么,根据官方文档来查询,才是最重要的,要熟练使用官方文档!14.项目集成Swagger14.1 Swagger简介前后端分离前端 -> 前端控制层、视图层后端 -> 后端控制层、服务层、数据访问层前后端通过API进行交互前后端相对独立且松耦合产生的问题前后端集成,前端或者后端无法做到“及时协商,尽早解决”,最终导致问题集中爆发解决方案首先定义schema [ 计划的提纲 ],并实时跟踪最新的API,降低集成风险Swagger号称世界上最流行的API框架Restful Api 文档在线自动生成器 => API 文档 与API 定义同步更新直接运行,在线测试API支持多种语言 (如:Java,PHP等)官网:https://swagger.io/14.2 SpringBoot集成SwaggerSpringBoot集成Swagger => springfox,两个jar包Springfox-swagger2swagger-springmvc使用Swagger要求:jdk 1.8 + 否则swagger2无法运行步骤:1、新建一个SpringBoot-web项目2、添加Maven依赖<!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger2 --> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.9.2</version> </dependency> <!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger-ui --> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.9.2</version> </dependency>3、编写HelloController,测试确保运行成功!4、要使用Swagger,我们需要编写一个配置类-SwaggerConfig来配置 Swagger@Configuration //配置类 @EnableSwagger2// 开启Swagger2的自动配置 public class SwaggerConfig { }5、访问测试 :http://localhost:8080/swagger-ui.html ,可以看到swagger的界面;14.3 配置Swagger1、Swagger实例Bean是Docket,所以通过配置Docket实例来配置Swaggger。@Bean //配置docket以配置Swagger具体参数 public Docket docket() { return new Docket(DocumentationType.SWAGGER_2); }2、可以通过apiInfo()属性配置文档信息//配置文档信息 private ApiInfo apiInfo() { Contact contact = new Contact("联系人名字", "http://xxx.xxx.com/联系人访问链接", "联系人邮箱"); return new ApiInfo( "Swagger学习", // 标题 "学习演示如何配置Swagger", // 描述 "v1.0", // 版本 "http://terms.service.url/组织链接", // 组织链接 contact, // 联系人信息 "Apach 2.0 许可", // 许可 "许可链接", // 许可连接 new ArrayList<>()// 扩展 ); }3、Docket 实例关联上 apiInfo()@Bean public Docket docket() { return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo()); }4、重启项目,访问测试 http://localhost:8080/swagger-ui.html 看下效果;14.4 配置扫描接口1、构建Docket时通过select()方法配置怎么扫描接口。@Bean public Docket docket() { return new Docket(DocumentationType.SWAGGER_2) .apiInfo(apiInfo()) .select()// 通过.select()方法,去配置扫描接口,RequestHandlerSelectors配置如何扫描接口 .apis(RequestHandlerSelectors.basePackage("com.kuang.swagger.controller")) .build(); }2、重启项目测试,由于我们配置根据包的路径扫描接口,所以我们只能看到一个类3、除了通过包路径配置扫描接口外,还可以通过配置其他方式扫描接口,这里注释一下所有的配置方式:any() // 扫描所有,项目中的所有接口都会被扫描到 none() // 不扫描接口 // 通过方法上的注解扫描,如withMethodAnnotation(GetMapping.class)只扫描get请求 withMethodAnnotation(final Class<? extends Annotation> annotation) // 通过类上的注解扫描,如.withClassAnnotation(Controller.class)只扫描有controller注解的类中的接口 withClassAnnotation(final Class<? extends Annotation> annotation) basePackage(final String basePackage) // 根据包路径扫描接口4、除此之外,我们还可以配置接口扫描过滤:@Bean public Docket docket() { return new Docket(DocumentationType.SWAGGER_2) .apiInfo(apiInfo()) .select()// 通过.select()方法,去配置扫描接口,RequestHandlerSelectors配置如何扫描接口 .apis(RequestHandlerSelectors.basePackage("com.kuang.swagger.controller")) // 配置如何通过path过滤,即这里只扫描请求以/kuang开头的接口 .paths(PathSelectors.ant("/kuang/**")) .build(); }5、这里的可选值还有any() // 任何请求都扫描 none() // 任何请求都不扫描 regex(final String pathRegex) // 通过正则表达式控制 ant(final String antPattern) // 通过ant()控制14.5 配置Swagger开关1、通过enable()方法配置是否启用swagger,如果是false,swagger将不能在浏览器中访问了@Bean public Docket docket() { return new Docket(DocumentationType.SWAGGER_2) .apiInfo(apiInfo()) .enable(false) //配置是否启用Swagger,如果是false,在浏览器将无法访问 .select()// 通过.select()方法,去配置扫描接口,RequestHandlerSelectors配置如何扫描接口 .apis(RequestHandlerSelectors.basePackage("com.kuang.swagger.controller")) // 配置如何通过path过滤,即这里只扫描请求以/kuang开头的接口 .paths(PathSelectors.ant("/kuang/**")) .build(); }2、如何动态配置当项目处于test、dev环境时显示swagger,处于prod时不显示?@Bean public Docket docket(Environment environment) { // 设置要显示swagger的环境 Profiles of = Profiles.of("dev", "test"); // 判断当前是否处于该环境 // 通过 enable() 接收此参数判断是否要显示 boolean b = environment.acceptsProfiles(of); return new Docket(DocumentationType.SWAGGER_2) .apiInfo(apiInfo()) .enable(b) //配置是否启用Swagger,如果是false,在浏览器将无法访问 .select()// 通过.select()方法,去配置扫描接口,RequestHandlerSelectors配置如何扫描接口 .apis(RequestHandlerSelectors.basePackage("com.kuang.swagger.controller")) // 配置如何通过path过滤,即这里只扫描请求以/kuang开头的接口 .paths(PathSelectors.ant("/kuang/**")) .build(); }3、可以在项目中增加一个dev的配置文件查看效果!14.6 配置API分组1、如果没有配置分组,默认是default。通过groupName()方法即可配置分组:@Bean public Docket docket(Environment environment) { return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo()) .groupName("hello") // 配置分组 // 省略配置.... }2、重启项目查看分组3、如何配置多个分组?配置多个分组只需要配置多个docket即可:@Bean public Docket docket1(){ return new Docket(DocumentationType.SWAGGER_2).groupName("group1"); } @Bean public Docket docket2(){ return new Docket(DocumentationType.SWAGGER_2).groupName("group2"); } @Bean public Docket docket3(){ return new Docket(DocumentationType.SWAGGER_2).groupName("group3"); }4、重启项目查看即可14.7 实体配置1、新建一个实体类@ApiModel("用户实体") public class User { @ApiModelProperty("用户名") public String username; @ApiModelProperty("密码") public String password; }2、只要这个实体在请求接口的返回值上(即使是泛型),都能映射到实体项中:@RequestMapping("/getUser") public User getUser(){ return new User(); }3、重启查看测试注:并不是因为@ApiModel这个注解让实体显示在这里了,而是只要出现在接口方法的返回值上的实体都会显示在这里,而@ApiModel和@ApiModelProperty这两个注解只是为实体添加注释的。@ApiModel为类添加注释@ApiModelProperty为类属性添加注释14.8 常用注解Swagger的所有注解定义在io.swagger.annotations包下下面列一些经常用到的,未列举出来的可以另行查阅说明:Swagger注解简单说明@Api(tags = "xxx模块说明")作用在模块类上@ApiOperation("xxx接口说明")作用在接口方法上@ApiModel("xxxPOJO说明")作用在模型类上:如VO、BO@ApiModelProperty(value = "xxx属性说明",hidden = true)作用在类方法和属性上,hidden设置为true可以隐藏该属性@ApiParam("xxx参数说明")作用在参数、方法和字段上,类似@ApiModelProperty我们也可以给请求的接口配置一些注释@ApiOperation("狂神的接口") @PostMapping("/kuang") @ResponseBody public String kuang(@ApiParam("这个名字会被返回")String username){ return username; }这样的话,可以给一些比较难理解的属性或者接口,增加一些配置信息,让人更容易阅读!相较于传统的Postman或Curl方式测试接口,使用swagger简直就是傻瓜式操作,不需要额外说明文档(写得好本身就是文档)而且更不容易出错,只需要录入数据然后点击Execute,如果再配合自动化框架,可以说基本就不需要人为操作了。Swagger是个优秀的工具,现在国内已经有很多的中小型互联网公司都在使用它,相较于传统的要先出Word接口文档再测试的方式,显然这样也更符合现在的快速迭代开发行情。当然了,提醒下大家在正式环境要记得关闭Swagger,一来出于安全考虑二来也可以节省运行时内存。14.9 拓展:其他皮肤我们可以导入不同的包实现不同的皮肤定义:1、默认的 访问 http://localhost:8080/swagger-ui.html<dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.9.2</version> </dependency>2、bootstrap-ui 访问 http://localhost:8080/doc.html<!-- 引入swagger-bootstrap-ui包 /doc.html--> <dependency> <groupId>com.github.xiaoymin</groupId> <artifactId>swagger-bootstrap-ui</artifactId> <version>1.9.1</version> </dependency>3、Layui-ui 访问 http://localhost:8080/docs.html<!-- 引入swagger-ui-layer包 /docs.html--> <dependency> <groupId>com.github.caspar-chen</groupId> <artifactId>swagger-ui-layer</artifactId> <version>1.1.3</version> </dependency>4、mg-ui 访问 http://localhost:8080/document.html<!-- 引入swagger-ui-layer包 /document.html--> <dependency> <groupId>com.zyplayer</groupId> <artifactId>swagger-mg-ui</artifactId> <version>1.0.6</version> </dependency>15.异步、定时、邮件任务15.1 异步任务1、创建一个service包2、创建一个类AsyncService异步处理还是非常常用的,比如我们在网站上发送邮件,后台会去发送邮件,此时前台会造成响应不动,直到邮件发送完毕,响应才会成功,所以我们一般会采用多线程的方式去处理这些任务。编写方法,假装正在处理数据,使用线程设置一些延时,模拟同步等待的情况;@Service public class AsyncService { public void hello(){ try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("业务进行中...."); } }3、编写controller包4、编写AsyncController类我们去写一个Controller测试一下@RestController public class AsyncController { @Autowired AsyncService asyncService; @GetMapping("/hello") public String hello(){ asyncService.hello(); return "success"; } }5、访问http://localhost:8080/hello进行测试,3秒后出现success,这是同步等待的情况。问题:我们如果想让用户直接得到消息,就在后台使用多线程的方式进行处理即可,但是每次都需要自己手动去编写多线程的实现的话,太麻烦了,我们只需要用一个简单的办法,在我们的方法上加一个简单的注解即可,如下:6、给hello方法添加@Async注解;//告诉Spring这是一个异步方法 @Async public void hello(){ try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("业务进行中...."); }SpringBoot就会自己开一个线程池,进行调用!但是要让这个注解生效,我们还需要在主程序上添加一个注解@EnableAsync ,开启异步注解功能;@EnableAsync //开启异步注解功能 @SpringBootApplication public class SpringbootTaskApplication { public static void main(String[] args) { SpringApplication.run(SpringbootTaskApplication.class, args); } }7、重启测试,网页瞬间响应,后台代码依旧执行!15.2 定时任务项目开发中经常需要执行一些定时任务,比如需要在每天凌晨的时候,分析一次前一天的日志信息,Spring为我们提供了异步执行任务调度的方式,提供了两个接口。TaskExecutor接口TaskScheduler接口两个注解:@EnableScheduling@Scheduledcron表达式:测试步骤:1、创建一个ScheduledService我们里面存在一个hello方法,他需要定时执行,怎么处理呢?@Service public class ScheduledService { //秒 分 时 日 月 周几 //0 * * * * MON-FRI //注意cron表达式的用法; @Scheduled(cron = "0 * * * * 0-7") public void hello(){ System.out.println("hello....."); } }2、这里写完定时任务之后,我们需要在主程序上增加@EnableScheduling 开启定时任务功能@EnableAsync //开启异步注解功能 @EnableScheduling //开启基于注解的定时任务 @SpringBootApplication public class SpringbootTaskApplication { public static void main(String[] args) { SpringApplication.run(SpringbootTaskApplication.class, args); } }3、我们来详细了解下cron表达式;http://www.bejson.com/othertools/cron/4、常用的表达式(1)0/2 * * * * ? 表示每2秒 执行任务 (1)0 0/2 * * * ? 表示每2分钟 执行任务 (1)0 0 2 1 * ? 表示在每月的1日的凌晨2点调整任务 (2)0 15 10 ? * MON-FRI 表示周一到周五每天上午10:15执行作业 (3)0 15 10 ? 6L 2002-2006 表示2002-2006年的每个月的最后一个星期五上午10:15执行作 (4)0 0 10,14,16 * * ? 每天上午10点,下午2点,4点 (5)0 0/30 9-17 * * ? 朝九晚五工作时间内每半小时 (6)0 0 12 ? * WED 表示每个星期三中午12点 (7)0 0 12 * * ? 每天中午12点触发 (8)0 15 10 ? * * 每天上午10:15触发 (9)0 15 10 * * ? 每天上午10:15触发 (10)0 15 10 * * ? 每天上午10:15触发 (11)0 15 10 * * ? 2005 2005年的每天上午10:15触发 (12)0 * 14 * * ? 在每天下午2点到下午2:59期间的每1分钟触发 (13)0 0/5 14 * * ? 在每天下午2点到下午2:55期间的每5分钟触发 (14)0 0/5 14,18 * * ? 在每天下午2点到2:55期间和下午6点到6:55期间的每5分钟触发 (15)0 0-5 14 * * ? 在每天下午2点到下午2:05期间的每1分钟触发 (16)0 10,44 14 ? 3 WED 每年三月的星期三的下午2:10和2:44触发 (17)0 15 10 ? * MON-FRI 周一至周五的上午10:15触发 (18)0 15 10 15 * ? 每月15日上午10:15触发 (19)0 15 10 L * ? 每月最后一日的上午10:15触发 (20)0 15 10 ? * 6L 每月的最后一个星期五上午10:15触发 (21)0 15 10 ? * 6L 2002-2005 2002年至2005年的每月的最后一个星期五上午10:15触发 (22)0 15 10 ? * 6#3 每月的第三个星期五上午10:15触发15.3 邮件任务邮件发送,在我们的日常开发中,也非常的多,Springboot也帮我们做了支持邮件发送需要引入spring-boot-start-mailSpringBoot 自动配置MailSenderAutoConfiguration定义MailProperties内容,配置在application.yml中自动装配JavaMailSender测试邮件发送测试:1、引入pom依赖<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-mail</artifactId> </dependency>看它引入的依赖,可以看到 jakarta.mail<dependency> <groupId>com.sun.mail</groupId> <artifactId>jakarta.mail</artifactId> <version>1.6.4</version> <scope>compile</scope> </dependency>2、查看自动配置类:MailSenderAutoConfiguration这个类中存在bean,JavaMailSenderImpl然后我们去看下配置文件@ConfigurationProperties( prefix = "spring.mail" ) public class MailProperties { private static final Charset DEFAULT_CHARSET; private String host; private Integer port; private String username; private String password; private String protocol = "smtp"; private Charset defaultEncoding; private Map<String, String> properties; private String jndiName; }3、配置文件:spring.mail.username=24736743@qq.com spring.mail.password=你的qq授权码 spring.mail.host=smtp.qq.com # qq需要配置ssl spring.mail.properties.mail.smtp.ssl.enable=true获取授权码:在QQ邮箱中的设置->账户->开启pop3和smtp服务4、Spring单元测试@Autowired JavaMailSenderImpl mailSender; @Test public void contextLoads() { //邮件设置1:一个简单的邮件 SimpleMailMessage message = new SimpleMailMessage(); message.setSubject("通知-明天来狂神这听课"); message.setText("今晚7:30开会"); message.setTo("24736743@qq.com"); message.setFrom("24736743@qq.com"); mailSender.send(message); } @Test public void contextLoads2() throws MessagingException { //邮件设置2:一个复杂的邮件 MimeMessage mimeMessage = mailSender.createMimeMessage(); MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true); helper.setSubject("通知-明天来狂神这听课"); helper.setText("<b style='color:red'>今天 7:30来开会</b>",true); //发送附件 helper.addAttachment("1.jpg",new File("")); helper.addAttachment("2.jpg",new File("")); helper.setTo("24736743@qq.com"); helper.setFrom("24736743@qq.com"); mailSender.send(mimeMessage); }查看邮箱,邮件接收成功!我们只需要使用Thymeleaf进行前后端结合即可开发自己网站邮件收发功能了!16.富文本编辑器16.1 简介思考:我们平时在博客园,或者CSDN等平台进行写作的时候,有同学思考过他们的编辑器是怎么实现的吗?在博客园后台的选项设置中,可以看到一个文本编辑器的选项:其实这个就是富文本编辑器,市面上有许多非常成熟的富文本编辑器,比如:Editor.md——功能非常丰富的编辑器,左端编辑,右端预览,非常方便,完全免费官网:https://pandao.github.io/editor.md/wangEditor——基于javascript和css开发的 Web富文本编辑器, 轻量、简洁、界面美观、易用、开源免费。官网:http://www.wangeditor.com/TinyMCE——TinyMCE是一个轻量级的基于浏览器的所见即所得编辑器,由JavaScript写成。它对IE6+和Firefox1.5+都有着非常良好的支持。功能齐全,界面美观,就是文档是英文的,对开发人员英文水平有一定要求。官网:https://www.tiny.cloud/docs/demo/full-featured/博客园百度ueditor——UEditor是由百度web前端研发部开发所见即所得富文本web编辑器,具有轻量,功能齐全,可定制,注重用户体验等特点,开源基于MIT协议,允许自由使用和修改代码,缺点是已经没有更新了官网:https://ueditor.baidu.com/website/onlinedemo.htmlkindeditor——界面经典。官网:http://kindeditor.net/demo.phpTextbox——Textbox是一款极简但功能强大的在线文本编辑器,支持桌面设备和移动设备。主要功能包含内置的图像处理和存储、文件拖放、拼写检查和自动更正。此外,该工具还实现了屏幕阅读器等辅助技术,并符合WAI-ARIA可访问性标准。官网:https://textbox.io/CKEditor——国外的,界面美观。官网:https://ckeditor.com/ckeditor-5/demo/quill——功能强大,还可以编辑公式等官网:https://quilljs.com/simditor——界面美观,功能较全。官网:https://simditor.tower.im/summernote——UI好看,精美官网:https://summernote.org/jodit——功能齐全官网:https://xdsoft.net/jodit/froala Editor——界面非常好看,功能非常强大,非常好用(非免费)官网:https://www.froala.com/wysiwyg-editor总之,目前可用的富文本编辑器有很多......这只是其中的一部分16.2 Editor.md我这里使用的就是Editor.md,作为一个资深码农,Mardown必然是我们程序猿最喜欢的格式,看下面,就爱上了!我们可以在官网下载它:https://pandao.github.io/editor.md/ , 得到它的压缩包!解压以后,在examples目录下面,可以看到他的很多案例使用!学习,其实就是看人家怎么写的,然后进行模仿就好了!我们可以将整个解压的文件倒入我们的项目,将一些无用的测试和案例删掉即可!16.3 基础工程搭建数据库设计article:文章表字段 备注idint文章的唯一IDauthorvarchar作者titlevarchar标题contentlongtext文章的内容建表SQL:CREATE TABLE `article` ( `id` int(10) NOT NULL AUTO_INCREMENT COMMENT 'int文章的唯一ID', `author` varchar(50) NOT NULL COMMENT '作者', `title` varchar(100) NOT NULL COMMENT '标题', `content` longtext NOT NULL COMMENT '文章的内容', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8基础项目搭建1、建一个SpringBoot项目配置spring: datasource: username: root password: 123456 #?serverTimezone=UTC解决时区的报错 url: jdbc:mysql://localhost:3306/springboot?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8 driver-class-name: com.mysql.cj.jdbc.Driver<resources> <resource> <directory>src/main/java</directory> <includes> <include>**/*.xml</include> </includes> <filtering>true</filtering> </resource> </resources>2、实体类://文章类 @Data @NoArgsConstructor @AllArgsConstructor public class Article implements Serializable { private int id; //文章的唯一ID private String author; //作者名 private String title; //标题 private String content; //文章的内容 }3、mapper接口:@Mapper @Repository public interface ArticleMapper { //查询所有的文章 List<Article> queryArticles(); //新增一个文章 int addArticle(Article article); //根据文章id查询文章 Article getArticleById(int id); //根据文章id删除文章 int deleteArticleById(int id); }<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.kuang.mapper.ArticleMapper"> <select id="queryArticles" resultType="Article"> select * from article </select> <select id="getArticleById" resultType="Article"> select * from article where id = #{id} </select> <insert id="addArticle" parameterType="Article"> insert into article (author,title,content) values (#{author},#{title},#{content}); </insert> <delete id="deleteArticleById" parameterType="int"> delete from article where id = #{id} </delete> </mapper>既然已经提供了 myBatis 的映射配置文件,自然要告诉 spring boot 这些文件的位置mybatis: mapper-locations: classpath:com/kuang/mapper/*.xml type-aliases-package: com.kuang.pojo编写一个Controller测试下,是否ok;16.4 文章编辑整合(重点)1、导入 editor.md 资源 ,删除多余文件2、编辑文章页面 editor.html、需要引入 jQuery;<!DOCTYPE html> <html class="x-admin-sm" lang="zh" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>秦疆'Blog</title> <meta name="renderer" content="webkit"> <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> <meta name="viewport" content="width=device-width,user-scalable=yes, minimum-scale=0.4, initial-scale=0.8,target-densitydpi=low-dpi" /> <!--Editor.md--> <link rel="stylesheet" th:href="@{/editormd/css/editormd.css}"/> <link rel="shortcut icon" href="https://pandao.github.io/editor.md/favicon.ico" type="image/x-icon" /> </head> <body> <div class="layui-fluid"> <div class="layui-row layui-col-space15"> <div class="layui-col-md12"> <!--博客表单--> <form name="mdEditorForm"> <div> 标题:<input type="text" name="title"> </div> <div> 作者:<input type="text" name="author"> </div> <div id="article-content"> <textarea name="content" id="content" style="display:none;"> </textarea> </div> </form> </div> </div> </div> </body> <!--editormd--> <script th:src="@{/editormd/lib/jquery.min.js}"></script> <script th:src="@{/editormd/editormd.js}"></script> <script type="text/javascript"> var testEditor; //window.onload = function(){ } $(function() { testEditor = editormd("article-content", { width : "95%", height : 400, syncScrolling : "single", path : "../editormd/lib/", saveHTMLToTextarea : true, // 保存 HTML 到 Textarea emoji: true, theme: "dark",//工具栏主题 previewTheme: "dark",//预览主题 editorTheme: "pastel-on-dark",//编辑主题 tex : true, // 开启科学公式TeX语言支持,默认关闭 flowChart : true, // 开启流程图支持,默认关闭 sequenceDiagram : true, // 开启时序/序列图支持,默认关闭, //图片上传 imageUpload : true, imageFormats : ["jpg", "jpeg", "gif", "png", "bmp", "webp"], imageUploadURL : "/article/file/upload", onload : function() { console.log('onload', this); }, /*指定需要显示的功能按钮*/ toolbarIcons : function() { return ["undo","redo","|", "bold","del","italic","quote","ucwords","uppercase","lowercase","|", "h1","h2","h3","h4","h5","h6","|", "list-ul","list-ol","hr","|", "link","reference-link","image","code","preformatted-text", "code-block","table","datetime","emoji","html-entities","pagebreak","|", "goto-line","watch","preview","fullscreen","clear","search","|", "help","info","releaseIcon", "index"] }, /*自定义功能按钮,下面我自定义了2个,一个是发布,一个是返回首页*/ toolbarIconTexts : { releaseIcon : "<span bgcolor=\"gray\">发布</span>", index : "<span bgcolor=\"red\">返回首页</span>", }, /*给自定义按钮指定回调函数*/ toolbarHandlers:{ releaseIcon : function(cm, icon, cursor, selection) { //表单提交 mdEditorForm.method = "post"; mdEditorForm.action = "/article/addArticle";//提交至服务器的路径 mdEditorForm.submit(); }, index : function(){ window.location.href = '/'; }, } }); }); </script> </html>3、编写Controller,进行跳转,以及保存文章@Controller @RequestMapping("/article") public class ArticleController { @GetMapping("/toEditor") public String toEditor(){ return "editor"; } @PostMapping("/addArticle") public String addArticle(Article article){ articleMapper.addArticle(article); return "editor"; } }图片上传问题1、前端js中添加配置//图片上传 imageUpload : true, imageFormats : ["jpg", "jpeg", "gif", "png", "bmp", "webp"], imageUploadURL : "/article/file/upload", // //这个是上传图片时的访问地址2、后端请求,接收保存这个图片, 需要导入 FastJson 的依赖!//博客图片上传问题 @RequestMapping("/file/upload") @ResponseBody public JSONObject fileUpload(@RequestParam(value = "editormd-image-file", required = true) MultipartFile file, HttpServletRequest request) throws IOException { //上传路径保存设置 //获得SpringBoot当前项目的路径:System.getProperty("user.dir") String path = System.getProperty("user.dir")+"/upload/"; //按照月份进行分类: Calendar instance = Calendar.getInstance(); String month = (instance.get(Calendar.MONTH) + 1)+"月"; path = path+month; File realPath = new File(path); if (!realPath.exists()){ realPath.mkdir(); } //上传文件地址 System.out.println("上传文件保存地址:"+realPath); //解决文件名字问题:我们使用uuid; String filename = "ks-"+UUID.randomUUID().toString().replaceAll("-", ""); //通过CommonsMultipartFile的方法直接写文件(注意这个时候) file.transferTo(new File(realPath +"/"+ filename)); //给editormd进行回调 JSONObject res = new JSONObject(); res.put("url","/upload/"+month+"/"+ filename); res.put("success", 1); res.put("message", "upload success!"); return res; }3、解决文件回显显示的问题,设置虚拟目录映射!在我们自己拓展的MvcConfig中进行配置即可!@Configuration public class MyMvcConfig implements WebMvcConfigurer { // 文件保存在真实目录/upload/下, // 访问的时候使用虚路径/upload,比如文件名为1.png,就直接/upload/1.png就ok了。 @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/upload/**") .addResourceLocations("file:"+System.getProperty("user.dir")+"/upload/"); } }表情包问题自己手动下载,emoji 表情包,放到图片路径下:修改editormd.js文件// Emoji graphics files url path editormd.emoji = { path : "../editormd/plugins/emoji-dialog/emoji/", ext : ".png" };16.5文章展示1、Controller 中增加方法@GetMapping("/{id}") public String show(@PathVariable("id") int id,Model model){ Article article = articleMapper.getArticleById(id); model.addAttribute("article",article); return "article"; }2、编写页面 article.html<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"> <title th:text="${article.title}"></title> </head> <body> <div> <!--文章头部信息:标题,作者,最后更新日期,导航--> <h2 style="margin: auto 0" th:text="${article.title}"></h2> 作者:<span style="float: left" th:text="${article.author}"></span> <!--文章主体内容--> <div id="doc-content"> <textarea style="display:none;" placeholder="markdown" th:text="${article.content}"></textarea> </div> </div> <link rel="stylesheet" th:href="@{/editormd/css/editormd.preview.css}" /> <script th:src="@{/editormd/lib/jquery.min.js}"></script> <script th:src="@{/editormd/lib/marked.min.js}"></script> <script th:src="@{/editormd/lib/prettify.min.js}"></script> <script th:src="@{/editormd/lib/raphael.min.js}"></script> <script th:src="@{/editormd/lib/underscore.min.js}"></script> <script th:src="@{/editormd/lib/sequence-diagram.min.js}"></script> <script th:src="@{/editormd/lib/flowchart.min.js}"></script> <script th:src="@{/editormd/lib/jquery.flowchart.min.js}"></script> <script th:src="@{/editormd/editormd.js}"></script> <script type="text/javascript"> var testEditor; $(function () { testEditor = editormd.markdownToHTML("doc-content", {//注意:这里是上面DIV的id htmlDecode: "style,script,iframe", emoji: true, taskList: true, tocm: true, tex: true, // 默认不解析 flowChart: true, // 默认不解析 sequenceDiagram: true, // 默认不解析 codeFold: true });}); </script> </body> </html>重启项目,访问进行测试!大功告成!17. Dubbo和Zookeeper集成17.1 分布式理论什么是分布式系统?在《分布式系统原理与范型》一书中有如下定义:“分布式系统是若干独立计算机的集合,这些计算机对于用户来说就像单个相关系统”;分布式系统是由一组通过网络进行通信、为了完成共同的任务而协调工作的计算机节点组成的系统。分布式系统的出现是为了用廉价的、普通的机器完成单个计算机无法完成的计算、存储任务。其目的是利用更多的机器,处理更多的数据。分布式系统(distributed system)是建立在网络之上的软件系统。首先需要明确的是,只有当单个节点的处理能力无法满足日益增长的计算、存储任务的时候,且硬件的提升(加内存、加磁盘、使用更好的CPU)高昂到得不偿失的时候,应用程序也不能进一步优化的时候,我们才需要考虑分布式系统。因为,分布式系统要解决的问题本身就是和单机系统一样的,而由于分布式系统多节点、通过网络通信的拓扑结构,会引入很多单机系统没有的问题,为了解决这些问题又会引入更多的机制、协议,带来更多的问题。。。Dubbo文档随着互联网的发展,网站应用的规模不断扩大,常规的垂直应用架构已无法应对,分布式服务架构以及流动计算架构势在必行,急需一个治理系统确保架构有条不紊的演进。在Dubbo的官网文档有这样一张图单一应用架构当网站流量很小时,只需一个应用,将所有功能都部署在一起,以减少部署节点和成本。此时,用于简化增删改查工作量的数据访问框架(ORM)是关键。适用于小型网站,小型管理系统,将所有功能都部署到一个功能里,简单易用。缺点:1、性能扩展比较难2、协同开发问题3、不利于升级维护垂直应用架构当访问量逐渐增大,单一应用增加机器带来的加速度越来越小,将应用拆成互不相干的几个应用,以提升效率。此时,用于加速前端页面开发的Web框架(MVC)是关键。通过切分业务来实现各个模块独立部署,降低了维护和部署的难度,团队各司其职更易管理,性能扩展也更方便,更有针对性。缺点:公用模块无法重复利用,开发性的浪费分布式服务架构当垂直应用越来越多,应用之间交互不可避免,将核心业务抽取出来,作为独立的服务,逐渐形成稳定的服务中心,使前端应用能更快速的响应多变的市场需求。此时,用于提高业务复用及整合的分布式服务框架(RPC)是关键。流动计算架构当服务越来越多,容量的评估,小服务资源的浪费等问题逐渐显现,此时需增加一个调度中心基于访问压力实时管理集群容量,提高集群利用率。此时,用于提高机器利用率的资源调度和治理中心(SOA)[ Service Oriented Architecture]是关键。17.2 什么是RPCRPC【Remote Procedure Call】是指远程过程调用,是一种进程间通信方式,他是一种技术的思想,而不是规范。它允许程序调用另一个地址空间(通常是共享网络的另一台机器上)的过程或函数,而不用程序员显式编码这个远程调用的细节。即程序员无论是调用本地的还是远程的函数,本质上编写的调用代码基本相同。也就是说两台服务器A,B,一个应用部署在A服务器上,想要调用B服务器上应用提供的函数/方法,由于不在一个内存空间,不能直接调用,需要通过网络来表达调用的语义和传达调用的数据。为什么要用RPC呢?就是无法在一个进程内,甚至一个计算机内通过本地调用的方式完成的需求,比如不同的系统间的通讯,甚至不同的组织间的通讯,由于计算能力需要横向扩展,需要在多台机器组成的集群上部署应用。RPC就是要像调用本地的函数一样去调远程函数;推荐阅读文章:https://www.jianshu.com/p/2accc2840a1bRPC基本原理步骤解析:RPC两个核心模块:通讯,序列化。17.3 测试环境搭建DubboApache Dubbo |ˈdʌbəʊ| 是一款高性能、轻量级的开源Java RPC框架,它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现。dubbo官网 http://dubbo.apache.org/zh-cn/index.html1.了解Dubbo的特性2.查看官方文档dubbo基本概念服务提供者(Provider):暴露服务的服务提供方,服务提供者在启动时,向注册中心注册自己提供的服务。服务消费者(Consumer):调用远程服务的服务消费方,服务消费者在启动时,向注册中心订阅自己所需的服务,服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。注册中心(Registry):注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者监控中心(Monitor):服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心调用关系说明l 服务容器负责启动,加载,运行服务提供者。l 服务提供者在启动时,向注册中心注册自己提供的服务。l 服务消费者在启动时,向注册中心订阅自己所需的服务。l 注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。l 服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。l 服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。Dubbo环境搭建点进dubbo官方文档,推荐我们使用Zookeeper 注册中心什么是zookeeper呢?可以查看官方文档Window下安装zookeeper1、下载zookeeper :地址, 我们下载3.4.14 , 最新版!解压zookeeper2、运行/bin/zkServer.cmd ,初次运行会报错,没有zoo.cfg配置文件;可能遇到问题:闪退 !解决方案:编辑zkServer.cmd文件末尾添加pause 。这样运行出错就不会退出,会提示错误信息,方便找到原因。3、修改zoo.cfg配置文件将conf文件夹下面的zoo_sample.cfg复制一份改名为zoo.cfg即可。注意几个重要位置:dataDir=./ 临时数据存储的目录(可写相对路径)clientPort=2181 zookeeper的端口号修改完成后再次启动zookeeper4、使用zkCli.cmd测试ls /:列出zookeeper根下保存的所有节点[zk: 127.0.0.1:2181(CONNECTED) 4] ls / [zookeeper]create –e /kuangshen 123:创建一个kuangshen节点,值为123get /kuangshen:获取/kuangshen节点的值我们再来查看一下节点window下安装dubbo-admindubbo本身并不是一个服务软件。它其实就是一个jar包,能够帮你的java程序连接到zookeeper,并利用zookeeper消费、提供服务。但是为了让用户更好的管理监控众多的dubbo服务,官方提供了一个可视化的监控程序dubbo-admin,不过这个监控即使不装也不影响使用。我们这里来安装一下:1、下载dubbo-admin地址 :https://github.com/apache/dubbo-admin/tree/master2、解压进入目录修改 dubbo-admin\src\main\resources \application.properties 指定zookeeper地址erver.port=7001 spring.velocity.cache=false spring.velocity.charset=UTF-8 spring.velocity.layout-url=/templates/default.vm spring.messages.fallback-to-system-locale=false spring.messages.basename=i18n/message spring.root.password=root spring.guest.password=guest dubbo.registry.address=zookeeper://127.0.0.1:21813、在项目目录下打包dubbo-adminmvn clean package -Dmaven.test.skip=true第一次打包的过程有点慢,需要耐心等待!直到成功!4、执行 dubbo-admin\target 下的dubbo-admin-0.0.1-SNAPSHOT.jarjava -jar dubbo-admin-0.0.1-SNAPSHOT.jar【注意:zookeeper的服务一定要打开!】执行完毕,我们去访问一下 http://localhost:7001/ , 这时候我们需要输入登录账户和密码,我们都是默认的root-root;登录成功后,查看界面安装完成!17.4 SpringBoot + Dubbo + zookeeper框架搭建1. 启动zookeeper !2. IDEA创建一个空项目;3.创建一个模块,实现服务提供者:provider-server , 选择web依赖即可4.项目创建完毕,我们写一个服务,比如卖票的服务;编写接口package com.kuang.provider.service; public interface TicketService { public String getTicket(); }编写实现类package com.kuang.provider.service; public class TicketServiceImpl implements TicketService { @Override public String getTicket() { return "《狂神说Java》"; } }5.创建一个模块,实现服务消费者:consumer-server , 选择web依赖即可6.项目创建完毕,我们写一个服务,比如用户的服务;编写servicepackage com.kuang.consumer.service; public class UserService { //我们需要去拿去注册中心的服务 }需求:现在我们的用户想使用买票的服务,这要怎么弄呢 ?服务提供者1、将服务提供者注册到注册中心,我们需要整合Dubbo和zookeeper,所以需要导包我们从dubbo官网进入github,看下方的帮助文档,找到dubbo-springboot,找到依赖包<!-- Dubbo Spring Boot Starter --> <dependency> <groupId>org.apache.dubbo</groupId> <artifactId>dubbo-spring-boot-starter</artifactId> <version>2.7.3</version> </dependency> zookeeper的包我们去maven仓库下载,zkclient;<!-- https://mvnrepository.com/artifact/com.github.sgroschupf/zkclient --> <dependency> <groupId>com.github.sgroschupf</groupId> <artifactId>zkclient</artifactId> <version>0.1</version> </dependency>【新版的坑】zookeeper及其依赖包,解决日志冲突,还需要剔除日志依赖;<!-- 引入zookeeper --> <dependency> <groupId>org.apache.curator</groupId> <artifactId>curator-framework</artifactId> <version>2.12.0</version> </dependency> <dependency> <groupId>org.apache.curator</groupId> <artifactId>curator-recipes</artifactId> <version>2.12.0</version> </dependency> <dependency> <groupId>org.apache.zookeeper</groupId> <artifactId>zookeeper</artifactId> <version>3.4.14</version> <!--排除这个slf4j-log4j12--> <exclusions> <exclusion> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> </exclusion> </exclusions> </dependency>2、在springboot配置文件中配置dubbo相关属性!#当前应用名字 dubbo.application.name=provider-server #注册中心地址 dubbo.registry.address=zookeeper://127.0.0.1:2181 #扫描指定包下服务 dubbo.scan.base-packages=com.kuang.provider.service3、在service的实现类中配置服务注解,发布服务!注意导包问题import org.apache.dubbo.config.annotation.Service; import org.springframework.stereotype.Component; @Service //将服务发布出去 @Component //放在容器中 public class TicketServiceImpl implements TicketService { @Override public String getTicket() { return "《狂神说Java》"; } }逻辑理解 :应用启动起来,dubbo就会扫描指定的包下带有@component注解的服务,将它发布在指定的注册中心中!服务消费者1、导入依赖,和之前的依赖一样;<!--dubbo--> <!-- Dubbo Spring Boot Starter --> <dependency> <groupId>org.apache.dubbo</groupId> <artifactId>dubbo-spring-boot-starter</artifactId> <version>2.7.3</version> </dependency> <!--zookeeper--> <!-- https://mvnrepository.com/artifact/com.github.sgroschupf/zkclient --> <dependency> <groupId>com.github.sgroschupf</groupId> <artifactId>zkclient</artifactId> <version>0.1</version> </dependency> <!-- 引入zookeeper --> <dependency> <groupId>org.apache.curator</groupId> <artifactId>curator-framework</artifactId> <version>2.12.0</version> </dependency> <dependency> <groupId>org.apache.curator</groupId> <artifactId>curator-recipes</artifactId> <version>2.12.0</version> </dependency> <dependency> <groupId>org.apache.zookeeper</groupId> <artifactId>zookeeper</artifactId> <version>3.4.14</version> <!--排除这个slf4j-log4j12--> <exclusions> <exclusion> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> </exclusion> </exclusions> </dependency>2、配置参数#当前应用名字 dubbo.application.name=consumer-server #注册中心地址 dubbo.registry.address=zookeeper://127.0.0.1:21813. 本来正常步骤是需要将服务提供者的接口打包,然后用pom文件导入,我们这里使用简单的方式,直接将服务的接口拿过来,路径必须保证正确,即和服务提供者相同;4. 完善消费者的服务类package com.kuang.consumer.service; import com.kuang.provider.service.TicketService; import org.apache.dubbo.config.annotation.Reference; import org.springframework.stereotype.Service; @Service //注入到容器中 public class UserService { @Reference //远程引用指定的服务,他会按照全类名进行匹配,看谁给注册中心注册了这个全类名 TicketService ticketService; public void bugTicket(){ String ticket = ticketService.getTicket(); System.out.println("在注册中心买到"+ticket); } }5. 测试类编写;@RunWith(SpringRunner.class) @SpringBootTest public class ConsumerServerApplicationTests { @Autowired UserService userService; @Test public void contextLoads() { userService.bugTicket(); } }启动测试1. 开启zookeeper2. 打开dubbo-admin实现监控【可以不用做】3. 开启服务者4. 消费者消费测试,结果:监控中心 :ok , 这就是SpingBoot + dubbo + zookeeper实现分布式开发的应用,其实就是一个服务拆分的思想;18.集成SpringSecurity18.1 安全简介在 Web 开发中,安全一直是非常重要的一个方面。安全虽然属于应用的非功能性需求,但是应该在应用开发的初期就考虑进来。如果在应用开发的后期才考虑安全的问题,就可能陷入一个两难的境地:一方面,应用存在严重的安全漏洞,无法满足用户的要求,并可能造成用户的隐私数据被攻击者窃取;另一方面,应用的基本架构已经确定,要修复安全漏洞,可能需要对系统的架构做出比较重大的调整,因而需要更多的开发时间,影响应用的发布进程。因此,从应用开发的第一天就应该把安全相关的因素考虑进来,并在整个应用的开发过程中。市面上存在比较有名的:Shiro,Spring Security !这里需要阐述一下的是,每一个框架的出现都是为了解决某一问题而产生了,那么Spring Security框架的出现是为了解决什么问题呢?首先我们看下它的官网介绍:Spring Security官网地址Spring Security is a powerful and highly customizable authentication and access-control framework. It is the de-facto standard for securing Spring-based applications.Spring Security is a framework that focuses on providing both authentication and authorization to Java applications. Like all Spring projects, the real power of Spring Security is found in how easily it can be extended to meet custom requirementsSpring Security是一个功能强大且高度可定制的身份验证和访问控制框架。它实际上是保护基于spring的应用程序的标准。Spring Security是一个框架,侧重于为Java应用程序提供身份验证和授权。与所有Spring项目一样,Spring安全性的真正强大之处在于它可以轻松地扩展以满足定制需求从官网的介绍中可以知道这是一个权限框架。想我们之前做项目是没有使用框架是怎么控制权限的?对于权限 一般会细分为功能权限,访问权限,和菜单权限。代码会写的非常的繁琐,冗余。怎么解决之前写权限代码繁琐,冗余的问题,一些主流框架就应运而生而Spring Scecurity就是其中的一种。Spring 是一个非常流行和成功的 Java 应用开发框架。Spring Security 基于 Spring 框架,提供了一套 Web 应用安全性的完整解决方案。一般来说,Web 应用的安全性包括用户认证(Authentication)和用户授权(Authorization)两个部分。用户认证指的是验证某个用户是否为系统中的合法主体,也就是说用户能否访问该系统。用户认证一般要求用户提供用户名和密码。系统通过校验用户名和密码来完成认证过程。用户授权指的是验证某个用户是否有权限执行某个操作。在一个系统中,不同用户所具有的权限是不同的。比如对一个文件来说,有的用户只能进行读取,而有的用户可以进行修改。一般来说,系统会为不同的用户分配不同的角色,而每个角色则对应一系列的权限。对于上面提到的两种应用情景,Spring Security 框架都有很好的支持。在用户认证方面,Spring Security 框架支持主流的认证方式,包括 HTTP 基本认证、HTTP 表单验证、HTTP 摘要认证、OpenID 和 LDAP 等。在用户授权方面,Spring Security 提供了基于角色的访问控制和访问控制列表(Access Control List,ACL),可以对应用中的领域对象进行细粒度的控制。18.2 实战测试实验环境搭建1、新建一个初始的springboot项目web模块,thymeleaf模块2、导入静态资源welcome.html |views |level1 1.html 2.html 3.html |level2 1.html 2.html 3.html |level3 1.html 2.html 3.html Login.html3、controller跳转!package com.kuang.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; @Controller public class RouterController { @RequestMapping({"/","/index"}) public String index(){ return "index"; } @RequestMapping("/toLogin") public String toLogin(){ return "views/login"; } @RequestMapping("/level1/{id}") public String level1(@PathVariable("id") int id){ return "views/level1/"+id; } @RequestMapping("/level2/{id}") public String level2(@PathVariable("id") int id){ return "views/level2/"+id; } @RequestMapping("/level3/{id}") public String level3(@PathVariable("id") int id){ return "views/level3/"+id; } }4、测试实验环境是否OK!认识SpringSecuritySpring Security 是针对Spring项目的安全框架,也是Spring Boot底层安全模块默认的技术选型,他可以实现强大的Web安全控制,对于安全控制,我们仅需要引入 spring-boot-starter-security 模块,进行少量的配置,即可实现强大的安全管理!记住几个类:WebSecurityConfigurerAdapter:自定义Security策略AuthenticationManagerBuilder:自定义认证策略@EnableWebSecurity:开启WebSecurity模式Spring Security的两个主要目标是 “认证” 和 “授权”(访问控制)。“认证”(Authentication)身份验证是关于验证您的凭据,如用户名/用户ID和密码,以验证您的身份。身份验证通常通过用户名和密码完成,有时与身份验证因素结合使用。“授权” (Authorization)授权发生在系统成功验证您的身份后,最终会授予您访问资源(如信息,文件,数据库,资金,位置,几乎任何内容)的完全权限。这个概念是通用的,而不是只在Spring Security 中存在。认证和授权目前,我们的测试环境,是谁都可以访问的,我们使用 Spring Security 增加上认证和授权的功能1、引入 Spring Security 模块<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>2、编写 Spring Security 配置类参考官网:https://spring.io/projects/spring-security 查看我们自己项目中的版本,找到对应的帮助文档:https://docs.spring.io/spring-security/site/docs/5.3.0.RELEASE/reference/html5 #servlet-applications 8.16.43、编写基础配置类package com.kuang.config; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; @EnableWebSecurity // 开启WebSecurity模式 public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { } }4、定制请求的授权规则@Override protected void configure(HttpSecurity http) throws Exception { // 定制请求的授权规则 // 首页所有人可以访问 http.authorizeRequests().antMatchers("/").permitAll() .antMatchers("/level1/**").hasRole("vip1") .antMatchers("/level2/**").hasRole("vip2") .antMatchers("/level3/**").hasRole("vip3"); }5、测试一下:发现除了首页都进不去了!因为我们目前没有登录的角色,因为请求需要登录的角色拥有对应的权限才可以!6、在configure()方法中加入以下配置,开启自动配置的登录功能!// 开启自动配置的登录功能 // /login 请求来到登录页 // /login?error 重定向到这里表示登录失败 http.formLogin();7、测试一下:发现,没有权限的时候,会跳转到登录的页面!8、查看刚才登录页的注释信息;我们可以定义认证规则,重写configure(AuthenticationManagerBuilder auth)方法//定义认证规则 @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { //在内存中定义,也可以在jdbc中去拿.... auth.inMemoryAuthentication() .withUser("kuangshen").password("123456").roles("vip2","vip3") .and() .withUser("root").password("123456").roles("vip1","vip2","vip3") .and() .withUser("guest").password("123456").roles("vip1","vip2");9、测试,我们可以使用这些账号登录进行测试!发现会报错!There is no PasswordEncoder mapped for the id “null”10、原因,我们要将前端传过来的密码进行某种方式加密,否则就无法登录,修改代码//定义认证规则 @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { //在内存中定义,也可以在jdbc中去拿.... //Spring security 5.0中新增了多种加密方式,也改变了密码的格式。 //要想我们的项目还能够正常登陆,需要修改一下configure中的代码。我们要将前端传过来的密码进行某种方式加密 //spring security 官方推荐的是使用bcrypt加密方式。 auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder()) .withUser("kuangshen").password(new BCryptPasswordEncoder().encode("123456")).roles("vip2","vip3") .and() .withUser("root").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2","vip3") .and() .withUser("guest").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2"); }11、测试,发现,登录成功,并且每个角色只能访问自己认证下的规则!搞定权限控制和注销1、开启自动配置的注销的功能//定制请求的授权规则 @Override protected void configure(HttpSecurity http) throws Exception { //.... //开启自动配置的注销的功能 // /logout 注销请求 http.logout(); }2、我们在前端,增加一个注销的按钮,index.html 导航栏中<a class="item" th:href="@{/logout}"> <i class="address card icon"></i> 注销 </a>3、我们可以去测试一下,登录成功后点击注销,发现注销完毕会跳转到登录页面!4、但是,我们想让他注销成功后,依旧可以跳转到首页,该怎么处理呢?// .logoutSuccessUrl("/"); 注销成功来到首页 http.logout().logoutSuccessUrl("/");5、测试,注销完毕后,发现跳转到首页OK6、我们现在又来一个需求:用户没有登录的时候,导航栏上只显示登录按钮,用户登录之后,导航栏可以显示登录的用户信息及注销按钮!还有就是,比如kuangshen这个用户,它只有 vip2,vip3功能,那么登录则只显示这两个功能,而vip1的功能菜单不显示!这个就是真实的网站情况了!该如何做呢?我们需要结合thymeleaf中的一些功能sec:authorize="isAuthenticated()":是否认证登录!来显示不同的页面Maven依赖:<!-- https://mvnrepository.com/artifact/org.thymeleaf.extras/thymeleaf-extras-springsecurity4 --> <dependency> <groupId>org.thymeleaf.extras</groupId> <artifactId>thymeleaf-extras-springsecurity5</artifactId> <version>3.0.4.RELEASE</version> </dependency>7、修改我们的 前端页面导入命名空间xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5"修改导航栏,增加认证判断<!--登录注销--> <div class="right menu"> <!--如果未登录--> <div sec:authorize="!isAuthenticated()"> <a class="item" th:href="@{/login}"> <i class="address card icon"></i> 登录 </a> </div> <!--如果已登录--> <div sec:authorize="isAuthenticated()"> <a class="item"> <i class="address card icon"></i> 用户名:<span sec:authentication="principal.username"></span> 角色:<span sec:authentication="principal.authorities"></span> </a> </div> <div sec:authorize="isAuthenticated()"> <a class="item" th:href="@{/logout}"> <i class="address card icon"></i> 注销 </a> </div> </div>8、重启测试,我们可以登录试试看,登录成功后确实,显示了我们想要的页面;9、如果注销404了,就是因为它默认防止csrf跨站请求伪造,因为会产生安全问题,我们可以将请求改为post表单提交,或者在spring security中关闭csrf功能;我们试试:在 配置中增加 http.csrf().disable();http.csrf().disable();//关闭csrf功能:跨站请求伪造,默认只能通过post方式提交logout请求 http.logout().logoutSuccessUrl("/");10、我们继续将下面的角色功能块认证完成!<!-- sec:authorize="hasRole('vip1')" --> <div class="column" sec:authorize="hasRole('vip1')"> <div class="ui raised segment"> <div class="ui"> <div class="content"> <h5 class="content">Level 1</h5> <hr> <div><a th:href="@{/level1/1}"><i class="bullhorn icon"></i> Level-1-1</a></div> <div><a th:href="@{/level1/2}"><i class="bullhorn icon"></i> Level-1-2</a></div> <div><a th:href="@{/level1/3}"><i class="bullhorn icon"></i> Level-1-3</a></div> </div> </div> </div> </div> <div class="column" sec:authorize="hasRole('vip2')"> <div class="ui raised segment"> <div class="ui"> <div class="content"> <h5 class="content">Level 2</h5> <hr> <div><a th:href="@{/level2/1}"><i class="bullhorn icon"></i> Level-2-1</a></div> <div><a th:href="@{/level2/2}"><i class="bullhorn icon"></i> Level-2-2</a></div> <div><a th:href="@{/level2/3}"><i class="bullhorn icon"></i> Level-2-3</a></div> </div> </div> </div> </div> <div class="column" sec:authorize="hasRole('vip3')"> <div class="ui raised segment"> <div class="ui"> <div class="content"> <h5 class="content">Level 3</h5> <hr> <div><a th:href="@{/level3/1}"><i class="bullhorn icon"></i> Level-3-1</a></div> <div><a th:href="@{/level3/2}"><i class="bullhorn icon"></i> Level-3-2</a></div> <div><a th:href="@{/level3/3}"><i class="bullhorn icon"></i> Level-3-3</a></div> </div> </div> </div> </div>11、测试一下!12、权限控制和注销搞定!记住我现在的情况,我们只要登录之后,关闭浏览器,再登录,就会让我们重新登录,但是很多网站的情况,就是有一个记住密码的功能,这个该如何实现呢?很简单1、开启记住我功能//定制请求的授权规则 @Override protected void configure(HttpSecurity http) throws Exception { //。。。。。。。。。。。 //记住我 http.rememberMe(); }2、我们再次启动项目测试一下,发现登录页多了一个记住我功能,我们登录之后关闭 浏览器,然后重新打开浏览器访问,发现用户依旧存在!思考:如何实现的呢?其实非常简单我们可以查看浏览器的cookie3、我们点击注销的时候,可以发现,spring security 帮我们自动删除了这个 cookie4、结论:登录成功后,将cookie发送给浏览器保存,以后登录带上这个cookie,只要通过检查就可以免登录了。如果点击注销,则会删除这个cookie,具体的原理我们在JavaWeb阶段都讲过了,这里就不在多说了!定制登录页现在这个登录页面都是spring security 默认的,怎么样可以使用我们自己写的Login界面呢?1、在刚才的登录页配置后面指定 loginpagehttp.formLogin().loginPage("/toLogin");2、然后前端也需要指向我们自己定义的 login请求<a class="item" th:href="@{/toLogin}"> <i class="address card icon"></i> 登录 </a>3、我们登录,需要将这些信息发送到哪里,我们也需要配置,login.html 配置提交请求及方式,方式必须为post:在 loginPage()源码中的注释上有写明:<form th:action="@{/login}" method="post"> <div class="field"> <label>Username</label> <div class="ui left icon input"> <input type="text" placeholder="Username" name="username"> <i class="user icon"></i> </div> </div> <div class="field"> <label>Password</label> <div class="ui left icon input"> <input type="password" name="password"> <i class="lock icon"></i> </div> </div> <input type="submit" class="ui blue submit button"/> </form>4、这个请求提交上来,我们还需要验证处理,怎么做呢?我们可以查看formLogin()方法的源码!我们配置接收登录的用户名和密码的参数!http.formLogin() .usernameParameter("username") .passwordParameter("password") .loginPage("/toLogin") .loginProcessingUrl("/login"); // 登陆表单提交请求5、在登录页增加记住我的多选框<input type="checkbox" name="remember"> 记住我6、后端验证处理!//定制记住我的参数! http.rememberMe().rememberMeParameter("remember");7、测试,OK完整配置代码package com.kuang.config; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { //定制请求的授权规则 @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests().antMatchers("/").permitAll() .antMatchers("/level1/**").hasRole("vip1") .antMatchers("/level2/**").hasRole("vip2") .antMatchers("/level3/**").hasRole("vip3"); //开启自动配置的登录功能:如果没有权限,就会跳转到登录页面! // /login 请求来到登录页 // /login?error 重定向到这里表示登录失败 http.formLogin() .usernameParameter("username") .passwordParameter("password") .loginPage("/toLogin") .loginProcessingUrl("/login"); // 登陆表单提交请求 //开启自动配置的注销的功能 // /logout 注销请求 // .logoutSuccessUrl("/"); 注销成功来到首页 http.csrf().disable();//关闭csrf功能:跨站请求伪造,默认只能通过post方式提交logout请求 http.logout().logoutSuccessUrl("/"); //记住我 http.rememberMe().rememberMeParameter("remember"); } //定义认证规则 @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { //在内存中定义,也可以在jdbc中去拿.... //Spring security 5.0中新增了多种加密方式,也改变了密码的格式。 //要想我们的项目还能够正常登陆,需要修改一下configure中的代码。我们要将前端传过来的密码进行某种方式加密 //spring security 官方推荐的是使用bcrypt加密方式。 auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder()) .withUser("kuangshen").password(new BCryptPasswordEncoder().encode("123456")).roles("vip2","vip3") .and() .withUser("root").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2","vip3") .and() .withUser("guest").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2"); } }参考资料http://mp.weixin.qq.com/mp/homepage?__biz=Mzg2NTAzMTExNg==&hid=1&sn=3247dca1433a891523d9e4176c90c499&scene=18#wechat_redirecthttps://www.bilibili.com/video/BV19E411v7Ty
2022年06月05日
937 阅读
0 评论
0 点赞
2022-05-05
SpringMVC学习笔记
1、回顾MVC1.1、什么是MVCMVC是模型(Model)、视图(View)、控制器(Controller)的简写,是一种软件设计规范。是将业务逻辑、数据、显示分离的方法来组织代码。MVC主要作用是降低了视图与业务逻辑间的双向偶合。MVC不是一种设计模式,MVC是一种架构模式。当然不同的MVC存在差异。Model(模型):数据模型,提供要展示的数据,因此包含数据和行为,可以认为是领域模型或JavaBean组件(包含数据和行为),不过现在一般都分离开来:Value Object(数据Dao) 和 服务层(行为Service)。也就是模型提供了模型数据查询和模型数据的状态更新等功能,包括数据和业务。View(视图):负责进行模型的展示,一般就是我们见到的用户界面,客户想看到的东西。Controller(控制器):接收用户请求,委托给模型进行处理(状态改变),处理完毕后把返回的模型数据返回给视图,由视图负责展示。也就是说控制器做了个调度员的工作。最典型的MVC就是JSP + servlet + javabean的模式。1.2、Model1时代在web早期的开发中,通常采用的都是Model1。Model1中,主要分为两层,视图层和模型层。Model1优点:架构简单,比较适合小型项目开发;Model1缺点:JSP职责不单一,职责过重,不便于维护;1.3、Model2时代Model2把一个项目分成三部分,包括视图、控制、模型。用户发请求Servlet接收请求数据,并调用对应的业务逻辑方法业务处理完毕,返回更新后的数据给servletservlet转向到JSP,由JSP来渲染页面响应给前端更新后的页面职责分析:Controller:控制器取得表单数据调用业务逻辑转向指定的页面Model:模型业务逻辑保存数据的状态View:视图显示页面Model2这样不仅提高的代码的复用率与项目的扩展性,且大大降低了项目的维护成本。Model 1模式的实现比较简单,适用于快速开发小规模项目,Model1中JSP页面身兼View和Controller两种角色,将控制逻辑和表现逻辑混杂在一起,从而导致代码的重用性非常低,增加了应用的扩展性和维护的难度。Model2消除了Model1的缺点。1.4、回顾Servlet新建一个Maven工程当做父工程!pom依赖!<dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.1.9.RELEASE</version> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> <version>2.5</version> </dependency> <dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>jsp-api</artifactId> <version>2.2</version> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> </dependencies>建立一个Moudle:springmvc-01-servlet , 添加Web app的支持!导入servlet 和 jsp 的 jar 依赖<dependency> <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> <version>2.5</version> </dependency> <dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>jsp-api</artifactId> <version>2.2</version> </dependency>编写一个Servlet类,用来处理用户的请求package com.kuang.servlet; //实现Servlet接口 public class HelloServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //取得参数 String method = req.getParameter("method"); if (method.equals("add")){ req.getSession().setAttribute("msg","执行了add方法"); } if (method.equals("delete")){ req.getSession().setAttribute("msg","执行了delete方法"); } //业务逻辑 //视图跳转 req.getRequestDispatcher("/WEB-INF/jsp/hello.jsp").forward(req,resp); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doGet(req,resp); } }编写Hello.jsp,在WEB-INF目录下新建一个jsp的文件夹,新建hello.jsp<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Kuangshen</title> </head> <body> ${msg} </body> </html>在web.xml中注册Servlet<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0"> <servlet> <servlet-name>HelloServlet</servlet-name> <servlet-class>com.kuang.servlet.HelloServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>HelloServlet</servlet-name> <url-pattern>/user</url-pattern> </servlet-mapping> </web-app>配置Tomcat,并启动测试localhost:8080/user?method=addlocalhost:8080/user?method=deleteMVC框架要做哪些事情将url映射到java类或java类的方法 .封装用户提交的数据 .处理请求--调用相关的业务处理--封装响应数据 .将响应的数据进行渲染 . jsp / html 等表示层数据 .说明: 常见的服务器端MVC框架有:Struts、Spring MVC、ASP.NET MVC、Zend Framework、JSF;常见前端MVC框架:vue、angularjs、react、backbone;由MVC演化出了另外一些模式如:MVP、MVVM 等等....2、什么是SpringMVC2.1、概述Spring MVC是Spring Framework的一部分,是基于Java实现MVC的轻量级Web框架。查看官方文档:https://docs.spring.io/spring/docs/5.2.0.RELEASE/spring-framework-reference/web.html#spring-web我们为什么要学习SpringMVC呢?Spring MVC的特点:轻量级,简单易学高效 , 基于请求响应的MVC框架与Spring兼容性好,无缝结合约定优于配置功能强大:RESTful、数据验证、格式化、本地化、主题等简洁灵活Spring的web框架围绕DispatcherServlet [ 调度Servlet ] 设计。DispatcherServlet的作用是将请求分发到不同的处理器。从Spring 2.5开始,使用Java 5或者以上版本的用户可以采用基于注解形式进行开发,十分简洁;正因为SpringMVC好 , 简单 , 便捷 , 易学 , 天生和Spring无缝集成(使用SpringIoC和Aop) , 使用约定优于配置 . 能够进行简单的junit测试 . 支持Restful风格 .异常处理 , 本地化 , 国际化 , 数据验证 , 类型转换 , 拦截器 等等......所以我们要学习 .最重要的一点还是用的人多 , 使用的公司多 .2.2、中心控制器 Spring的web框架围绕DispatcherServlet设计。DispatcherServlet的作用是将请求分发到不同的处理器。从Spring 2.5开始,使用Java 5或者以上版本的用户可以采用基于注解的controller声明方式。 Spring MVC框架像许多其他MVC框架一样, 以请求为驱动 , 围绕一个中心Servlet分派请求及提供其他功能,DispatcherServlet是一个实际的Servlet (它继承自HttpServlet 基类)。SpringMVC的原理如下图所示: 当发起请求时被前置的控制器拦截到请求,根据请求参数生成代理请求,找到请求对应的实际控制器,控制器处理请求,创建数据模型,访问数据库,将模型响应给中心控制器,控制器使用模型与视图渲染视图结果,将结果返回给中心控制器,再将结果返回给请求者。2.3、SpringMVC执行原理图为SpringMVC的一个较完整的流程图,实线表示SpringMVC框架提供的技术,不需要开发者实现,虚线表示需要开发者实现。简要分析执行流程DispatcherServlet表示前置控制器,是整个SpringMVC的控制中心。用户发出请求,DispatcherServlet接收请求并拦截请求。我们假设请求的url为 : http://localhost:8080/SpringMVC/hello如上url拆分成三部分:http://localhost:8080服务器域名SpringMVC部署在服务器上的web站点hello表示控制器通过分析,如上url表示为:请求位于服务器localhost:8080上的SpringMVC站点的hello控制器。HandlerMapping为处理器映射。DispatcherServlet调用HandlerMapping,HandlerMapping根据请求url查找Handler。HandlerExecution表示具体的Handler,其主要作用是根据url查找控制器,如上url被查找控制器为:hello。HandlerExecution将解析后的信息传递给DispatcherServlet,如解析控制器映射等。HandlerAdapter表示处理器适配器,其按照特定的规则去执行Handler。Handler让具体的Controller执行。Controller将具体的执行信息返回给HandlerAdapter,如ModelAndView。HandlerAdapter将视图逻辑名或模型传递给DispatcherServlet。DispatcherServlet调用视图解析器(ViewResolver)来解析HandlerAdapter传递的逻辑视图名。视图解析器将解析的逻辑视图名传给DispatcherServlet。DispatcherServlet根据视图解析器解析的视图结果,调用具体的视图。最终视图呈现给用户。3.Hello,SpringMVC3.1 配置版1、新建一个Moudle , springmvc-02-hello , 添加web的支持!2、确定导入了SpringMVC 的依赖!3、配置web.xml , 注册DispatcherServlet<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0"> <!--1.注册DispatcherServlet--> <servlet> <servlet-name>springmvc</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <!--关联一个springmvc的配置文件:【servlet-name】-servlet.xml--> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:springmvc-servlet.xml</param-value> </init-param> <!--启动级别-1--> <load-on-startup>1</load-on-startup> </servlet> <!--/ 匹配所有的请求;(不包括.jsp)--> <!--/* 匹配所有的请求;(包括.jsp)--> <servlet-mapping> <servlet-name>springmvc</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> </web-app>4、编写SpringMVC 的 配置文件!名称:springmvc-servlet.xml : [servletname]-servlet.xml说明,这里的名称要求是按照官方来的<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> </beans>5、添加 处理映射器<bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/>6、添加 处理器适配器<bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"/>7、添加 视图解析器<!--视图解析器:DispatcherServlet给他的ModelAndView--> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="InternalResourceViewResolver"> <!--前缀--> <property name="prefix" value="/WEB-INF/jsp/"/> <!--后缀--> <property name="suffix" value=".jsp"/> </bean>8、编写我们要操作业务Controller ,要么实现Controller接口,要么增加注解;需要返回一个ModelAndView,装数据,封视图;package com.kuang.controller; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.mvc.Controller; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; //注意:这里我们先导入Controller接口 public class HelloController implements Controller { public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception { //ModelAndView 模型和视图 ModelAndView mv = new ModelAndView(); //封装对象,放在ModelAndView中。Model mv.addObject("msg","HelloSpringMVC!"); //封装要跳转的视图,放在ModelAndView中 mv.setViewName("hello"); //: /WEB-INF/jsp/hello.jsp return mv; } }9、将自己的类交给SpringIOC容器,注册bean<!--Handler--> <bean id="/hello" class="com.kuang.controller.HelloController"/>10、写要跳转的jsp页面,显示ModelandView存放的数据,以及我们的正常页面;<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Kuangshen</title> </head> <body> ${msg} </body> </html>11、配置Tomcat 启动测试!可能遇到的问题:访问出现404,排查步骤:查看控制台输出,看一下是不是缺少了什么jar包。如果jar包存在,显示无法输出,就在IDEA的项目发布中,添加lib依赖!重启Tomcat 即可解决!3.2 注解版(推荐)1、新建一个Moudle,springmvc-03-hello-annotation 。添加web支持!2、由于Maven可能存在资源过滤的问题,我们将配置完善<build> <resources> <resource> <directory>src/main/java</directory> <includes> <include>**/*.properties</include> <include>**/*.xml</include> </includes> <filtering>false</filtering> </resource> <resource> <directory>src/main/resources</directory> <includes> <include>**/*.properties</include> <include>**/*.xml</include> </includes> <filtering>false</filtering> </resource> </resources> </build>3、在pom.xml文件引入相关的依赖:主要有Spring框架核心库、Spring MVC、servlet , JSTL等。我们在父依赖中已经引入了!4、配置web.xml注意点: <?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0"> <!--1.注册servlet--> <servlet> <servlet-name>SpringMVC</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <!--通过初始化参数指定SpringMVC配置文件的位置,进行关联--> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:springmvc-servlet.xml</param-value> </init-param> <!-- 启动顺序,数字越小,启动越早 --> <load-on-startup>1</load-on-startup> </servlet> <!--所有请求都会被springmvc拦截 --> <servlet-mapping> <servlet-name>SpringMVC</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> </web-app>/ 和 /* 的区别:< url-pattern > / </ url-pattern > 不会匹配到.jsp, 只针对我们编写的请求;即:.jsp 不会进入spring的 DispatcherServlet类 。< url-pattern > /* </ url-pattern > 会匹配 *.jsp,会出现返回 jsp视图 时再次进入spring的DispatcherServlet 类,导致找不到对应的controller所以报404错。注意web.xml版本问题,要最新版!注册DispatcherServlet关联SpringMVC的配置文件启动级别为1映射路径为 / 【不要用/*,会404】5、添加Spring MVC配置文件在resource目录下添加springmvc-servlet.xml配置文件,配置的形式与Spring容器配置基本类似,为了支持基于注解的IOC,设置了自动扫描包的功能,具体配置信息如下:<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd"> <!-- 自动扫描包,让指定包下的注解生效,由IOC容器统一管理 --> <context:component-scan base-package="com.kuang.controller"/> <!-- 让Spring MVC不处理静态资源 --> <mvc:default-servlet-handler /> <!-- 支持mvc注解驱动 在spring中一般采用@RequestMapping注解来完成映射关系 要想使@RequestMapping注解生效 必须向上下文中注册DefaultAnnotationHandlerMapping 和一个AnnotationMethodHandlerAdapter实例 这两个实例分别在类级别和方法级别处理。 而annotation-driven配置帮助我们自动完成上述两个实例的注入。 --> <mvc:annotation-driven /> <!-- 视图解析器 --> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="internalResourceViewResolver"> <!-- 前缀 --> <property name="prefix" value="/WEB-INF/jsp/" /> <!-- 后缀 --> <property name="suffix" value=".jsp" /> </bean> </beans>在视图解析器中我们把所有的视图都存放在/WEB-INF/目录下,这样可以保证视图安全,因为这个目录下的文件,客户端不能直接访问。让IOC的注解生效静态资源过滤 :HTML . JS . CSS . 图片 , 视频 .....MVC的注解驱动配置视图解析器6、创建Controller编写一个Java控制类:com.kuang.controller.HelloController , 注意编码规范package com.kuang.controller; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; @Controller @RequestMapping("/HelloController") public class HelloController { //真实访问地址 : 项目名/HelloController/hello @RequestMapping("/hello") public String sayHello(Model model){ //向模型中添加属性msg与值,可以在JSP页面中取出并渲染 model.addAttribute("msg","hello,SpringMVC"); //web-inf/jsp/hello.jsp return "hello"; } }@Controller是为了让Spring IOC容器初始化时自动扫描到;@RequestMapping是为了映射请求路径,这里因为类与方法上都有映射所以访问时应该是/HelloController/hello;方法中声明Model类型的参数是为了把Action中的数据带到视图中;方法返回的结果是视图的名称hello,加上配置文件中的前后缀变成WEB-INF/jsp/hello.jsp。7、创建视图层在WEB-INF/ jsp目录中创建hello.jsp , 视图可以直接取出并展示从Controller带回的信息;可以通过EL表示取出Model中存放的值,或者对象;<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>SpringMVC</title> </head> <body> ${msg} </body> </html>8、配置Tomcat运行配置Tomcat , 开启服务器 , 访问 对应的请求路径!OK,运行成功!3.3 小结实现步骤其实非常的简单:新建一个web项目导入相关jar包编写web.xml , 注册DispatcherServlet编写springmvc配置文件接下来就是去创建对应的控制类 , controller最后完善前端视图和controller之间的对应测试运行调试.使用springMVC必须配置的三大件:处理器映射器、处理器适配器、视图解析器通常,我们只需要手动配置视图解析器,而处理器映射器和处理器适配器只需要开启注解驱动即可,而省去了大段的xml配置再来回顾下原理吧~4. 控制器Controller4.1 控制器Controller控制器复杂提供访问应用程序的行为,通常通过接口定义或注解定义两种方法实现。控制器负责解析用户的请求并将其转换为一个模型。在Spring MVC中一个控制器类可以包含多个方法在Spring MVC中,对于Controller的配置方式有很多种4.2 实现Controller接口Controller是一个接口,在org.springframework.web.servlet.mvc包下,接口中只有一个方法;//实现该接口的类获得控制器功能 public interface Controller { //处理请求且返回一个模型与视图对象 ModelAndView handleRequest(HttpServletRequest var1, HttpServletResponse var2) throws Exception; }测试新建一个Moudle,springmvc-04-controller 。将刚才的03 拷贝一份, 我们进行操作!删掉HelloControllermvc的配置文件只留下 视图解析器!编写一个Controller类,ControllerTest1//定义控制器 //注意点:不要导错包,实现Controller接口,重写方法; public class ControllerTest1 implements Controller { public ModelAndView handleRequest(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception { //返回一个模型视图对象 ModelAndView mv = new ModelAndView(); mv.addObject("msg","Test1Controller"); mv.setViewName("test"); return mv; } }编写完毕后,去Spring配置文件中注册请求的bean;name对应请求路径,class对应处理请求的类<bean name="/t1" class="com.kuang.controller.ControllerTest1"/>编写前端test.jsp,注意在WEB-INF/jsp目录下编写,对应我们的视图解析器<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Kuangshen</title> </head> <body> ${msg} </body> </html>配置Tomcat运行测试,我这里没有项目发布名配置的就是一个 / ,所以请求不用加项目名,OK!说明:实现接口Controller定义控制器是较老的办法缺点是:一个控制器中只有一个方法,如果要多个方法则需要定义多个Controller;定义的方式比较麻烦;4.3 使用注解@Controller(推荐)@Controller注解类型用于声明Spring类的实例是一个控制器(在讲IOC时还提到了另外3个注解);Spring可以使用扫描机制来找到应用程序中所有基于注解的控制器类,为了保证Spring能找到你的控制器,需要在配置文件中声明组件扫描。<!-- 自动扫描指定的包,下面所有注解类交给IOC容器管理 --> <context:component-scan base-package="com.kuang.controller"/>增加一个ControllerTest2类,使用注解实现;//@Controller注解的类会自动添加到Spring上下文中 @Controller public class ControllerTest2{ //映射访问路径 @RequestMapping("/t2") public String index(Model model){ //Spring MVC会自动实例化一个Model对象用于向视图中传值 model.addAttribute("msg", "ControllerTest2"); //返回视图位置 return "test"; } }运行tomcat测试可以发现,我们的两个请求都可以指向一个视图,但是页面结果的结果是不一样的,从这里可以看出视图是被复用的,而控制器与视图之间是弱偶合关系。注解方式是平时使用的最多的方式!4.4 RequestMapping@RequestMapping@RequestMapping注解用于映射url到控制器类或一个特定的处理程序方法。可用于类或方法上。用于类上,表示类中的所有响应请求的方法都是以该地址作为父路径。为了测试结论更加准确,我们可以加上一个项目名测试 myweb只注解在方法上面@Controller public class TestController { @RequestMapping("/h1") public String test(){ return "test"; } }访问路径:http://localhost:8080 / 项目名 / h1同时注解类与方法@Controller @RequestMapping("/admin") public class TestController { @RequestMapping("/h1") public String test(){ return "test"; } }访问路径:http://localhost:8080 / 项目名/ admin /h1 , 需要先指定类的路径再指定方法的路径;4.5 RestFul 风格概念Restful就是一个资源定位及资源操作的风格。不是标准也不是协议,只是一种风格。基于这个风格设计的软件可以更简洁,更有层次,更易于实现缓存等机制。功能资源:互联网所有的事物都可以被抽象为资源资源操作:使用POST、DELETE、PUT、GET,使用不同方法对资源进行操作。分别对应 添加、 删除、修改、查询。传统方式操作资源 :通过不同的参数来实现不同的效果!方法单一,post 和 get http://127.0.0.1/item/queryItem.action?id=1 查询,GET http://127.0.0.1/item/saveItem.action 新增,POST http://127.0.0.1/item/updateItem.action 更新,POST http://127.0.0.1/item/deleteItem.action?id=1 删除,GET或POST使用RESTful操作资源 :可以通过不同的请求方式来实现不同的效果!如下:请求地址一样,但是功能可以不同! http://127.0.0.1/item/1 查询,GET http://127.0.0.1/item 新增,POST http://127.0.0.1/item 更新,PUT http://127.0.0.1/item/1 删除,DELETE学习测试在新建一个类 RestFulController@Controller public class RestFulController { }在Spring MVC中可以使用 @PathVariable 注解,让方法参数的值对应绑定到一个URI模板变量上。@Controller public class RestFulController { //映射访问路径 @RequestMapping("/commit/{p1}/{p2}") public String index(@PathVariable int p1, @PathVariable int p2, Model model){ int result = p1+p2; //Spring MVC会自动实例化一个Model对象用于向视图中传值 model.addAttribute("msg", "结果:"+result); //返回视图位置 return "test"; } }我们来测试请求查看下思考:使用路径变量的好处?使路径变得更加简洁;获得参数更加方便,框架会自动进行类型转换。通过路径变量的类型可以约束访问参数,如果类型不一样,则访问不到对应的请求方法,如这里访问是的路径是/commit/1/a,则路径与方法不匹配,而不会是参数转换失败。 ![图片](https://mmbiz.qpic.cn/mmbiz_png/uJDAUKrGC7JOmNdhqNbrRK9XaseXIDsumkKQDDBx9D7E67IK9dmVtHnjn0WjPSOZxKnwz9AXrTPRLy6Xu42yrg/640?wx_fmt=png&wxfrom=5&wx_lazy=1&wx_co=1) 我们来修改下对应的参数类型,再次测试//映射访问路径 @RequestMapping("/commit/{p1}/{p2}") public String index(@PathVariable int p1, @PathVariable String p2, Model model){ String result = p1+p2; //Spring MVC会自动实例化一个Model对象用于向视图中传值 model.addAttribute("msg", "结果:"+result); //返回视图位置 return "test"; }使用method属性指定请求类型用于约束请求的类型,可以收窄请求范围。指定请求谓词的类型如GET, POST, HEAD, OPTIONS, PUT, PATCH, DELETE, TRACE等我们来测试一下:增加一个方法//映射访问路径,必须是POST请求 @RequestMapping(value = "/hello",method = {RequestMethod.POST}) public String index2(Model model){ model.addAttribute("msg", "hello!"); return "test"; }我们使用浏览器地址栏进行访问默认是Get请求,会报错405:如果将POST修改为GET则正常了;//映射访问路径,必须是Get请求 @RequestMapping(value = "/hello",method = {RequestMethod.GET}) public String index2(Model model){ model.addAttribute("msg", "hello!"); return "test"; }小结:Spring MVC 的 @RequestMapping 注解能够处理 HTTP 请求的方法, 比如 GET, PUT, POST, DELETE 以及 PATCH。所有的地址栏请求默认都会是 HTTP GET 类型的。方法级别的注解变体有如下几个:组合注解@GetMapping @PostMapping @PutMapping @DeleteMapping @PatchMapping@GetMapping 是一个组合注解,平时使用的会比较多!它所扮演的是 @RequestMapping(method =RequestMethod.GET) 的一个快捷方式。5.结果跳转方式5.1 ModelAndView设置ModelAndView对象 , 根据view的名称 , 和视图解析器跳到指定的页面 .页面 : {视图解析器前缀} + viewName +{视图解析器后缀}<!-- 视图解析器 --> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="internalResourceViewResolver"> <!-- 前缀 --> <property name="prefix" value="/WEB-INF/jsp/" /> <!-- 后缀 --> <property name="suffix" value=".jsp" /> </bean>对应的controller类public class ControllerTest1 implements Controller { public ModelAndView handleRequest(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception { //返回一个模型视图对象 ModelAndView mv = new ModelAndView(); mv.addObject("msg","ControllerTest1"); mv.setViewName("test"); return mv; } }5.2 ServletAPI通过设置ServletAPI , 不需要视图解析器 .1、通过HttpServletResponse进行输出2、通过HttpServletResponse实现重定向3、通过HttpServletResponse实现转发@Controller public class ResultGo { @RequestMapping("/result/t1") public void test1(HttpServletRequest req, HttpServletResponse rsp) throws IOException { rsp.getWriter().println("Hello,Spring BY servlet API"); } @RequestMapping("/result/t2") public void test2(HttpServletRequest req, HttpServletResponse rsp) throws IOException { rsp.sendRedirect("/index.jsp"); } @RequestMapping("/result/t3") public void test3(HttpServletRequest req, HttpServletResponse rsp) throws Exception { //转发 req.setAttribute("msg","/result/t3"); req.getRequestDispatcher("/WEB-INF/jsp/test.jsp").forward(req,rsp); } }5.3 SpringMVC通过SpringMVC来实现转发和重定向 - 无需视图解析器;测试前,需要将视图解析器注释掉@Controller public class ResultSpringMVC { @RequestMapping("/rsm/t1") public String test1(){ //转发 return "/index.jsp"; } @RequestMapping("/rsm/t2") public String test2(){ //转发二 return "forward:/index.jsp"; } @RequestMapping("/rsm/t3") public String test3(){ //重定向 return "redirect:/index.jsp"; } }通过SpringMVC来实现转发和重定向 - 有视图解析器;重定向 , 不需要视图解析器 , 本质就是重新请求一个新地方嘛 , 所以注意路径问题.可以重定向到另外一个请求实现 .@Controller public class ResultSpringMVC2 { @RequestMapping("/rsm2/t1") public String test1(){ //转发 return "test"; } @RequestMapping("/rsm2/t2") public String test2(){ //重定向 return "redirect:/index.jsp"; //return "redirect:hello.do"; //hello.do为另一个请求/ } }6.数据处理6.1 处理提交数据1、提交的域名称和处理方法的参数名一致提交数据 : http://localhost:8080/hello?name=kuangshen处理方法 :@RequestMapping("/hello") public String hello(String name){ System.out.println(name); return "hello"; }后台输出 : kuangshen2、提交的域名称和处理方法的参数名不一致提交数据 : http://localhost:8080/hello?username=kuangshen处理方法 ://@RequestParam("username") : username提交的域的名称 . @RequestMapping("/hello") public String hello(@RequestParam("username") String name){ System.out.println(name); return "hello"; }后台输出 : kuangshen3、提交的是一个对象要求提交的表单域和对象的属性名一致 , 参数使用对象即可1、实体类public class User { private int id; private String name; private int age; //构造 //get/set //tostring() }2、提交数据 : http://localhost:8080/mvc04/user?name=kuangshen&id=1&age=153、处理方法 :@RequestMapping("/user") public String user(User user){ System.out.println(user); return "hello"; }后台输出 : User { id=1, name='kuangshen', age=15 }说明:如果使用对象的话,前端传递的参数名和对象名必须一致,否则就是null。6.2 数据显示到前端第一种 : 通过ModelAndView我们前面一直都是如此 . 就不过多解释public class ControllerTest1 implements Controller { public ModelAndView handleRequest(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception { //返回一个模型视图对象 ModelAndView mv = new ModelAndView(); mv.addObject("msg","ControllerTest1"); mv.setViewName("test"); return mv; } }第二种 : 通过ModelMapModelMap@RequestMapping("/hello") public String hello(@RequestParam("username") String name, ModelMap model){ //封装要显示到视图中的数据 //相当于req.setAttribute("name",name); model.addAttribute("name",name); System.out.println(name); return "hello"; }第三种 : 通过ModelModel@RequestMapping("/ct2/hello") public String hello(@RequestParam("username") String name, Model model){ //封装要显示到视图中的数据 //相当于req.setAttribute("name",name); model.addAttribute("msg",name); System.out.println(name); return "test"; }对比就对于新手而言简单来说使用区别就是:Model 只有寥寥几个方法只适合用于储存数据,简化了新手对于Model对象的操作和理解; ModelMap 继承了 LinkedMap ,除了实现了自身的一些方法,同样的继承 LinkedMap 的方法和特性; ModelAndView 可以在储存数据的同时,可以进行设置返回的逻辑视图,进行控制展示层的跳转。当然更多的以后开发考虑的更多的是性能和优化,就不能单单仅限于此的了解。请使用80%的时间打好扎实的基础,剩下18%的时间研究框架,2%的时间去学点英文,框架的官方文档永远是最好的教程。6.3 乱码问题测试步骤:1、我们可以在首页编写一个提交的表单<form action="/e/t" method="post"> <input type="text" name="name"> <input type="submit"> </form>2、后台编写对应的处理类@Controller public class Encoding { @RequestMapping("/e/t") public String test(Model model,String name){ model.addAttribute("msg",name); //获取表单提交的值 return "test"; //跳转到test页面显示输入的值 } }3、输入中文测试,发现乱码不得不说,乱码问题是在我们开发中十分常见的问题,也是让我们程序猿比较头大的问题!以前乱码问题通过过滤器解决 , 而SpringMVC给我们提供了一个过滤器 , 可以在web.xml中配置 .修改了xml文件需要重启服务器!<filter> <filter-name>encoding</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>utf-8</param-value> </init-param> </filter> <filter-mapping> <filter-name>encoding</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>但是我们发现 , 有些极端情况下.这个过滤器对get的支持不好 .处理方法 :1、修改tomcat配置文件 :设置编码!<Connector URIEncoding="utf-8" port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" />2、自定义过滤器package com.kuang.filter; import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.util.Map; /** * 解决get和post请求 全部乱码的过滤器 */ public class GenericEncodingFilter implements Filter { @Override public void destroy() { } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { //处理response的字符编码 HttpServletResponse myResponse=(HttpServletResponse) response; myResponse.setContentType("text/html;charset=UTF-8"); // 转型为与协议相关对象 HttpServletRequest httpServletRequest = (HttpServletRequest) request; // 对request包装增强 HttpServletRequest myrequest = new MyRequest(httpServletRequest); chain.doFilter(myrequest, response); } @Override public void init(FilterConfig filterConfig) throws ServletException { } } //自定义request对象,HttpServletRequest的包装类 class MyRequest extends HttpServletRequestWrapper { private HttpServletRequest request; //是否编码的标记 private boolean hasEncode; //定义一个可以传入HttpServletRequest对象的构造函数,以便对其进行装饰 public MyRequest(HttpServletRequest request) { super(request);// super必须写 this.request = request; } // 对需要增强方法 进行覆盖 @Override public Map getParameterMap() { // 先获得请求方式 String method = request.getMethod(); if (method.equalsIgnoreCase("post")) { // post请求 try { // 处理post乱码 request.setCharacterEncoding("utf-8"); return request.getParameterMap(); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } } else if (method.equalsIgnoreCase("get")) { // get请求 Map<String, String[]> parameterMap = request.getParameterMap(); if (!hasEncode) { // 确保get手动编码逻辑只运行一次 for (String parameterName : parameterMap.keySet()) { String[] values = parameterMap.get(parameterName); if (values != null) { for (int i = 0; i < values.length; i++) { try { // 处理get乱码 values[i] = new String(values[i] .getBytes("ISO-8859-1"), "utf-8"); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } } } } hasEncode = true; } return parameterMap; } return super.getParameterMap(); } //取一个值 @Override public String getParameter(String name) { Map<String, String[]> parameterMap = getParameterMap(); String[] values = parameterMap.get(name); if (values == null) { return null; } return values[0]; // 取回参数的第一个值 } //取所有值 @Override public String[] getParameterValues(String name) { Map<String, String[]> parameterMap = getParameterMap(); String[] values = parameterMap.get(name); return values; } }这个也是我在网上找的一些大神写的,一般情况下,SpringMVC默认的乱码处理就已经能够很好的解决了!然后在web.xml中配置这个过滤器即可!乱码问题,需要平时多注意,在尽可能能设置编码的地方,都设置为统一编码 UTF-8!7.整合SSM7.1 环境要求环境:IDEAMySQL 5.7.19Tomcat 9Maven 3.6要求:需要熟练掌握MySQL数据库,Spring,JavaWeb及MyBatis知识,简单的前端知识;7.2 数据库环境创建一个存放书籍数据的数据库表CREATE DATABASE `ssmbuild`; USE `ssmbuild`; DROP TABLE IF EXISTS `books`; CREATE TABLE `books` ( `bookID` INT(10) NOT NULL AUTO_INCREMENT COMMENT '书id', `bookName` VARCHAR(100) NOT NULL COMMENT '书名', `bookCounts` INT(11) NOT NULL COMMENT '数量', `detail` VARCHAR(200) NOT NULL COMMENT '描述', KEY `bookID` (`bookID`) ) ENGINE=INNODB DEFAULT CHARSET=utf8 INSERT INTO `books`(`bookID`,`bookName`,`bookCounts`,`detail`)VALUES (1,'Java',1,'从入门到放弃'), (2,'MySQL',10,'从删库到跑路'), (3,'Linux',5,'从进门到进牢');7.3 基本环境搭建1、新建一Maven项目!ssmbuild , 添加web的支持2、导入相关的pom依赖!<dependencies> <!--Junit--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> <!--数据库驱动--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.47</version> </dependency> <!-- 数据库连接池 --> <dependency> <groupId>com.mchange</groupId> <artifactId>c3p0</artifactId> <version>0.9.5.2</version> </dependency> <!--Servlet - JSP --> <dependency> <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> <version>2.5</version> </dependency> <dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>jsp-api</artifactId> <version>2.2</version> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> <!--Mybatis--> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.2</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>2.0.2</version> </dependency> <!--Spring--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.1.9.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.1.9.RELEASE</version> </dependency> </dependencies>3、Maven资源过滤设置<build> <resources> <resource> <directory>src/main/java</directory> <includes> <include>**/*.properties</include> <include>**/*.xml</include> </includes> <filtering>false</filtering> </resource> <resource> <directory>src/main/resources</directory> <includes> <include>**/*.properties</include> <include>**/*.xml</include> </includes> <filtering>false</filtering> </resource> </resources> </build>4、建立基本结构和配置框架!com.kuang.pojocom.kuang.daocom.kuang.servicecom.kuang.controllermybatis-config.xml<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> </configuration>applicationContext.xml<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> </beans>Mybatis层编写1、数据库配置文件 database.propertiesjdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/ssmbuild?useSSL=true&useUnicode=true&characterEncoding=utf8 jdbc.username=root jdbc.password=1234562、IDEA关联数据库3、编写MyBatis的核心配置文件<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <typeAliases> <package name="com.kuang.pojo"/> </typeAliases> <mappers> <mapper resource="com/kuang/dao/BookMapper.xml"/> </mappers> </configuration>4、编写数据库对应的实体类 com.kuang.pojo.Books使用lombok插件!package com.kuang.pojo; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @Data @AllArgsConstructor @NoArgsConstructor public class Books { private int bookID; private String bookName; private int bookCounts; private String detail; }5、编写Dao层的 Mapper接口!package com.kuang.dao; import com.kuang.pojo.Books; import java.util.List; public interface BookMapper { //增加一个Book int addBook(Books book); //根据id删除一个Book int deleteBookById(int id); //更新Book int updateBook(Books books); //根据id查询,返回一个Book Books queryBookById(int id); //查询全部Book,返回list集合 List<Books> queryAllBook(); }6、编写接口对应的 Mapper.xml 文件。需要导入MyBatis的包;<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.kuang.dao.BookMapper"> <!--增加一个Book--> <insert id="addBook" parameterType="Books"> insert into ssmbuild.books(bookName,bookCounts,detail) values (#{bookName}, #{bookCounts}, #{detail}) </insert> <!--根据id删除一个Book--> <delete id="deleteBookById" parameterType="int"> delete from ssmbuild.books where bookID=#{bookID} </delete> <!--更新Book--> <update id="updateBook" parameterType="Books"> update ssmbuild.books set bookName = #{bookName},bookCounts = #{bookCounts},detail = #{detail} where bookID = #{bookID} </update> <!--根据id查询,返回一个Book--> <select id="queryBookById" resultType="Books"> select * from ssmbuild.books where bookID = #{bookID} </select> <!--查询全部Book--> <select id="queryAllBook" resultType="Books"> SELECT * from ssmbuild.books </select> </mapper>7、编写Service层的接口和实现类接口:package com.kuang.service; import com.kuang.pojo.Books; import java.util.List; //BookService:底下需要去实现,调用dao层 public interface BookService { //增加一个Book int addBook(Books book); //根据id删除一个Book int deleteBookById(int id); //更新Book int updateBook(Books books); //根据id查询,返回一个Book Books queryBookById(int id); //查询全部Book,返回list集合 List<Books> queryAllBook(); }实现类:package com.kuang.service; import com.kuang.dao.BookMapper; import com.kuang.pojo.Books; import java.util.List; public class BookServiceImpl implements BookService { //调用dao层的操作,设置一个set接口,方便Spring管理 private BookMapper bookMapper; public void setBookMapper(BookMapper bookMapper) { this.bookMapper = bookMapper; } public int addBook(Books book) { return bookMapper.addBook(book); } public int deleteBookById(int id) { return bookMapper.deleteBookById(id); } public int updateBook(Books books) { return bookMapper.updateBook(books); } public Books queryBookById(int id) { return bookMapper.queryBookById(id); } public List<Books> queryAllBook() { return bookMapper.queryAllBook(); } }OK,到此,底层需求操作编写完毕!7.4 Spring层1、配置Spring整合MyBatis,我们这里数据源使用c3p0连接池;2、我们去编写Spring整合Mybatis的相关的配置文件;spring-dao.xml<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"> <!-- 配置整合mybatis --> <!-- 1.关联数据库文件 --> <context:property-placeholder location="classpath:database.properties"/> <!-- 2.数据库连接池 --> <!--数据库连接池 dbcp 半自动化操作 不能自动连接 c3p0 自动化操作(自动的加载配置文件 并且设置到对象里面) --> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <!-- 配置连接池属性 --> <property name="driverClass" value="${jdbc.driver}"/> <property name="jdbcUrl" value="${jdbc.url}"/> <property name="user" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> <!-- c3p0连接池的私有属性 --> <property name="maxPoolSize" value="30"/> <property name="minPoolSize" value="10"/> <!-- 关闭连接后不自动commit --> <property name="autoCommitOnClose" value="false"/> <!-- 获取连接超时时间 --> <property name="checkoutTimeout" value="10000"/> <!-- 当获取连接失败重试次数 --> <property name="acquireRetryAttempts" value="2"/> </bean> <!-- 3.配置SqlSessionFactory对象 --> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <!-- 注入数据库连接池 --> <property name="dataSource" ref="dataSource"/> <!-- 配置MyBaties全局配置文件:mybatis-config.xml --> <property name="configLocation" value="classpath:mybatis-config.xml"/> </bean> <!-- 4.配置扫描Dao接口包,动态实现Dao接口注入到spring容器中 --> <!--解释 :https://www.cnblogs.com/jpfss/p/7799806.html--> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <!-- 注入sqlSessionFactory --> <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/> <!-- 给出需要扫描Dao接口包 --> <property name="basePackage" value="com.kuang.dao"/> </bean> </beans>3、Spring整合service层<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- 扫描service相关的bean --> <context:component-scan base-package="com.kuang.service" /> <!--BookServiceImpl注入到IOC容器中--> <bean id="BookServiceImpl" class="com.kuang.service.BookServiceImpl"> <property name="bookMapper" ref="bookMapper"/> </bean> <!-- 配置事务管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!-- 注入数据库连接池 --> <property name="dataSource" ref="dataSource" /> </bean> </beans>Spring层搞定!再次理解一下,Spring就是一个大杂烩,一个容器!对吧!7.5 SpringMVC层1、web.xml<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0"> <!--DispatcherServlet--> <servlet> <servlet-name>DispatcherServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <!--一定要注意:我们这里加载的是总的配置文件,之前被这里坑了!--> <param-value>classpath:applicationContext.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>DispatcherServlet</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> <!--encodingFilter--> <filter> <filter-name>encodingFilter</filter-name> <filter-class> org.springframework.web.filter.CharacterEncodingFilter </filter-class> <init-param> <param-name>encoding</param-name> <param-value>utf-8</param-value> </init-param> </filter> <filter-mapping> <filter-name>encodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <!--Session过期时间--> <session-config> <session-timeout>15</session-timeout> </session-config> </web-app>2、spring-mvc.xml<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd"> <!-- 配置SpringMVC --> <!-- 1.开启SpringMVC注解驱动 --> <mvc:annotation-driven /> <!-- 2.静态资源默认servlet配置--> <mvc:default-servlet-handler/> <!-- 3.配置jsp 显示ViewResolver视图解析器 --> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="viewClass" value="org.springframework.web.servlet.view.JstlView" /> <property name="prefix" value="/WEB-INF/jsp/" /> <property name="suffix" value=".jsp" /> </bean> <!-- 4.扫描web相关的bean --> <context:component-scan base-package="com.kuang.controller" /> </beans>3、Spring配置整合文件,applicationContext.xml<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <import resource="spring-dao.xml"/> <import resource="spring-service.xml"/> <import resource="spring-mvc.xml"/> </beans>配置文件,暂时结束!Controller 和 视图层编写1、BookController 类编写 , 方法一:查询全部书籍@Controller @RequestMapping("/book") public class BookController { @Autowired @Qualifier("BookServiceImpl") private BookService bookService; @RequestMapping("/allBook") public String list(Model model) { List<Books> list = bookService.queryAllBook(); model.addAttribute("list", list); return "allBook"; } }2、编写首页 index.jsp<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %> <!DOCTYPE HTML> <html> <head> <title>首页</title> <style type="text/css"> a { text-decoration: none; color: black; font-size: 18px; } h3 { width: 180px; height: 38px; margin: 100px auto; text-align: center; line-height: 38px; background: deepskyblue; border-radius: 4px; } </style> </head> <body> <h3> <a href="${pageContext.request.contextPath}/book/allBook">点击进入列表页</a> </h3> </body> </html>3、书籍列表页面 allbook.jsp<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>书籍列表</title> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <!-- 引入 Bootstrap --> <link href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet"> </head> <body> <div class="container"> <div class="row clearfix"> <div class="col-md-12 column"> <div class="page-header"> <h1> <small>书籍列表 —— 显示所有书籍</small> </h1> </div> </div> </div> <div class="row"> <div class="col-md-4 column"> <a class="btn btn-primary" href="${pageContext.request.contextPath}/book/toAddBook">新增</a> </div> </div> <div class="row clearfix"> <div class="col-md-12 column"> <table class="table table-hover table-striped"> <thead> <tr> <th>书籍编号</th> <th>书籍名字</th> <th>书籍数量</th> <th>书籍详情</th> <th>操作</th> </tr> </thead> <tbody> <c:forEach var="book" items="${requestScope.get('list')}"> <tr> <td>${book.getBookID()}</td> <td>${book.getBookName()}</td> <td>${book.getBookCounts()}</td> <td>${book.getDetail()}</td> <td> <a href="${pageContext.request.contextPath}/book/toUpdateBook?id=${book.getBookID()}">更改</a> | <a href="${pageContext.request.contextPath}/book/del/${book.getBookID()}">删除</a> </td> </tr> </c:forEach> </tbody> </table> </div> </div> </div>4、BookController 类编写 , 方法二:添加书籍@RequestMapping("/toAddBook") public String toAddPaper() { return "addBook"; } @RequestMapping("/addBook") public String addPaper(Books books) { System.out.println(books); bookService.addBook(books); return "redirect:/book/allBook"; }5、添加书籍页面:addBook.jsp<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>新增书籍</title> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <!-- 引入 Bootstrap --> <link href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet"> </head> <body> <div class="container"> <div class="row clearfix"> <div class="col-md-12 column"> <div class="page-header"> <h1> <small>新增书籍</small> </h1> </div> </div> </div> <form action="${pageContext.request.contextPath}/book/addBook" method="post"> 书籍名称:<input type="text" name="bookName"><br><br><br> 书籍数量:<input type="text" name="bookCounts"><br><br><br> 书籍详情:<input type="text" name="detail"><br><br><br> <input type="submit" value="添加"> </form> </div>6、BookController 类编写 , 方法三:修改书籍@RequestMapping("/toUpdateBook") public String toUpdateBook(Model model, int id) { Books books = bookService.queryBookById(id); System.out.println(books); model.addAttribute("book",books ); return "updateBook"; } @RequestMapping("/updateBook") public String updateBook(Model model, Books book) { System.out.println(book); bookService.updateBook(book); Books books = bookService.queryBookById(book.getBookID()); model.addAttribute("books", books); return "redirect:/book/allBook"; }7、修改书籍页面 updateBook.jsp<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>修改信息</title> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <!-- 引入 Bootstrap --> <link href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet"> </head> <body> <div class="container"> <div class="row clearfix"> <div class="col-md-12 column"> <div class="page-header"> <h1> <small>修改信息</small> </h1> </div> </div> </div> <form action="${pageContext.request.contextPath}/book/updateBook" method="post"> <input type="hidden" name="bookID" value="${book.getBookID()}"/> 书籍名称:<input type="text" name="bookName" value="${book.getBookName()}"/> 书籍数量:<input type="text" name="bookCounts" value="${book.getBookCounts()}"/> 书籍详情:<input type="text" name="detail" value="${book.getDetail() }"/> <input type="submit" value="提交"/> </form> </div>8、BookController 类编写 , 方法四:删除书籍@RequestMapping("/del/{bookId}") public String deleteBook(@PathVariable("bookId") int id) { bookService.deleteBookById(id); return "redirect:/book/allBook"; }配置Tomcat,进行运行!到目前为止,这个SSM项目整合已经完全的OK了,可以直接运行进行测试!这个练习十分的重要,大家需要保证,不看任何东西,自己也可以完整的实现出来!7.6 小结及展望这个是同学们的第一个SSM整合案例,一定要烂熟于心!SSM框架的重要程度是不言而喻的,学到这里,大家已经可以进行基本网站的单独开发。但是这只是增删改查的基本操作。可以说学到这里,大家才算是真正的步入了后台开发的门。也就是能找一个后台相关工作的底线。或许很多人,工作就做这些事情,但是对于个人的提高来说,还远远不够!我们后面还要学习一些 SpringMVC 的知识!Ajax 和 Json文件上传和下载拦截器8.Json8.1 什么是JSON?JSON(JavaScript Object Notation, JS 对象标记) 是一种轻量级的数据交换格式,目前使用特别广泛。采用完全独立于编程语言的文本格式来存储和表示数据。简洁和清晰的层次结构使得 JSON 成为理想的数据交换语言。易于人阅读和编写,同时也易于机器解析和生成,并有效地提升网络传输效率。在 JavaScript 语言中,一切都是对象。因此,任何JavaScript 支持的类型都可以通过 JSON 来表示,例如字符串、数字、对象、数组等。看看他的要求和语法格式:对象表示为键值对,数据由逗号分隔花括号保存对象方括号保存数组JSON 键值对是用来保存 JavaScript 对象的一种方式,和 JavaScript 对象的写法也大同小异,键/值对组合中的键名写在前面并用双引号 "" 包裹,使用冒号 : 分隔,然后紧接着值:{"name": "QinJiang"} {"age": "3"} {"sex": "男"}很多人搞不清楚 JSON 和 JavaScript 对象的关系,甚至连谁是谁都不清楚。其实,可以这么理解:JSON 是 JavaScript 对象的字符串表示法,它使用文本表示一个 JS 对象的信息,本质是一个字符串。var obj = {a: 'Hello', b: 'World'}; //这是一个对象,注意键名也是可以使用引号包裹的 var json = '{"a": "Hello", "b": "World"}'; //这是一个 JSON 字符串,本质是一个字符串JSON 和 JavaScript 对象互转要实现从JSON字符串转换为JavaScript 对象,使用 JSON.parse() 方法:var obj = JSON.parse('{"a": "Hello", "b": "World"}'); //结果是 {a: 'Hello', b: 'World'}要实现从JavaScript 对象转换为JSON字符串,使用 JSON.stringify() 方法:var json = JSON.stringify({a: 'Hello', b: 'World'}); //结果是 '{"a": "Hello", "b": "World"}'代码测试1、新建一个module ,springmvc-05-json , 添加web的支持2、在web目录下新建一个 json-1.html , 编写测试内容<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>JSON_秦疆</title> </head> <body> <script type="text/javascript"> //编写一个js的对象 var user = { name:"秦疆", age:3, sex:"男" }; //将js对象转换成json字符串 var str = JSON.stringify(user); console.log(str); //将json字符串转换为js对象 var user2 = JSON.parse(str); console.log(user2.age,user2.name,user2.sex); </script> </body> </html>3、在IDEA中使用浏览器打开,查看控制台输出!8.2 Controller返回JSON数据Jackson应该是目前比较好的json解析工具了当然工具不止这一个,比如还有阿里巴巴的 fastjson 等等。我们这里使用Jackson,使用它需要导入它的jar包;<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-core --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.9.8</version> </dependency>配置SpringMVC需要的配置web.xml<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0"> <!--1.注册servlet--> <servlet> <servlet-name>SpringMVC</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <!--通过初始化参数指定SpringMVC配置文件的位置,进行关联--> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:springmvc-servlet.xml</param-value> </init-param> <!-- 启动顺序,数字越小,启动越早 --> <load-on-startup>1</load-on-startup> </servlet> <!--所有请求都会被springmvc拦截 --> <servlet-mapping> <servlet-name>SpringMVC</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> <filter> <filter-name>encoding</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>utf-8</param-value> </init-param> </filter> <filter-mapping> <filter-name>encoding</filter-name> <url-pattern>/</url-pattern> </filter-mapping> </web-app>springmvc-servlet.xml<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd"> <!-- 自动扫描指定的包,下面所有注解类交给IOC容器管理 --> <context:component-scan base-package="com.kuang.controller"/> <!-- 视图解析器 --> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="internalResourceViewResolver"> <!-- 前缀 --> <property name="prefix" value="/WEB-INF/jsp/" /> <!-- 后缀 --> <property name="suffix" value=".jsp" /> </bean> </beans>我们随便编写一个User的实体类,然后我们去编写我们的测试Controller;package com.kuang.pojo; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; //需要导入lombok @Data @AllArgsConstructor @NoArgsConstructor public class User { private String name; private int age; private String sex; }这里我们需要两个新东西,一个是@ResponseBody,一个是ObjectMapper对象,我们看下具体的用法编写一个Controller;@Controller public class UserController { @RequestMapping("/json1") @ResponseBody public String json1() throws JsonProcessingException { //创建一个jackson的对象映射器,用来解析数据 ObjectMapper mapper = new ObjectMapper(); //创建一个对象 User user = new User("秦疆1号", 3, "男"); //将我们的对象解析成为json格式 String str = mapper.writeValueAsString(user); //由于@ResponseBody注解,这里会将str转成json格式返回;十分方便 return str; } }配置Tomcat , 启动测试一下!http://localhost:8080/json1发现出现了乱码问题,我们需要设置一下他的编码格式为utf-8,以及它返回的类型;通过@RequestMaping的produces属性来实现,修改下代码//produces:指定响应体返回类型和编码 @RequestMapping(value = "/json1",produces = "application/json;charset=utf-8")再次测试, http://localhost:8080/json1 , 乱码问题OK!【注意:使用json记得处理乱码问题】8.3 代码优化乱码统一解决上一种方法比较麻烦,如果项目中有许多请求则每一个都要添加,可以通过Spring配置统一指定,这样就不用每次都去处理了!我们可以在springmvc的配置文件上添加一段消息StringHttpMessageConverter转换配置!<mvc:annotation-driven> <mvc:message-converters register-defaults="true"> <bean class="org.springframework.http.converter.StringHttpMessageConverter"> <constructor-arg value="UTF-8"/> </bean> <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"> <property name="objectMapper"> <bean class="org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean"> <property name="failOnEmptyBeans" value="false"/> </bean> </property> </bean> </mvc:message-converters> </mvc:annotation-driven>返回json字符串统一解决在类上直接使用 @RestController ,这样子,里面所有的方法都只会返回 json 字符串了,不用再每一个都添加@ResponseBody !我们在前后端分离开发中,一般都使用 @RestController ,十分便捷!@RestController public class UserController { //produces:指定响应体返回类型和编码 @RequestMapping(value = "/json1") public String json1() throws JsonProcessingException { //创建一个jackson的对象映射器,用来解析数据 ObjectMapper mapper = new ObjectMapper(); //创建一个对象 User user = new User("秦疆1号", 3, "男"); //将我们的对象解析成为json格式 String str = mapper.writeValueAsString(user); //由于@ResponseBody注解,这里会将str转成json格式返回;十分方便 return str; } }启动tomcat测试,结果都正常输出!8.4 测试集合输出增加一个新的方法@RequestMapping("/json2") public String json2() throws JsonProcessingException { //创建一个jackson的对象映射器,用来解析数据 ObjectMapper mapper = new ObjectMapper(); //创建一个对象 User user1 = new User("秦疆1号", 3, "男"); User user2 = new User("秦疆2号", 3, "男"); User user3 = new User("秦疆3号", 3, "男"); User user4 = new User("秦疆4号", 3, "男"); List<User> list = new ArrayList<User>(); list.add(user1); list.add(user2); list.add(user3); list.add(user4); //将我们的对象解析成为json格式 String str = mapper.writeValueAsString(list); return str; }运行结果 : 十分完美,没有任何问题!8.5 输出时间对象增加一个新的方法@RequestMapping("/json3") public String json3() throws JsonProcessingException { ObjectMapper mapper = new ObjectMapper(); //创建时间一个对象,java.util.Date Date date = new Date(); //将我们的对象解析成为json格式 String str = mapper.writeValueAsString(date); return str; }运行结果 :默认日期格式会变成一个数字,是1970年1月1日到当前日期的毫秒数!Jackson 默认是会把时间转成timestamps形式解决方案:取消timestamps形式 , 自定义时间格式@RequestMapping("/json4") public String json4() throws JsonProcessingException { ObjectMapper mapper = new ObjectMapper(); //不使用时间戳的方式 mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false); //自定义日期格式对象 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); //指定日期格式 mapper.setDateFormat(sdf); Date date = new Date(); String str = mapper.writeValueAsString(date); return str; }运行结果 : 成功的输出了时间!8.6 抽取为工具类如果要经常使用的话,这样是比较麻烦的,我们可以将这些代码封装到一个工具类中;我们去编写下package com.kuang.utils; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; import java.text.SimpleDateFormat; public class JsonUtils { public static String getJson(Object object) { return getJson(object,"yyyy-MM-dd HH:mm:ss"); } public static String getJson(Object object,String dateFormat) { ObjectMapper mapper = new ObjectMapper(); //不使用时间差的方式 mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false); //自定义日期格式对象 SimpleDateFormat sdf = new SimpleDateFormat(dateFormat); //指定日期格式 mapper.setDateFormat(sdf); try { return mapper.writeValueAsString(object); } catch (JsonProcessingException e) { e.printStackTrace(); } return null; } }我们使用工具类,代码就更加简洁了!@RequestMapping("/json5") public String json5() throws JsonProcessingException { Date date = new Date(); String json = JsonUtils.getJson(date); return json; }大功告成!完美!8.7 FastJsonfastjson.jar是阿里开发的一款专门用于Java开发的包,可以方便的实现json对象与JavaBean对象的转换,实现JavaBean对象与json字符串的转换,实现json对象与json字符串的转换。实现json的转换方法很多,最后的实现结果都是一样的。fastjson 的 pom依赖!<dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.60</version> </dependency>fastjson 三个主要的类:JSONObject 代表 json 对象JSONObject实现了Map接口, 猜想 JSONObject底层操作是由Map实现的。JSONObject对应json对象,通过各种形式的get()方法可以获取json对象中的数据,也可利用诸如size(),isEmpty()等方法获取"键:值"对的个数和判断是否为空。其本质是通过实现Map接口并调用接口中的方法完成的。JSONArray 代表 json 对象数组内部是有List接口中的方法来完成操作的。JSON代表 JSONObject和JSONArray的转化JSON类源码分析与使用仔细观察这些方法,主要是实现json对象,json对象数组,javabean对象,json字符串之间的相互转化。代码测试,我们新建一个FastJsonDemo 类package com.kuang.controller; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.kuang.pojo.User; import java.util.ArrayList; import java.util.List; public class FastJsonDemo { public static void main(String[] args) { //创建一个对象 User user1 = new User("秦疆1号", 3, "男"); User user2 = new User("秦疆2号", 3, "男"); User user3 = new User("秦疆3号", 3, "男"); User user4 = new User("秦疆4号", 3, "男"); List<User> list = new ArrayList<User>(); list.add(user1); list.add(user2); list.add(user3); list.add(user4); System.out.println("*******Java对象 转 JSON字符串*******"); String str1 = JSON.toJSONString(list); System.out.println("JSON.toJSONString(list)==>"+str1); String str2 = JSON.toJSONString(user1); System.out.println("JSON.toJSONString(user1)==>"+str2); System.out.println("\n****** JSON字符串 转 Java对象*******"); User jp_user1=JSON.parseObject(str2,User.class); System.out.println("JSON.parseObject(str2,User.class)==>"+jp_user1); System.out.println("\n****** Java对象 转 JSON对象 ******"); JSONObject jsonObject1 = (JSONObject) JSON.toJSON(user2); System.out.println("(JSONObject) JSON.toJSON(user2)==>"+jsonObject1.getString("name")); System.out.println("\n****** JSON对象 转 Java对象 ******"); User to_java_user = JSON.toJavaObject(jsonObject1, User.class); System.out.println("JSON.toJavaObject(jsonObject1, User.class)==>"+to_java_user); } }这种工具类,我们只需要掌握使用就好了,在使用的时候在根据具体的业务去找对应的实现。和以前的commons-io那种工具包一样,拿来用就好了!9.Ajax研究9.1 简介AJAX = Asynchronous JavaScript and XML(异步的 JavaScript 和 XML)。AJAX 是一种在无需重新加载整个网页的情况下,能够更新部分网页的技术。Ajax 不是一种新的编程语言,而是一种用于创建更好更快以及交互性更强的Web应用程序的技术。在 2005 年,Google 通过其 Google Suggest 使 AJAX 变得流行起来。Google Suggest能够自动帮你完成搜索单词。Google Suggest 使用 AJAX 创造出动态性极强的 web 界面:当您在谷歌的搜索框输入关键字时,JavaScript 会把这些字符发送到服务器,然后服务器会返回一个搜索建议的列表。就和国内百度的搜索框一样!传统的网页(即不用ajax技术的网页),想要更新内容或者提交一个表单,都需要重新加载整个网页。使用ajax技术的网页,通过在后台服务器进行少量的数据交换,就可以实现异步局部更新。使用Ajax,用户可以创建接近本地桌面应用的直接、高可用、更丰富、更动态的Web用户界面。9.2 伪造Ajax我们可以使用前端的一个标签来伪造一个ajax的样子。iframe标签1、新建一个module :sspringmvc-06-ajax , 导入web支持!2、编写一个 ajax-frame.html 使用 iframe 测试,感受下效果<!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <title>kuangshen</title> </head> <body> <script type="text/javascript"> window.onload = function(){ var myDate = new Date(); document.getElementById('currentTime').innerText = myDate.getTime(); }; function LoadPage(){ var targetUrl = document.getElementById('url').value; console.log(targetUrl); document.getElementById("iframePosition").src = targetUrl; } </script> <div> <p>请输入要加载的地址:<span id="currentTime"></span></p> <p> <input id="url" type="text" value="https://www.baidu.com/"/> <input type="button" value="提交" onclick="LoadPage()"> </p> </div> <div> <h3>加载页面位置:</h3> <iframe id="iframePosition" style="width: 100%;height: 500px;"></iframe> </div> </body> </html>3、使用IDEA开浏览器测试一下!利用AJAX可以做:注册时,输入用户名自动检测用户是否已经存在。登陆时,提示用户名密码错误删除数据行时,将行ID发送到后台,后台在数据库中删除,数据库删除成功后,在页面DOM中将数据行也删除。....等等9.3 jQuery.ajax纯JS原生实现Ajax我们不去讲解这里,直接使用jquery提供的,方便学习和使用,避免重复造轮子,有兴趣的同学可以去了解下JS原生XMLHttpRequest !Ajax的核心是XMLHttpRequest对象(XHR)。XHR为向服务器发送请求和解析服务器响应提供了接口。能够以异步方式从服务器获取新数据。jQuery 提供多个与 AJAX 有关的方法。通过 jQuery AJAX 方法,您能够使用 HTTP Get 和 HTTP Post 从远程服务器上请求文本、HTML、XML 或 JSON – 同时您能够把这些外部数据直接载入网页的被选元素中。jQuery 不是生产者,而是大自然搬运工。jQuery Ajax本质就是 XMLHttpRequest,对他进行了封装,方便调用!jQuery.ajax(...) 部分参数: url:请求地址 type:请求方式,GET、POST(1.9.0之后用method) headers:请求头 data:要发送的数据 contentType:即将发送信息至服务器的内容编码类型(默认: "application/x-www-form-urlencoded; charset=UTF-8") async:是否异步 timeout:设置请求超时时间(毫秒) beforeSend:发送请求前执行的函数(全局) complete:完成之后执行的回调函数(全局) success:成功之后执行的回调函数(全局) error:失败之后执行的回调函数(全局) accepts:通过请求头发送给服务器,告诉服务器当前客户端可接受的数据类型 dataType:将服务器端返回的数据转换成指定类型 "xml": 将服务器端返回的内容转换成xml格式 "text": 将服务器端返回的内容转换成普通文本格式 "html": 将服务器端返回的内容转换成普通文本格式,在插入DOM中时,如果包含JavaScript标签,则会尝试去执行。 "script": 尝试将返回值当作JavaScript去执行,然后再将服务器端返回的内容转换成普通文本格式 "json": 将服务器端返回的内容转换成相应的JavaScript对象 "jsonp": JSONP 格式使用 JSONP 形式调用函数时,如 "myurl?callback=?" jQuery 将自动替换 ? 为正确的函数名,以执行回调函数我们来个简单的测试,使用最原始的HttpServletResponse处理 , .最简单 , 最通用1、配置web.xml 和 springmvc的配置文件,复制上面案例的即可 【记得静态资源过滤和注解驱动配置上】<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd"> <!-- 自动扫描指定的包,下面所有注解类交给IOC容器管理 --> <context:component-scan base-package="com.kuang.controller"/> <mvc:default-servlet-handler /> <mvc:annotation-driven /> <!-- 视图解析器 --> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="internalResourceViewResolver"> <!-- 前缀 --> <property name="prefix" value="/WEB-INF/jsp/" /> <!-- 后缀 --> <property name="suffix" value=".jsp" /> </bean> </beans>2、编写一个AjaxController@Controller public class AjaxController { @RequestMapping("/a1") public void ajax1(String name , HttpServletResponse response) throws IOException { if ("admin".equals(name)){ response.getWriter().print("true"); }else{ response.getWriter().print("false"); } } }3、导入jquery , 可以使用在线的CDN , 也可以下载导入<script src="https://code.jquery.com/jquery-3.1.1.min.js"></script> <script src="${pageContext.request.contextPath}/statics/js/jquery-3.1.1.min.js"></script>4、编写index.jsp测试<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>$Title$</title> <%--<script src="https://code.jquery.com/jquery-3.1.1.min.js"></script>--%> <script src="${pageContext.request.contextPath}/statics/js/jquery-3.1.1.min.js"></script> <script> function a1(){ $.post({ url:"${pageContext.request.contextPath}/a1", data:{'name':$("#txtName").val()}, success:function (data,status) { alert(data); alert(status); } }); } </script> </head> <body> <%--onblur:失去焦点触发事件--%> 用户名:<input type="text" id="txtName" onblur="a1()"/> </body> </html>5、启动tomcat测试!打开浏览器的控制台,当我们鼠标离开输入框的时候,可以看到发出了一个ajax的请求!是后台返回给我们的结果!测试成功!9.4 Springmvc实现实体类user@Data @AllArgsConstructor @NoArgsConstructor public class User { private String name; private int age; private String sex; }我们来获取一个集合对象,展示到前端页面@RequestMapping("/a2") public List<User> ajax2(){ List<User> list = new ArrayList<User>(); list.add(new User("秦疆1号",3,"男")); list.add(new User("秦疆2号",3,"男")); list.add(new User("秦疆3号",3,"男")); return list; //由于@RestController注解,将list转成json格式返回 }前端页面<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Title</title> </head> <body> <input type="button" id="btn" value="获取数据"/> <table width="80%" align="center"> <tr> <td>姓名</td> <td>年龄</td> <td>性别</td> </tr> <tbody id="content"> </tbody> </table> <script src="${pageContext.request.contextPath}/statics/js/jquery-3.1.1.min.js"></script> <script> $(function () { $("#btn").click(function () { $.post("${pageContext.request.contextPath}/a2",function (data) { console.log(data) var html=""; for (var i = 0; i <data.length ; i++) { html+= "<tr>" + "<td>" + data[i].name + "</td>" + "<td>" + data[i].age + "</td>" + "<td>" + data[i].sex + "</td>" + "</tr>" } $("#content").html(html); }); }) }) </script> </body> </html>成功实现了数据回显!可以体会一下Ajax的好处!9.5 注册提示效果我们再测试一个小Demo,思考一下我们平时注册时候,输入框后面的实时提示怎么做到的;如何优化我们写一个Controller@RequestMapping("/a3") public String ajax3(String name,String pwd){ String msg = ""; //模拟数据库中存在数据 if (name!=null){ if ("admin".equals(name)){ msg = "OK"; }else { msg = "用户名输入错误"; } } if (pwd!=null){ if ("123456".equals(pwd)){ msg = "OK"; }else { msg = "密码输入有误"; } } return msg; //由于@RestController注解,将msg转成json格式返回 }前端页面 login.jsp<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>ajax</title> <script src="${pageContext.request.contextPath}/statics/js/jquery-3.1.1.min.js"></script> <script> function a1(){ $.post({ url:"${pageContext.request.contextPath}/a3", data:{'name':$("#name").val()}, success:function (data) { if (data.toString()=='OK'){ $("#userInfo").css("color","green"); }else { $("#userInfo").css("color","red"); } $("#userInfo").html(data); } }); } function a2(){ $.post({ url:"${pageContext.request.contextPath}/a3", data:{'pwd':$("#pwd").val()}, success:function (data) { if (data.toString()=='OK'){ $("#pwdInfo").css("color","green"); }else { $("#pwdInfo").css("color","red"); } $("#pwdInfo").html(data); } }); } </script> </head> <body> <p> 用户名:<input type="text" id="name" onblur="a1()"/> <span id="userInfo"></span> </p> <p> 密码:<input type="text" id="pwd" onblur="a2()"/> <span id="pwdInfo"></span> </p> </body> </html>【记得处理json乱码问题】测试一下效果,动态请求响应,局部刷新,就是如此!9.6 获取baidu接口Demo<!DOCTYPE HTML> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <title>JSONP百度搜索</title> <style> #q{ width: 500px; height: 30px; border:1px solid #ddd; line-height: 30px; display: block; margin: 0 auto; padding: 0 10px; font-size: 14px; } #ul{ width: 520px; list-style: none; margin: 0 auto; padding: 0; border:1px solid #ddd; margin-top: -1px; display: none; } #ul li{ line-height: 30px; padding: 0 10px; } #ul li:hover{ background-color: #f60; color: #fff; } </style> <script> // 2.步骤二 // 定义demo函数 (分析接口、数据) function demo(data){ var Ul = document.getElementById('ul'); var html = ''; // 如果搜索数据存在 把内容添加进去 if (data.s.length) { // 隐藏掉的ul显示出来 Ul.style.display = 'block'; // 搜索到的数据循环追加到li里 for(var i = 0;i<data.s.length;i++){ html += '<li>'+data.s[i]+'</li>'; } // 循环的li写入ul Ul.innerHTML = html; } } // 1.步骤一 window.onload = function(){ // 获取输入框和ul var Q = document.getElementById('q'); var Ul = document.getElementById('ul'); // 事件鼠标抬起时候 Q.onkeyup = function(){ // 如果输入框不等于空 if (this.value != '') { // ☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆JSONPz重点☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆ // 创建标签 var script = document.createElement('script'); //给定要跨域的地址 赋值给src //这里是要请求的跨域的地址 我写的是百度搜索的跨域地址 script.src = 'https://sp0.baidu.com/5a1Fazu8AA54nxGko9WTAnF6hhy/su?wd='+this.value+'&cb=demo'; // 将组合好的带src的script标签追加到body里 document.body.appendChild(script); } } } </script> </head> <body> <input type="text" id="q" /> <ul id="ul"> </ul> </body> </html>10.拦截器10.1 概述SpringMVC的处理器拦截器类似于Servlet开发中的过滤器Filter,用于对处理器进行预处理和后处理。开发者可以自己定义一些拦截器来实现特定的功能。过滤器与拦截器的区别:拦截器是AOP思想的具体应用。过滤器servlet规范中的一部分,任何java web工程都可以使用在url-pattern中配置了/*之后,可以对所有要访问的资源进行拦截拦截器拦截器是SpringMVC框架自己的,只有使用了SpringMVC框架的工程才能使用拦截器只会拦截访问的控制器方法, 如果访问的是jsp/html/css/image/js是不会进行拦截的10.2 自定义拦截器那如何实现拦截器呢?想要自定义拦截器,必须实现 HandlerInterceptor 接口。1、新建一个Moudule , springmvc-07-Interceptor , 添加web支持2、配置web.xml 和 springmvc-servlet.xml 文件3、编写一个拦截器package com.kuang.interceptor; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class MyInterceptor implements HandlerInterceptor { //在请求处理的方法之前执行 //如果返回true执行下一个拦截器 //如果返回false就不执行下一个拦截器 public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception { System.out.println("------------处理前------------"); return true; } //在请求处理方法执行之后执行 public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception { System.out.println("------------处理后------------"); } //在dispatcherServlet处理后执行,做清理工作. public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception { System.out.println("------------清理------------"); } }4、在springmvc的配置文件中配置拦截器<!--关于拦截器的配置--> <mvc:interceptors> <mvc:interceptor> <!--/** 包括路径及其子路径--> <!--/admin/* 拦截的是/admin/add等等这种 , /admin/add/user不会被拦截--> <!--/admin/** 拦截的是/admin/下的所有--> <mvc:mapping path="/**"/> <!--bean配置的就是拦截器--> <bean class="com.kuang.interceptor.MyInterceptor"/> </mvc:interceptor> </mvc:interceptors>5、编写一个Controller,接收请求package com.kuang.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; //测试拦截器的控制器 @Controller public class InterceptorController { @RequestMapping("/interceptor") @ResponseBody public String testFunction() { System.out.println("控制器中的方法执行了"); return "hello"; } }6、前端 index.jsp<a href="${pageContext.request.contextPath}/interceptor">拦截器测试</a>7、启动tomcat 测试一下!10.3 验证用户是否登录 (认证用户)实现思路1、有一个登陆页面,需要写一个controller访问页面。2、登陆页面有一提交表单的动作。需要在controller中处理。判断用户名密码是否正确。如果正确,向session中写入用户信息。返回登陆成功。3、拦截用户请求,判断用户是否登陆。如果用户已经登陆。放行, 如果用户未登陆,跳转到登陆页面测试:1、编写一个登陆页面 login.jsp<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Title</title> </head> <h1>登录页面</h1> <hr> <body> <form action="${pageContext.request.contextPath}/user/login"> 用户名:<input type="text" name="username"> <br> 密码:<input type="password" name="pwd"> <br> <input type="submit" value="提交"> </form> </body> </html>2、编写一个Controller处理请求package com.kuang.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import javax.servlet.http.HttpSession; @Controller @RequestMapping("/user") public class UserController { //跳转到登陆页面 @RequestMapping("/jumplogin") public String jumpLogin() throws Exception { return "login";d } //跳转到成功页面 @RequestMapping("/jumpSuccess") public String jumpSuccess() throws Exception { return "success"; } //登陆提交 @RequestMapping("/login") public String login(HttpSession session, String username, String pwd) throws Exception { // 向session记录用户身份信息 System.out.println("接收前端==="+username); session.setAttribute("user", username); return "success"; } //退出登陆 @RequestMapping("logout") public String logout(HttpSession session) throws Exception { // session 过期 session.invalidate(); return "login"; } }3、编写一个登陆成功的页面 success.jsp<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Title</title> </head> <body> <h1>登录成功页面</h1> <hr> ${user} <a href="${pageContext.request.contextPath}/user/logout">注销</a> </body> </html>4、在 index 页面上测试跳转!启动Tomcat 测试,未登录也可以进入主页!<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>$Title$</title> </head> <body> <h1>首页</h1> <hr> <%--登录--%> <a href="${pageContext.request.contextPath}/user/jumplogin">登录</a> <a href="${pageContext.request.contextPath}/user/jumpSuccess">成功页面</a> </body> </html>5、编写用户登录拦截器package com.kuang.interceptor; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.io.IOException; public class LoginInterceptor implements HandlerInterceptor { public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws ServletException, IOException { // 如果是登陆页面则放行 System.out.println("uri: " + request.getRequestURI()); if (request.getRequestURI().contains("login")) { return true; } HttpSession session = request.getSession(); // 如果用户已登陆也放行 if(session.getAttribute("user") != null) { return true; } // 用户没有登陆跳转到登陆页面 request.getRequestDispatcher("/WEB-INF/jsp/login.jsp").forward(request, response); return false; } public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception { } public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception { } }6、在Springmvc的配置文件中注册拦截器<!--关于拦截器的配置--> <mvc:interceptors> <mvc:interceptor> <mvc:mapping path="/**"/> <bean id="loginInterceptor" class="com.kuang.interceptor.LoginInterceptor"/> </mvc:interceptor> </mvc:interceptors>7、再次重启Tomcat测试!OK,测试登录拦截功能无误.11.文件上传和下载11.1 准备工作文件上传是项目开发中最常见的功能之一 ,springMVC 可以很好的支持文件上传,但是SpringMVC上下文中默认没有装配MultipartResolver,因此默认情况下其不能处理文件上传工作。如果想使用Spring的文件上传功能,则需要在上下文中配置MultipartResolver。前端表单要求:为了能上传文件,必须将表单的method设置为POST,并将enctype设置为multipart/form-data。只有在这样的情况下,浏览器才会把用户选择的文件以二进制数据发送给服务器;对表单中的 enctype 属性做个详细的说明:application/x-www=form-urlencoded:默认方式,只处理表单域中的 value 属性值,采用这种编码方式的表单会将表单域中的值处理成 URL 编码方式。multipart/form-data:这种编码方式会以二进制流的方式来处理表单数据,这种编码方式会把文件域指定文件的内容也封装到请求参数中,不会对字符编码。text/plain:除了把空格转换为 "+" 号外,其他字符都不做编码处理,这种方式适用直接通过表单发送邮件。<form action="" enctype="multipart/form-data" method="post"> <input type="file" name="file"/> <input type="submit"> </form>一旦设置了enctype为multipart/form-data,浏览器即会采用二进制流的方式来处理表单数据,而对于文件上传的处理则涉及在服务器端解析原始的HTTP响应。在2003年,Apache Software Foundation发布了开源的Commons FileUpload组件,其很快成为Servlet/JSP程序员上传文件的最佳选择。Servlet3.0规范已经提供方法来处理文件上传,但这种上传需要在Servlet中完成。而Spring MVC则提供了更简单的封装。Spring MVC为文件上传提供了直接的支持,这种支持是用即插即用的MultipartResolver实现的。Spring MVC使用Apache Commons FileUpload技术实现了一个MultipartResolver实现类:CommonsMultipartResolver。因此,SpringMVC的文件上传还需要依赖Apache Commons FileUpload的组件。11.2 文件上传1、导入文件上传的jar包,commons-fileupload , Maven会自动帮我们导入他的依赖包 commons-io包;<!--文件上传--> <dependency> <groupId>commons-fileupload</groupId> <artifactId>commons-fileupload</artifactId> <version>1.3.3</version> </dependency> <!--servlet-api导入高版本的--> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>4.0.1</version> </dependency>2、配置bean:multipartResolver【注意!!!这个bena的id必须为:multipartResolver , 否则上传文件会报400的错误!在这里栽过坑,教训!】<!--文件上传配置--> <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <!-- 请求的编码格式,必须和jSP的pageEncoding属性一致,以便正确读取表单的内容,默认为ISO-8859-1 --> <property name="defaultEncoding" value="utf-8"/> <!-- 上传文件大小上限,单位为字节(10485760=10M) --> <property name="maxUploadSize" value="10485760"/> <property name="maxInMemorySize" value="40960"/> </bean>CommonsMultipartFile 的 常用方法:String getOriginalFilename():获取上传文件的原名InputStream getInputStream():获取文件流void transferTo(File dest):将上传文件保存到一个目录文件中我们去实际测试一下3、编写前端页面<form action="/upload" enctype="multipart/form-data" method="post"> <input type="file" name="file"/> <input type="submit" value="upload"> </form>4、Controllerpackage com.kuang.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.multipart.commons.CommonsMultipartFile; import javax.servlet.http.HttpServletRequest; import java.io.*; @Controller public class FileController { //@RequestParam("file") 将name=file控件得到的文件封装成CommonsMultipartFile 对象 //批量上传CommonsMultipartFile则为数组即可 @RequestMapping("/upload") public String fileUpload(@RequestParam("file") CommonsMultipartFile file , HttpServletRequest request) throws IOException { //获取文件名 : file.getOriginalFilename(); String uploadFileName = file.getOriginalFilename(); //如果文件名为空,直接回到首页! if ("".equals(uploadFileName)){ return "redirect:/index.jsp"; } System.out.println("上传文件名 : "+uploadFileName); //上传路径保存设置 String path = request.getServletContext().getRealPath("/upload"); //如果路径不存在,创建一个 File realPath = new File(path); if (!realPath.exists()){ realPath.mkdir(); } System.out.println("上传文件保存地址:"+realPath); InputStream is = file.getInputStream(); //文件输入流 OutputStream os = new FileOutputStream(new File(realPath,uploadFileName)); //文件输出流 //读取写出 int len=0; byte[] buffer = new byte[1024]; while ((len=is.read(buffer))!=-1){ os.write(buffer,0,len); os.flush(); } os.close(); is.close(); return "redirect:/index.jsp"; } }5、测试上传文件,OK!采用file.Transto 来保存上传的文件1、编写Controller/* * 采用file.Transto 来保存上传的文件 */ @RequestMapping("/upload2") public String fileUpload2(@RequestParam("file") CommonsMultipartFile file, HttpServletRequest request) throws IOException { //上传路径保存设置 String path = request.getServletContext().getRealPath("/upload"); File realPath = new File(path); if (!realPath.exists()){ realPath.mkdir(); } //上传文件地址 System.out.println("上传文件保存地址:"+realPath); //通过CommonsMultipartFile的方法直接写文件(注意这个时候) file.transferTo(new File(realPath +"/"+ file.getOriginalFilename())); return "redirect:/index.jsp"; }2、前端表单提交地址修改3、访问提交测试,OK!11.3 文件下载文件下载步骤:1、设置 response 响应头2、读取文件 -- InputStream3、写出文件 -- OutputStream4、执行操作5、关闭流 (先开后关)代码实现:@RequestMapping(value="/download") public String downloads(HttpServletResponse response ,HttpServletRequest request) throws Exception{ //要下载的图片地址 String path = request.getServletContext().getRealPath("/upload"); String fileName = "基础语法.jpg"; //1、设置response 响应头 response.reset(); //设置页面不缓存,清空buffer response.setCharacterEncoding("UTF-8"); //字符编码 response.setContentType("multipart/form-data"); //二进制传输数据 //设置响应头 response.setHeader("Content-Disposition", "attachment;fileName="+URLEncoder.encode(fileName, "UTF-8")); File file = new File(path,fileName); //2、 读取文件--输入流 InputStream input=new FileInputStream(file); //3、 写出文件--输出流 OutputStream out = response.getOutputStream(); byte[] buff =new byte[1024]; int index=0; //4、执行 写出操作 while((index= input.read(buff))!= -1){ out.write(buff, 0, index); out.flush(); } out.close(); input.close(); return null; }前端<a href="/download">点击下载</a>测试,文件下载OK,大家可以和我们之前学习的JavaWeb原生的方式对比一下,就可以知道这个便捷多了!参考资料【狂神说Java】SpringBoot最新教程IDEA版通俗易懂SpringMVC运行原理
2022年05月05日
902 阅读
0 评论
0 点赞
2022-04-28
spring5学习笔记
1.spring简介1.1 介绍spring:春天--给软件行业带来了春天2002年,首次推出了spring框架的雏形:interface212004年3月24日诞生Rod Johnson,Spring Framework创始人spring理念:使现有的技术更加容易复用,本身是一个大杂烩,整合了现有的技术框架官网:https://spring.io/Spring Framework 中文文档:https://www.docs4dev.com/docs/zh/spring-framework/官方下载地址 : https://repo.spring.io/libs-release-local/org/springframework/spring/GitHub : https://github.com/spring-projectsjava两套框架SSH:Struct2+Spring+HibernateSSH:SpringMVC+Spring+Mybatis1.2 优点spring是一个开源免费的框架(容器)spring是一个轻量级的、非入侵式的框架支持事务处理支持对框架整合的指出1.3 核心特点控制反转(IOC)面向切面编程(AOP)1.4 组成Spring 框架是一个分层架构,由 7 个定义良好的模块组成。Spring 模块构建在核心容器之上,核心容器定义了创建、配置和管理 bean 的方式 .组成 Spring 框架的每个模块(或组件)都可以单独存在,或者与其他一个或多个模块联合实现。每个模块的功能如下:核心容器:核心容器提供 Spring 框架的基本功能。核心容器的主要组件是 BeanFactory,它是工厂模式的实现。BeanFactory 使用控制反转(IOC) 模式将应用程序的配置和依赖性规范与实际的应用程序代码分开。Spring 上下文:Spring 上下文是一个配置文件,向 Spring 框架提供上下文信息。Spring 上下文包括企业服务,例如 JNDI、EJB、电子邮件、国际化、校验和调度功能。Spring AOP:通过配置管理特性,Spring AOP 模块直接将面向切面的编程功能 , 集成到了 Spring 框架中。所以,可以很容易地使 Spring 框架管理任何支持 AOP的对象。Spring AOP 模块为基于 Spring 的应用程序中的对象提供了事务管理服务。通过使用 Spring AOP,不用依赖组件,就可以将声明性事务管理集成到应用程序中。Spring DAO:JDBC DAO 抽象层提供了有意义的异常层次结构,可用该结构来管理异常处理和不同数据库供应商抛出的错误消息。异常层次结构简化了错误处理,并且极大地降低了需要编写的异常代码数量(例如打开和关闭连接)。Spring DAO 的面向 JDBC 的异常遵从通用的 DAO 异常层次结构。Spring ORM:Spring 框架插入了若干个 ORM 框架,从而提供了 ORM 的对象关系工具,其中包括 JDO、Hibernate 和 iBatis SQL Map。所有这些都遵从 Spring 的通用事务和 DAO 异常层次结构。Spring Web 模块:Web 上下文模块建立在应用程序上下文模块之上,为基于 Web 的应用程序提供了上下文。所以,Spring 框架支持与 Jakarta Struts 的集成。Web 模块还简化了处理多部分请求以及将请求参数绑定到域对象的工作。Spring MVC 框架:MVC 框架是一个全功能的构建 Web 应用程序的 MVC 实现。通过策略接口,MVC 框架变成为高度可配置的,MVC 容纳了大量视图技术,其中包括 JSP、Velocity、Tiles、iText 和 POI。1.5 拓展在Spring的官网有这个介绍:现代化的Java开发!说白就是基于Spring的开发!Spring Boot与Spring CloudSpring Boot 是 Spring 的一套快速配置脚手架,可以基于Spring Boot 快速开发单个微服务;Spring Cloud是基于Spring Boot实现的;Spring Boot专注于快速、方便集成的单个微服务个体,Spring Cloud关注全局的服务治理框架;Spring Boot使用了约束优于配置的理念,很多集成方案已经帮你选择好了,能不配置就不配置 , Spring Cloud很大的一部分是基于Spring Boot来实现,Spring Boot可以离开Spring Cloud独立使用开发项目,但是Spring Cloud离不开Spring Boot,属于依赖的关系。SpringBoot在SpringClound中起到了承上启下的作用,如果你要学习SpringCloud必须要学习SpringBoot。。因为现在大多数公司都在使用SpringBoot进行快速开发,学习SpringBoot的前提,需要完全掌握Spring及SpringMVC!1.6 弊端弊端:发展了太久之后,违背了原来的理念!配置十分繁琐,人称:“配置地狱!”1.7 小结总结一句话:Spring是一个轻量级的控制反转(IOC)和面向切面编程(AOP)的框架。2. IOC理论推导2.1 分析实现0、新建一个空白的maven项目我们先用我们原来的方式写一段代码 .1、先写一个UserDao接口public interface UserDao { public void getUser(); }2、再去写Dao的实现类public class UserDaoImpl implements UserDao { @Override public void getUser() { System.out.println("获取用户数据"); } }3、然后去写UserService的接口public interface UserService { public void getUser(); }4、最后写Service的实现类public class UserServiceImpl implements UserService { private UserDao userDao = new UserDaoImpl(); @Override public void getUser() { userDao.getUser(); } }5、测试一下@Test public void test(){ UserService service = new UserServiceImpl(); service.getUser(); }这是我们原来的方式 , 开始大家也都是这么去写的对吧 . 那我们现在修改一下 .把Userdao的实现类增加一个 .public class UserDaoMySqlImpl implements UserDao { @Override public void getUser() { System.out.println("MySql获取用户数据"); } }紧接着我们要去使用MySql的话 , 我们就需要去service实现类里面修改对应的实现public class UserServiceImpl implements UserService { private UserDao userDao = new UserDaoMySqlImpl(); @Override public void getUser() { userDao.getUser(); } }在假设, 我们再增加一个Userdao的实现类 .public class UserDaoOracleImpl implements UserDao { @Override public void getUser() { System.out.println("Oracle获取用户数据"); } }那么我们要使用Oracle , 又需要去service实现类里面修改对应的实现 . 假设我们的这种需求非常大 , 这种方式就根本不适用了, 甚至反人类对吧 , 每次变动 , 都需要修改大量代码 . 这种设计的耦合性太高了, 牵一发而动全身 .那我们如何去解决呢 ? 我们可以在需要用到他的地方 , 不去实现它 , 而是留出一个接口 , 利用set , 我们去代码里修改下 .public class UserServiceImpl implements UserService { private UserDao userDao; // 利用set实现 public void setUserDao(UserDao userDao) { this.userDao = userDao; } @Override public void getUser() { userDao.getUser(); } }现在去我们的测试类里 , 进行测试 ;@Test public void test(){ UserServiceImpl service = new UserServiceImpl(); service.setUserDao( new UserDaoMySqlImpl() ); service.getUser(); //那我们现在又想用Oracle去实现呢 service.setUserDao( new UserDaoOracleImpl() ); service.getUser(); }大家发现了区别没有 ? 可能很多人说没啥区别 . 但是同学们 , 他们已经发生了根本性的变化 , 很多地方都不一样了 . 仔细去思考一下 , 以前所有东西都是由程序去进行控制创建 , 而现在是由我们自行控制创建对象 , 把主动权交给了调用者 . 程序不用去管怎么创建,怎么实现了 . 它只负责提供一个接口 .这种思想 , 从本质上解决了问题 , 我们程序员不再去管理对象的创建了 , 更多的去关注业务的实现 . 耦合性大大降低 . 这也就是IOC的原型 !2.2 IOC本质控制反转IoC(Inversion of Control),是一种设计思想,DI(依赖注入)是实现IoC的一种方法,也有人认为DI只是IoC的另一种说法。没有IoC的程序中 , 我们使用面向对象编程 , 对象的创建与对象间的依赖关系完全硬编码在程序中,对象的创建由程序自己控制,控制反转后将对象的创建转移给第三方,个人认为所谓控制反转就是:获得依赖对象的方式反转了。IoC是Spring框架的核心内容,使用多种方式完美的实现了IoC,可以使用XML配置,也可以使用注解,新版本的Spring也可以零配置实现IoC。Spring容器在初始化时先读取配置文件,根据配置文件或元数据创建与组织对象存入容器中,程序使用时再从Ioc容器中取出需要的对象。采用XML方式配置Bean的时候,Bean的定义信息是和实现分离的,而采用注解的方式可以把两者合为一体,Bean的定义信息直接以注解的形式定义在实现类中,从而达到了零配置的目的。控制反转是一种通过描述(XML或注解)并通过第三方去生产或获取特定对象的方式。在Spring中实现控制反转的是IoC容器,其实现方法是依赖注入(Dependency Injection,DI)。3.HelloSpring3.1 实现HelloSpring0、导入Jar包<dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.1.10.RELEASE</version> </dependency>1、编写一个Hello实体类package com.kuang.pojo; public class Hello { private String name; public String getName(){ return name; } public void setName(String name){ this.name = name; } public void show(){ System.out.println("Hello"+name); } }2、编写我们的spring文件 , 这里我们命名为beans.xml<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!--bean就是java对象 , 由Spring创建和管理--> <bean id="hello" class="com.kuang.pojo.Hello"> <property name="name" value="Spring"/> </bean> </beans>3、我们可以去进行测试了 .@Test public void test(){ //解析beans.xml文件 , 生成管理相应的Bean对象 ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); //getBean : 参数即为spring配置文件中bean的id . Hello hello = (Hello) context.getBean("hello"); hello.show(); }3.2 思考Hello 对象是谁创建的 ? hello 对象是由Spring创建的Hello 对象的属性是怎么设置的 ? hello 对象的属性是由Spring容器设置的这个过程就叫控制反转 :控制 : 谁来控制对象的创建 , 传统应用程序的对象是由程序本身控制创建的 , 使用Spring后 , 对象是由Spring来创建的反转 : 程序本身不创建对象 , 而变成被动的接收对象 .依赖注入 : 就是利用set方法来进行注入的.IOC是一种编程思想,由主动的编程变成被动的接收可以通过newClassPathXmlApplicationContext去浏览一下底层源码 .3.3 修改案例一我们在案例一中, 新增一个Spring配置文件beans.xml<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="MysqlImpl" class="com.kuang.dao.impl.UserDaoMySqlImpl"/> <bean id="OracleImpl" class="com.kuang.dao.impl.UserDaoOracleImpl"/> <bean id="ServiceImpl" class="com.kuang.service.impl.UserServiceImpl"> <!--注意: 这里的name并不是属性 , 而是set方法后面的那部分 , 首字母小写--> <!--引用另外一个bean , 不是用value 而是用 ref--> <property name="userDao" ref="OracleImpl"/> </bean> </beans>测试!@Test public void test2(){ ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); UserServiceImpl serviceImpl = (UserServiceImpl) context.getBean("ServiceImpl"); serviceImpl.getUser(); }OK , 到了现在 , 我们彻底不用再程序中去改动了 , 要实现不同的操作 , 只需要在xml配置文件中进行修改 , 所谓的IoC,一句话搞定 : 对象由Spring 来创建 , 管理 , 装配 !3.4 IOC创建对象方式3.4.1 通过无参构造方法来创建1、User.javapublic class User { private String name; public User() { System.out.println("user无参构造方法"); } public void setName(String name) { this.name = name; } public void show(){ System.out.println("name="+ name ); } }2、beans.xml<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="user" class="com.kuang.pojo.User"> <property name="name" value="kuangshen"/> </bean> </beans>3、测试类@Test public void test(){ ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); //在执行getBean的时候, user已经创建好了 , 通过无参构造 User user = (User) context.getBean("user"); //调用对象的方法 . user.show(); }结果可以发现,在调用show方法之前,User对象已经通过无参构造初始化了!3.4.2 通过有参构造方法来创建1、UserT . javapublic class UserT { private String name; public UserT(String name) { this.name = name; } public void setName(String name) { this.name = name; } public void show(){ System.out.println("name="+ name ); } }2、beans.xml 有三种方式编写<!-- 第一种根据index参数下标设置 --> <bean id="userT" class="com.kuang.pojo.UserT"> <!-- index指构造方法 , 下标从0开始 --> <constructor-arg index="0" value="kuangshen2"/> </bean> <!-- 第二种根据参数名字设置 --> <bean id="userT" class="com.kuang.pojo.UserT"> <!-- name指参数名 --> <constructor-arg name="name" value="kuangshen2"/> </bean> <!-- 第三种根据参数类型设置 --> <bean id="userT" class="com.kuang.pojo.UserT"> <constructor-arg type="java.lang.String" value="kuangshen2"/> </bean>3、测试@Test public void testT(){ ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); UserT user = (UserT) context.getBean("userT"); user.show(); }结论:在配置文件加载的时候。其中管理的对象都已经初始化了!3.5 其他Spring配置3.5.1 别名alias 设置别名 , 为bean设置别名 , 可以设置多个别名<!--设置别名:在获取Bean的时候可以使用别名获取--> <alias name="userT" alias="userNew"/>3.5.2 Bean的配置<!--bean就是java对象,由Spring创建和管理--> <!-- id 是bean的标识符,要唯一,如果没有配置id,name就是默认标识符 如果配置id,又配置了name,那么name是别名 name可以设置多个别名,可以用逗号,分号,空格隔开 如果不配置id和name,可以根据applicationContext.getBean(.class)获取对象; class是bean的全限定名=包名+类名 --> <bean id="hello" name="hello2 h2,h3;h4" class="com.kuang.pojo.Hello"> <property name="name" value="Spring"/> </bean>3.5.3 import团队的合作通过import来实现 .<import resource="{path}/beans.xml"/>4.依赖注入(DI)4.1 概念依赖注入(Dependency Injection,DI)。依赖 : 指Bean对象的创建依赖于容器 . Bean对象的依赖资源 .注入 : 指Bean对象所依赖的资源 , 由容器来设置和装配 .4.2 构造器注入我们在之前的案例已经讲过了4.3 Set 注入 (重点)要求被注入的属性 , 必须有set方法 , set方法的方法名由set + 属性首字母大写 , 如果属性是boolean类型 , 没有set方法 , 是 is .测试pojo类 :Address.java public class Address { private String address; public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } }Student.java package com.kuang.pojo; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; public class Student { private String name; private Address address; private String[] books; private List<String> hobbys; private Map<String,String> card; private Set<String> games; private String wife; private Properties info; public void setName(String name) { this.name = name; } public void setAddress(Address address) { this.address = address; } public void setBooks(String[] books) { this.books = books; } public void setHobbys(List<String> hobbys) { this.hobbys = hobbys; } public void setCard(Map<String, String> card) { this.card = card; } public void setGames(Set<String> games) { this.games = games; } public void setWife(String wife) { this.wife = wife; } public void setInfo(Properties info) { this.info = info; } public void show(){ System.out.println("name="+ name + ",address="+ address.getAddress() + ",books=" ); for (String book:books){ System.out.print("<<"+book+">>\t"); } System.out.println("\n爱好:"+hobbys); System.out.println("card:"+card); System.out.println("games:"+games); System.out.println("wife:"+wife); System.out.println("info:"+info); } }1、常量注入 <bean id="student" class="com.kuang.pojo.Student"> <property name="name" value="小明"/> </bean>测试: @Test public void test01(){ ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); Student student = (Student) context.getBean("student"); System.out.println(student.getName()); }2、Bean注入 注意点:这里的值是一个引用,ref <bean id="addr" class="com.kuang.pojo.Address"> <property name="address" value="重庆"/> </bean> <bean id="student" class="com.kuang.pojo.Student"> <property name="name" value="小明"/> <property name="address" ref="addr"/> </bean>3、数组注入 <bean id="student" class="com.kuang.pojo.Student"> <property name="name" value="小明"/> <property name="address" ref="addr"/> <property name="books"> <array> <value>西游记</value> <value>红楼梦</value> <value>水浒传</value> </array> </property> </bean>4、List注入 <property name="hobbys"> <list> <value>听歌</value> <value>看电影</value> <value>爬山</value> </list> </property>5、Map注入 <property name="card"> <map> <entry key="中国邮政" value="456456456465456"/> <entry key="建设" value="1456682255511"/> </map> </property>6、set注入 <property name="games"> <set> <value>LOL</value> <value>BOB</value> <value>COC</value> </set> </property>7、Null注入 <property name="wife"><null/></property>8、Properties注入 <property name="info"> <props> <prop key="学号">20190604</prop> <prop key="性别">男</prop> <prop key="姓名">小明</prop> </props> </property>测试结果:4.4 p命名和c命名注入User.java :【注意:这里没有有参构造器!】 public class User { private String name; private int age; public void setName(String name) { this.name = name; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "User{" + "name='" + name + '\'' + ", age=" + age + '}'; } }1、P命名空间注入 : 需要在头文件中加入约束文件 导入约束 : xmlns:p="http://www.springframework.org/schema/p" <!--P(属性: properties)命名空间 , 属性依然要设置set方法--> <bean id="user" class="com.kuang.pojo.User" p:name="狂神" p:age="18"/>2、c 命名空间注入 : 需要在头文件中加入约束文件 导入约束 : xmlns:c="http://www.springframework.org/schema/c" <!--C(构造: Constructor)命名空间 , 属性依然要设置set方法--> <bean id="user" class="com.kuang.pojo.User" c:name="狂神" c:age="18"/>发现问题:爆红了,刚才我们没有写有参构造!解决:把有参构造器加上,这里也能知道,c 就是所谓的构造器注入!测试代码: @Test public void test02(){ ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); User user = (User) context.getBean("user"); System.out.println(user); }4.5 Bean的作用域在Spring中,那些组成应用程序的主体及由Spring IoC容器所管理的对象,被称之为bean。简单地讲,bean就是由IoC容器初始化、装配及管理的对象 .几种作用域中,request、session作用域仅在基于web的应用中使用(不必关心你所采用的是什么web应用框架),只能用在基于web的Spring ApplicationContext环境。Singleton当一个bean的作用域为Singleton,那么Spring IoC容器中只会存在一个共享的bean实例,并且所有对bean的请求,只要id与该bean定义相匹配,则只会返回bean的同一实例。Singleton是单例类型,就是在创建起容器时就同时自动创建了一个bean的对象,不管你是否使用,他都存在了,每次获取到的对象都是同一个对象。注意,Singleton作用域是Spring中的缺省作用域。要在XML中将bean定义成singleton,可以这样配置: <bean id="ServiceImpl" class="cn.csdn.service.ServiceImpl" scope="singleton">测试: @Test public void test03(){ ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); User user = (User) context.getBean("user"); User user2 = (User) context.getBean("user"); System.out.println(user==user2); }Prototype当一个bean的作用域为Prototype,表示一个bean定义对应多个对象实例。Prototype作用域的bean会导致在每次对该bean请求(将其注入到另一个bean中,或者以程序的方式调用容器的getBean()方法)时都会创建一个新的bean实例。Prototype是原型类型,它在我们创建容器的时候并没有实例化,而是当我们获取bean的时候才会去创建一个对象,而且我们每次获取到的对象都不是同一个对象。根据经验,对有状态的bean应该使用prototype作用域,而对无状态的bean则应该使用singleton作用域。在XML中将bean定义成prototype,可以这样配置: <bean id="account" class="com.foo.DefaultAccount" scope="prototype"/> 或者 <bean id="account" class="com.foo.DefaultAccount" singleton="false"/>Request当一个bean的作用域为Request,表示在一次HTTP请求中,一个bean定义对应一个实例;即每个HTTP请求都会有各自的bean实例,它们依据某个bean定义创建而成。该作用域仅在基于web的Spring ApplicationContext情形下有效。考虑下面bean定义: <bean id="loginAction" class=cn.csdn.LoginAction" scope="request"/>针对每次HTTP请求,Spring容器会根据loginAction bean的定义创建一个全新的LoginAction bean实例,且该loginAction bean实例仅在当前HTTP request内有效,因此可以根据需要放心的更改所建实例的内部状态,而其他请求中根据loginAction bean定义创建的实例,将不会看到这些特定于某个请求的状态变化。当处理请求结束,request作用域的bean实例将被销毁。Session当一个bean的作用域为Session,表示在一个HTTP Session中,一个bean定义对应一个实例。该作用域仅在基于web的Spring ApplicationContext情形下有效。考虑下面bean定义: <bean id="userPreferences" class="com.foo.UserPreferences" scope="session"/>针对某个HTTP Session,Spring容器会根据userPreferences bean定义创建一个全新的userPreferences bean实例,且该userPreferences bean仅在当前HTTP Session内有效。与request作用域一样,可以根据需要放心的更改所创建实例的内部状态,而别的HTTP Session中根据userPreferences创建的实例,将不会看到这些特定于某个HTTP Session的状态变化。当HTTP Session最终被废弃的时候,在该HTTP Session作用域内的bean也会被废弃掉。5.Bean的自动装配5.1 自动装配说明自动装配是使用spring满足bean依赖的一种方法spring会在应用上下文中为某个bean寻找其依赖的bean。Spring中bean有三种装配机制,分别是:在xml中显式配置;在java中显式配置;隐式的bean发现机制和自动装配。这里我们主要讲第三种:自动化的装配bean。Spring的自动装配需要从两个角度来实现,或者说是两个操作:组件扫描(component scanning):spring会自动发现应用上下文中所创建的bean;自动装配(autowiring):spring自动满足bean之间的依赖,也就是我们说的IoC/DI;组件扫描和自动装配组合发挥巨大威力,使得显示的配置降低到最少。推荐不使用自动装配xml配置 , 而使用注解 .5.2 测试环境搭建1、新建一个项目2、新建两个实体类,Cat Dog 都有一个叫的方法public class Cat { public void shout() { System.out.println("miao~"); } } public class Dog { public void shout() { System.out.println("wang~"); } }3、新建一个用户类 Userpublic class User { private Cat cat; private Dog dog; private String str; }4、编写Spring配置文件<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="dog" class="com.kuang.pojo.Dog"/> <bean id="cat" class="com.kuang.pojo.Cat"/> <bean id="user" class="com.kuang.pojo.User"> <property name="cat" ref="cat"/> <property name="dog" ref="dog"/> <property name="str" value="qinjiang"/> </bean> </beans>5、测试public class MyTest { @Test public void testMethodAutowire() { ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); User user = (User) context.getBean("user"); user.getCat().shout(); user.getDog().shout(); } }结果正常输出,环境OK5.3 autowire byName (按名称自动装配)由于在手动配置xml过程中,常常发生字母缺漏和大小写等错误,而无法对其进行检查,使得开发效率降低。采用自动装配将避免这些错误,并且使配置简单化。测试:1、修改bean配置,增加一个属性 autowire="byName"<bean id="user" class="com.kuang.pojo.User" autowire="byName"> <property name="str" value="qinjiang"/> </bean>2、再次测试,结果依旧成功输出!3、我们将 cat 的bean id修改为 catXXX4、再次测试, 执行时报空指针java.lang.NullPointerException。因为按byName规则找不对应set方法,真正的setCat就没执行,对象就没有初始化,所以调用时就会报空指针错误。小结:当一个bean节点带有 autowire byName的属性时。将查找其类中所有的set方法名,例如setCat,获得将set去掉并且首字母小写的字符串,即cat。去spring容器中寻找是否有此字符串名称id的对象。如果有,就取出注入;如果没有,就报空指针异常。5.4 autowire byType (按类型自动装配)使用autowire byType首先需要保证:同一类型的对象,在spring容器中唯一。如果不唯一,会报不唯一的异常。测试:1、将user的bean配置修改一下 : autowire="byType"2、测试,正常输出3、在注册一个cat 的bean对象!<bean id="dog" class="com.kuang.pojo.Dog"/> <bean id="cat" class="com.kuang.pojo.Cat"/> <bean id="cat2" class="com.kuang.pojo.Cat"/> <bean id="user" class="com.kuang.pojo.User" autowire="byType"> <property name="str" value="qinjiang"/> </bean>4、测试,报错:NoUniqueBeanDefinitionException5、删掉cat2,将cat的bean名称改掉!测试!因为是按类型装配,所以并不会报异常,也不影响最后的结果。甚至将id属性去掉,也不影响结果。这就是按照类型自动装配!5.5 使用注解jdk1.5开始支持注解,spring2.5开始全面支持注解。准备工作:利用注解的方式注入属性。1、在spring配置文件中引入context文件头xmlns:context="http://www.springframework.org/schema/context" http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/context/spring-aop.xsd">2、开启属性注解支持!<context:annotation-config/>@Autowired@Autowired是按类型自动转配的,不支持id匹配。需要导入 spring-aop的包!测试:1、将User类中的set方法去掉,使用@Autowired注解public class User { @Autowired private Cat cat; @Autowired private Dog dog; private String str; public Cat getCat() { return cat; } public Dog getDog() { return dog; } public String getStr() { return str; } }2、此时配置文件内容<context:annotation-config/> <bean id="dog" class="com.kuang.pojo.Dog"/> <bean id="cat" class="com.kuang.pojo.Cat"/> <bean id="user" class="com.kuang.pojo.User"/>3、测试,成功输出结果!【小狂神科普时间】@Autowired(required=false) 说明:false,对象可以为null;true,对象必须存对象,不能为null。//如果允许对象为null,设置required = false,默认为true @Autowired(required = false) private Cat cat;@Qualifier@Autowired是根据类型+id自动装配的,加上@Qualifier则可以根据byName的方式自动装配@Qualifier不能单独使用。测试实验步骤:1、配置文件修改内容,保证类型存在对象。且名字不为类的默认名字!<bean id="dog1" class="com.kuang.pojo.Dog"/> <bean id="dog2" class="com.kuang.pojo.Dog"/> <bean id="cat1" class="com.kuang.pojo.Cat"/> <bean id="cat2" class="com.kuang.pojo.Cat"/>2、没有加Qualifier测试,直接报错3、在属性上添加Qualifier注解@Autowired @Qualifier(value = "cat2") private Cat cat; @Autowired @Qualifier(value = "dog2") private Dog dog;测试,成功输出!@Resource@Resource如有指定的name属性,先按该属性进行byName方式查找装配;其次再进行默认的byName方式进行装配;如果以上都不成功,则按byType的方式自动装配。都不成功,则报异常。实体类:public class User { //如果允许对象为null,设置required = false,默认为true @Resource(name = "cat2") private Cat cat; @Resource private Dog dog; private String str; }beans.xml<bean id="dog" class="com.kuang.pojo.Dog"/> <bean id="cat1" class="com.kuang.pojo.Cat"/> <bean id="cat2" class="com.kuang.pojo.Cat"/> <bean id="user" class="com.kuang.pojo.User"/>测试:结果OK配置文件2:beans.xml , 删掉cat2<bean id="dog" class="com.kuang.pojo.Dog"/> <bean id="cat1" class="com.kuang.pojo.Cat"/>实体类上只保留注解@Resource private Cat cat; @Resource private Dog dog;结果:OK结论:先进行byName查找,失败;再进行byType查找,成功。@Autowired与@Resource异同:1、@Autowired与@Resource都可以用来装配bean。都可以写在字段上,或写在setter方法上。2、@Autowired默认按类型装配(属于spring规范),默认情况下必须要求依赖对象必须存在,如果要允许null 值,可以设置它的required属性为false,如:@Autowired(required=false) ,如果我们想使用名称装配可以结合@Qualifier注解进行使用3、@Resource(属于J2EE复返),默认按照名称进行装配,名称可以通过name属性进行指定。如果没有指定name属性,当注解写在字段上时,默认取字段名进行按照名称查找,如果注解写在setter方法上默认取属性名进行装配。当找不到与名称匹配的bean时才按照类型进行装配。但是需要注意的是,如果name属性一旦指定,就只会按照名称进行装配。它们的作用相同都是用注解方式注入对象,但执行顺序不同。@Autowired先byType,@Resource先byName。6. 静态/动态代理模式为什么要学习代理模式,因为AOP的底层机制就是动态代理!代理模式:静态代理动态代理学习aop之前 , 我们要先了解一下代理模式!6.1 静态代理静态代理角色分析抽象角色 : 一般使用接口或者抽象类来实现真实角色 : 被代理的角色代理角色 : 代理真实角色 ; 代理真实角色后 , 一般会做一些附属的操作 .客户 : 使用代理角色来进行一些操作 .代码实现Rent . java 即抽象角色//抽象角色:租房 public interface Rent { public void rent(); }Host . java 即真实角色//真实角色: 房东,房东要出租房子 public class Host implements Rent{ public void rent() { System.out.println("房屋出租"); } }Proxy . java 即代理角色//代理角色:中介 public class Proxy implements Rent { private Host host; public Proxy() { } public Proxy(Host host) { this.host = host; } //租房 public void rent(){ seeHouse(); host.rent(); fare(); } //看房 public void seeHouse(){ System.out.println("带房客看房"); } //收中介费 public void fare(){ System.out.println("收中介费"); } }Client . java 即客户//客户类,一般客户都会去找代理! public class Client { public static void main(String[] args) { //房东要租房 Host host = new Host(); //中介帮助房东 Proxy proxy = new Proxy(host); //你去找中介! proxy.rent(); } }分析:在这个过程中,你直接接触的就是中介,就如同现实生活中的样子,你看不到房东,但是你依旧租到了房东的房子通过代理,这就是所谓的代理模式,程序源自于生活,所以学编程的人,一般能够更加抽象的看待生活中发生的事情。静态代理的好处:可以使得我们的真实角色更加纯粹 . 不再去关注一些公共的事情 .公共的业务由代理来完成 . 实现了业务的分工 ,公共业务发生扩展时变得更加集中和方便 .缺点 :类多了 , 多了代理类 , 工作量变大了 . 开发效率降低 .我们想要静态代理的好处,又不想要静态代理的缺点,所以 , 就有了动态代理 !6.2 静态代理再理解同学们练习完毕后,我们再来举一个例子,巩固大家的学习!练习步骤:1、创建一个抽象角色,比如咋们平时做的用户业务,抽象起来就是增删改查!//抽象角色:增删改查业务 public interface UserService { void add(); void delete(); void update(); void query(); }2、我们需要一个真实对象来完成这些增删改查操作//真实对象,完成增删改查操作的人 public class UserServiceImpl implements UserService { public void add() { System.out.println("增加了一个用户"); } public void delete() { System.out.println("删除了一个用户"); } public void update() { System.out.println("更新了一个用户"); } public void query() { System.out.println("查询了一个用户"); } }3、需求来了,现在我们需要增加一个日志功能,怎么实现!思路1 :在实现类上增加代码 【麻烦!】思路2:使用代理来做,能够不改变原来的业务情况下,实现此功能就是最好的了!4、设置一个代理类来处理日志!代理角色//代理角色,在这里面增加日志的实现 public class UserServiceProxy implements UserService { private UserServiceImpl userService; public void setUserService(UserServiceImpl userService) { this.userService = userService; } public void add() { log("add"); userService.add(); } public void delete() { log("delete"); userService.delete(); } public void update() { log("update"); userService.update(); } public void query() { log("query"); userService.query(); } public void log(String msg){ System.out.println("执行了"+msg+"方法"); } }5、测试访问类:public class Client { public static void main(String[] args) { //真实业务 UserServiceImpl userService = new UserServiceImpl(); //代理类 UserServiceProxy proxy = new UserServiceProxy(); //使用代理类实现日志功能! proxy.setUserService(userService); proxy.add(); } }OK,到了现在代理模式大家应该都没有什么问题了,重点大家需要理解其中的思想;我们在不改变原来的代码的情况下,实现了对原有功能的增强,这是AOP中最核心的思想聊聊AOP:纵向开发,横向开发6.3 动态代理动态代理的角色和静态代理的一样 .动态代理的代理类是动态生成的 . 静态代理的代理类是我们提前写好的动态代理分为两类 : 一类是基于接口动态代理 , 一类是基于类的动态代理基于接口的动态代理----JDK动态代理基于类的动态代理--cglib现在用的比较多的是 javasist 来生成动态代理 . 百度一下javasist我们这里使用JDK的原生代码来实现,其余的道理都是一样的!、JDK的动态代理需要了解两个类核心 : InvocationHandler 和 Proxy , 打开JDK帮助文档看看【InvocationHandler:调用处理程序】Object invoke(Object proxy, 方法 method, Object[] args); //参数 //proxy - 调用该方法的代理实例 //method -所述方法对应于调用代理实例上的接口方法的实例。方法对象的声明类将是该方法声明的接口,它可以是代理类继承该方法的代理接口的超级接口。 //args -包含的方法调用传递代理实例的参数值的对象的阵列,或null如果接口方法没有参数。原始类型的参数包含在适当的原始包装器类的实例中,例如java.lang.Integer或java.lang.Boolean 。【Proxy : 代理】//生成代理类 public Object getProxy(){ return Proxy.newProxyInstance(this.getClass().getClassLoader(), rent.getClass().getInterfaces(),this); }代码实现 抽象角色和真实角色和之前的一样!Rent . java 即抽象角色//抽象角色:租房 public interface Rent { public void rent(); }Host . java 即真实角色//真实角色: 房东,房东要出租房子 public class Host implements Rent{ public void rent() { System.out.println("房屋出租"); } }ProxyInvocationHandler. java 即代理角色public class ProxyInvocationHandler implements InvocationHandler { private Rent rent; public void setRent(Rent rent) { this.rent = rent; } //生成代理类,重点是第二个参数,获取要代理的抽象角色!之前都是一个角色,现在可以代理一类角色 public Object getProxy(){ return Proxy.newProxyInstance(this.getClass().getClassLoader(), rent.getClass().getInterfaces(),this); } // proxy : 代理类 method : 代理类的调用处理程序的方法对象. // 处理代理实例上的方法调用并返回结果 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { seeHouse(); //核心:本质利用反射实现! Object result = method.invoke(rent, args); fare(); return result; } //看房 public void seeHouse(){ System.out.println("带房客看房"); } //收中介费 public void fare(){ System.out.println("收中介费"); } }Client . java//租客 public class Client { public static void main(String[] args) { //真实角色 Host host = new Host(); //代理实例的调用处理程序 ProxyInvocationHandler pih = new ProxyInvocationHandler(); pih.setRent(host); //将真实角色放置进去! Rent proxy = (Rent)pih.getProxy(); //动态生成对应的代理类! proxy.rent(); } }核心:一个动态代理 , 一般代理某一类业务 , 一个动态代理可以代理多个类,代理的是接口!、6.4 深化理解我们来使用动态代理实现代理我们后面写的UserService!我们也可以编写一个通用的动态代理实现的类!所有的代理对象设置为Object即可!public class ProxyInvocationHandler implements InvocationHandler { private Object target; public void setTarget(Object target) { this.target = target; } //生成代理类 public Object getProxy(){ return Proxy.newProxyInstance(this.getClass().getClassLoader(), target.getClass().getInterfaces(),this); } // proxy : 代理类 // method : 代理类的调用处理程序的方法对象. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { log(method.getName()); Object result = method.invoke(target, args); return result; } public void log(String methodName){ System.out.println("执行了"+methodName+"方法"); } }测试!public class Test { public static void main(String[] args) { //真实对象 UserServiceImpl userService = new UserServiceImpl(); //代理对象的调用处理程序 ProxyInvocationHandler pih = new ProxyInvocationHandler(); pih.setTarget(userService); //设置要代理的对象 UserService proxy = (UserService)pih.getProxy(); //动态生成代理类! proxy.delete(); } }测试,增删改查,查看结果!动态代理的好处静态代理有的它都有,静态代理没有的,它也有!可以使得我们的真实角色更加纯粹 . 不再去关注一些公共的事情 .公共的业务由代理来完成 . 实现了业务的分工 ,公共业务发生扩展时变得更加集中和方便 .一个动态代理 , 一般代理某一类业务一个动态代理可以代理多个类,代理的是接口!7.AOP7.1 什么是AOPAOP(Aspect Oriented Programming)意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。7.2 Aop在Spring中的作用提供声明式事务;允许用户自定义切面以下名词需要了解下:横切关注点:跨越应用程序多个模块的方法或功能。即是,与我们业务逻辑无关的,但是我们需要关注的部分,就是横切关注点。如日志 , 安全 , 缓存 , 事务等等 ....切面(ASPECT):横切关注点 被模块化 的特殊对象。即,它是一个类。通知(Advice):切面必须要完成的工作。即,它是类中的一个方法。目标(Target):被通知对象。代理(Proxy):向目标对象应用通知之后创建的对象。切入点(PointCut):切面通知 执行的 “地点”的定义。连接点(JointPoint):与切入点匹配的执行点。SpringAOP中,通过Advice定义横切逻辑,Spring中支持5种类型的Advice:即 Aop 在 不改变原有代码的情况下 , 去增加新的功能 .7.3 使用Spring实现Aop【重点】使用AOP织入,需要导入一个依赖包!<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver --> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.4</version> </dependency>第一种方式:通过 Spring API 实现首先编写我们的业务接口和实现类public interface UserService { public void add(); public void delete(); public void update(); public void search(); } public class UserServiceImpl implements UserService{ @Override public void add() { System.out.println("增加用户"); } @Override public void delete() { System.out.println("删除用户"); } @Override public void update() { System.out.println("更新用户"); } @Override public void search() { System.out.println("查询用户"); } }然后去写我们的增强类 , 我们编写两个 , 一个前置增强 一个后置增强public class BeforeLog implements MethodBeforeAdvice { //method : 要执行的目标对象的方法 //objects : 被调用的方法的参数 //Object : 目标对象 @Override public void before(Method method, Object[] objects, Object o) throws Throwable { System.out.println( o.getClass().getName() + "的" + method.getName() + "方法被执行了"); } } public class AfterLog implements AfterReturningAdvice { //returnValue 返回值 //method被调用的方法 //args 被调用的方法的对象的参数 //target 被调用的目标对象 @Override public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable { System.out.println("执行了" + target.getClass().getName() +"的"+method.getName()+"方法," +"返回值:"+returnValue); } }最后去spring的文件中注册 , 并实现aop切入实现 , 注意导入约束 .<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!--注册bean--> <bean id="userService" class="com.kuang.service.UserServiceImpl"/> <bean id="beforLog" class="com.kuang.log.BeforeLog"/> <bean id="afterLog" class="com.kuang.log.AfterLog"/> <!--aop的配置--> <aop:config> <!--切入点 expression:表达式匹配要执行的方法--> <aop:pointcut id="pointcut" expression="execution(* com.kuang.service.UserServiceImpl.*(..))"/> <!--执行环绕; advice-ref执行方法 . pointcut-ref切入点--> <aop:advisor advice-ref="beforLog" pointcut-ref="pointcut"/> <aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/> </aop:config> </beans>测试public class MyTest { @Test public void test(){ ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); UserService userService = (UserService) context.getBean("userService"); userService.search(); } }Aop的重要性 : 很重要 . 一定要理解其中的思路 , 主要是思想的理解这一块 .Spring的Aop就是将公共的业务 (日志 , 安全等) 和领域业务结合起来 , 当执行领域业务时 , 将会把公共业务加进来 . 实现公共业务的重复利用 . 领域业务更纯粹 , 程序猿专注领域业务 , 其本质还是动态代理 .第二种方式:自定义类来实现Aop目标业务类不变依旧是userServiceImpl第一步 : 写我们自己的一个切入类public class DiyPointcut { public void before(){ System.out.println("---------方法执行前---------"); } public void after(){ System.out.println("---------方法执行后---------"); } }去spring中配置<!--第二种方式自定义实现--> <!--注册bean--> <bean id="diy" class="com.kuang.config.DiyPointcut"/> <!--aop的配置--> <aop:config> <!--第二种方式:使用AOP的标签实现--> <aop:aspect ref="diy"> <aop:pointcut id="diyPonitcut" expression="execution(* com.kuang.service.UserServiceImpl.*(..))"/> <aop:before pointcut-ref="diyPonitcut" method="before"/> <aop:after pointcut-ref="diyPonitcut" method="after"/> </aop:aspect> </aop:config>测试:public class MyTest { @Test public void test(){ ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); UserService userService = (UserService) context.getBean("userService"); userService.add(); } }第三种方式:使用注解实现第一步:编写一个注解实现的增强类package com.kuang.config; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; @Aspect public class AnnotationPointcut { @Before("execution(* com.kuang.service.UserServiceImpl.*(..))") public void before(){ System.out.println("---------方法执行前---------"); } @After("execution(* com.kuang.service.UserServiceImpl.*(..))") public void after(){ System.out.println("---------方法执行后---------"); } @Around("execution(* com.kuang.service.UserServiceImpl.*(..))") public void around(ProceedingJoinPoint jp) throws Throwable { System.out.println("环绕前"); System.out.println("签名:"+jp.getSignature()); //执行目标方法proceed Object proceed = jp.proceed(); System.out.println("环绕后"); System.out.println(proceed); } }第二步:在Spring配置文件中,注册bean,并增加支持注解的配置<!--第三种方式:注解实现--> <bean id="annotationPointcut" class="com.kuang.config.AnnotationPointcut"/> <aop:aspectj-autoproxy/>aop:aspectj-autoproxy:说明通过aop命名空间的<aop:aspectj-autoproxy />声明自动为spring容器中那些配置@aspectJ切面的bean创建代理,织入切面。当然,spring 在内部依旧采用AnnotationAwareAspectJAutoProxyCreator进行自动代理的创建工作,但具体实现的细节已经被<aop:aspectj-autoproxy />隐藏起来了 <aop:aspectj-autoproxy />有一个proxy-target-class属性,默认为false,表示使用jdk动态代理织入增强,当配为<aop:aspectj-autoproxy poxy-target-class="true"/>时,表示使用CGLib动态代理技术织入增强。不过即使proxy-target-class设置为false,如果目标类没有声明接口,则spring将自动使用CGLib动态代理。8.整合MyBatis8.1 步骤1、导入相关jar包<!--junit--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> <!--mybatis--> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.2</version> </dependency> <!--mysql-connector-java--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.47</version> </dependency> <!--spring相关--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.1.10.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.1.10.RELEASE</version> </dependency> <!--aspectJ AOP 织入器--> <!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver --> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.4</version> </dependency> <!--mybatis-spring整合包 【重点】--> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>2.0.2</version> </dependency>配置Maven静态资源过滤问题!<build> <resources> <resource> <directory>src/main/java</directory> <includes> <include>**/*.properties</include> <include>**/*.xml</include> </includes> <filtering>true</filtering> </resource> </resources> </build>2、编写配置文件3、代码实现8.2 回忆MyBatis编写pojo实体类package com.kuang.pojo; public class User { private int id; //id private String name; //姓名 private String pwd; //密码 }实现mybatis的配置文件<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <typeAliases> <package name="com.kuang.pojo"/> </typeAliases> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=utf8"/> <property name="username" value="root"/> <property name="password" value="123456"/> </dataSource> </environment> </environments> <mappers> <package name="com.kuang.dao"/> </mappers> </configuration>UserDao接口编写public interface UserMapper { public List<User> selectUser(); }接口对应的Mapper映射文件<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.kuang.dao.UserMapper"> <select id="selectUser" resultType="User"> select * from user </select> </mapper>测试类@Test public void selectUser() throws IOException { String resource = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); SqlSession sqlSession = sqlSessionFactory.openSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); List<User> userList = mapper.selectUser(); for (User user: userList){ System.out.println(user); } sqlSession.close(); }8.3 MyBatis-Spring学习引入Spring之前需要了解mybatis-spring包中的一些重要类;http://www.mybatis.org/spring/zh/index.html什么是 MyBatis-Spring?MyBatis-Spring 会帮助你将 MyBatis 代码无缝地整合到 Spring 中。知识基础在开始使用 MyBatis-Spring 之前,你需要先熟悉 Spring 和 MyBatis 这两个框架和有关它们的术语。这很重要MyBatis-Spring 需要以下版本:MyBatis-SpringMyBatisSpring 框架Spring BatchJava2.03.5+5.0+4.0+Java 8+1.33.4+3.2.2+2.1+Java 6+如果使用 Maven 作为构建工具,仅需要在 pom.xml 中加入以下代码即可:<dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>2.0.2</version> </dependency>要和 Spring 一起使用 MyBatis,需要在 Spring 应用上下文中定义至少两样东西:一个 SqlSessionFactory 和至少一个数据映射器类。在 MyBatis-Spring 中,可使用SqlSessionFactoryBean来创建 SqlSessionFactory。要配置这个工厂 bean,只需要把下面代码放在 Spring 的 XML 配置文件中:<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource" /> </bean>注意:SqlSessionFactory需要一个 DataSource(数据源)。这可以是任意的 DataSource,只需要和配置其它 Spring 数据库连接一样配置它就可以了。在基础的 MyBatis 用法中,是通过 SqlSessionFactoryBuilder 来创建 SqlSessionFactory 的。而在 MyBatis-Spring 中,则使用 SqlSessionFactoryBean 来创建。在 MyBatis 中,你可以使用 SqlSessionFactory 来创建 SqlSession。一旦你获得一个 session 之后,你可以使用它来执行映射了的语句,提交或回滚连接,最后,当不再需要它的时候,你可以关闭 session。SqlSessionFactory有一个唯一的必要属性:用于 JDBC 的 DataSource。这可以是任意的 DataSource 对象,它的配置方法和其它 Spring 数据库连接是一样的。一个常用的属性是 configLocation,它用来指定 MyBatis 的 XML 配置文件路径。它在需要修改 MyBatis 的基础配置非常有用。通常,基础配置指的是 < settings> 或 < typeAliases>元素。需要注意的是,这个配置文件并不需要是一个完整的 MyBatis 配置。确切地说,任何环境配置(),数据源()和 MyBatis 的事务管理器()都会被忽略。SqlSessionFactoryBean 会创建它自有的 MyBatis 环境配置(Environment),并按要求设置自定义环境的值。SqlSessionTemplate 是 MyBatis-Spring 的核心。作为 SqlSession 的一个实现,这意味着可以使用它无缝代替你代码中已经在使用的 SqlSession。模板可以参与到 Spring 的事务管理中,并且由于其是线程安全的,可以供多个映射器类使用,你应该总是用 SqlSessionTemplate 来替换 MyBatis 默认的 DefaultSqlSession 实现。在同一应用程序中的不同类之间混杂使用可能会引起数据一致性的问题。可以使用 SqlSessionFactory 作为构造方法的参数来创建 SqlSessionTemplate 对象。<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate"> <constructor-arg index="0" ref="sqlSessionFactory" /> </bean>现在,这个 bean 就可以直接注入到你的 DAO bean 中了。你需要在你的 bean 中添加一个 SqlSession 属性,就像下面这样:public class UserDaoImpl implements UserDao { private SqlSession sqlSession; public void setSqlSession(SqlSession sqlSession) { this.sqlSession = sqlSession; } public User getUser(String userId) { return sqlSession.getMapper...; } }按下面这样,注入 SqlSessionTemplate:<bean id="userDao" class="org.mybatis.spring.sample.dao.UserDaoImpl"> <property name="sqlSession" ref="sqlSession" /> </bean>8.4 整合实现一1、引入Spring配置文件beans.xml<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">2、配置数据源替换mybaits的数据源<!--配置数据源:数据源有非常多,可以使用第三方的,也可使使用Spring的--> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=utf8"/> <property name="username" value="root"/> <property name="password" value="123456"/> </bean>3、配置SqlSessionFactory,关联MyBatis<!--配置SqlSessionFactory--> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource"/> <!--关联Mybatis--> <property name="configLocation" value="classpath:mybatis-config.xml"/> <property name="mapperLocations" value="classpath:com/kuang/dao/*.xml"/> </bean>4、注册sqlSessionTemplate,关联sqlSessionFactory;<!--注册sqlSessionTemplate , 关联sqlSessionFactory--> <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate"> <!--利用构造器注入--> <constructor-arg index="0" ref="sqlSessionFactory"/> </bean>5、增加Dao接口的实现类;私有化sqlSessionTemplatepublic class UserMapperImpl implements UserMapper { //sqlSession不用我们自己创建了,Spring来管理 private SqlSessionTemplate sqlSession; public void setSqlSession(SqlSessionTemplate sqlSession) { this.sqlSession = sqlSession; } public List<User> selectUser() { UserMapper mapper = sqlSession.getMapper(UserMapper.class); return mapper.selectUser(); } }6、注册bean实现<bean id="UserMapper" class="com.kuang.mapper.UserMapperImpl"> <property name="sqlSession" ref="sqlSession"/> </bean>7、测试 @Test public void test2(){ ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); UserMapper mapper = (UserMapper) context.getBean("userDao"); List<User> user = mapper.selectUser(); System.out.println(user); }结果成功输出!现在我们的Mybatis配置文件的状态!发现都可以被Spring整合!<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <typeAliases> <package name="com.kuang.pojo"/> </typeAliases> </configuration>8.5 整合实现二mybatis-spring1.2.3版以上的才有这个 .官方文档截图 :dao继承Support类 , 直接利用 getSqlSession() 获得 , 然后直接注入SqlSessionFactory . 比起方式1 , 不需要管理SqlSessionTemplate , 而且对事务的支持更加友好 . 可跟踪源码查看测试:1、将我们上面写的UserDaoImpl修改一下public class UserDaoImpl extends SqlSessionDaoSupport implements UserMapper { public List<User> selectUser() { UserMapper mapper = getSqlSession().getMapper(UserMapper.class); return mapper.selectUser(); } }2、修改bean的配置<bean id="userDao" class="com.kuang.dao.UserDaoImpl"> <property name="sqlSessionFactory" ref="sqlSessionFactory" /> </bean>3、测试@Test public void test2(){ ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); UserMapper mapper = (UserMapper) context.getBean("userDao"); List<User> user = mapper.selectUser(); System.out.println(user); }9.声明式事务9.1 回顾事务事务在项目开发过程非常重要,涉及到数据的一致性的问题,不容马虎!事务管理是企业级应用程序开发中必备技术,用来确保数据的完整性和一致性。事务就是把一系列的动作当成一个独立的工作单元,这些动作要么全部完成,要么全部不起作用。事务四个属性ACID原子性(atomicity)事务是原子性操作,由一系列动作组成,事务的原子性确保动作要么全部完成,要么完全不起作用一致性(consistency)一旦所有事务动作完成,事务就要被提交。数据和资源处于一种满足业务规则的一致性状态中隔离性(isolation)可能多个事务会同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏持久性(durability)事务一旦完成,无论系统发生什么错误,结果都不会受到影响。通常情况下,事务的结果被写到持久化存储器中9.2 测试将上面的代码拷贝到一个新项目中在之前的案例中,我们给userDao接口新增两个方法,删除和增加用户;//添加一个用户 public int addUser(User user); //根据id删除用户 public int deleteUser(int id);mapper文件,我们故意把 deletes 写错,测试!<insert id="addUser" parameterType="com.kuang.pojo.User"> insert into user (id,name,pwd) values (#{id},#{name},#{pwd}) </insert> <delete id="deleteUser" parameterType="int"> deletes from user where id = #{id} </delete>编写接口的实现类,在实现类中,我们去操作一波public class UserDaoImpl extends SqlSessionDaoSupport implements UserMapper { //增加一些操作 public List<User> selectUser() { User user = new User(4,"小明","123456"); UserMapper mapper = getSqlSession().getMapper(UserMapper.class); mapper.addUser(user); mapper.deleteUser(4); return mapper.selectUser(); } //新增 public int addUser(User user) { UserMapper mapper = getSqlSession().getMapper(UserMapper.class); return mapper.addUser(user); } //删除 public int deleteUser(int id) { UserMapper mapper = getSqlSession().getMapper(UserMapper.class); return mapper.deleteUser(id); } }测试@Test public void test2(){ ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); UserMapper mapper = (UserMapper) context.getBean("userDao"); List<User> user = mapper.selectUser(); System.out.println(user); }报错:sql异常,delete写错了结果 :插入成功!没有进行事务的管理;我们想让他们都成功才成功,有一个失败,就都失败,我们就应该需要事务!以前我们都需要自己手动管理事务,十分麻烦!但是Spring给我们提供了事务管理,我们只需要配置即可;9.3 Spring中的事务管理Spring在不同的事务管理API之上定义了一个抽象层,使得开发人员不必了解底层的事务管理API就可以使用Spring的事务管理机制。Spring支持编程式事务管理和声明式的事务管理。编程式事务管理将事务管理代码嵌到业务方法中来控制事务的提交和回滚缺点:必须在每个事务操作业务逻辑中包含额外的事务管理代码声明式事务管理一般情况下比编程式事务好用。将事务管理代码从业务方法中分离出来,以声明的方式来实现事务管理。将事务管理作为横切关注点,通过aop方法模块化。Spring中通过Spring AOP框架支持声明式事务管理。使用Spring管理事务,注意头文件的约束导入 : txxmlns:tx="http://www.springframework.org/schema/tx" http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">事务管理器无论使用Spring的哪种事务管理策略(编程式或者声明式)事务管理器都是必须的。就是 Spring的核心事务管理抽象,管理封装了一组独立于技术的方法。JDBC事务<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean>配置好事务管理器后我们需要去配置事务的通知<!--配置事务通知--> <tx:advice id="txAdvice" transaction-manager="transactionManager"> <tx:attributes> <!--配置哪些方法使用什么样的事务,配置事务的传播特性--> <tx:method name="add" propagation="REQUIRED"/> <tx:method name="delete" propagation="REQUIRED"/> <tx:method name="update" propagation="REQUIRED"/> <tx:method name="search*" propagation="REQUIRED"/> <tx:method name="get" read-only="true"/> <tx:method name="*" propagation="REQUIRED"/> </tx:attributes> </tx:advice>spring事务传播特性:事务传播行为就是多个事务方法相互调用时,事务如何在这些方法间传播。spring支持7种事务传播行为:propagation_requierd:如果当前没有事务,就新建一个事务,如果已存在一个事务中,加入到这个事务中,这是最常见的选择。propagation_supports:支持当前事务,如果没有当前事务,就以非事务方法执行。propagation_mandatory:使用当前事务,如果没有当前事务,就抛出异常。propagation_required_new:新建事务,如果当前存在事务,把当前事务挂起。propagation_not_supported:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。propagation_never:以非事务方式执行操作,如果当前事务存在则抛出异常。propagation_nested:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与propagation_required类似的操作Spring 默认的事务传播行为是 PROPAGATION_REQUIRED,它适合于绝大多数的情况。假设 ServiveX#methodX() 都工作在事务环境下(即都被 Spring 事务增强了),假设程序中存在如下的调用链:Service1#method1()->Service2#method2()->Service3#method3(),那么这 3 个服务类的 3 个方法通过 Spring 的事务传播机制都工作在同一个事务中。就好比,我们刚才的几个方法存在调用,所以会被放在一组事务当中!配置AOP导入aop的头文件!<!--配置aop织入事务--> <aop:config> <aop:pointcut id="txPointcut" expression="execution(* com.kuang.dao.*.*(..))"/> <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/> </aop:config>进行测试删掉刚才插入的数据,再次测试!@Test public void test2(){ ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); UserMapper mapper = (UserMapper) context.getBean("userDao"); List<User> user = mapper.selectUser(); System.out.println(user); }思考问题?为什么需要配置事务?如果不配置,就需要我们手动提交控制事务;事务在项目开发过程非常重要,涉及到数据的一致性的问题,不容马虎!参考资料【狂神说Java】Spring5最新完整教程IDEA版通俗易懂
2022年04月28日
863 阅读
0 评论
0 点赞
2022-04-18
javaWeb学习笔记
1、基本概念1.1、前言web开发:web,网页的意思,www.baidu.com·静态webhtml,css提供给所有人看的数据始终不会发生变化!动态web淘宝,几乎是所有的网站;提供给所有人看的数据始终会发生变化,每个人在不同的时间,不同的地点看到的信息各不相同!技术栈:Servlet/JSP,ASP,PHP1.2、web应用程序web应用程序:可以提供浏览器访问的程序;a.html、b.html.….多个web资源,这些web资源可以被外界访问,对外界提供服务;你们能访问到的任何一个页面或者资源,都存在于这个世界的某一个角落的计算机上。URL这个统一的web资源会被放在同一个文件夹下,web应用程序>Tomcat:服务器一个web应用由多部分组成(静态web,动态web)html,css,jsjsp,servletJava程序jar包配置文件(Properties)Web酸用程序编写完毕后,若想提供给外界访问;需费一个服务蔬来统一管理1.3、静态web*.htm, *.html这些都是网页的后缀、如果服务器上一直存在这些东西,我们就可以直接进行读取、需要网络;静态web存在的缺点Web页面无法动态更新,所有用户看到都是同一个页面轮播图,点击特效:伪动态JavaScript[实际开发中,它用的最多]VBScript它无法和数据库交互(数据无法持久化,用户无法交互)1.4、 动态web页面会动态展示,“web页面的展示效果因人而异” 缺点:假如服务器的动态web资源出现了错误,我们需要重新编写我们的后台程序,重新发布(停机维护);优点:Web页面可以动态更新,所有用户看到都不是同一个页面它可以与数据库交互(数据持久化:注册,商品信息,用户信息………) 2、web服务器2.1、技术讲解ASP:微软:国内最早流行的就是ASP;·在HTML中嵌入了VB的脚本,ASP+COM;·在ASP开发中,基本一个页面都有几干行的业务代码,页面极其换乱·维护成本高!C#IISphp:PHP开发速度很快,功能很强大,跨平台,代码很简单(70%,WP)无法承载大访问量的情况(局限性)jSP/Servlet: B/S;浏览和服务器C/S:客户端和服务器sun公司主推的B/S架构基于Java语言的(所有的大公司,或者一些开源的组件,都是用Java写的)可以承载三高问题带来的影响;语法像ASP,ASP->JSP,加强市场强度;2.2、web服务器服务器是一种被动的操作,用来处理用户的一些请求和给用户一些响应信息; lIS 微软的;ASP.,Windows中自带的 Tomcat 面向百度编程: Tomcat是Apache 软件基金会(Apache Software Foundation)的jakarta项目中的一个核心项目,最新的Servlet 和JSP 规范总是能在Tomcat中得到体现,因为Tomcat 技术先进、性能稳定,而且免费,因而深受lava爱好者的喜爱并得到了部分软件开发商的认可,成为目前比较流行的Web应用服务器。Tomcat 服务器是一个免费的开放源代码的Web应用服务器,属于轻量级应用服务器,在中小型系统和并发访问用户不是很多的场合下被普遍使用,是开发和调试JSP程序的首选。对于一个Java初学web的人来说,它是最佳的选择Tomcat 实际上运行JSP页面和Serlet。Tornct最新版易9.0工作3-5年之后,可以尝试手写Tomcat服务器;下载tomcat:安装or解压了解配置文件及目录结构这个东西的作用3、Tomcat3.1安装tomcat tomcat官网:http://tomcat.apache.org/ 3.2、Tomcat启动和配置文件夹作用: 访问测试:http://localhost:8080/ 可能遇到的问题:Java环境变量没有配置闪退问题:需要配置兼容性乱码问题:配置文件中设置可以修改 conf/logging.properties 中的 java.util.logging.ConsoleHandler.encoding = GBK 解决乱码问题3.3、配置可以配置启动的端口号tomcat的默认端口号为:8080mysql:3306http:80https:443<Connector port="8081" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" />可以配置主机的名称默认的主机名为:localhost->127.0.0.1默认网站应用存放的位置为:webapps <Host name="www.qinjiang.com" appBase="webapps" unpackWARs="true" autoDeploy="true">**高难度面试题: 请你谈谈网站是如何进行访问的!**输入一个域名;回车检查本机的C:\Windows\System32\drivers\etc\hosts配置文件下有没有这个域名映射;有:直接返回对应的ip地址,这个地址中,有我们需要访问的web程序,可以直接访问 127.0.0.1 www.qinjiang.com没有:去DNS服务器找,找到的话就返回,找不到就返回找不到; 4.可以配置一下环境变量(可选性)3.4、发布一个web网站不会就先模仿将自己写的网站,放到服务器(Tomcat)中指定的web应用的文件夹(webapps)下,就可以访问了网站应该有的结构--webapps :Tomcat服务器的web目录 -ROOT -kuangstudy :网站的目录名 - WEB-INF -classes : java程序 -lib:web应用所依赖的jar包 -web.xml :网站配置文件 - index.html 默认的首页 - static -css -style.css -js -img -.....HTTP协议:面试 Maven:构建工具Maven安装包Servlet入门HelloWorld!Servlet配置 ·原理4、Http4.1、什么是HTTPHTTP(超文本传输协议)是一个简单的请求-响应协议,它通常运行在TCP之上。文本:html,字符串,…超文本:图片,音乐,视频,定位,地图.……端口:80Https:安全的端口:4434.2、两个时代http1.0HTTP/1.0:客户端可以与web服务器连接后,只能获得一个web资源,断开连接http2.0HTTP/1.1:客户端可以与web服务器连接后,可以获得多个web资源。4.3、Http请求客户端–发请求(Request)–服务器百度:Request URL:https://www.baidu.com/ 请求地址 Request Method:GET get方法/post方法 Status Code:200 OK 状态码:200 Remote(远程) Address:14.215.177.39:443 Accept:text/html Accept-Encoding:gzip, deflate, br Accept-Language:zh-CN,zh;q=0.9 语言 Cache-Control:max-age=0 Connection:keep-alive1、请求行请求行中的请求方式:GET请求方式:Get,Post,HEAD,DELETE,PUT,TRACT.…get:请求能够携带的参数比较少,大小有限制,会在浏览器的URL地址栏显示数据内容,不安全,但高效post:请求能够携带的参数没有限制,大小没有限制,不会在浏览器的URL地址栏显示数据内容,安全,但不高效。2、消息头Accept:告诉浏览器,它所支持的数据类型 Accept-Encoding:支持哪种编码格式 GBK UTF-8 GB2312 ISO8859-1 Accept-Language:告诉浏览器,它的语言环境 Cache-Control:缓存控制 Connection:告诉浏览器,请求完成是断开还是保持连接 HOST:主机..../.4.4、Http响应服务器–响应…….客户端百度:Cache-Control:private 缓存控制 Connection:Keep-Alive 连接 Content-Encoding:gzip 编码 Content-Type:text/html 类型 1、响应体Accept:告诉浏览器,它所支持的数据类型 Accept-Encoding:支持哪种编码格式 GBK UTF-8 GB2312 ISO8859-1 Accept-Language:告诉浏览器,它的语言环境 Cache-Control:缓存控制 Connection:告诉浏览器,请求完成是断开还是保持连接 HOST:主机..../. Refresh:告诉客户端,多久刷新一次; Location:让网页重新定位;2、响应状态码200:请求响应成功200 3xx:请求重定向重定向:你重新到我给你新位置去;4xx:找不到资源404:资源不存在;5xx:服务器错误500:服务器代码错误502:网关错误常见面试题: 当你的浏览器中地址栏输入地址并回车的一瞬间到页面能够展示回来,经历了什么?5、Maven我为什么要学习这个技术?在Javaweb开发中,需要使用大量的jar包,我们手动去导入;如何能够让一个东西自动帮我导入和配置这个jar包。由此,Maven诞生了!5.1 Maven项目架构管理工具我们目前用来就是方便导入jar包的! Maven的核心思想:约定大于配置有约束,不要去违反。Maven会规定好你该如何去编写我们Java代码,必须要按照这个规范来;5.2下载安装Maven官网:https://maven.apache.org/ 下载完成后,解压即可; 小狂神友情建议:电脑上的所有环境都放在一个文件夹下,方便管理;5.3配置环境变量在我们的系统环境变量中配置如下配置:M2\_HOME maven目录下的bin目录MAVEN\_HOME maven的目录在系统的path中配置%MAVEN\_HOME%\bin 测试Maven是否安装成功,保证必须配置完毕!5.4阿里云镜像镜像:mirrors作用:加速我们的下载国内建议使用阿里云的镜像<mirror> <id>nexus-aliyun</id> <mirrorOf>*,!jeecg,!jeecg-snapshots</mirrorOf> <name>Nexus aliyun</name> <url>http://maven.aliyun.com/nexus/content/groups/public</url> </mirror>D:Enmvironment\apache-maven-3.6.2conf\settings.xml (狂神老师配置源和仓库的文件位置)5.5本地仓库在本地的仓库,远程仓库; 建立一个本地仓库:localRepository<localRepository>D:\Environment\apache-maven-3.6.2\maven-repo</localRepository>5.6 ~ 5.13笔记-下载地址下载地址:https://lanzoui.com/ibuibxi本站缓存后面的 5.6 ~ 5.13 + 案例演示 (图)**后面第 5 剩下部分的笔记建议配合狂神的 “javaweb-06:IDEA中Maven的操作”、“javaweb-07:解决大家遇到的一些问题” 仔细(回)看**6、Servlet6.1、Servlet简介Servlet就是sun公司开发动态web的一门技术Sun在这些API中提供一个接口叫做:Servlet,如果你想开发一个Servlet程序,只需要完成两个小步骤:编写一个类,实现Serlet接口把开发好java类部署到web服务器中。把实现了Servlet接口的Java程序叫做,ServletSerlvet接口Sun公司有两个默认的实现类:HttpServlet,GenericServled6.2、HelloServlet构建一个普通的Maven项目,等理面的sc目录,以后我们的学习就在这个项目里面建立Moudel;这个空的工程就题Maven主工程;关于Maven父子工程的理解; 父项目中会有 <modules> <module>servlet-01</module> </modules>子项目会有 <parent> <artifactId>javaweb-02-servlet</artifactId> <groupId>com.kuang</groupId> <version>1.0-SNAPSHOT</version> </parent>父项目中的java子项目可以直接使用son extends fatherMaven环境优化修改web.xml为最新的<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0" metadata-complete="true"> </web-app>将maven的结构搭建完整编写一个Servlet程序编写一个普通类实现Servlet接口,这里我们直接继承HttpServlet public class HelloServlet extends HttpServlet { //由于get或者post只是请求实现的不同的方式,可以相互调用,业务逻辑都一样; @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //ServletOutputStream outputStream = resp.getOutputStream(); PrintWriter writer = resp.getWriter(); //响应流 writer.print("Hello,Serlvet"); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doGet(req, resp); } } 编写Servlet的映射 为什么需要映射:我们写的是JAVA程序,但是要通过浏览器访问,而浏览器需要连接web服务器,所以我们需 要再web服务中注册我们写的Servlet,还需给他一个浏览器能够访问的路径; <!--注册Servlet--> <servlet> <servlet-name>hello</servlet-name> <servlet-class>com.kuang.servlet.HelloServlet</servlet-class> </servlet> <!--Servlet的请求路径--> <servlet-mapping> <servlet-name>hello</servlet-name> <url-pattern>/hello</url-pattern> </servlet-mapping>配置Tomcat 注意:配置项目发布的路径就可以了 启动测试,OK! 6.3、Servlet原理Servlet是由Web服务器调用,web服务器在收到浏览器请求之后,会: 6.4、Mapping问题一个Servlet可以指定一个映射路径 <servlet-mapping> <servlet-name>hello</servlet-name> <url-pattern>/hello</url-pattern> </servlet-mapping>一个servlet可以指定多个映射路径 <servlet-mapping> <servlet-name>hello</servlet-name> <url-pattern>/hello</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>hello</servlet-name> <url-pattern>/hello2</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>hello</servlet-name> <url-pattern>/hello3</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>hello</servlet-name> <url-pattern>/hello4</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>hello</servlet-name> <url-pattern>/hello5</url-pattern> </servlet-mapping> 一个servlet可以指定通用映射路径 <servlet-mapping> <servlet-name>hello</servlet-name> <url-pattern>/hello/*</url-pattern> </servlet-mapping>默认请求路径 <!--默认请求路径--> <servlet-mapping> <servlet-name>hello</servlet-name> <url-pattern>/*</url-pattern> </servlet-mapping>指定一些后缀或者前缀等等… <!--可以自定义后缀实现请求映射 注意点,*前面不能加项目映射的路径 hello/sajdlkajda.qinjiang --> <servlet-mapping> <servlet-name>hello</servlet-name> <url-pattern>*.qinjiang</url-pattern> </servlet-mapping>优先级问题 指定了固有的映射路径优先级最高,如果找不到就会走默认的处理请求; <!--404--> <servlet> <servlet-name>error</servlet-name> <servlet-class>com.kuang.servlet.ErrorServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>error</servlet-name> <url-pattern>/*</url-pattern> </servlet-mapping> 6.5、ServletContextweb容器在启动的时候,它会为每个web程序都创建一个对应的ServletContext对象,它代表了当前的web应用;1、共享数据我在这个Servlet中保存的数据,可以在另外一个servlet中拿到;public class HelloServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //this.getInitParameter() 初始化参数 //this.getServletConfig() Servlet配置 //this.getServletContext() Servlet上下文 ServletContext context = this.getServletContext(); String username = "秦疆"; //数据 context.setAttribute("username",username); //将一个数据保存在了ServletContext中,名字为:username 。值 username } } public class GetServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { ServletContext context = this.getServletContext(); String username = (String) context.getAttribute("username"); resp.setContentType("text/html"); resp.setCharacterEncoding("utf-8"); resp.getWriter().print("名字"+username); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doGet(req, resp); } } <servlet> <servlet-name>hello</servlet-name> <servlet-class>com.kuang.servlet.HelloServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>hello</servlet-name> <url-pattern>/hello</url-pattern> </servlet-mapping> <servlet> <servlet-name>getc</servlet-name> <servlet-class>com.kuang.servlet.GetServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>getc</servlet-name> <url-pattern>/getc</url-pattern> </servlet-mapping>测试访问结果;2、获取初始化参数 <!--配置一些web应用初始化参数--> <context-param> <param-name>url</param-name> <param-value>jdbc:mysql://localhost:3306/mybatis</param-value> </context-param>protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { ServletContext context = this.getServletContext(); String url = context.getInitParameter("url"); resp.getWriter().print(url); }3、请求转发@Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { ServletContext context = this.getServletContext(); System.out.println("进入了ServletDemo04"); //RequestDispatcher requestDispatcher = context.getRequestDispatcher("/gp"); //转发的请求路径 //requestDispatcher.forward(req,resp); //调用forward实现请求转发; context.getRequestDispatcher("/gp").forward(req,resp); } 4、读取资源文件 Properties在java目录下新建properties在resources目录下新建properties发现:都被打包到了同一个路径下:classes,我们俗称这个路径为classpath: 思路:需要一个文件流username=root12312 password=zxczxczxcpublic class ServletDemo05 extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { InputStream is = this.getServletContext().getResourceAsStream("/WEB-INF/classes/com/kuang/servlet/aa.properties"); Properties prop = new Properties(); prop.load(is); String user = prop.getProperty("username"); String pwd = prop.getProperty("password"); resp.getWriter().print(user+":"+pwd); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doGet(req, resp); } } 访问测试即可ok;6.6、HttpServletResponseweb服务器接收到客户端的http请求,针对这个请求,分别创建一个代表请求的HttpServletRequest 对象,代表响应的一个HttpServletResponse;如果要获取客户端请求过来的参数:找HttpServletRequest如果要给客户端响应一些信息:找HttpServletResponse1、简单分类负责向浏览器发送数据的方法 servletOutputstream getOutputstream() throws IOException; Printwriter getwriter() throws IOException;负责向浏览器发送响应头的方法void setCharacterEncoding(String var1); void setContentLength(int var1); void setContentLengthLong(long var1); void setContentType(String var1); void setDateHeader(String varl,long var2) void addDateHeader(String var1,long var2) void setHeader(String var1,String var2); void addHeader(String var1,String var2); void setIntHeader(String var1,int var2); void addIntHeader(String varl,int var2);响应的状态码 2、下载文件向浏览器输出消息(一直在讲,就不说了)下载文件要获取下载文件的路径下载的文件名是啥?设置想办法让浏览器能够支持下载我们需要的东西获取下载文件的输入流创建缓冲区获取OutputStream对象将FileOutputStream流写入到bufer缓冲区使用OutputStream将缓冲区中的数据输出到客户端! @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 1.待下载文件的路径和文件名 String filePath = "C:\\Users\\itrb\\Desktop\\数据标注\\res.jpg"; String fileName = filePath.substring(filePath.lastIndexOf("\\") + 1); // 2.设置让浏览器能够支持(Content-Disposition)下载我们需要的东西,中文文件名URLEncoder.encode编码,否则有可能乱码 resp.setHeader("Content-Disposition","attachment;filename="+ URLEncoder.encode(fileName,"UTF-8")); // 3.获取待下载文件的输入流 FileInputStream fileInputStream = new FileInputStream(filePath); //4.创建缓冲区 int len = 0; byte[] buffer = new byte[1024]; // 5.获取输出流 OutputStream out = resp.getOutputStream(); // 6.往输出流写入文件 while ((len=fileInputStream.read(buffer))>0){ out.write(buffer); } // 7.关闭流 fileInputStream.close(); out.close(); }3、验证码功能验证怎么来的?前端实现后端实现,需要用到Java的图片类,生产一个图片package top.inat.servlet; import javax.imageio.ImageIO; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.awt.*; import java.awt.image.BufferedImage; import java.io.IOException; import java.util.Random; public class ImageServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //让浏览器3秒自动刷新一次; resp.setHeader("refresh","3"); //在内存中创建一个图片 BufferedImage image = new BufferedImage(80,20,BufferedImage.TYPE_INT_RGB); //得到图片 Graphics2D g = (Graphics2D) image.getGraphics(); //笔 //设置图片的背景颜色 g.setColor(Color.white); g.fillRect(0,0,80,20); //给图片写数据 g.setColor(Color.BLUE); g.setFont(new Font(null,Font.BOLD,20)); g.drawString(makeNum(),0,20); //告诉浏览器,这个请求用图片的方式打开 resp.setContentType("image/jpeg"); //网站存在缓存,不让浏览器缓存 resp.setDateHeader("expires",-1); resp.setHeader("Cache-Control","no-cache"); resp.setHeader("Pragma","no-cache"); //把图片写给浏览器 ImageIO.write(image,"jpg", resp.getOutputStream()); } //生成随机数 private String makeNum(){ Random random = new Random(); String num = random.nextInt(9999999) + ""; StringBuffer sb = new StringBuffer(); for (int i = 0; i < 7-num.length() ; i++) { sb.append("0"); } num = sb.toString() + num; return num; } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doGet(req, resp); } }<servlet> <servlet-name>image</servlet-name> <servlet-class>top.inat.servlet.ImageServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>image</servlet-name> <url-pattern>/image</url-pattern> </servlet-mapping> 常见场景:用户登录 void sendRedirect(String var1) throws IOException;测试:@override protected void doGet(HttpservletRequest req, HttpservletResponse resp) throws ServletException, IOException { resp. sendRedirect("/r/img");//重定向 /* resp. setHeader("Location","/r/img"); resp. setstatus (302); */ } index.jsp<html> <head> <meta charset="GBK"> <title>Login Test</title> </head> <body> <h2>Hello World!</h2> <%--这里提交交的路径,需要寻找到项目的路径--%> <%--${pageContext.request.contextPath}代表当前的项目--%> <form action="${pageContext.request.contextPath}/login" method="get"> username: <input type="text" name="username"> <br> password: <input type="password" name="password"> <br> <input type="submit"> </form> </body> </html>RequestTest.javapackage top.inat.servlet; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; public class LoginServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //处理方求 String username = req.getParameter("username"); String password = req.getParameter("password"); System.out.println(username + ":" + password); resp.sendRedirect("/success.jsp"); } }重定向页面success.jsp<%@ page contentType="text/html; charset=UTF-8" language="java" %> <html> <head> <title>Title</title> </head> <body> <h1>success</h1> </body> </html>web.xml配置<servlet> <servlet-name>login</servlet-name> <servlet-class>top.inat.servlet.LoginServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>login</servlet-name> <url-pattern>/login</url-pattern> </servlet-mapping>导入依赖的jar包<dependencies> <!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api --> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>4.0.1</version> <scope>provided</scope> </dependency> <!-- https://mvnrepository.com/artifact/javax.servlet.jsp/javax.servlet.jsp-api --> <dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>javax.servlet.jsp-api</artifactId> <version>2.3.1</version> <scope>provided</scope> </dependency> </dependencies>6.7、HttpServletRequestHttpServletRequest代表客户端的请求,用户通过Http协议访问服务器, HTTP请求中的所有信息会被封装到HttpServletRequest,通过这个HttpServletRequest的方法,获得客户端的所有信息; 获取参数,请求转发 自己创建类,且需要继承HttpServlet类@Override protected void doGet(HttpservletRequest req. HttpservletResponse resp) throws ServletException, IOException { req. setcharacterEncoding("utf-8"); resp.setcharacterEncoding("utf-8"); String username = req.getParameter("username"); String password = req.getParameter("password"); String[] hobbys = req.getParameterValues("hobbys"); System.out.println("=========="); //后台接收中文乱码问题 System. out.println(username); System. out.println(password); System. out.println(Arrays.tostring(hobbys)); System. out.println("============"); system. out.println(req.getContextPath()); //通过请求转发 //这里的/代表当前的web应用 req.getRequestDispatcher("/success.jsp").forward(req,resp); }7、Cookie、Session7.1、会话会话:用户打开一个浏览器,点击了很多超链接,访问多个web资源,关闭浏览器,这个过程可以称之为会话;有状态会话:一个同学来过教室,下次再来教室,我们会知道这个同学,曾经来过,称之为有状态会话;你能怎么证明你是西开的学生?发票 西开给你发票学校登记 西开标记你来过了一个网站,怎么证明你来过?客户端 服务端服务端给客户端一个 信件,客户端下次访问服务端带上信件就可以了; cookie服务器登记你来过了,下次你来的时候我来匹配你; seesion7.2、保存会话的两种技术cookie客户端技术 (响应,请求)session服务器技术,利用这个技术,可以保存用户的会话信息? 我们可以把信息或者数据放在Session中!常见常见:网站登录之后,你下次不用再登录了,第二次访问直接就上去了!7.3、Cookie从请求中拿到cookie信息服务器响应给客户端cookieCookie[] cookies = req.getCookies(); //获得Cookie cookie.getName(); //获得cookie中的key cookie.getValue(); //获得cookie中的vlaue new Cookie("lastLoginTime", System.currentTimeMillis()+""); //新建一个cookie cookie.setMaxAge(24*60*60); //设置cookie的有效期 resp.addCookie(cookie); //响应给客户端一个cookiecookie:一般会保存在本地的 用户目录下 appdata;一个网站cookie是否存在上限!聊聊细节问题一个Cookie只能保存一个信息;一个web站点可以给浏览器发送多个cookie,最多存放20个cookie;Cookie大小有限制4kb;300个cookie浏览器上限删除Cookie;不设置有效期,关闭浏览器,自动失效;设置有效期时间为 0 ;编码解码:URLEncoder.encode("秦疆","utf-8") URLDecoder.decode(cookie.getValue(),"UTF-8")7.4、Session(重点) 什么是Session:服务器会给每一个用户(浏览器)创建一个Seesion对象;一个Seesion独占一个浏览器,只要浏览器没有关闭,这个Session就存在;用户登录之后,整个网站它都可以访问!–> 保存用户的信息;保存购物车的信息……Session和cookie的区别:Cookie是把用户的数据写给用户的浏览器,浏览器保存 (可以保存多个)Session把用户的数据写到用户独占Session中,服务器端保存 (保存重要的信息,减少服务器资源的浪费)Session对象由服务创建;使用场景:保存一个登录用户的信息;购物车信息;在整个网站中经常会使用的数据,我们将它保存在Session中;设置Session:package top.inat.servlet; import top.inat.pojo.Person; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.io.IOException; public class SetSession extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //解决乱码问题 req.setCharacterEncoding("UTF-8"); resp.setCharacterEncoding("UTF-8"); resp.setContentType("text/html;charset=utf-8"); //得到Session HttpSession session = req.getSession(); //给Session中存东西 session.setAttribute("user",new Person("秦疆",1)); //获取Session的ID String sessionId = session.getId(); resp.getWriter().write("session创建成功,ID:"+sessionId); //Session创建的时候做了什么事情; //Cookie cookie = new Cookie("JSESSIONID",sessionId); //resp.addCookie(cookie); } }获取Session:package top.inat.servlet; import top.inat.pojo.Person; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.io.IOException; import java.net.URLEncoder; public class GetSession extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //得到Session HttpSession session = req.getSession(); Object sessionData = session.getAttribute("user"); if(sessionData!=null){ Person person = (Person)sessionData; System.out.println(person.toString()); resp.setCharacterEncoding("UTF-8"); resp.getWriter().write("session is : "+person.toString()); }else { resp.setCharacterEncoding("UTF-8"); resp.getWriter().write("session is : null"); } } }注销Session:HttpSession session = req.getSession(); session.removeAttribute("user"); //手动注销Session session.invalidate();会话自动过期:web.xml配置<!--设置Session默认的失效时间--> <session-config> <!--15分钟后Session自动失效,以分钟为单位--> <session-timeout>15</session-timeout> </session-config>8、JSP8.1、什么是JSPJava Server Pages : Java服务器端页面,也和Servlet一样,用于动态Web技术!最大的特点:写JSP就像在写HTML区别:HTML只给用户提供静态的数据JSP页面中可以嵌入JAVA代码,为用户提供动态数据;8.2、JSP原理思路:JSP到底怎么执行的!代码层面没有任何问题服务器内部工作tomcat中有一个work目录;IDEA中使用Tomcat的会在IDEA的tomcat中生产一个work目录我电脑的地址:C:\Users\Administrator.IntelliJIdea2018.1\system\tomcat\Unnamed\_javaweb-session-cookie\work\Catalina\localhost\ROOT\org\apache\jsp发现页面转变成了Java程序! 浏览器向服务器发送请求,不管访问什么资源,其实都是在访问Servlet!JSP最终也会被转换成为一个Java类!JSP 本质上就是一个Servlet//初始化 public void _jspInit() {} //销毁 public void _jspDestroy() {} //JSPService public void _jspService(.HttpServletRequest request,HttpServletResponse response){}判断请求内置一些对象final javax.servlet.jsp.PageContext pageContext; //页面上下文 javax.servlet.http.HttpSession session = null; //session final javax.servlet.ServletContext application; //applicationContext final javax.servlet.ServletConfig config; //config javax.servlet.jsp.JspWriter out = null; //out final java.lang.Object page = this; //page:当前 HttpServletRequest request //请求 HttpServletResponse response //响应输出页面前增加的代码response.setContentType("text/html"); //设置响应的页面类型 pageContext = _jspxFactory.getPageContext(this, request, response, null, true, 8192, true); _jspx_page_context = pageContext; application = pageContext.getServletContext(); config = pageContext.getServletConfig(); session = pageContext.getSession(); out = pageContext.getOut(); _jspx_out = out;以上的这些个对象我们可以在JSP页面中直接使用!在JSP页面中;只要是 JAVA代码就会原封不动的输出;如果是HTML代码,就会被转换为:out.write("<html>\r\n");这样的格式,输出到前端!8.3、JSP基础语法任何语言都有自己的语法,JAVA中有,。 JSP 作为java技术的一种应用,它拥有一些自己扩充的语法(了解,知道即可!),Java所有语法都支持!JSP表达式 <%--JSP表达式 作用:用来将程序的输出,输出到客户端 <%= 变量或者表达式%> --%> <%= new java.util.Date()%>jsp脚本片段<%--jsp脚本片段--%> <% int sum = 0; for (int i = 1; i <=100 ; i++) { sum+=i; } out.println("<h1>Sum="+sum+"</h1>"); %>脚本片段的再实现 <% int x = 10; out.println(x); %> <p>这是一个JSP文档</p> <% int y = 2; out.println(y); %> <hr> <%--在代码嵌入HTML元素--%> <% for (int i = 0; i < 5; i++) { %> <h1>Hello,World <%=i%> </h1> <% } %>JSP声明 <%! static { System.out.println("Loading Servlet!"); } private int globalVar = 0; public void kuang(){ System.out.println("进入了方法Kuang!"); } %>JSP声明:会被编译到JSP生成Java的类中!其他的,就会被生成到\_jspService方法中!在JSP,嵌入Java代码即可!<%%> <%=%> <%!%> <%--注释--%>JSP的注释,不会在客户端显示,HTML就会!8.4、JSP指令<%@page args.... %> // 定制错误页面等 <%@include file=""%> <%--@include会将两个页面合二为一--%> <%@include file="common/header.jsp"%> <h1>网页主体</h1> <%@include file="common/footer.jsp"%> <hr> <%--jSP标签 jsp:include:拼接页面,本质还是三个 --%> <jsp:include page="/common/header.jsp"/> <h1>网页主体</h1> <jsp:include page="/common/footer.jsp"/>8.5、9大内置对象PageContext 存东西Request 存东西ResponseSession 存东西Application 【SerlvetContext】 存东西config 【SerlvetConfig】outpage ,不用了解exceptionpageContext.setAttribute("name1","秦疆1号"); //保存的数据只在一个页面中有效 request.setAttribute("name2","秦疆2号"); //保存的数据只在一次请求中有效,请求转发会携带这个数据 session.setAttribute("name3","秦疆3号"); //保存的数据只在一次会话中有效,从打开浏览器到关闭浏览器 application.setAttribute("name4","秦疆4号"); //保存的数据只在服务器中有效,从打开服务器到关闭服务器request:客户端向服务器发送请求,产生的数据,用户看完就没用了,比如:新闻,用户看完没用的!session:客户端向服务器发送请求,产生的数据,用户用完一会还有用,比如:购物车;application:客户端向服务器发送请求,产生的数据,一个用户用完了,其他用户还可能使用,比如:聊天数据;8.6、JSP标签、JSTL标签、EL表达式<!-- JSTL表达式的依赖 --> <dependency> <groupId>javax.servlet.jsp.jstl</groupId> <artifactId>jstl-api</artifactId> <version>1.2</version> </dependency> <!-- standard标签库 --> <dependency> <groupId>taglibs</groupId> <artifactId>standard</artifactId> <version>1.1.2</version> </dependency>EL表达式: ${ }获取数据执行运算获取web开发的常用对象JSP标签<%--jsp:include--%> <%-- http://localhost:8080/jsptag.jsp?name=kuangshen&age=12 --%> <jsp:forward page="/jsptag2.jsp"> <jsp:param name="name" value="kuangshen"></jsp:param> <jsp:param name="age" value="12"></jsp:param> </jsp:forward>JSTL表达式JSTL标签库的使用就是为了弥补HTML标签的不足;它自定义许多标签,可以供我们使用,标签的功能和Java代码一样!格式化标签SQL标签XML 标签核心标签 (掌握部分)JSTL标签库使用步骤引入对应的 taglib使用其中的方法在Tomcat 也需要引入 jstl的包,否则会报错:JSTL解析错误c:if<head> <title>Title</title> </head> <body> <h4>if测试</h4> <hr> <form action="coreif.jsp" method="get"> <%-- EL表达式获取表单中的数据 ${param.参数名} --%> <input type="text" name="username" value="${param.username}"> <input type="submit" value="登录"> </form> <%--判断如果提交的用户名是管理员,则登录成功--%> <c:if test="${param.username=='admin'}" var="isAdmin"> <c:out value="管理员欢迎您!"/> </c:if> <%--自闭合标签--%> <c:out value="${isAdmin}"/> </body>c:choose c:when<body> <%--定义一个变量score,值为85--%> <c:set var="score" value="55"/> <c:choose> <c:when test="${score>=90}"> 你的成绩为优秀 </c:when> <c:when test="${score>=80}"> 你的成绩为一般 </c:when> <c:when test="${score>=70}"> 你的成绩为良好 </c:when> <c:when test="${score<=60}"> 你的成绩为不及格 </c:when> </c:choose> </body>c:forEach<% ArrayList<String> people = new ArrayList<>(); people.add(0,"张三"); people.add(1,"李四"); people.add(2,"王五"); people.add(3,"赵六"); people.add(4,"田六"); request.setAttribute("list",people); %> <%-- var , 每一次遍历出来的变量 items, 要遍历的对象 begin, 哪里开始 end, 到哪里 step, 步长 --%> <c:forEach var="people" items="${list}"> <c:out value="${people}"/> <br> </c:forEach> <hr> <c:forEach var="people" items="${list}" begin="1" end="3" step="1" > <c:out value="${people}"/> <br> </c:forEach> 9、JavaBean实体类JavaBean有特定的写法:必须要有一个无参构造属性必须私有化必须有对应的get/set方法;一般用来和数据库的字段做映射 ORM;ORM :对象关系映射表—>类字段–>属性行记录---->对象people表idnameageaddress1秦疆1号3西安2秦疆2号18西安3秦疆3号100西安class People{ private int id; private String name; private int id; private String address; } class A{ new People(1,"秦疆1号",3,"西安"); new People(2,"秦疆2号",3,"西安"); new People(3,"秦疆3号",3,"西安"); }过滤器文件上传邮件发送JDBC 复习 : 如何使用JDBC , JDBC crud, jdbc 事务10、MVC三层架构什么是MVC: Model view Controller 模型、视图、控制器10.1、以前的架构用户直接访问控制层,控制层就可以直接操作数据库;servlet--CRUD-->数据库 弊端:程序十分臃肿,不利于维护 servlet的代码中:处理请求、响应、视图跳转、处理JDBC、处理业务代码、处理逻辑代码 架构:没有什么是加一层解决不了的! 程序猿调用 ↑ JDBC (实现该接口) ↑ Mysql Oracle SqlServer ....(不同厂商)10.2、MVC三层架构![[(img-BWDJGUCN-1588757845419)(JavaWeb.assets/1568424227281.png)]](/usr/uploads/auto_save_image/9592f95019658738dc15d361abdf4563.png)Model业务处理 :业务逻辑(Service)数据持久层:CRUD (Dao - 数据持久化对象)View展示数据提供链接发起Servlet请求 (a,form,img…)Controller (Servlet)接收用户的请求 :(req:请求参数、Session信息….)交给业务层处理对应的代码控制视图的跳转登录--->接收用户的登录请求--->处理用户的请求(获取用户登录的参数,username,password)---->交给业务层处理登录业务(判断用户名密码是否正确:事务)--->Dao层查询用户名和密码是否正确-->数据库11、Filter (重点)比如 Shiro安全框架技术就是用Filter来实现的Filter:过滤器 ,用来过滤网站的数据;处理中文乱码登录验证….(比如用来过滤网上骂人的话,我***我自己 0-0) Filter开发步骤:导包编写过滤器导包不要错 (注意)![[(img-HHsC3JBD-1588757845420)(JavaWeb.assets/1568425162525.png)]](/usr/uploads/auto_save_image/d77950ebea39793fce0f02c34f7dd8d5.png)实现Filter接口,重写对应的方法即可 public class CharacterEncodingFilter implements Filter { //初始化:web服务器启动,就以及初始化了,随时等待过滤对象出现! public void init(FilterConfig filterConfig) throws ServletException { System.out.println("CharacterEncodingFilter初始化"); } //Chain : 链 /* 1. 过滤中的所有代码,在过滤特定请求的时候都会执行 2. 必须要让过滤器继续同行 chain.doFilter(request,response); */ public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { request.setCharacterEncoding("utf-8"); response.setCharacterEncoding("utf-8"); response.setContentType("text/html;charset=UTF-8"); System.out.println("CharacterEncodingFilter执行前...."); chain.doFilter(request,response); //让我们的请求继续走,如果不写,程序到这里就被拦截停止! System.out.println("CharacterEncodingFilter执行后...."); } //销毁:web服务器关闭的时候,过滤器会销毁 public void destroy() { System.out.println("CharacterEncodingFilter销毁"); } } 在web.xml中配置 Filter <filter> <filter-name>CharacterEncodingFilter</filter-name> <filter-class>com.kuang.filter.CharacterEncodingFilter</filter-class> </filter> <filter-mapping> <filter-name>CharacterEncodingFilter</filter-name> <!--只要是 /servlet的任何请求,会经过这个过滤器--> <url-pattern>/servlet/*</url-pattern> <!--<url-pattern>/*</url-pattern>--> <!-- 别偷懒写个 /* --> </filter-mapping>12、监听器实现一个监听器的接口;(有n种监听器)编写一个监听器实现监听器的接口…依赖的jar包//统计网站在线人数 : 统计session public class OnlineCountListener implements HttpSessionListener { //创建session监听: 看你的一举一动 //一旦创建Session就会触发一次这个事件! public void sessionCreated(HttpSessionEvent se) { ServletContext ctx = se.getSession().getServletContext(); System.out.println(se.getSession().getId()); Integer onlineCount = (Integer) ctx.getAttribute("OnlineCount"); if (onlineCount==null){ onlineCount = new Integer(1); }else { int count = onlineCount.intValue(); onlineCount = new Integer(count+1); } ctx.setAttribute("OnlineCount",onlineCount); } //销毁session监听 //一旦销毁Session就会触发一次这个事件! public void sessionDestroyed(HttpSessionEvent se) { ServletContext ctx = se.getSession().getServletContext(); Integer onlineCount = (Integer) ctx.getAttribute("OnlineCount"); if (onlineCount==null){ onlineCount = new Integer(0); }else { int count = onlineCount.intValue(); onlineCount = new Integer(count-1); } ctx.setAttribute("OnlineCount",onlineCount); } /* Session销毁: 1. 手动销毁 getSession().invalidate(); 2. 自动销毁 */ } web.xml中注册监听器<!--注册监听器--> <listener> <listener-class>com.kuang.listener.OnlineCountListener</listener-class> </listener>看情况是否使用!13、过滤器、监听器常见应用监听器:GUI编程中经常使用;public class TestPanel { public static void main(String[] args) { Frame frame = new Frame("中秋节快乐"); //新建一个窗体 Panel panel = new Panel(null); //面板 frame.setLayout(null); //设置窗体的布局 frame.setBounds(300,300,500,500); frame.setBackground(new Color(0,0,255)); //设置背景颜色 panel.setBounds(50,50,300,300); panel.setBackground(new Color(0,255,0)); //设置背景颜色 frame.add(panel); frame.setVisible(true); //监听事件,监听关闭事件 frame.addWindowListener(new WindowAdapter() { @Override public void windowClosing(WindowEvent e) { super.windowClosing(e); } }); } }用户登录之后才能进入主页!用户注销后就不能进入主页了!用户登录之后,向Sesison中放入用户的数据进入主页的时候要判断用户是否已经登录;要求:在过滤器中实现!HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) resp; if (request.getSession().getAttribute(Constant.USER_SESSION)==null){ response.sendRedirect("/error.jsp"); } chain.doFilter(request,response);14、JDBC什么是JDBC : Java连接数据库!![[(img-rZzTXmtn-1588757845422)(JavaWeb.assets/1568439601825.png)]](/usr/uploads/auto_save_image/db8ad90b34dbe3b84a5f730e5148e3ab.png)需要jar包的支持:java.sqljavax.sqlmysql-conneter-java… 连接驱动(必须要导入)实验环境搭建 CREATE TABLE users( id INT PRIMARY KEY, `name` VARCHAR(40), `password` VARCHAR(40), email VARCHAR(60), birthday DATE ); INSERT INTO users(id,`name`,`password`,email,birthday) VALUES(1,'张三','123456','zs@qq.com','2000-01-01'); INSERT INTO users(id,`name`,`password`,email,birthday) VALUES(2,'李四','123456','ls@qq.com','2000-01-01'); INSERT INTO users(id,`name`,`password`,email,birthday) VALUES(3,'王五','123456','ww@qq.com','2000-01-01'); SELECT* FROM users; 导入数据库依赖<!--mysql的驱动--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.47</version> </dependency>IDEA中连接数据库:![[(img-XErw4ElS-1588757845423)(JavaWeb.assets/1568440926845.png)]](/usr/uploads/auto_save_image/c0a6ec2b544b455988beeb40af2afae8.png)JDBC 固定步骤:加载驱动连接数据库,代表数据库向数据库发送SQL的对象Statement : CRUD编写SQL (根据业务,不同的SQL)执行SQL关闭连接(先开的后关)public class TestJdbc { public static void main(String[] args) throws ClassNotFoundException, SQLException { //配置信息 //useUnicode=true&characterEncoding=utf-8 解决中文乱码 String url="jdbc:mysql://localhost:3306/jdbc?useUnicode=true&characterEncoding=utf-8"; String username = "root"; String password = "123456"; //1.加载驱动 Class.forName("com.mysql.jdbc.Driver"); //2.连接数据库,代表数据库 Connection connection = DriverManager.getConnection(url, username, password); //3.向数据库发送SQL的对象Statement,PreparedStatement : CRUD Statement statement = connection.createStatement(); //4.编写SQL String sql = "select * from users"; //5.执行查询SQL,返回一个 ResultSet : 结果集 ResultSet rs = statement.executeQuery(sql); while (rs.next()){ System.out.println("id="+rs.getObject("id")); System.out.println("name="+rs.getObject("name")); System.out.println("password="+rs.getObject("password")); System.out.println("email="+rs.getObject("email")); System.out.println("birthday="+rs.getObject("birthday")); } //6.关闭连接,释放资源(一定要做) 先开后关 rs.close(); statement.close(); connection.close(); } } 预编译SQLpublic class TestJDBC2 { public static void main(String[] args) throws Exception { //配置信息 //useUnicode=true&characterEncoding=utf-8 解决中文乱码 String url="jdbc:mysql://localhost:3306/jdbc?useUnicode=true&characterEncoding=utf-8"; String username = "root"; String password = "123456"; //1.加载驱动 Class.forName("com.mysql.jdbc.Driver"); //2.连接数据库,代表数据库 Connection connection = DriverManager.getConnection(url, username, password); //3.编写SQL String sql = "insert into users(id, name, password, email, birthday) values (?,?,?,?,?);"; //4.预编译 PreparedStatement preparedStatement = connection.prepareStatement(sql); preparedStatement.setInt(1,2);//给第一个占位符? 的值赋值为1; preparedStatement.setString(2,"狂神说Java");//给第二个占位符? 的值赋值为狂神说Java; preparedStatement.setString(3,"123456");//给第三个占位符? 的值赋值为123456; preparedStatement.setString(4,"24736743@qq.com");//给第四个占位符? 的值赋值为1; preparedStatement.setDate(5,new Date(new java.util.Date().getTime()));//给第五个占位符? 的值赋值为new Date(new java.util.Date().getTime()); //5.执行SQL int i = preparedStatement.executeUpdate(); if (i>0){ System.out.println("插入成功@"); } //6.关闭连接,释放资源(一定要做) 先开后关 preparedStatement.close(); connection.close(); } } 事务要么都成功,要么都失败!ACID原则:保证数据的安全。开启事务 事务提交 commit() 事务回滚 rollback() 关闭事务 转账: A:1000 B:1000 A(900) --100--> B(1100) Junit单元测试依赖<!--单元测试--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency>简单使用@Test注解只有在方法上有效,只要加了这个注解的方法,就可以直接运行!@Test public void test(){ System.out.println("Hello"); }![[(img-OsUubVNQ-1588757845424)(JavaWeb.assets/1568442261610.png)]](/usr/uploads/auto_save_image/df333138d8696914cc043efe81cf1c40.png)失败的时候是红色:![[(img-qv2oTEGI-1588757845425)(JavaWeb.assets/1568442289597.png)]](/usr/uploads/auto_save_image/05bc8e3d626d91daf31b62ef3b779101.png)搭建一个环境CREATE TABLE account( id INT PRIMARY KEY AUTO_INCREMENT, `name` VARCHAR(40), money FLOAT ); INSERT INTO account(`name`,money) VALUES('A',1000); INSERT INTO account(`name`,money) VALUES('B',1000); INSERT INTO account(`name`,money) VALUES('C',1000); @Test public void test() { //配置信息 //useUnicode=true&characterEncoding=utf-8 解决中文乱码 String url="jdbc:mysql://localhost:3306/jdbc?useUnicode=true&characterEncoding=utf-8"; String username = "root"; String password = "123456"; Connection connection = null; //1.加载驱动 try { Class.forName("com.mysql.jdbc.Driver"); //2.连接数据库,代表数据库 connection = DriverManager.getConnection(url, username, password); //3.通知数据库开启事务,false 开启 connection.setAutoCommit(false); String sql = "update account set money = money-100 where name = 'A'"; connection.prepareStatement(sql).executeUpdate(); //制造错误 //int i = 1/0; String sql2 = "update account set money = money+100 where name = 'B'"; connection.prepareStatement(sql2).executeUpdate(); connection.commit();//以上两条SQL都执行成功了,就提交事务! System.out.println("success"); } catch (Exception e) { try { //如果出现异常,就通知数据库回滚事务 connection.rollback(); } catch (SQLException e1) { e1.printStackTrace(); } e.printStackTrace(); }finally { try { connection.close(); } catch (SQLException e) { e.printStackTrace(); } } }15、SMBMS(超市管理项目)完结
2022年04月18日
737 阅读
0 评论
0 点赞
2022-04-05
JAVA判断一个数是否是质数/批量求质数
1.质数定义:只能被1或者自身整除的自然数(不包括1),称为质数。2.判断一个数是否是质数方法一:根据质数的定义求(效率最低)利用它的定义可以循环判断该数除以比它小的每个自然数(大于1),如果有能被它整除的,则它就不是质数。 时间复杂度:$O(n^2)$/** * 判断传入数值是否为素数 * @param num * @return flag */ private static boolean isPrime1(int num){ boolean flag=true; //判断是否为质数的标记 for (int j=num-1;j>1;j--){ //逐个除以[1,n]区间的数字 if (num%j==0){ flag = false; //若能整除,则置false标记为合数 } } return flag; }方法二:利用合数定理(原理和方法一一致,效率提高了)如果一个数是合数,那么它的最小质因数肯定小于等于他的平方根。 例如,20的最小质因数4必然小于20开方。 时间复杂度:$O(n^{\frac{1}{2}})$/** * 方法二 * @param num * @return */ private static boolean isPrime2(int num){ int s = (int) Math.sqrt(num); //求出数字n的开方 boolean flag = true; //判断是否为质数的标记 for (int j=2;j<=s;j++){ //逐个除以[1,n^1/2]区间的数字 if (num % j == 0){ flag = false; //若能整除,则置false标记为合数 } } return flag; }3.批量求质数--筛法求质数(效率最高,但会比较浪费内存)首先建立一个boolean类型的数组,用来存储你要判断某个范围内自然数中的质数.例:你要输出小于100的质数,可以建立一个大小为101(建立11个存储位置是为了让数组位置与其大小相同)的boolean数组,位置1置false,其他位置初始化为true。即数组初始状态 [null,false,true,true......]然后再获取数字n的开方,即10,然后再从2开始,2的倍数位置置false,即 4 6 8 10....,3的倍数位置置false即 6 9 12..........,以此类推,就把初始数列的合数位置值变成false,true值的位置则为素数。/** * 通过对数组进行标记后,再逐个判断 * @param num * @return */ private static boolean [] isPrime3(int num){ boolean [] array = new boolean[num+1]; //加1为了数组位置一致 array[1] = false; //false为合数,ture为质数 1不是质数 for (int i = 2;i < num;i++){ array[i] = true; //生成一个长度为n的布尔数列 [null,false,true........] } int s = (int)Math.sqrt(num); for (int i = 2;i<=s;i++){ if (array[i]){ //检查是否已经置fales(可以防止重复置false) for (int j=i;j*i<=num;j++){ //将i的倍数下标位置置为false array[j*i] = false; } } } return array; //标记好后的数组返回 }
2022年04月05日
1,216 阅读
7 评论
0 点赞
2022-04-05
生产者消费者模型原理及java实现
1.概念生产者消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。这个阻塞队列就是用来给生产者和消费者解耦的。2.321原则三种角色:生产者、消费者、仓库两种关系:生产者与生产者之间是互斥关系,消费者与消费者之间是互斥关系,生产者与消费者之间是同步与互斥关系。一个交易场所:仓库3.优点解耦–生产者。消费者之间不直接通信,降低了耦合度。支持并发支持忙闲不均4.PV原语描述s1初始值为缓冲区大小、s2初始值为0 生产者: 生产一个产品; P(s1); 送产品到缓冲区; V(s2); 消费者: P(s2); 从缓冲区取出产品; V(s2); 消费水平;5.代码实现5.1 synchronized + wait() + notify() 方式package ProducerAndConsumer; import java.util.ArrayList; import java.util.List; class Produce extends Thread { List<Object> productBuffer; // 产品缓冲区 int bufferCapacity; // 缓冲区容量 public Produce(int bufferCapacity,List<Object> productBuffer){ this.productBuffer = productBuffer; this.bufferCapacity = bufferCapacity; } @Override public void run() { while (true){ // 生产行为 synchronized (productBuffer){ // 如果某一个生产者能执行进来,说明此线程具有productBuffer对象的控制权,其它线程(生产者&消费者)都必须等待 if(productBuffer.size()==bufferCapacity){ // 缓冲区满了 try { productBuffer.wait(1); // 释放控制权并等待 } catch (InterruptedException e) { e.printStackTrace(); } }else { // 缓冲区没满可以继续生产 productBuffer.add(new Object()); System.out.println("生产者生产了1件物品,当前缓冲区里还有" + productBuffer.size() + "件物品"); productBuffer.notifyAll(); // 唤醒等待队列中所有线程 } } // 模拟生产缓冲时间 try { Thread.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } } } } class Consumer extends Thread{ List<Object> productBuffer; // 产品缓冲区 int bufferCapacity; // 缓冲区容量 public Consumer(int bufferCapacity,List<Object> productBuffer){ this.productBuffer = productBuffer; this.bufferCapacity = bufferCapacity; } @Override public void run(){ while (true){//消费行为 synchronized (productBuffer){ if(productBuffer.isEmpty()){ //产品缓冲区为空,不能消费,只能等待 try { productBuffer.wait(1); } catch (InterruptedException e) { e.printStackTrace(); } }else { // 缓冲区没空可以继续消费 productBuffer.remove(0); System.out.println("消费者消费了1个物品,当前缓冲区里还有" + productBuffer.size() + "件物品"); productBuffer.notifyAll(); } } // 模拟消费缓冲时间 try { Thread.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } } } } public class ProducerAndConsumer { public static void main(String[] args) { List<Object> productBuffer = new ArrayList<>(); // 产品缓冲区 int bufferCapacity = 3; // 缓冲区容量 for (int i = 0; i < 3; i++) { new Produce(bufferCapacity,productBuffer).start(); } for (int i = 0; i < 3; i++) { new Consumer(bufferCapacity,productBuffer).start(); } } }5.2 可重入锁ReentrantLock (配合Condition)方式package ProducerAndConsumer; import java.util.ArrayList; import java.util.List; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; class Produce extends Thread { List<Object> productBuffer; // 产品缓冲区 int bufferCapacity; // 缓冲区容量 ReentrantLock lock; // 可重入锁 Condition producerCondition; // 生产者condition Condition consumerCondition; // 消费者condition public Produce(int bufferCapacity,List<Object> productBuffer,ReentrantLock lock,Condition producerCondition,Condition consumerCondition){ this.productBuffer = productBuffer; this.bufferCapacity = bufferCapacity; this.lock = lock; this.producerCondition = producerCondition; this.consumerCondition = consumerCondition; } @Override public void run() { while (true) { // 生产行为 lock.lock(); //加锁 if (productBuffer.size() == bufferCapacity) { // 缓冲区满了 try { producerCondition.await(); // 释放控制权并等待 } catch (InterruptedException e) { e.printStackTrace(); } } else { // 缓冲区没满可以继续生产 productBuffer.add(new Object()); System.out.println("生产者生产了1件物品,当前缓冲区里还有" + productBuffer.size() + "件物品"); consumerCondition.signal(); // 唤醒消费者线程 } lock.unlock(); //解锁 // 模拟生产缓冲时间 try { Thread.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } } } } class Consumer extends Thread{ List<Object> productBuffer; // 产品缓冲区 int bufferCapacity; // 缓冲区容量 ReentrantLock lock; // 可重入锁 Condition producerCondition; // 生产者condition Condition consumerCondition; // 消费者condition public Consumer(int bufferCapacity,List<Object> productBuffer,ReentrantLock lock,Condition producerCondition,Condition consumerCondition){ this.productBuffer = productBuffer; this.bufferCapacity = bufferCapacity; this.lock = lock; this.producerCondition = producerCondition; this.consumerCondition = consumerCondition; } @Override public void run(){ while (true){//消费行为 lock.lock();//加锁 if(productBuffer.isEmpty()){ //产品缓冲区为空,不能消费,只能等待 try { consumerCondition.await(); } catch (InterruptedException e) { e.printStackTrace(); } }else { // 缓冲区没空可以继续消费 productBuffer.remove(0); System.out.println("消费者消费了1个物品,当前缓冲区里还有" + productBuffer.size() + "件物品"); producerCondition.signal(); // 唤醒生产者线程继续生产 } lock.unlock(); //解锁 // 模拟消费缓冲时间 try { Thread.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } } } } public class ProducerAndConsumer { public static void main(String[] args) { List<Object> productBuffer = new ArrayList<>(); // 产品缓冲区 int bufferCapacity = 3; // 缓冲区容量 ReentrantLock lock = new ReentrantLock(); // 可重入锁 Condition producerCondition = lock.newCondition(); // 生产者condition Condition consumerCondition = lock.newCondition(); // 消费者condition for (int i = 0; i < 3; i++) { new Produce(bufferCapacity,productBuffer,lock,producerCondition,consumerCondition).start(); } for (int i = 0; i < 3; i++) { new Consumer(bufferCapacity,productBuffer,lock,producerCondition,consumerCondition).start(); } } }参考资料PV操作-生产者/消费者关系Java 学习笔记 使用synchronized实现生产者消费者模式 经典面试题 -- 手写生产者消费者模式
2022年04月05日
672 阅读
0 评论
0 点赞
2022-03-08
java实现大数的十六进制转八进制
1.问题描述问题描述 给定n个十六进制正整数,输出它们对应的八进制数。输入格式 输入的第一行为一个正整数n (1<=n<=10)。 接下来n行,每行一个由0-9、大写字母A-F组成的字符串,表示要转换的十六进制正整数,每个十六进制数长度不超过100000。输出格式 输出n行,每行为输入对应的八进制正整数。注意 输入的十六进制数不会有前导0,比如012A。 输出的八进制数也不能有前导0。样例输入 2 39 123ABC样例输出 71 4435274提示 先将十六进制数转换成某进制数,再由某进制数转换成八进制。2.代码实现import java.util.Scanner; public class Main { //16进制转2进制 public static String hex2Bin(String hex){ StringBuilder sb = new StringBuilder(); char[] hex_chars = hex.toCharArray(); for(char item:hex_chars){ switch (item){ case '0':sb.append("0000");break; case '1':sb.append("0001");break; case '2':sb.append("0010");break; case '3':sb.append("0011");break; case '4':sb.append("0100");break; case '5':sb.append("0101");break; case '6':sb.append("0110");break; case '7':sb.append("0111");break; case '8':sb.append("1000");break; case '9':sb.append("1001");break; case 'A':sb.append("1010");break; case 'B':sb.append("1011");break; case 'C':sb.append("1100");break; case 'D':sb.append("1101");break; case 'E':sb.append("1110");break; case 'F':sb.append("1111");break; default:sb.append("ERROR");break; } } return sb.toString(); } //2进制转8进制 public static String bin2Otc(String bin){ StringBuilder sb = new StringBuilder(); //将字符串的长度补齐到3的整数倍 int binLength = bin.length(); if(binLength%3==1){ bin = "00"+bin; }else if(binLength%3==2){ bin = "0"+bin; } //每3位转换出一个8进制数 for(int i=0;i<bin.length()/3;i++){ String octItem = bin.substring(i*3,i*3+3); switch (octItem){ case "000":sb.append("0");break; case "001":sb.append("1");break; case "010":sb.append("2");break; case "011":sb.append("3");break; case "100":sb.append("4");break; case "101":sb.append("5");break; case "110":sb.append("6");break; case "111":sb.append("7");break; default:sb.append("ERROR");break; } } return sb.toString(); } public static void main(String[] args) { Scanner scanner = new Scanner(System.in); int num_count = scanner.nextInt(); String []nums = new String[num_count]; for(int i=0;i<num_count;i++){ nums[i] = scanner.next(); } for(int i=0;i<num_count;i++){ String bin = hex2Bin(nums[i]); String oct = bin2Otc(bin); if(oct.charAt(0)=='0'){ oct = oct.substring(1); } System.out.println(oct); } } }参考资料1.http://lx.lanqiao.cn/problem.page?gpid=T51
2022年03月08日
613 阅读
0 评论
0 点赞
2022-03-03
Maven基础学习笔记
Maven基础学习笔记1. Maven简介1.1 Maven是什么Maven的本质是一个项目管理工具,将项目开发过程抽象成一个项目的对象模型(POM)POM(Project Object Model):项目对象模型1.2 Maven的作用项目构建:提供标准的、跨平台的自动化项目构建方式依赖管理:方便快捷的管理项目依赖的资源(jar包),避免资源间版本冲突问题统一开发结构:提供标准的、统一的项目结构2.下载与安装2.1 Maven下载官网:https://maven.apache.org/下载地址:https://maven.apache.org/download.cgi2.2 Maven安装Maven 属于绿色版软件,解压即安装2.3 Maven环境变量配置依赖java,需要配置JAVA_HOME设置Maven自身的环境变量,需要配置MAVEN_HOME并在Path下添加%MAVEN_HOME%\bin测试配置结果mvn3. Maven基础概念3.1 仓库仓库:用于存储资源,包含各种jar包仓库分类:本地仓库:自己电脑上存储资源的仓库,连接远程仓库获取资源远程仓库:非本机电脑的仓库,为本地仓库提供资源中央仓库:Maven团队维护,存储所有资源的仓库私服: 部门/公司范围内存储资源的仓库,从中央仓库获取资源私服的作用:保存具有版权的资源,包含购买或自主研发的jar包中央仓库的jar都是开源的,不能存储具有版权的资源一定范围内共享资源,仅对内部开放,不对外共享3.2 坐标什么是坐标?maven中的坐标用于描述资源的位置https://repo1.maven.org/maven2/Maven坐标组成groupId:定义当前Maven项目隶属于组织名称(通常是域名反写,例如:org.mybatis)artifactld:定义当前Maven项目名称(通常是模块名称,例如CRM\SMS)version:定义当前项目版本号packing:定义项目的打包方式示例:来自https://mvnrepository.com/<!-- https://mvnrepository.com/artifact/junit/junit --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.13.2</version> <scope>test</scope> </dependency>Maven坐标的作用使用唯一标识,唯一性定位资源位置,通过该标识可以将资源的识别与下载工作交给机器(maven工具)来完成4.仓库配置4.1 本地仓库配置修改本地仓库的位置默认位置:${user.home}/.m2/repository自定义位置1.新建好本地文件夹2.修改%MAVEN_HOME%/conf/bin/settings.xml<!-- localRepository | The path to the local repository maven will use to store artifacts. | | Default: ${user.home}/.m2/repository <localRepository>/path/to/local/repo</localRepository> --> # 在下方添加如下内容 <localRepository>/path/to/local/repo</localRepository>4.2 远程仓库配置镜像在setting文件中配置阿里云镜像仓库修改%MAVEN_HOME%/conf/bin/settings.xml<mirrors> # 在这里面添加 <mirror> <id>alimaven</id> <name>aliyun maven</name> <url>https://maven.aliyun.com/repository/public</url> <mirrorOf>central</mirrorOf> </mirror> </mirrors>5. 第一个Maven项目(手工制作)5.1 Maven工程目录结构在src同层目录下创建pom.xml文件<!-- 属性 --> <properties> <spring.version>4.2.4.RELEASE</spring.version> <hibernate.version>5.0.7.Final</hibernate.version> <struts.version>2.3.24</struts.version> </properties> <!-- 锁定版本,struts2-2.3.24、spring4.2.4、hibernate5.0.7 --> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-orm</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-core</artifactId> <version>${hibernate.version}</version> </dependency> <dependency> <groupId>org.apache.struts</groupId> <artifactId>struts2-core</artifactId> <version>${struts.version}</version> </dependency> <dependency> <groupId>org.apache.struts</groupId> <artifactId>struts2-spring-plugin</artifactId> <version>${struts.version}</version> </dependency> </dependencies> </dependencyManagement> <!-- 依赖管理 --> <dependencies> <!-- spring --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-orm</artifactId> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> </dependency> <!-- hibernate --> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-core</artifactId> </dependency> <!-- 数据库驱动 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.6</version> <scope>runtime</scope> </dependency> <!-- c3p0 --> <dependency> <groupId>c3p0</groupId> <artifactId>c3p0</artifactId> <version>0.9.1.2</version> </dependency> <!-- 导入 struts2 --> <dependency> <groupId>org.apache.struts</groupId> <artifactId>struts2-core</artifactId> </dependency> <dependency> <groupId>org.apache.struts</groupId> <artifactId>struts2-spring-plugin</artifactId> </dependency> <!-- servlet jsp --> <dependency> <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> <version>2.5</version> <scope>provided</scope> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>jsp-api</artifactId> <version>2.0</version> <scope>provided</scope> </dependency> <!-- 日志 --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.7.2</version> </dependency> <!-- junit --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.9</version> <scope>test</scope> </dependency> <!-- jstl --> <dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> </dependencies> <build> <plugins> <!-- 设置编译版本为1.7 --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>1.7</source> <target>1.7</target> <encoding>UTF-8</encoding> </configuration> </plugin> <!-- maven内置 的tomcat6插件 --> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>tomcat-maven-plugin</artifactId> <version>1.1</version> <configuration> <!-- 可以灵活配置工程路径 --> <path>/Hello</path> <!-- 可以灵活配置端口号 --> <port>8080</port> </configuration> </plugin> </plugins> </build>5.2 Maven项目构建命令Maven构建命令使用mvn开头,后面添加功能参数,可以一次执行多个命令,使用空格分隔mvn compile #编译 mvn clean #清理 mvn test #测试 mvn package #打包 mvn install #安装到本地仓库手动执行的话在pom.xml文件目录下执行6.第一个Maven项目(IDEA生成)https://www.bilibili.com/video/BV1Ah411S7ZE?p=9&spm_id_from=pageDriverjava项目web项目7.依赖管理7.1 添加依赖在pom.xml文件里的<dependencies></dependencies>中添加<dependency></dependency>7.2 依赖传递依赖具有传递性直接依赖:在当前项目中通过依赖配置建立的依赖关系间接依赖:被资源的资源如果依赖其他资源,当前项目间接依赖其他资源7.3 依赖传递冲突问题路径优先:当依赖中出现相同资源时,层级越深,优先级越低,层级越浅,优先级越高声明优先:当资源在相同层级被依赖时,配置顺序靠前的覆盖顺序靠后的特殊优先:当同级配置了相同资源的不同版本,后配置的覆盖先配置的(在同一个pom.xml文件中进行配置)7.4 可选依赖与排除依赖可选依赖控制资源不被调用者看到具体的依赖情况排除依赖指主动断开依赖的资源,被排除的资源无需要指定具体的版本--不需要7.5 依赖范围依赖的jar默认情况可以在任何地方使用,可以通过scope标签设定其作用范围作用范围主程序范围内有效(main文件夹范围内)测试程序范围有效(test文件夹范围内)是否参与打包(package指令范围内)依赖范围传递性(了解即可)带有依赖范围的资源在进行传递时,作用范围将受到影响8.生命周期与插件8.1 项目构建生命周期maven构建生命周期描述的是一次构建过程经历了多少个事件maven对项目构建的生命周期划分为3套clean:清理工作default:核心工作,例如编译、测试、打包、部署等site:产生报告,发布站点等8.2 插件插件与生命周期内的阶段绑定,在执行到对应生命周期时执行对应的插件功能默认maven在各个生命周期上绑定有预设的功能通过插件可以自定义其他功能参考资料黑马程序员Maven全套教程,maven项目管理从基础到高级
2022年03月03日
805 阅读
0 评论
0 点赞
2022-01-15
java学习:字符串的格式化及日期时间格式化
1. 常规类型格式化为字符串1.1 转换符转 换 符说 明示 例%s字符串类型"mingrisoft"%c字符类型'm'%b布尔类型true%d整数类型(十进制)99%x整数类型(十六进制)FF%o整数类型(八进制)77%f浮点类型99.99%a十六进制浮点类型FF.35AE%e指数类型9.38e+5%g通用浮点类型(f和e类型中较短的) %h散列码 %%百分比类型%%n换行符 %tx日期与时间类型(x代表不同的日期与时间转换符 1.2.使用案例public static void main(String[] args) { String str=null; str=String.format("Hi,%s", "王力"); System.out.println(str); str=String.format("Hi,%s:%s.%s", "王南","王力","王张"); System.out.println(str); System.out.printf("字母a的大写是:%c %n", 'A'); System.out.printf("3>7的结果是:%b %n", 3>7); System.out.printf("100的一半是:%d %n", 100/2); System.out.printf("100的16进制数是:%x %n", 100); System.out.printf("100的8进制数是:%o %n", 100); System.out.printf("50元的书打8.5折扣是:%f 元%n", 50*0.85); System.out.printf("上面价格的16进制数是:%a %n", 50*0.85); System.out.printf("上面价格的指数表示:%e %n", 50*0.85); System.out.printf("上面价格的指数和浮点数结果的长度较短的是:%g %n", 50*0.85); System.out.printf("上面的折扣是%d%% %n", 85); System.out.printf("字母A的散列码是:%h %n", 'A'); }输出结果Hi,王力 Hi,王南:王力.王张 字母a的大写是:A 3>7的结果是:false 100的一半是:50 100的16进制数是:64 100的8进制数是:144 50元的书打8.5折扣是:42.500000 元 上面价格的16进制数是:0x1.54p5 上面价格的指数表示:4.250000e+01 上面价格的指数和浮点数结果的长度较短的是:42.5000 上面的折扣是85% 字母A的散列码是:411.3 搭配的标志标 志说 明示 例结 果+为正数或者负数添加符号("%+d",15)+15−左对齐("%-5d",15)\15 \ 0数字前面补0("%04d", 99)0099空格在整数之前添加指定数量的空格("% 4d", 99)\99\ ,以“,”对数字分组("%,f", 9999.99)9,999.990000(使用括号包含负数("%(f", -99.99)(99.990000)#如果是浮点数则包含小数点,如果是16进制或8进制则添加0x或0("%#x", 99)("%#o", 99)0x63 0143<格式化前一个转换符所描述的参数("%f和%<3.2f", 99.45)99.450000和99.45$被格式化的参数索引("%1$d,%2$s", 99,"abc")99,abc1.4.使用案例public static void main(String[] args) { String str=null; //$使用 str=String.format("格式参数$的使用:%1$d,%2$s", 99,"abc"); System.out.println(str); //+使用 System.out.printf("显示正负数的符号:%+d与%d%n", 99,-99); //补O使用 System.out.printf("最牛的编号是:%03d%n", 7); //空格使用 System.out.printf("Tab键的效果是:% 8d%n", 7); //,使用 System.out.printf("整数分组的效果是:%,d%n", 9989997); //空格和小数点后面个数 System.out.printf("一本书的价格是:% 50.5f元%n", 49.8); }输出结果格式参数$的使用:99,abc 显示正负数的符号:+99与-99 最牛的编号是:007 Tab键的效果是: 7 整数分组的效果是:9,989,997 一本书的价格是: 49.80000元 2. 日期格式化2.1 转换符转 换 符说 明示 例%tx日期与时间类型(x代表不同的日期与时间转换符 c包括全部日期和时间信息星期六 十月 27 14:21:20 CST 2007F“年-月-日”格式2007-10-27D“月/日/年”格式10/27/07r“HH:MM:SS PM”格式(12时制)02:25:51 下午T“HH:MM:SS”格式(24时制)14:28:16R“HH:MM”格式(24时制)14:282.2 使用案例public static void main(String[] args) { Date date=new Date(); //c的使用 System.out.printf("全部日期和时间信息:%tc%n",date); //f的使用 System.out.printf("年-月-日格式:%tF%n",date); //d的使用 System.out.printf("月/日/年格式:%tD%n",date); //r的使用 System.out.printf("HH:MM:SS PM格式(12时制):%tr%n",date); //t的使用 System.out.printf("HH:MM:SS格式(24时制):%tT%n",date); //R的使用 System.out.printf("HH:MM格式(24时制):%tR",date); }输出结果全部日期和时间信息:星期一 九月 10 10:43:36 CST 2012 年-月-日格式:2012-09-10 月/日/年格式:09/10/12 HH:MM:SS PM格式(12时制):10:43:36 上午 HH:MM:SS格式(24时制):10:43:36 HH:MM格式(24时制):10:43public static void main(String[] args) { Date date=new Date(); //b的使用,月份简称 String str=String.format(Locale.US,"英文月份简称:%tb",date); System.out.println(str); System.out.printf("本地月份简称:%tb%n",date); //B的使用,月份全称 str=String.format(Locale.US,"英文月份全称:%tB",date); System.out.println(str); System.out.printf("本地月份全称:%tB%n",date); //a的使用,星期简称 str=String.format(Locale.US,"英文星期的简称:%ta",date); System.out.println(str); //A的使用,星期全称 System.out.printf("本地星期的简称:%tA%n",date); //C的使用,年前两位 System.out.printf("年的前两位数字(不足两位前面补0):%tC%n",date); //y的使用,年后两位 System.out.printf("年的后两位数字(不足两位前面补0):%ty%n",date); //j的使用,一年的天数 System.out.printf("一年中的天数(即年的第几天):%tj%n",date); //m的使用,月份 System.out.printf("两位数字的月份(不足两位前面补0):%tm%n",date); //d的使用,日(二位,不够补零) System.out.printf("两位数字的日(不足两位前面补0):%td%n",date); //e的使用,日(一位不补零) System.out.printf("月份的日(前面不补0):%te",date); }输出结果英文月份简称:Sep 本地月份简称:九月 英文月份全称:September 本地月份全称:九月 英文星期的简称:Mon 本地星期的简称:星期一 年的前两位数字(不足两位前面补0):20 年的后两位数字(不足两位前面补0):12 一年中的天数(即年的第几天):254 两位数字的月份(不足两位前面补0):09 两位数字的日(不足两位前面补0):10 月份的日(前面不补0):103.时间格式化3.1 转换符转 换 符说 明示 例H2位数字24时制的小时(不足2位前面补0)15I2位数字12时制的小时(不足2位前面补0)03k2位数字24时制的小时(前面不补0)15l2位数字12时制的小时(前面不补0)3M2位数字的分钟(不足2位前面补0)03S2位数字的秒(不足2位前面补0)09L3位数字的毫秒(不足3位前面补0)015N9位数字的毫秒数(不足9位前面补0)562000000p小写字母的上午或下午标记中:下午英:pmz相对于GMT的RFC822时区的偏移量+0800Z时区缩写字符串CSTs1970-1-1 00:00:00 到现在所经过的秒数1193468128Q1970-1-1 00:00:00 到现在所经过的毫秒数11934681289843.2 使用案例public static void main(String[] args) { Date date = new Date(); //H的使用 System.out.printf("2位数字24时制的小时(不足2位前面补0):%tH%n", date); //I的使用 System.out.printf("2位数字12时制的小时(不足2位前面补0):%tI%n", date); //k的使用 System.out.printf("2位数字24时制的小时(前面不补0):%tk%n", date); //l的使用 System.out.printf("2位数字12时制的小时(前面不补0):%tl%n", date); //M的使用 System.out.printf("2位数字的分钟(不足2位前面补0):%tM%n", date); //S的使用 System.out.printf("2位数字的秒(不足2位前面补0):%tS%n", date); //L的使用 System.out.printf("3位数字的毫秒(不足3位前面补0):%tL%n", date); //N的使用 System.out.printf("9位数字的毫秒数(不足9位前面补0):%tN%n", date); //p的使用 String str = String.format(Locale.US, "小写字母的上午或下午标记(英):%tp", date); System.out.println(str); System.out.printf("小写字母的上午或下午标记(中):%tp%n", date); //z的使用 System.out.printf("相对于GMT的RFC822时区的偏移量:%tz%n", date); //Z的使用 System.out.printf("时区缩写字符串:%tZ%n", date); //s的使用 System.out.printf("1970-1-1 00:00:00 到现在所经过的秒数:%ts%n", date); //Q的使用 System.out.printf("1970-1-1 00:00:00 到现在所经过的毫秒数:%tQ%n", date); }输出结果2位数字24时制的小时(不足2位前面补0):11 2位数字12时制的小时(不足2位前面补0):11 2位数字24时制的小时(前面不补0):11 2位数字12时制的小时(前面不补0):11 2位数字的分钟(不足2位前面补0):03 2位数字的秒(不足2位前面补0):52 3位数字的毫秒(不足3位前面补0):773 9位数字的毫秒数(不足9位前面补0):773000000 小写字母的上午或下午标记(英):am 小写字母的上午或下午标记(中):上午 相对于GMT的RFC822时区的偏移量:+0800 时区缩写字符串:CST 1970-1-1 00:00:00 到现在所经过的秒数:1347246232 1970-1-1 00:00:00 到现在所经过的毫秒数:1347246232773参考资料Java 字符串格式化详解JAVA字符串格式化-String.format()的使用java字符串格式化(String类format方法)
2022年01月15日
544 阅读
0 评论
0 点赞
2022-01-15
java学习:Junit4单元测试的基本用法及注解和执行顺序
1. Junit最简单的用法案例新建一个类被测试类,里面包含一些测试方法package junit.util; /** * 被测试类,通过Junit对此类的方法进行单元测试 */ public class Claculate { public int add(int a, int b) { return a + b; } public int subtract(int a, int b) { return a - b; } public int multiply(int a, int b) { return a * b; } public int divide(int a, int b) { return a / b; } }新建一个Junit的测试类用来测试上面的测试方法,新增Junit的测试类方法如下:package junit.util; import static org.junit.Assert.*; import junit.util.Claculate; import org.junit.Test; /** * junit的测试方法必须使用@Test注解 * 测试方法必须以public void修饰,并且不包含参数 */ public class ClaculateTest { @Test public void testAdd() { /** * assertEquals这个方法是一个断言方法 * 第一个参数表示预期的结果 * 第二个参数表示程序的执行结果 * 当预期结果与执行结果是一致的时候,则表示单元测试成功 */ assertEquals(4, new Claculate().add(1, 3)); } @Test public void testSubtract() { assertEquals(4, new Claculate().subtract(9, 5)); } @Test public void testMultiply() { assertEquals(6, new Claculate().multiply(2, 3)); } @Test(expected=ArithmeticException.class) public void testDivide() { assertEquals(3, new Claculate().divide(9, 0)); } }上面的这个测试类,分别对被测试类Claculate的四个方法进行了测试,测试是选择使用Junit方式进行执行,如果想要执行单个测试方法,可以选择单个方法进行执行2.junit中的常用注解及执行顺序2.1 常用注解为什么引入注解?在实际项目中,进行JUnit测试时,通常会涉及到一些初始化的东西,可能有些配置项需要在测试前进行加载的,JUnit提供了一些初始化的方法用于初始化注解名作用@Before初始化方法,对于每一个测试方法都要执行一次(注意与BeforeClass区别,后者是对于所有方法执行一次)@After释放资源,对于每一个测试方法都要执行一次(注意与AfterClass区别,后者是对于所有方法执行一次)@Test测试方法,在这里可以测试期望异常和超时时间@Ignore忽略的测试方法@BeforeClass针对所有测试,只执行一次,且必须为static void@AfterClass针对所有测试,只执行一次,且必须为static void2.2 执行顺序一个JUnit4的单元测试用例执行顺序为:@BeforeClass -> @Before -> @Test -> @After -> @AfterClass; 每一个测试方法的调用顺序为:@Before -> @Test -> @After; 参考资料junit用法,before,beforeClass,after, afterClass的执行顺序Junit4单元测试的基本用法 Junit的基本使用(详解)
2022年01月15日
767 阅读
0 评论
0 点赞
2022-01-14
[转载]:Java 相关一些面试题
junit 用法,before,beforeClass,after, afterClass 的执行顺序分布式锁nginx 的请求转发算法,如何配置根据权重转发用 hashmap 实现 redis 有什么问题(死锁,死循环,可用 ConcurrentHashmap)线程的状态线程的阻塞的方式sleep 和 wait 的区别hashmap 的底层实现一万个人抢 100 个红包,如何实现(不用队列),如何保证 2 个人不能抢到同一个红包,可用分布式锁java 内存模型,垃圾回收机制,不可达算法两个 Integer 的引用对象传给一个 swap 方法在方法内部交换引用,返回后,两个引用的值是否会发现变化aop 的底层实现,动态代理是如何动态,假如有 100 个对象,如何动态的为这 100 个对象代理是否用过 maven install。 maven test。git(make install 是安装本地 jar 包)tomcat 的各种配置,如何配置 docBasespring 的 bean 配置的几种方式web.xml 的配置spring 的监听器。zookeeper 的实现机制,有缓存,如何存储注册服务的IO 会阻塞吗?readLine 是不是阻塞的用过 spring 的线程池还是 java 的线程池?字符串的格式化方法 (20,21 这两个问题问的太低级了)时间的格式化方法定时器用什么做的线程如何退出结束java 有哪些锁?乐观锁 悲观锁 synchronized 可重入锁 读写锁,用过 reentrantlock 吗?reentrantlock 与 synmchronized 的区别ThreadLocal 的使用场景java 的内存模型,垃圾回收机制为什么线程执行要调用 start 而不是直接 run(直接 run,跟普通方法没什么区别,先调 start,run 才会作为一个线程方法运行)qmq 消息的实现机制 (qmq 是去哪儿网自己封装的消息队列)遍历 hashmap 的三种方式jvm 的一些命令memcache 和 redis 的区别mysql 的行级锁加在哪个位置ConcurrentHashmap 的锁是如何加的?是不是分段越多越好myisam 和 innodb 的区别(innodb 是行级锁,myisam 是表级锁)mysql 其他的性能优化方式linux 系统日志在哪里看如何查看网络进程统计一个整数的二进制表示中 bit 为 1 的个数jvm 内存模型,java 内存模型如何把 java 内存的数据全部 dump 出来如何手动触发全量回收垃圾,如何立即触发垃圾回收hashmap 如果只有一个写其他全读会出什么问题git rebasemongodb 和 hbase 的区别如何解决并发问题volatile 的用途java 线程池(好像之前我的理解有问题)mysql 的 binlog代理模式mysql 是如何实现事务的读写分离何时强制要读主库,读哪个从库是通过什么方式决定的,从库的同步 mysql 用的什么方式mysql 的存储引擎mysql 的默认隔离级别,其他隔离级别将一个链表反转(用三个指针,但是每次只发转一个)spring Aop 的实现原理,具体说说何时会内存泄漏,内存泄漏会抛哪些异常是否用过 Autowire 注解spring 的注入 bean 的方式sql 语句各种条件的执行顺序,如 select, where, order by, group byselect xx from xx where xx and xx order by xx limit xx; 如何优化这个(看 explain)四则元算写代码统计 100G 的 ip 文件中出现 ip 次数最多的 100 个 ipzookeeper 的事物,结点,服务提供方挂了如何告知消费方5 台服务器如何选出 leader (选举算法)适配器和代理模式的区别读写锁static 加锁事务隔离级别门面模式,类图 (外观模式)mybatis 如何映射表结构二叉树遍历主从复制mysql 引擎区别静态内部类加载到了哪个区?方法区class 文件编译后加载到了哪web 的 http 请求如何整体响应时间变长导致处理的请求数变少,该如何处理?用队列,当处理不了那么多 http 请求时将请求放到队列中慢慢处理,web 如何实现队列线程安全的单例模式快速排序性能考虑volatile 关键字用法求表的 size,或做数据统计可用什么存储引擎读多写少可用什么引擎假如要统计多个表应该用什么引擎concurrenhashmap 求 size 是如何加锁的,如果刚求完一段后这段发生了变化该如何处理1000 个苹果放 10 个篮子,怎么放,能让我拿到所有可能的个数可重入的读写锁,可重入是如何实现的?是否用过 NIOjava 的 concurrent 包用过没sting s=new string (“abc”) 分别在堆栈上新建了哪些对象java 虚拟机的区域分配,各区分别存什么分布式事务(JTA)threadlocal 使用时注意的问题(ThreadLocal 和 Synchonized 都用于解决多线程并发访问。但是 ThreadLocal 与 synchronized 有本质的区别。synchronized 是利用锁的机制,使变量或代码块在某一时该只能被一个线程访问。而 ThreadLocal 为每一个线程都提供了变量的副本,使得每个线程在某一时间访问到的并不是同一个对象,这样就隔离了多个线程对数据的数据共享。而 Synchronized 却正好相反,它用于在多个线程间通信时能够获得数据共享)java 有哪些容器 (集合,tomcat 也是一种容器)二分查找算法myisam 的优点,和 innodb 的区别redis 能存哪些类型http 协议格式,get 和 post 的区别可重入锁中对应的 wait 和 notifyredis 能把内存空间交换进磁盘中吗 (这个应该是可以的,但是那个面试官非跟我说不可以)java 线程池中基于缓存和基于定长的两种线程池,当请求太多时分别是如何处理的?定长的事用的队列,如果队列也满了呢?交换进磁盘?基于缓存的线程池解决方法呢?synchronized 加在方法上用的什么锁可重入锁中的 lock 和 trylock 的区别innodb 对一行数据的读会枷锁吗?不枷锁,读实际读的是副本redis 做缓存是分布式存的?不同的服务器上存的数据是否重复?guavacache 呢?是否重复?不同的机器存的数据不同用 awk 统计一个 ip 文件中 top10对表做统计时可直接看 schema info 信息,即查看表的系统信息mysql 目前用的版本公司经验丰富的人给了什么帮助?(一般 boss 面会问这些)自己相对于一样的应届生有什么优势自己的好的总结习惯给自己今后的工作带了什么帮助,举例为证原子类,线程安全的对象,异常的处理方式4 亿个 int 数,如何找出重复的数(用 hash 方法,建一个 2 的 32 次方个 bit 的 hash 数组,每取一个 int 数,可 hash 下 2 的 32 次方找到它在 hash 数组中的位置,然后将 bit 置 1 表示已存在)4 亿个 url,找出其中重复的(考虑内存不够,通过 hash 算法,将 url 分配到 1000 个文件中,不同的文件间肯定就不会重复了,再分别找出重复的)有 1 万个数组,每个数组有 1000 个整数,每个数组都是降序的,从中找出最大的 N 个数,N<1000LinkedHashmap 的底层实现类序列化时类的版本号的用途,如果没有指定一个版本号,系统是怎么处理的?如果加了字段会怎么样?Override 和 Overload 的区别,分别用在什么场景java 的反射是如何实现的参考资料Java 相关一些面试题
2022年01月14日
473 阅读
0 评论
0 点赞
1
2
3