CSS交互动画指南之transition
WEB 动画世界已经变成了一个庞大的工具和技术丛林,像 GSAP 和 Framer Motion 以及 React Spring 这样的库如雨后春笋般涌现,帮助WEB项目开发向 DOM 添加动作。不过,最基本和最关键的部分是 CSS 中的 transition 。它是大多数前端开发人员学习的第一个动画工具,WEB 的交互动画大部分是由 CSS 来完成的。前面介绍 keyframes 的使用《CSS交互动画指南之keyframes》,在本文中,将深入了解并学习更多关于 CSS 的动画之 transition ,文章涉及的代码示例效果可以点击查看动画效果。基础知识创建动画所需的主要角色是一些会发生变化的 CSS 属性,下面是一个鼠标悬停时移动的按钮示例,没有动画:.btn {
width: 80px;
height: 80px;
border-radius: 50%;
border: none;
margin: 15px;
background-color: #6a5acd;
place-content: center;
color: white;
text-align: center;
}
.btn:hover {
transform: translateX(40px);
}当鼠标悬停在按钮上时,这个片段使用 :hover 伪类来指定一个额外的 CSS 声明,类似于 JavaScript 中的 onMouseEnter 事件。为了向右移动元素,使用 transform: translateY(40px),虽然可以为此使用 margin-left,但 transform: translate 是一个更佳的方式。从上面的效果来看,按钮的移动很生硬,下面就为按钮增加 transition 属性:.transition {
transition: transform 250ms;
}transition 属性值可以有多个值,但有两个是必需的:动画属性的名称动画的持续时间如果动画是为多个属性设置的,用逗号分隔的列表作为transition 属性值:.btn-2 {
transition: transform 250ms, opacity 400ms;
}
.btn-2:hover {
transform: scale(1.2);
opacity: 0;
}transition 有一个特殊的值:all,即为任何 CSS 属性的变化增加动画效果。动画效果当告诉一个元素从一个位置转换到另一个位置时,浏览器需要计算出每个 中间帧 应该过渡。例如:假设从左到右移动一个元素,持续时间为 1 秒,流畅的动画应该以 60fps* 的速度切换,这意味着需要在开始和结束之间腾出 60 个单独的位置,就像电影胶片。如果按照均匀分布,每个浅色的圆圈代表一个帧动画。当圆圈从左向右移动时,这些是向用户显示的帧,如下:在这个动画中,使用了一个线性定时函数,意味着元素以恒定的速度移动,图片中的圆圈每一帧的移动量都是相同的。在 CSS 中有几种动画效果可供使用,通过 transition-timing-function 属性来指定:.btn-3 {
transition: transform 250ms;
transition-timing-function: linear;
/* 或者使用推荐方式 */
/* transition: transform 250ms linear;*/
}ease-outease-out 的效果一头野牛冲了进来,但它耗尽了能量,到最后,就像一只昏昏欲睡的乌龟一样缓慢前行。从图片效果可以看出前几帧的速度特别的块,以及它在最后变得迅速降低。ease-ineease-in 的效果正好与ease-out 相反,就有点洗衣机脱水,开始慢慢转动,然后快速加速。ease-in-outease-in-out 是前面两个动画效果的组合:easeease 与 ease-in-out 不同,它不是对称的,它的特点是有一个短暂的加速和很大的减速。ease 是默认值,如果没有指定动画效果,则默认使用ease 。自定义曲线如果提供的内置选项不能满足需求,可以使用三次贝塞尔 timing 函数自定义缓动曲线。.btn-4 {
transition: transform 250ms cubic-bezier(0.1, 0.2, 0.3, 0.4);
}从上面语法可以看到所有值都是这个 cubic-bezier 函数的预设值,它需要4个数字,代表2个控制点。关于如何定义对应的预设值,推荐一个在线工具 cubic-bezier ,调试出满意的动画曲线后,单击顶部的“复制”并将其粘贴到 CSS 中就可以实现相应的动画效果。还可以从这个 Easing functions 缓动函数扩展集中进行选择,需要主要的是有些效果 CSS 的支持不是很好,需要根据实际情况选择。动画演示前面提到动画应该以60帧/秒的速度运行。然而,当计算时,意识到这意味着浏览器只有16.6毫秒来绘制每一帧,时间真的不多。作为参考,眨眼大约需要 100-300毫秒。对于动画的速率,需要设置一个合理的值,要不然设备跟不上,帧会被丢弃。在实践中,性能不佳通常会以可变帧率的形式出现,动画性能是一个非常深入和有趣的主题,这里不做详细介绍,但以下几点还是有必要了解一下:有些CSS属性在制作动画时要比其他属性耗资源得多。例如,高度改变动画就是一个非常费资源的属性,因为它影响布局,当一个元素的高度缩小时,就会引起连锁反应,它的所有兄弟元素需要向上移动,以填补空间。其他属性,如background-color,对于动画效果来说也有点影响性能,虽然它不会影响布局,但确实需要在每个动画帧上设置一个颜色。transform 和 opacity ,是非常推荐的动画效果,对性能影响不大。如果动画当前调整了 width 或 left 等属性,则可以通过 transform 来进行转换(尽管并不总是可以达到完全相同的效果)请务必在网站/应用所针对的最低端设备上测试动画,为低端设备提供兼容的方案,如去掉动画效果。硬件加速根据最终用户浏览器和操作系统的不同,如下图的效果,就存在小缺陷:仔细观察按钮字母,注意到它们在转换的开始和结束时出现了轻微的偏移,这是因为计算机的 CPU 和 GPU 之间的切换导致的。当使用 transform 和 opacity 为元素设置动画时,浏览器有时会尝试优化此动画效果。它不是在每一帧上光栅化像素,而是将所有内容作为纹理传输到 GPU,而 GPU 非常擅长进行这类基于纹理的转换,因此,得到了非常流畅、非常高性能的动画,这被称为硬件加速。硬件加速因此也叫 GPU 加速,是利用 GPU 进行渲染,减少 CPU 操作的一种优化方案。由于 GPU 中的 transform 等 CSS 属性不会触发重绘,所以能大大提高网页的性能。可以通过添加以下 CSS 的 will-change 属性来设置硬件加速:will-change 为web开发者提供了一种告知浏览器该元素会有哪些变化的方法,这样浏览器可以在元素属性真正发生变化之前提前做好对应的优化准备工作。.btn-5 {
will-change: transform;
}will-change 允许向浏览器声明将要为所选元素设置动画,并且应该针对这种情况进行优化,这样浏览器会一直让 GPU 处理这个元素。不再需要 CPU 和 GPU 之间的切换,不再有 卡入到位 的现象。CSS 中的以下属性能触发硬件加速:transformopacityfilterwill-change如果有一些元素不需要用到上述属性,但是需要触发硬件加速效果,可以使用一些小技巧来诱导浏览器开启硬件加速,如下:.item {
-webkit-transform: translateZ(0);
-moz-transform: translateZ(0);
-ms-transform: translateZ(0);
-o-transform: translateZ(0);
transform: translateZ(0);
/**或者**/
transform: rotateZ(360deg);
transform: translate3d(0, 0, 0);
}即使用 translateZ() (或 translate3d() )这种hack方式(有时也称为 null 变换 hack )来让浏览器对 animation 或 transform 行为使用硬件加速,通过向一个不会在三维空间中转换的元素添加简单的3D变换来实现硬件加速。总结前面介绍 keyframes 的使用《CSS交互动画指南之keyframes》,结合本文的 transition,基本可以为前端项目增加相应的改善性的动画。
使用ECS安装java
Java是一种通用的,基于类的,面向对象的编程语言。它是用于应用程序开发的计算平台。因此,Java是快速,安全和可靠的。它被广泛用于在笔记本电脑,数据中心,游戏机,科学超级计算机,手机等中开发Java应用程序。Java遵循WORA(Write Once, Run Anywhere。一次写入,到处运行)的原理,并且与平台无关。它也简单易学。查找java 11版本使用较多,搜索java 11版本安装[root@iZ2zee2m456rb6bde98jpkZ ~]# dnf search java-11
Last metadata expiration check: 0:33:04 ago on Wed 29 Jun 2022 10:47:19 AM CST.
====================================================================== Name Matched: java-11 ======================================================================
java-11-openjdk.x86_64 : OpenJDK 11 Runtime Environment
java-11-openjdk-demo.x86_64 : OpenJDK 11 Demos
java-11-openjdk-demo-fastdebug.x86_64 : OpenJDK 11 Demos optimised with full debugging on
java-11-openjdk-demo-slowdebug.x86_64 : OpenJDK 11 Demos unoptimised with full debugging on
java-11-openjdk-devel.x86_64 : OpenJDK 11 Development Environment
java-11-openjdk-devel-fastdebug.x86_64 : OpenJDK 11 Development Environment optimised with full debugging on
java-11-openjdk-devel-slowdebug.x86_64 : OpenJDK 11 Development Environment unoptimised with full debugging on
java-11-openjdk-fastdebug.x86_64 : OpenJDK 11 Runtime Environment optimised with full debugging on
java-11-openjdk-headless.x86_64 : OpenJDK 11 Headless Runtime Environment
java-11-openjdk-headless-fastdebug.x86_64 : OpenJDK 11 Runtime Environment optimised with full debugging on
java-11-openjdk-headless-slowdebug.x86_64 : OpenJDK 11 Runtime Environment unoptimised with full debugging on
java-11-openjdk-javadoc.x86_64 : OpenJDK 11 API documentation
java-11-openjdk-javadoc-zip.x86_64 : OpenJDK 11 API documentation compressed in a single archive
java-11-openjdk-jmods.x86_64 : JMods for OpenJDK 11
java-11-openjdk-jmods-fastdebug.x86_64 : JMods for OpenJDK 11 optimised with full debugging on
java-11-openjdk-jmods-slowdebug.x86_64 : JMods for OpenJDK 11 unoptimised with full debugging on
java-11-openjdk-slowdebug.x86_64 : OpenJDK 11 Runtime Environment unoptimised with full debugging on
java-11-openjdk-src.x86_64 : OpenJDK 11 Source Bundle
java-11-openjdk-src-fastdebug.x86_64 : OpenJDK 11 Source Bundle for packages with debugging on and optimisation
java-11-openjdk-src-slowdebug.x86_64 : OpenJDK 11 Source Bundle for packages with debugging on and no optimisation
java-11-openjdk-static-libs.x86_64 : OpenJDK 11 libraries for static linking
java-11-openjdk-static-libs-fastdebug.x86_64 : OpenJDK 11 libraries for static linking optimised with full debugging on
java-11-openjdk-static-libs-slowdebug.x86_64 : OpenJDK 11 libraries for static linking unoptimised with full debugging on安装使用dnf install 命令安装[root@iZ2zee2m456rb6bde98jpkZ ~]# dnf install java-11-openjdk java-11-openjdk-devel
Last metadata expiration check: 0:34:34 ago on Wed 29 Jun 2022 10:47:19 AM CST.
Dependencies resolved.
===================================================================================================================================================================
Package Architecture Version Repository Size
===================================================================================================================================================================
Installing:
java-11-openjdk x86_64 1:11.0.15.0.9-2.al8 alinux3-updates 270 k
java-11-openjdk-devel x86_64 1:11.0.15.0.9-2.al8 alinux3-updates 3.4 M
Installing dependencies:
aajohan-comfortaa-fonts noarch 3.001-2.1.al8 alinux3-os 148 k
alsa-lib x86_64 1.2.4-5.1.al8 alinux3-updates 469 k
copy-jdk-configs noarch 4.0-2.al8 alinux3-updates 30 k
fontconfig x86_64 2.13.1-4.al8 alinux3-updates 276 k
fontpackages-filesystem noarch 1.44-22.1.al8 alinux3-os 16 k
giflib x86_64 5.1.4-3.2.al8 alinux3-os 52 k
graphite2 x86_64 1.3.10-10.2.al8 alinux3-os 120 k
harfbuzz x86_64 1.7.5-3.2.al8 alinux3-os 268 k
java-11-openjdk-headless x86_64 1:11.0.15.0.9-2.al8 alinux3-updates 40 M
javapackages-filesystem noarch 5.3.1-7.3.al8 alinux3-updates 32 k
lcms2 x86_64 2.9-2.2.al8 alinux3-os 165 k
libX11 x86_64 1.6.8-4.1.al8 alinux3-updates 612 k
libX11-common noarch 1.6.8-4.1.al8 alinux3-updates 158 k
libXau x86_64 1.0.9-3.1.al8 alinux3-os 38 k
libXcomposite x86_64 0.4.4-14.2.al8 alinux3-os 29 k
libXext x86_64 1.3.4-1.1.al8 alinux3-os 46 k
libXi x86_64 1.7.10-1.1.al8 alinux3-os 49 k
libXrender x86_64 0.9.10-7.2.al8 alinux3-os 33 k
libXtst x86_64 1.2.3-7.2.al8 alinux3-os 22 k
libfontenc x86_64 1.1.3-8.2.al8 alinux3-os 37 k
libjpeg-turbo x86_64 1.5.3-10.2.al8 alinux3-os 156 k
libxcb x86_64 1.13.1-1.3.al8 alinux3-os 230 k
lksctp-tools x86_64 1.0.18-3.2.al8 alinux3-os 101 k
lua x86_64 5.3.4-11.2.al8 alinux3-os 195 k
ttmkfdir x86_64 3.0.9-54.2.al8 alinux3-os 64 k
tzdata-java noarch 2022a-1.al8 alinux3-updates 191 k
xorg-x11-font-utils x86_64 1:7.5-40.2.al8 alinux3-os 104 k
xorg-x11-fonts-Type1 noarch 7.5-19.1.al8 alinux3-os 522 k
Transaction Summary
===================================================================================================================================================================
Install 30 Packages
Total download size: 47 M配置java环境变量[root@iZ2zee2m456rb6bde98jpkZ ~]# which java
/usr/bin/java
[root@iZ2zee2m456rb6bde98jpkZ ~]# ls -l /usr/bin/java
lrwxrwxrwx 1 root root 22 Jun 29 11:22 /usr/bin/java -> /etc/alternatives/java
[root@iZ2zee2m456rb6bde98jpkZ ~]# ls -l /etc/alternatives/java
lrwxrwxrwx 1 root root 62 Jun 29 11:22 /etc/alternatives/java -> /usr/lib/jvm/java-11-openjdk-11.0.15.0.9-2.al8.x86_64/bin/java
[root@iZ2zee2m456rb6bde98jpkZ ~]# echo "export JAVA_HOME=/usr/lib/jvm/java-11-openjdk-11.0.15.0.9-2.al8.x86_64/" >> .bashrc
[root@iZ2zee2m456rb6bde98jpkZ ~]# echo "export PATH=$JAVA_HOME/bin:$PATH" >> .bashrc
[root@iZ2zee2m456rb6bde98jpkZ ~]# source .bashrc 运行jar包使用IDEA创建spring cloud项目编译生成jar包,scp上传到ECS服务器用java运行jar包[root@iZ2zee2m456rb6bde98jpkZ ~]# java -jar demo1-0.0.1-SNAPSHOT.jar
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.7.1)
2022-06-29 11:30:32.062 INFO 15164 --- [ main] com.example.demo.Demo1Application : Starting Demo1Application v0.0.1-SNAPSHOT using Java 11.0.15 on iZ2zee2m456rb6bde98jpkZ with PID 15164 (/root/demo1-0.0.1-SNAPSHOT.jar started by root in /root)
2022-06-29 11:30:32.065 INFO 15164 --- [ main] com.example.demo.Demo1Application : No active profile set, falling back to 1 default profile: "default"
2022-06-29 11:30:32.920 INFO 15164 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http)
2022-06-29 11:30:32.928 INFO 15164 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2022-06-29 11:30:32.928 INFO 15164 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.64]
2022-06-29 11:30:32.978 INFO 15164 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2022-06-29 11:30:32.979 INFO 15164 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 859 ms
2022-06-29 11:30:33.282 INFO 15164 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
2022-06-29 11:30:33.297 INFO 15164 --- [ main] com.example.demo.Demo1Application : Started Demo1Application in 1.626 seconds (JVM running for 1.968)
2022-06-29 11:31:20.263 INFO 15164 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet'
2022-06-29 11:31:20.263 INFO 15164 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet'
2022-06-29 11:31:20.265 INFO 15164 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 2 ms用浏览器打开ip:8080访问,可以看到提示错误信息,服务器是可以访问,是没有编写程序逻辑
【On Nacos】SpringCloud 方式使用 Nacos
如果大家想要了解更多的 Nacos 教程,欢迎 star 《on-nacos》开源项目。基于 Nacos 2.x 的入门、原理、源码、实战介绍,帮助开发者快速上手 Nacos。本文介绍下如何在 Spring Cloud 项目中使用 Nacos,Nacos 主要分为两个部分,配置中心和服务注册与发现。在使用 Spring Cloud 项目中使用 Nacos ,首先要保证启动一个 Nacos 服务,具体可以参考《快速上手 Nacos》来搭建一个单机的 Nacos 服务。Nacos 接入 Spring Cloud 的源代码可以参考 spring-cloud-alibaba 这个项目,感兴趣的朋友可以查看源代码。Spring Cloud Alibaba 的版本对应关系可以参考:版本说明 Wiki本篇文章的详细的代码示例点击【nacos-spring-cloud】查看配置中心创建配置打开控制台 http://127.0.0.1:8848/nacos ,进入 配置管理-配置列表 点击+号新建配置,这里创建个数据源配置例子: nacos-datasource.yamlspring:
datasource:
name: datasource
url: jdbc:mysql://127.0.0.1:3306/nacos?characterEncoding=UTF-8&characterSetResults=UTF-8&zeroDateTimeBehavior=convertToNull&useDynamicCharsetInfo=false&useSSL=false
username: root
password: root
driverClassName: com.mysql.jdbc.Driver添加依赖配置创建好就可以在控制台 配置管理-配置列表中查看到。接下来演示下怎么在 Spring Cloud 项目中获取到 Nacos 中的配置信息。需要在项目中添加以下依赖:<!--配置中心-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
<version>${latest.version}</version>
</dependency>如果项目的 Spring Boot 版本 小于 2.4.0 需要在项目中创建 bootstrap.properties 然后在项目中的 bootstrap.properties 文件中添加 nacos 的一些配置:spring.cloud.nacos.config.server-addr=127.0.0.1:8848
spring.cloud.nacos.config.name=nacos-datasource
spring.cloud.nacos.config.file-extension=yaml
spring.cloud.nacos.config.username=nacos
spring.cloud.nacos.config.password=nacosSpring Boot 2.4.0 版本开始默认不启动 bootstrap 容器 本项目演示如何在 Spring boot < 2.4.0 版本使用 nacos。如果 Spring Boot 版本 ≥ 2.4.0 可以参考 **Nacos Config 2.4.x Example 进行配置在 SpringCloud Alibaba 中,Nacos dataId 的拼接格式为:${prefix} - ${spring.profiles.active} . ${file-extension}prefix默认为spring.application.name的值,也可以通过配置项 @spring.cloud.nacos.config.prefix`来配置。spring.profiles.active即为当前环境对应的 profile,详情可以参考 Spring Boot文档注意,当 active profile 为空时,对应的连接符-也将不存在,dataId 的拼接格式变成${prefix}.${file-extension}file-extension为配置内容的数据格式,可以通过配置项spring.cloud.nacos.config.file-extension来配置。获取配置@Value 注解获取配置/**
* @author lixiaoshuang
*/
@RestController
@RequestMapping(path = "springcloud/nacos/config")
public class AnnotationGetController {
@Value(value = "${spring.datasource.name:}")
private String name;
@Value(value = "${spring.datasource.url:}")
private String url;
@Value(value = "${spring.datasource.username:}")
private String username;
@Value(value = "${spring.datasource.password:}")
private String password;
@Value(value = "${spring.datasource.driverClassName:}")
private String driverClassName;
@GetMapping(path = "annotation/get")
private Map<String, String> getNacosDataSource2() {
Map<String, String> result = new HashMap<>();
result.put("name", name);
result.put("url", url);
result.put("username", username);
result.put("password", password);
result.put("driverClassName", driverClassName);
return result;
}
}启动服务后,通过访问 http://localhost:8333/springcloud/nacos/config/annotation/get 获取配置信息@ConfigurationProperties 注解绑定配置到类/**
* @author lixiaoshuang
*/
@Data
@ConfigurationProperties(prefix = "spring.datasource")
@Component
public class NacosDataSourceConfig {
private String name;
private String url;
private String username;
private String password;
private String driverClassName;
}
/**
* @author lixiaoshuang
*/
@RestController
@RequestMapping(path = "springcloud/nacos/config")
public class BindingClassController {
@Resource
private NacosDataSourceConfig nacosDataSourceConfig;
@GetMapping(path = "binding/class/get")
private Map<String, String> getNacosDataSource() {
Map<String, String> result = new HashMap<>();
result.put("name", nacosDataSourceConfig.getName());
result.put("url", nacosDataSourceConfig.getUrl());
result.put("username", nacosDataSourceConfig.getUsername());
result.put("password", nacosDataSourceConfig.getPassword());
result.put("driverClassName", nacosDataSourceConfig.getDriverClassName());
return result;
}
}启动服务后,通过访问 http://localhost:8333/springcloud/nacos/config/binding/class/get 获取配置信息服务注册&发现服务注册在项目中添加以下依赖:<!--注册中心-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>${latest.version}</version>
</dependency>然后在项目中的 application.properties 文件中添加 nacos 的一些配置:spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
spring.cloud.nacos.discovery.username=naocs
spring.cloud.nacos.discovery.password=nacos当添加完配置启动服务以后看到下面这段日志,就说明服务注册成功了。nacos registry, DEFAULT_GROUP SPRING-CLOUD-CONFIG 192.168.1.8:8444 register finished服务发现整合 @LoadBalanced RestTemplate首先模拟一个订单服务的接口,可以通过服务发现的方式调用此接口/**
* @author lixiaoshuang
*/
@RestController
public class OrderServiceController {
@GetMapping("/order/{orderid}")
public String echo(@PathVariable String orderid) {
System.out.println("接到远程调用订单服务请求");
return "[ORDER] : " + "订单id:[" + orderid + "] 的订单信息";
}
}先访问下 http://localhost:8444/order/1234 获取订单id 为 1234 的订单信息,看接口是否能调通:接下来在模拟一个业务接口,调用订单接口。这里通过使用 @LoadBalanced 注解使 RestTemplate 具备负载均衡和服务发现的能力,这样就可以通过指定服务名调用。/**
* @author lixiaoshuang
*/
@RestController
public class RestTemplateController {
@Autowired
public RestTemplate restTemplate;
@Bean
@LoadBalanced
public RestTemplate RestTemplate() {
return new RestTemplate();
}
@GetMapping("/call/order/{orderid}")
public String callEcho(@PathVariable String orderid) {
// 访问应用 SPRING-CLOUD-CONFIG 的 REST "/order/{orderid}"
return restTemplate.getForObject("http://SPRING-CLOUD-CONFIG/order/" + orderid, String.class);
}
}访问接口 http://localhost:8444/call/order/1234 获取订单 1234 的订单信息,内部将通过服务名 SPRING-CLOUD-CONFIG 发现服务,并调用 /order/{orderid} 接口获取订单信息。观察 order 服务的日志会发现有远程调用的日志
SpringBoot启动类的扫描注解的用法及冲突原则
背景SpringBoot 启动类上,配置扫描包路径有三种方式,最近看到一个应用上三种注解都用上了,代码如下:@SpringBootApplication(scanBasePackages ={"a","b"})
@ComponentScan(basePackages = {"a","b","c"})
@MapperScan({"XXX"})
public class XXApplication extends SpringBootServletInitializer
}那么,疑问来了:SpringBoot 中,这三种注解生效优先级如何、第一种和第二种有没有区别呢?本文来整理下这三个注解的注意事项。SpringBootApplication 注解这是 SpringBoot 的注解,本质是三个 Spring 注解的和 @Configuration @EnableAutoConfiguration @ComponentScan 它默认扫描启动类所在包及其所有子包,但是不包括第三方的 jar 包的其他目录,通过scanBasePackages 属性可以重新设置扫描包路径。注意:如果我们需要扫描依赖 jar 包中的注解,而依赖包的路径跟不包含在 SpringBoot 启动类路径中的话,我们就要单独使用 @ComponentScan 注解扫描第三方包。同时必须指定本工程的扫描路径,因为一旦有这个注解后,它优先,默认扫描包就失效了。例如这个工程:编辑SpringBoot 启动类的工程目录为 cn.com.a.b,引用的第三方公共包 xxx.common.jar 的目录也是 cn.com.a.b ,那么第三方 jar 包中的注解天然能直接被扫描到。其他的 jar 包中,如果有注解,就无法扫描到了。ComponentScan注解这个是 Spring 框架的注解,它用来指定组件扫描路径,如果用这个注解,它的值必须包含整个工程中全部需要扫描的路径。因为它会覆盖 SpringBootApplication 的默认扫描路径,导致其失效。失效表现有两种:第一,如果 ComponentScan 只包括一个值且就是默认启动类目录,SpringBootApplication 生效, ComponentScan 注解失效,报错:编辑第二,如果 ComponentScan 指定多个具体子目录,此时 SpringBootApplication 会失效,Spring 只会扫描 ComponentScan 指定目录下的注解。如果恰好有目录外的 Controller 类,很遗憾,这些控制器将无法访问。回到开头那段代码:@SpringBootApplication(scanBasePackages ={})
@ComponentScan(basePackages = {})这里指定了 ComponentScan 注解后, scanBasePackages 就失效了。因此,如果 ComponentScan 的 basePackages 值不包括 cn.com.a.b 即启动类所在的包,仅指定了第三方 jar 的目录,那么这个工程下任何的注解都无法被扫描到。MapperScan 注解这个是 MyBatis 的注解,会将指定目录下所有 DAO 类封装成 MyBatis 的 BaseMapper 类,然后注入 Spring 容器中,不需要额外的注解,就可以完成注入。启示录SpringBoot 包扫描路径,两个注解的冲突行为,我反复验证了好久确定了现象,但是没有找到合理的解释。这篇文章在草稿箱酝酿了快两周了,一直搁置着。今天搜到了一篇文章,说二者同时使用时, SpringBootApplication 会失效,至此 SpringBoot 扫描路径的疑惑终于消除了。
云起学习小记
这次举办的活动非常的好,一共有五期,分别是从零到一上手玩转云服务器、Linux操作系统实战入门、MySQL数据库进阶实战、零基础容器技术实战、轻松入门学习大数据。让我们以练带学的方式去学习这些基础知识。感谢云起实验室举办的那么好的活动。第一期学习非常的有意思,实验难度适中。0基础的同学也可以快速入门。第一期我们学习了上云基础,动手实操ECS云服务器,动手快速搭建LAMP环境、使用ECS服务器部署MySQL数据库、通过workbench远程登录ECS,快速搭建Docker环境》及 《从零搭建Spring Boot的Hello World》、使用PolarDB和ECS搭建门户网站,整体来说这一周的学习还是很丰富的。从零到上手玩转云服务器。了解了ECS服务器的基本操作、了解了基本应用例如LAMP、Docker的安装。 第二期是Linux基础。让我们一起好好学习吧。
开发环境中使用Maven|学习笔记
开发者学堂课程【项目管理工具Maven学习:开发环境中使用Maven】学习笔记,与课程紧密联系,让用户快速学习知识。课程地址:https://developer.aliyun.com/learning/course/23目录:一、仓库二、Eclipse 一、仓库1、坐标关于坐标,首先会想到平面几何,x,y!!任何一个坐标都能够唯一标识该平面中的一点。在实际生活中,将地址看成是一个坐标。省,市,县,街道,门牌号等等。Maven的世界中拥有数量巨大的构件,也就是平时用的一些jar,war等文件。如果没有Maven我们只能去各个官方网站查找下载,去本地搜索,论坛上发帖询问。想要找一个jar包,大量的时间花费在搜索,浏览网页等工作上面。因为没有统一的规范,所以无法自动化处理。现在,Maven定义了这样一组规则:世界上任何一个构件都可以使用Maven坐标唯一标识,Maven坐标元素包括: groupld 、artifactld、version、packaing.classifier.Maven提供了一个中央仓库。该中央仓库包含了世界上大部分流行的开源项目构件。在开发自己的项目的时候,也需要为其定义适当的坐标,这是Maven强制要求的,在这个基础上,其他Maven项目才能引用该项目生成的构件。2、坐标详解Maven坐标为各种组件引入了秩序,任何一个组件都必须明确定义自己的坐标。Groupld:定义当前Maven项目隶属的实际项目。由于Maven中模块的概念,因此一个实际项目往往会被划分为很多模块。比如spring是一个实际项目,其对应的Maven 模块会有很多,如spring-core,spring-webmvc 等。groupId:的表示方法与java包名的表示方法类似,通常与域名反向一一对应。ArtifactId:该元素定义实际项目中的一个Maven模块,推荐的做法是使用实际项目名称作为atifactId的前缀。比如: spring-bean。Version:该元素定义Maven项目当前所处的版本。Packaging:该元素定义Maven项目的打包方式。首先,打包方式通常与所生成构件的文件扩展名对应。Maven默认的打包方式是jat。也可以是war,ear,可执行的jar。Classifier:该元素用来帮助定义构建输出的一些附属构件。比如javadoc和sourcesjar。这是java文档和源代码。3、仓库的概念●当第一次运行Maven命令的时候,你需要Intenet链接,因为它需要从网上下载一些文件。那么它从哪里下载呢?它是从Maven默认的远程库下载的。 这个远程仓库由Maven 的核心插件和可供下载的jar文件。●对于Maven来说,仓库只分为两类:本地仓库和远程仓库。当Maven根据坐标寻找构件的时候,它首先会查看本地仓库,如果本地仓库存在,则直接使用;如果本地没有,Maven就会去远程仓库查找,发现需要的构件之后,下载到本地仓库再使用。如果本地仓库和远程仓库都没有,Maven就会报错。●提示:远程仓库分为三种:中央仓库,私服,其他公共库。 中央仓库是默认配置下,Maven下载jar包的地方。 ◆私服是另一种特殊的远程仓库,为了节省带宽和时间,应该在局域网内架设一个私有的仓库服务器,用其代理所有外部的远程仓库。内部的项目还能部署到私服上供其他项目使用。●私服可以通过nexus来搭建Maven。●为什么要搭建nexus私服,原因很简单,有些公司都不提供外网给项目组人员,因此就不能使用maven访问远程的仓库地址,所以很有必要在局域网里找一台有外网权限的机器,搭建nexus私服,然后开发人员连到这台私服上,这样的话就可以通过这台搭建了nexus私服的电脑访问maven的远程仓库。①首先确定环境安装好maven,jdk等必负的环境②这些都准备好之后,去下载最新版本的nexus下载地址:http://www.sonatype.org/nexus/go本地安装的是nexus-22-01-bundle,最新的版本是nexus-2.4.0-09-bundle③打开目录nexus-2.4.0-09-bundlenexus-24.0-09\binsw这个目录下面你会发现有很多系统版本的nexus环增。二、EclipseEclipse 是一个开放源代码的、基于Java的可扩展开发平台。就其本身而言,它只是一个框架和一组服务,用于通过插件组件构建开发环境。幸运的是,Eclipse 附带了一个标准的插件集,包括Java开发工具(Java Development Kit,JDK)。虽然大多数用户很乐于将Eclipse 当作Java 集成开发环境(IDE)来使用,但Eclipse 的目标却不仅限于此。Eclipse 还包括插件开发环境(Plug-in Development Environment,PDE),这个组件主要针对希望扩展Eclipse 的软件开发人员,因为它允许他们构建与Eclipse 环境无缝集成的工具。由于Eclipse 中的每样东西都是插件,对于给Eclipse 提供插件,以及给用户提供一致和统一的集成开发环境而言,所有工具开发人员都具有同等的发挥场所。这种平等和一致性并不仅限于 Java 开发工具。尽管Eclipse 是使用Java语言开发的,但它的用途并不限于Java 语言;例如,支持诸如C/C++、COBOL、PHP等编程语言的插件已经可用,或预计将会推出。Eclipse 框架还可用来作为与软件开发无关的其他应用程序类型的基础,比如内容管理系统。基于Eclipse 的应用程序的一个突出例子是IBM Rational Software Architect,它构成了 IBMJava 开发工具系列的基础。
《我想进大厂》之Spring夺命连环10问
1.说说Spring 里用到了哪些设计模式?单例模式:Spring 中的 Bean 默认情况下都是单例的。无需多说。工厂模式:工厂模式主要是通过 BeanFactory 和 ApplicationContext 来生产 Bean 对象。代理模式:最常见的 AOP 的实现方式就是通过代理来实现,Spring主要是使用 JDK 动态代理和 CGLIB 代理。模板方法模式:主要是一些对数据库操作的类用到,比如 JdbcTemplate、JpaTemplate,因为查询数据库的建立连接、执行查询、关闭连接几个过程,非常适用于模板方法。2.谈谈你对IOC 和 AOP 的理解?他们的实现原理是什么?IOC 叫做控制反转,指的是通过Spring来管理对象的创建、配置和生命周期,这样相当于把控制权交给了Spring,不需要人工来管理对象之间复杂的依赖关系,这样做的好处就是解耦。在Spring里面,主要提供了 BeanFactory 和 ApplicationContext 两种 IOC 容器,通过他们来实现对 Bean 的管理。AOP 叫做面向切面编程,他是一个编程范式,目的就是提高代码的模块性。Srping AOP 基于动态代理的方式实现,如果是实现了接口的话就会使用 JDK 动态代理,反之则使用 CGLIB 代理,Spring中 AOP 的应用主要体现在 事务、日志、异常处理等方面,通过在代码的前后做一些增强处理,可以实现对业务逻辑的隔离,提高代码的模块化能力,同时也是解耦。Spring主要提供了 Aspect 切面、JoinPoint 连接点、PointCut 切入点、Advice 增强等实现方式。3. JDK 动态代理和 CGLIB 代理有什么区别?JDK 动态代理主要是针对类实现了某个接口,AOP 则会使用 JDK 动态代理。他基于反射的机制实现,生成一个实现同样接口的一个代理类,然后通过重写方法的方式,实现对代码的增强。而如果某个类没有实现接口,AOP 则会使用 CGLIB 代理。他的底层原理是基于 asm 第三方框架,通过修改字节码生成成成一个子类,然后重写父类的方法,实现对代码的增强。4. Spring AOP 和 AspectJ AOP 有什么区别?Spring AOP 基于动态代理实现,属于运行时增强。AspectJ 则属于编译时增强,主要有3种方式:编译时织入:指的是增强的代码和源代码我们都有,直接使用 AspectJ 编译器编译就行了,编译之后生成一个新的类,他也会作为一个正常的 Java 类装载到JVM。编译后织入:指的是代码已经被编译成 class 文件或者已经打成 jar 包,这时候要增强的话,就是编译后织入,比如你依赖了第三方的类库,又想对他增强的话,就可以通过这种方式。加载时织入:指的是在 JVM 加载类的时候进行织入。总结下来的话,就是 Spring AOP 只能在运行时织入,不需要单独编译,性能相比 AspectJ 编译织入的方式慢,而 AspectJ 只支持编译前后和类加载时织入,性能更好,功能更加强大。5. FactoryBean 和 BeanFactory有什么区别?BeanFactory 是 Bean 的工厂, ApplicationContext 的父类,IOC 容器的核心,负责生产和管理 Bean 对象。FactoryBean 是 Bean,可以通过实现 FactoryBean 接口定制实例化 Bean 的逻辑,通过代理一个Bean对象,对方法前后做一些操作。6.SpringBean的生命周期说说?SpringBean 生命周期简单概括为4个阶段:实例化,创建一个Bean对象填充属性,为属性赋值初始化如果实现了xxxAware接口,通过不同类型的Aware接口拿到Spring容器的资源如果实现了BeanPostProcessor接口,则会回调该接口的postProcessBeforeInitialzation和postProcessAfterInitialization方法如果配置了init-method方法,则会执行init-method配置的方法销毁容器关闭后,如果Bean实现了DisposableBean接口,则会回调该接口的destroy方法如果配置了destroy-method方法,则会执行destroy-method配置的方法7.Spring是怎么解决循环依赖的?首先,Spring 解决循环依赖有两个前提条件:不全是构造器方式的循环依赖必须是单例基于上面的问题,我们知道Bean的生命周期,本质上解决循环依赖的问题就是三级缓存,通过三级缓存提前拿到未初始化的对象。第一级缓存:用来保存实例化、初始化都完成的对象第二级缓存:用来保存实例化完成,但是未初始化完成的对象第三级缓存:用来保存一个对象工厂,提供一个匿名内部类,用于创建二级缓存中的对象假设一个简单的循环依赖场景,A、B互相依赖。A对象的创建过程:创建对象A,实例化的时候把A对象工厂放入三级缓存A注入属性时,发现依赖B,转而去实例化B同样创建对象B,注入属性时发现依赖A,一次从一级到三级缓存查询A,从三级缓存通过对象工厂拿到A,把A放入二级缓存,同时删除三级缓存中的A,此时,B已经实例化并且初始化完成,把B放入一级缓存。着继续创建A,顺利从一级缓存拿到实例化且初始化完成的B对象,A对象创建也完成,删除二级缓存中的A,同时把A放入一级缓存最后,一级缓存中保存着实例化、初始化都完成的A、B对象因此,由于把实例化和初始化的流程分开了,所以如果都是用构造器的话,就没法分离这个操作,所以都是构造器的话就无法解决循环依赖的问题了。8. 为什么要三级缓存?二级不行吗?不可以,主要是为了生成代理对象。因为三级缓存中放的是生成具体对象的匿名内部类,他可以生成代理对象,也可以是普通的实例对象。使用三级缓存主要是为了保证不管什么时候使用的都是一个对象。假设只有二级缓存的情况,往二级缓存中放的显示一个普通的Bean对象,BeanPostProcessor去生成代理对象之后,覆盖掉二级缓存中的普通Bean对象,那么多线程环境下可能取到的对象就不一致了。9.Spring事务传播机制有哪些?PROPAGATION_REQUIRED:如果当前没有事务,就创建一个新事务,如果当前存在事务,就加入该事务,这也是通常我们的默认选择。PROPAGATION_REQUIRES_NEW:创建新事务,无论当前存不存在事务,都创建新事务。PROPAGATION_NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则按REQUIRED属性执行。PROPAGATION_NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。PROPAGATION_MANDATORY:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就抛出异常。PROPAGATION_SUPPORTS:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就以非事务执行。‘10.最后,说说Spring Boot 启动流程吧?这个流程,网上一搜基本都是这张图了,我也不想再画一遍了。那其实主要的流程就几个步骤:准备环境,根据不同的环境创建不同的Environment准备、加载上下文,为不同的环境选择不同的Spring Context,然后加载资源,配置Bean初始化,这个阶段刷新Spring Context,启动应用最后结束流程
微信小程序实现蓝牙开门前后端项目(一)
物料准备一个蓝牙模块详细设计业务流程图后台管理功能1、管理员(超级管理员)可以登录后台,维护小区的基本信息、维护小区物业的账号信息、查询操作日志、查询用户列表2、小区物业可以登录后台,维护小区业主的账号信息,查询用户列表小程序开门功能1、用户(业主)打开微信小程序,小程序检测用户是否登录状态,未登录,跳转到登录页,提示登录;已登录,跳转到开门页面2、检测用户是否开启蓝牙,未开启蓝牙,提示:请开启蓝牙;已开启蓝牙,检测附近是否有蓝牙设备,没有蓝牙设备,提示:没有检测到蓝牙设备,请联系物业或密码刷卡开门(因为开不了门,需要咨询物业);有蓝牙设备,使用预先下载的秘钥连接蓝牙设备,如果连接失败,重试3次,超过3次未成功,提示:设备连接失败,请联系物业或密码刷卡开门;如果连接成功,开门按钮高亮显示,用户可以点击开门按钮;3、用户点击开门按钮后,小程序会通过蓝牙接口调用蓝牙设备,发送开门指令;如果开门失败,会快速重试3次,超过3次,提示:设备连接失败,请联系物业或密码刷卡开门;如果开门成功,会有手机震动提醒。4、用户也可以在开门界面,摇动手机开门,流程同3小程序界面设计一共就两个页面,“我的页面”和“首页”。“我的页面” - 由于是第一版,主要是实现开门功能,我的页面非常简单,只有一个退出登录按钮,后期会增加访客邀请,开门记录等功能“首页” - 开门页面是该小程序的主页页面,由蓝牙状态、开门状态和开门按钮组成,开门按钮设计的比较大并且醒目,方便用户点击管理后台设计管理后台因为是管理人员使用,页面要求不是很高,因此使用了开源前端框架自带主题,该前端框架AdminLTE一共有8种主题可切换技术选型由于本软件跟后端没有太多交互,只是对用户进行基本的账户认证,对后端性能要求较低,因此后端只需采用SpringBoot单体应用即可。数据库采用流行的Mysql其他技术栈:Spring、Freemarker、Shiro、Mybatis前端:微信小程序表结构设计功能接口小程序调用接口小程序调用后端接口比较简单,主要就是验证账户,和保存用户信息用户登录接口地址:/api/mini/user/userLogin请求方式:POST请求字段:名称说明username用户名password密码返回字段:名称说明id用户idusername用户名小程序登录获取code接口地址:/api/mini/user/getLogin请求方式:POST请求字段:名称说明code小程序codeminiUserVO小程序用户信息Object+avatarUrl头像+province省+其他略 返回字段:名称说明sessionId状态idopenid小程序用户唯一idunionid userInfo小程序用户信息Object+avatarUrl头像+province省+其他略 好了,夜已深,今天就到此为止,本文专门用来说明该软件的业务流程、功能设计、接口设计和表结构设计,这是非常有必要的,这将帮我们理清开发思路,为下文的编码实现打下坚实的基础。实际上我们做任何项目都需要先设计(总体设计->详细设计->评审),评审通过后才开始编码。
学习资源
搞学习TED(最优质的演讲):https://www.ted.com/谷粉学术:https://gfsoso.99lb.net/scholar.html大学资源网:http://www.dxzy163.com/简答题:http://www.jiandati.com/网易公开课:https://open.163.com/ted/网易云课堂:https://study.163.com/中国大学MOOC:www.icourse163.org哔哩哔哩弹幕网:www.bilibili.com我要自学网:www.51zxw.net知乎:www.zhihu.com学堂在线:www.xuetangx.com爱课程:www.icourses.cn猫咪论文:https://lunwen.im/iData(论文搜索):www.cn-ki.net文泉考试:https://www.wqkaoshi.comCSDN:https://www.csdn.net/基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能。项目地址:https://github.com/YunaiV/ruoyi-vue-pro找书籍书栈网(极力推荐):https://www.bookstack.cn/码农之家(计算机电子书下载):www.xz577.com鸠摩搜书:www.jiumodiary.com云海电子图书馆:www.pdfbook.cn周读(书籍搜索):ireadweek.com知轩藏书:http://www.zxcs.me/脚本之家电子书下载:https://www.jb51.net/books/搜书VIP-电子书搜索:http://www.soshuvip.com/all.html书格(在线古籍图书馆):https://new.shuge.org/caj云阅读:http://cajviewer.cnki.net/cajcloud/必看网(人生必看的书籍):https://www.biikan.com/基于微服务的思想,构建在 B2C 电商场景下的项目实战。核心技术栈,是 Spring Boot + Dubbo 。未来,会重构成 Spring Cloud Alibaba 。项目地址:https://github.com/YunaiV/onemall冷知识 / 黑科技上班摸鱼必备(假装电脑系统升级):http://fakeupdate.net/PIECES 拼图(30 个 CSS 碎片进行拼图,呈现 30 种濒临灭绝的动物):http://www.species-in-pieces.com/图片立体像素画:https://pissang.github.io/voxelize-image/福利单词(一个不太正经的背单词网站):http://dict.ftqq.com查无此人(刷新网站,展现一张AI 生成的人脸照片):https://thispersondoesnotexist.com/在线制作地图图例:https://mapchart.net/创意光线绘画:http://weavesilk.com/星系观察:https://stellarium-web.org/煎蛋:http://jandan.net/渣男-说话的艺术:https://lovelive.tools/全历史:https://www.allhistory.com/iData:https://www.cn-ki.net/术语在线:http://www.termonline.cn/写代码GitHub:https://github.com/码云:https://gitee.com/源码之家:https://www.mycodes.net/JSON to Dart:https://javiercbk.github.io/json_to_dart/Json在线解析验证:https://www.json.cn/在线接口测试(Getman):https://getman.cn/资源搜索DogeDoge搜索引擎:www.dogedoge.com秘迹搜索:https://mijisou.com/小白盘:https://www.xiaobaipan.com/云盘精灵(资源搜索):www.yunpanjingling.com虫部落(资源搜索):www.chongbuluo.com如风搜(资源搜索):http://www.rufengso.net/爱扒:https://www.zyboe.com/小工具奶牛快传(在线传输文件利器):cowtransfer.com文叔叔(大文件传输,不限速):https://www.wenshushu.cn/云端超级应用空间(PS,PPT,Excel,Ai):https://uzer.me/香当网(年终总结,个人简历,事迹材料,租赁合同,演讲稿):https://www.xiangdang.net/二维码生成:https://cli.im/搜狗翻译:fanyi.sogou.com熵数(图表制作,数据可视化):https://dydata.io/appv2/#/pages/index/home拷贝兔:https://cp.anyknew.com/图片无限变放大:http://bigjpg.com/zh幕布(在线大纲笔记工具):mubu.com在线转换器(在线转换器转换任何测量单位):https://zh.justcnw.com/调查问卷制作:https://www.wenjuan.com/果核剥壳(软件下载):https://www.ghpym.com/软件下载:https://www.unyoo.com/MSDN我告诉你(windows10系统镜像下载):https://msdn.itellyou.cn/导航页(工具集)世界各国网址大全:http://www.world68.com/小森林导航:http://www.xsldh6.com/简捷工具:http://www.shulijp.com/NiceTool.net 好工具网:http://www.nicetool.net/现实君工具箱(综合型在线工具集成网站):http://tool.uixsj.cn/蓝调网站:http://lcoc.top/偷渡鱼:https://touduyu.com/牛导航:http://www.ziliao6.com/小呆导航:https://www.webjike.com/index.html简法主页:http://www.jianfast.com/KIM主页:https://kim.plopco.com/聚BT:https://jubt.net/cn/index.html精准云工具合集:https://jingzhunyun.com/兔2工具合集:https://www.tool2.cn/爱资料工具(在线实用工具集合):www.toolnb.com工具导航:https://hao.logosc.cn/看视频阿木影视:https://www.aosk.online/电影推荐(分类别致):http://www.mvcat.comAPP影院:https://app.movie去看TV:https://www.qukantv.net/动漫视频网:http://www.zzzfun.com/94神马电影网:http://www.9rmb.com/NO视频官网:http://www.novipnoad.com/蓝光画质电影:http://www.languang.co/在线看剧:http://dy.27234.cn/大数据导航:http://hao.199it.com/多功能图片网站:https://www.logosc.cn/so/牛牛TV:http://www.ziliao6.com/tv/VideoFk解析视频:http://www.videofk.com/蓝调网站:http://lcoc.top/vip2.3/永久资源采集网:http://www.yongjiuzy1.com/学设计码力全开(产品/设计师/独立开发者的资源库):https://www.maliquankai.com/designnav/免费音频素材:https://icons8.cn/music新CG儿(视频素材模板,无水印+免费下载):https://www.newcger.com/Iconfont(阿里巴巴矢量图标库):https://www.iconfont.cn/小图标下载:https://www.easyicon.net/Flight Icon:https://www.flighticon.co/第一字体转换器:http://www.diyiziti.com/doyoudosh(平面设计):www.doyoudo.com企业宣传视频在线制作:https://duomu.tv/MAKE海报设计官网:http://maka.im/一键海报神器:https://www.logosc.cn/photo/utm_source=hao.logosc.cn&utm_medium=referral字由(字体设计):http://www.hellofont.cn/查字体网站:https://fonts.safe.360.cn/爱给网(免费素材下载的网站,包括音效、配乐,3D、视频、游戏,平面、教程):http://www.aigei.com/在线视频剪辑:https://bilibili.clipchamp.com/editor搞文档即书(在线制作PPT):https://www.keysuper.com/PDF处理:https://smallpdf.com/cnPDF处理:https://www.ilovepdf.com/zh-cnPDF处理:https://www.pdfpai.com/PDF处理:https://www.hipdf.cn/图片压缩,PDF处理:https://docsmall.com/腾讯文档(在线协作编辑和管理文档):docs.qq.comProcessOn(在线协作制作结构图):www.processon.comiLovePDF(在线转换PDF利器):www.ilovepdf.comPPT在线制作:https://www.woodo.cn/PDF24工具(pdf处理工具):https://tools.pdf24.org/enIMGBOT(在线图片处理):www.imgbot.ai福昕云编辑(在线编辑PDF):edit.foxitcloud.cnTinyPNG(在线压缩图片):tinypng.comUZER.ME(在线使用各种大应用,在线使用CAD,MATLAB,Office三件套):uzer.me优品PPT(模板下载):http://www.ypppt.com/第一PPT(模板下载):http://www.1ppt.com/xiazai/三顿PPT导航:sandunppt.comExcel函数表:https://support.office.com/zh-cn/article/excel-%E5%87%BD%E6%95%B0%EF%BC%88%E6%8C%89%E5%AD%97%E6%AF%8D%E9%A1%BA%E5%BA%8F%EF%BC%89-b3944572-255d-4efb-bb96-c6d90033e188找图片电脑壁纸:http://lcoc.top/bizhi/https://unsplash.com/https://pixabay.com/https://www.pexels.com/https://visualhunt.com/https://www.ssyer.com/彼岸图网:http://pic.netbian.com/极像素(超高清大图):https://www.sigoo.com/免费版权图片搜索:https://www.logosc.cn/so/
设计模式:单例、原型和生成器
>在这篇文章中,我们将重点介绍其余的创建模式:Singleton,Builder和Prototype。在我看来,这些模式不如工厂重要。然而,了解它们仍然很有用。我将提供UML描述,简单的java示例(这样即使你不了解java,你也可以理解),并提出来自着名Java框架或API的真实示例。@[toc]# 创造模式创建模式是处理对象初始化并克服构造函数限制的设计模式。四**人帮**在他们的书“【设计模式:可重用面向对象软件的元素】中描述了其中的五个:- 单身人士,- 建筑工人,- 原型,- 抽象工厂,- 工厂模式。自本书出版(1994年)以来,已经发明了许多创造模式:- 其他类型的工厂(如静态工厂),- 池模式,- 惰性初始化,- 依赖注入,- 服务定位器,- ...>在这篇文章中,我们只关注我还没有描述过的GoF的创造模式的其余部分。正如我在引言中所说,它们不如工厂重要,因为你可以没有它们(而工厂是许多应用程序和框架的支柱)。但是,它们很有用,与工厂不同,它们不会使代码更难阅读。# 单例模式这种模式是最著名的。在过去的几十年里,它被过度使用,但自那以后它的受欢迎程度有所下降。我个人避免使用它,因为它使代码**更难以进行单元测试**并创建**紧密耦合**。我更喜欢使用处理类的授权实例数的工厂(如Spring容器),我们将讨论这种方法。我认为你应该避免单例模式。事实上,这种模式最重要的用途是能够在面试官问“什么是单例?”时回答他。这种模式非常有争议,仍然有人赞成它。话虽如此,根据GoF的说法,单例旨在:> *“确保一个类只有一个实例,并提供对它的全局访问点”*因此,类是单例有 2 个要求:- 拥有唯一实例- 可从任何地方访问有些人只考虑第一个要求(比如几年前的我)。在这种情况下,该类只是一个**实例**。让我们看一下如何在UML中执行单例[](http://coding-geek.com/wp-content/uploads/2015/06/singleton.png)在此 UML 图中,单例类有 3 项:- 类属性(实例):此属性包含单例类的唯一实例。- 一个类公共方法(getInstance()):它提供了获取类Singleton的唯一实例的唯一方法。可以从任何地方调用该方法,因为它是类方法(而不是实例方法)。- 私有构造函数(Singleton()):它阻止任何人使用构造函数实例化单例。在此示例中,需要 Singleton 实例的开发人员将调用 Singleton.getInstance() 类方法。单例类中的单例实例可以是:- 预初始化(这意味着在有人调用 getInstance()之前,它被实例化了)- lazy-initialized(这意味着它是在第一次调用getInstance()期间实例化的)当然,真正的单例还有其他方法和属性来执行其业务逻辑。## Java 实现这是使用预实例化方法在Java中创建单例的非常简单的方法。```javapublic class SimpleSingleton { private static final SimpleSingleton INSTANCE = new SimpleSingleton(); private SimpleSingleton() { } public static SimpleSingleton getInstance(){ return INSTANCE; }}```使用这种方式,当类装入器装入类时,仅创建一次单一实例。如果代码中从未使用过该类,则不会实例化该实例(因为 JVM 的类装入器不会加载它),因此会浪费内存。但是,如果该类出现并且您不使用它(例如,如果它仅在非常非常罕见的条件下使用),则单例将无条件初始化。除非您的单例占用大量内存,否则应以这种方式使用。不过,如果你只需要在真正使用时创建单例(惰性初始化),这里有一种方法可以在多线程环境中执行此操作。这部分有点棘手,因为它涉及线程一致性。```javapublic class TouchySingleton { private static volatile TouchySingleton instance; private TouchySingleton() { } public static TouchySingleton getInstance() { if (instance == null) { synchronized (TouchySingleton.class) { if (instance == null) { instance = new TouchySingleton(); } } } return instance; }}```正如我所说,它真的更难阅读(这就是为什么预先实例化的方式更好)。此单例涉及一个锁,以避免 2 个线程同时调用 getInstance() 创建 2 个实例。由于锁的成本很高,因此首先要进行没有锁的测试,然后使用锁进行测试(这是双重检查的锁定),以便在实例已经存在时不使用锁。另一个特殊性是,实例必须是易失性的,以确保在创建实例时,其状态在不同的处理器内核上是相同的。## 何时需要使用单例?- 当您只需要一个资源(数据库连接,套接字连接...- 避免无状态类的多个实例以避免内存浪费- 出于业务原因**您不应该使用单例在不同对象之间共享变量/数据,因为它会产生非常紧密的耦合!**## 为什么不使用单例?一开始,我说你不应该使用单例,因为你获得单例的方式。它基于一个类函数,可以在代码中的任何位置调用该函数。我在[stackoverflow上](https://stackoverflow.com/questions/137975/what-is-so-bad-about-singletons)读到了一个很好的答案,给出了它不好的4个原因:- 使用单例,可以隐藏类之间的依赖关系,而不是通过接口公开它们。这意味着您需要阅读每个方法的代码,以了解一个类是否正在使用另一个类。- 它们违反了[单一责任原则](https://en.wikipedia.org/wiki/Single_responsibility_principle):它们控制自己的创建和生命周期(使用惰性初始化,单例在创建时选择)。一个类应该只关注它要做什么。如果你有一个管理人员的单例,它应该只管理人员,而不是如何/何时创建。- 它们本质上会导致代码紧密耦合。这使得伪造或嘲笑它们进行单元测试变得非常困难。- 它们在应用程序的生存期内携带状态(对于有状态单例)。 - 它使单元测试变得困难,因为您可能最终会遇到需要订购测试的情况,这是一种无稽之谈。根据定义,每个单元测试都应该彼此独立。 - 此外,它使代码的可预测性降低。好吧,所以单例很糟糕。但是你应该使用什么呢?## 使用单个实例而不是单一实例单例只是一种特定类型的单个实例,可以使用其类方法到达任何地方。如果删除此第二个要求,则会删除许多问题。但是,如何处理单个实例呢?一种可能的方法是使用工厂和依赖注入来管理单个实例(这将是未来文章的主题)。让我们举个例子来理解:- 您有一个需要唯一数据库连接实例的 PersonBusiness 类。- PersonBusiness 将具有 DatabaseConnection 属性,而不是使用单例来获取此连接。- 此属性将由其构造函数在 PersonBusiness 的实例化时注入。当然,您可以注入任何类型的数据库连接: - 适用于您的开发环境的 MysqlDatabaseConnection - 面向生产环境的 OracleDatabaseConnection - 用于单元测试的模拟数据库连接- 在此阶段,没有任何操作可以阻止数据库连接是唯一的。这就是工厂有用的地方。您将PersonBusiness的创建委托给工厂,该工厂还负责数据库连接的创建: - 它选择要创建的连接类型(例如,使用指定连接类型的属性文件) - 它确保数据库连接是唯一的。如果你不明白我刚才说了什么,看看下一个java示例,然后再次重读这部分,它应该更全面。否则,请随时告诉我。下面是一个Java中的例子,其中工厂创建了一个MysqlDatabaseConnection,但你可以想象一个更复杂的工厂,它根据属性文件或环境变量决定连接的类型。```java////////////////An interface that represents a database connectionpublic interface DatabaseConnection { public void executeQuerry(String sql);}////////////////A concrete implementation of this interface////////////////In this example it's for a mysql database connectionpublic class MysqlDatabaseConnection implements DatabaseConnection { public MysqlDatabaseConnection() { // some stuff to create the database connection } public void executeQuerry(String sql) { // some stuff to execute a SQL query // on the database }}////////////////Our business class that needs a connectionpublic class PersonBusiness { DatabaseConnection connection; //dependency injection using the constructor // it is a singleton because the factory that //creates a PersonBusiness object ensure that //UniqueDatabaseConnection has only one instance PersonBusiness(DatabaseConnection connection){ this.connection = connection; } //a method that uses the injected singleton public void deletePerson(int id){ connection.executeQuerry("delete person where id="+id); }}////////////////A factory that creates business classes//////////////// with a unique MysqlDatabaseConnectionpublic class Factory { private static MysqlDatabaseConnection databaseConnection = new MysqlDatabaseConnection(); public static MysqlDatabaseConnection getUniqueMysqlDatabaseConnection(){ return databaseConnection; } public static PersonBusiness createPersonBusiness(){ //we inject a MysqlDataConnection but we could chose //another connection that implements the DatabaseConnection //this is why this is a loose coupling return new PersonBusiness(databaseConnection); }}```这不是一个很好的例子,因为PersonBusiness可以有一个实例,因为它没有状态。但你可以想象,有一个ContractBusiness和一个HouseBusiness也需要那个独特的DatabaseConnection。不过,我希望你看到,使用**依赖注入+ 工厂**,你最终会在你的业务类中得到一个数据库连接的单个实例,就像你使用了一个单例一样。但这一次,它是一个松散的耦合,这意味着你可以很容易地使用MockDatabaseConnection来测试PersonBusiness类,而不是使用MysqlDatabaseConnection。此外,很容易知道PersonBusiness正在使用DatabaseConnection。你只需要查看类的属性,而不是类的2000行代码中的一行(好吧,想象一下这个类有很多函数,整体需要2000行代码)。这种方法被大多数Java框架(Spring,Hibernate...)和Java容器(EJB容器)使用。它不是真正的单例,因为如果需要,您可以多次实例化类,并且无法从任何地方获取实例。但是,如果您仅通过工厂/容器创建实例,则最终会在代码中获得该类的唯一实例。==注意==:我认为Spring框架非常令人困惑,因为它的“单例”范围只是一个实例。我花了一些时间才明白,这不是一个真正的GoF的单例。## 一些想法当涉及到全局状态时,单个实例具有与单例相同的缺点。**您应该避免使用单个实例在不同类之间共享数据!** 我看到的唯一例外是缓存:- 想象一下,您有一个交易应用程序,每秒进行数百次调用,它只需要具有最后几分钟的股票价格。您可以使用在交易业务类之间共享的单个实例 (StockPriceManager),每个需要价格的函数都将从缓存中获取它。如果价格已过时,缓存将刷新它。在这种情况下,紧密耦合的缺点值得在性能上获得收益。但是,当你因为这个全局状态而需要理解生产中的错误时,你会哭泣(我去过那里,这并不好笑)。我告诉您使用单实例方法而不是单例,但有时当您在所有类中都需要此对象时,值得使用真正的单例。例如,当您需要记录以下内容时:- 每个类都需要记录,并且此日志类通常是唯一的(因为日志写在同一个文件中)。由于所有类都使用 log 类,因此您知道每个类都与此日志类具有隐式依赖关系。此外,这不是业务需求,因此对日志进行单元测试“不太重要”(我感到羞耻)。编写单例比使用依赖关系注入编写单个实例更容易。对于快速而肮脏的解决方案,我将使用单例。对于一个长期耐用的解决方案,我将使用单个实例。由于大多数应用程序都基于框架,因此单个实例的实现比从头开始更容易(假设您知道如何使用框架)。如果您想了解有关单例的更多信息:- 在 stackexchange 上,关于[单例模式的缺点](https://programmers.stackexchange.com/questions/40373/so-singletons-are-bad-then-what)也有一个非常好的答案。## 真实示例单实例模式使用工厂。如果使用可实例化工厂,则可能需要确保此工厂是唯一的。更广泛地说,当您使用工厂时,您可能希望它是唯一的,以避免2个工厂实例相互混淆。你可以使用“元工厂”来构建唯一的工厂,但你最终会遇到“元工厂”的相同问题。因此,执行此操作的唯一方法是使用单例创建工厂。旧图形库AWT中的[java.awt.Toolkit](https://docs.oracle.com/javase/8/docs/api/java/awt/Toolkit.html)就是这种情况。此类提供了一个 getDefaultToolkit() 方法,该方法提供唯一的 Toolkit 实例,这是获取实例的唯一方法。使用此工具包(这是一个工厂),您可以创建一个窗口,一个按钮,一个复选框...但是,您也可以遇到单例以解决其他问题。当您需要用Java监视系统时,必须使用java.lang.Runtime类。我想这个类必须是唯一的,因为它表示进程的全局状态(环境变量)。如果我引用[java API](https://docs.oracle.com/javase/8/docs/api/java/lang/Runtime.html):> “每个 Java 应用程序都有一个类 Runtime 实例,该实例允许应用程序与运行应用程序的环境进行交互。当前运行时可以从 **getRuntime** 方法获取。# 原型模式我在整个春季都使用过原型,但我从来没有需要使用自己的原型。此模式旨在通过复制而不是构造函数来构建对象。以下是GoF给出的定义:“使用原型实例指定要创建的对象类型,并通过复制此原型来创建新对象。和GoF一样,我不理解他们的句子(这是因为英语不是我的母语吗?)。如果你像我一样,这里有另一个解释:如果你不想或不能使用类的构造函数,原型模式允许你通过复制已经存在的实例来创建这个类的新实例。让我们看一下使用UML图的正式定义:[](http://coding-geek.com/wp-content/uploads/2015/06/prototype.png)在此图中- 原型是一个定义函数 clone() 的接口- 一个真正的原型必须实现这个接口,并实现 clone() 函数来返回自身的副本。开发人员必须实例化一次 ConcretePrototype。然后,他将能够通过以下方式创建ConcretePrototype的新实例:- 使用 clone() 函数复制第一个实例- 或者使用(再次)构造函数创建混凝土原型。## 何时使用原型?根据Gof的说法,应该使用原型:- 当系统应该独立于其产品的创建,组合和表示方式- 当要实例化的类是在运行时指定的,例如,通过动态加载- 以避免构建与产品类层次结构平行的工厂类层次结构- 当类的实例可以具有只有几种不同的状态组合之一时。安装相应数量的原型并克隆它们可能比每次都使用适当的状态手动实例化类更方便。未知类的动态加载是非常罕见的情况,如果需要复制动态加载的实例,则更是如此。这本书写于1994年。现在,你可以通过使用依赖注入来“避免构建工厂的类层次结构”(同样,我将在以后的文章中介绍这个奇妙的模式)。在我看来,最常见的情况是创建有状态实例比复制现有实例更昂贵,并且您需要创建大量此对象。例如,如果创建需要:- 从数据库连接获取数据,- 从系统(通过系统调用)或文件系统获取数据,- 从另一台服务器获取数据(使用套接字,Web服务或其他),- 计算大量数据(例如,如果需要对数据进行排序),- 做任何需要时间的事情。该对象必须是有状态的,因为如果它没有状态,则 Singleton(或单个实例)将完成该操作。还有另一个用例。如果您有一个可变的实例,并且您希望将其提供给代码的另一部分,出于安全原因,您可能希望提供副本而不是真实实例,因为客户端代码可以修改此实例,并对使用它的代码的其他部分产生影响。## Java 实现让我们看一个Java中的简单示例:- 我们有一个汽车比较器商务舱。此类包含比较 2 辆车的函数。- 要实例化 CarComparator,构造函数需要从数据库中加载默认配置来配置汽车比较算法(例如,在油耗上比速度或价格更重要)。- 此类不能是单一实例,因为配置可以由每个用户修改(因此每个用户都需要自己的实例)。- 这就是为什么我们只使用昂贵的构造函数创建一次实例。- 然后,当客户需要 CarComparator 的实例时,他将获得第一个实例的副本。```java//////////////////////////The Prototype interfacepublic interface Prototype { Prototype duplicate();}//////////////////////////The class we want to duplicatepublic class CarComparator implements Prototype{ private int priceWeigth; private int speedWeigth; private int fuelConsumptionWeigth; //a constructor that makes costly calls to a database //to get the default weigths public CarComparator(DatabaseConnection connect){ //I let you imagine the costly calls to the database } //A private constructor only use to duplicate the object private CarComparator(int priceWeigth,int speedWeigth,int fuelConsumptionWeigth){ this.priceWeigth=priceWeigth; this.speedWeigth=speedWeigth; this.fuelConsumptionWeigth=fuelConsumptionWeigth; } //The prototype method @Override public Prototype duplicate() { return new CarComparator(priceWeigth, speedWeigth, fuelConsumptionWeigth); } int compareCars(Car first, Car second){ //some kickass and top secret algorithm using the weigths } ////////////////The setters that lets the possibility to modify //////////////// the algorithm behaviour public void setPriceWeigth(int priceWeigth) { this.priceWeigth = priceWeigth; } public void setSpeedWeigth(int speedWeigth) { this.speedWeigth = speedWeigth; } public void setFuelConsumptionWeigth(int fuelConsumptionWeigth) { this.fuelConsumptionWeigth = fuelConsumptionWeigth; }////////////////////////// A factory that creates a CarComparator instance using////////////////////////// constructors then it creates the others by duplication.////////////////////////// When a client ask for a CarComparator////////////////////////// he gets a duplicatepublic class CarComparatorFactory { CarComparator carComparator; public BusinessClass (DatabaseConnection connect) { //We create one instance of CarComparator carComparator = new CarComparator(connect); } //we duplicate the instance so that //the duplicated instances can be modified public CarComparator getCarComparator(){ return carComparator.duplicate(); }}```如果你看下一部分,你会发现我本可以使用正确的Java接口制作一个更简单的代码,但我希望你理解一个原型。在此示例中,在启动时,将使用数据库中的默认配置创建原型,并且每个客户端将使用工厂的 getCarComparator() 方法获取此实例的副本。## **真实示例**Java API提供了一个名为[Cloneable的](https://docs.oracle.com/javase/8/docs/api/java/lang/Cloneable.html)原型接口。此接口定义了一个 clone() 函数,具体原型需要实现该函数。Java API 中的许多 Java 类都实现了此接口,例如来自集合 API 的集合。使用ArrayList,我可以克隆它并获取一个新的ArrayList,其中包含与原始数组相同的数据:```java// Let's initialize a list// with 10 integersArrayList<Integer> list = new ArrayList<Integer>();for (int i = 0; i < 10; i++) { list.add(i);}System.out.println("content of the list "+list);//Let's now duplicate the list using the prototype methodArrayList<Integer> duplicatedSet = (ArrayList<Integer>) list.clone();System.out.println("content of the duplicated list "+duplicatedSet);```此代码的结果是:> 集合的内容[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]> 重复集合的内容[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]# 生成器模式生成器模式对于分解代码非常有用。根据GoF,这种模式:*“将复杂对象的构造与其表示分开,以便相同的构造过程可以创建不同的表示。*它的目标随着时间的推移而变化,大多数时候它被用来避免创建许多构造函数,这些构造函数仅因参数数量而异。这是一种避免**伸缩构造函数反模式**的方法。## 它解决的问题让我们看一下此模式解决的问题。想象一个具有5个属性的类人:- 年龄- 重量- 高度- 编号- 名字我们希望能够构建一个人知道:- 只有这个年龄,- 或只有这个年龄和体重,- 或只有这个年龄,体重和身高,- 或仅此年龄,体重,身高和ID- 或仅此年龄,体重,身高,ID和姓名在java中,我们可以写这样的东西。```javapublic class Person { private int age; private int weigth; private int height; private int id; private String name; //////////////Here comes the telescopic constructors public Person() { //some stuff } public Person(int age) { this();//we're using the previous constructor this.age = age; } public Person(int age, int weigth) { this(age);//we're using the previous constructor this.weigth = weigth; } public Person(int age, int weigth, int height) { this(age, weigth);//we're using the previous constructor this.height= height; } public Person(int age, int weigth, int height,int id) { this(age, weigth, height);//we're using the previous constructor this.id = id; } public Person(int age, int weigth, int height,int id,String name) { this(age, weigth, height, id);//we're using the previous constructor this.name = name; } }```为了处理这个简单的需求,我们刚刚创建了5个构造函数,其中有很多代码。我知道java是一种非常冗长的语言(内部巨魔),但是如果有一种更干净的方式呢?此外,使用这种伸缩式方法,代码很难阅读。例如,如果您阅读以下代码,您是否可以轻松理解参数是什么?他们是年龄,身份还是身高?```javaPerson person1 = new Person (45, 45, 160, 1);Person person2 = new Person (45, 170, 150);```下一个问题:假设你现在希望能够创建一个包含你获得的每一个可能的信息部分的人,例如:- 一个年龄- 重量- 年龄和体重- 一个年龄和一个ID,- 一个年龄,一个体重和一个名字,- ...使用构造函数方法,您最终将获得 120 个构造函数 (5! = 120)。你还有另一个问题,如何处理使用相同类型的不同构造函数?例如,您如何同时拥有两者:- age 和 weigth(为 2 int)的构造函数,以及- 年龄和 id 的构造函数(也是 2 int)?您可以使用静态工厂方法,但它仍然需要 120 个静态工厂方法。**这就是建筑商发挥作用的地方!**此模式的思想是模拟**命名的可选参数**。这些类型的参数在某些语言(如python)中是本地可用的。由于UML版本非常复杂(我认为),我们将从一个简单的java示例开始,并以UML正式定义结束。## 一个简单的 java 示例在此示例中,我有一个人,但这次 id 字段是必填字段,其他字段是可选的。我创建了一个构建器,以便开发人员可以根据需要使用可选字段。```java//////////////////////the person class/////////////////////if you look at its constructor/////////////////////it requieres a builderpublic class Person { private final int id;// mandatory private int weigth;// optional private int height;// optional private int age;// optional private String name;// optional public Person(PersonBuilder builder) { age = builder.age; weigth = builder.weigth; height = builder.height; id = builder.id; name = builder.name; }}//////////////////////the builder that/////////////////////takes care of/////////////////////Person creationpublic class PersonBuilder { // Required parameters final int id; // Optional parameters - initialized to default values int height; int age; int weigth; String name = ""; public PersonBuilder(int id) { this.id = id; } public PersonBuilder age(int val) { age = val; return this; } public PersonBuilder weigth(int val) { weigth = val; return this; } public PersonBuilder height(int val) { height = val; return this; } public PersonBuilder name(String val) { name = val; return this; } public Person build() { return new Person(this); }}//////////////////////Here is how to use the builder in order to build a Person//////////////////////You can see how readable is the codepublic class SomeClass { public void someMethod(int id){ PersonBuilder pBuilder = new PersonBuilder(id); Person robert = pBuilder.name("Robert").age(18).weigth(80).build(); //some stuff } public void someMethodBis(int id){ PersonBuilder pBuilder = new PersonBuilder(id); Person jennifer = pBuilder.height(170).name("Jennifer").build(); //some stuff }}```在此示例中,我假设类 Person 和 PersonBuilder 位于同一个包中,这允许构建器使用 Person 构造函数,并且包外的类必须使用 PersonBuilder 来创建 Person。这个PersonBuilder有2种方法,一种用于构建人的**一部分**,另一种用于**创建**人。一个人的所有属性只能由同一包中的类修改。我应该使用getter和setter,但我想有一个简短的例子。您看到使用构建器的部分易于阅读,我们知道我们正在创建- 一个名叫罗伯特的人,他18岁,体重80岁,- 另一个名叫詹妮弗的人,她长170岁。这种技术的另一个优点是,您仍然可以创建**不可变的对象**。在我的示例中,如果我不在 Person 类中添加公共 setter,则 Person 实例是不可变的,因为包外部的任何类都不能修改其属性。## 正式定义现在让我们看一下UML:[](http://coding-geek.com/wp-content/uploads/2015/06/builder.png)这张图真的很抽象,GoF的构建器有:- 一个生成器界面,用于指定用于创建 Product 对象的部件的函数。在我的图中,只有一个方法,buildPart()。- 一个 ConcreteBuilder,它通过实现 Builder 接口来构造和组装产品的部件。- 控制器:它使用生成器界面构造产品。根据 GoF,此模式在以下情况下很有用:> • 创建复杂对象的算法应独立于构成对象的部件及其组装方式。> •构造过程必须允许对构造的对象进行不同的表示。GoF给出的例子是一个TextConverter构建器,它有3个实现要构建:ASCIIText或TeXText或TextWidget。3 个生成器实现(ASCIIConverter、TeXConverter 和 TextWidgetConverter)具有相同的函数,除了 createObject() 函数不同(这就是为什么此函数不在此模式的接口中)。使用此模式,转换文本的代码(控制器)使用生成器界面,因此它可以轻松地从 ASCII 切换到 TeX 或 TextWidget。此外,您可以添加新的转换器,而无需修改其余代码。在某种程度上,这种模式非常接近国家模式。**但这个问题是罕见的。**这种模式的另一个用途是由Java开发人员Joshua Bloch推广的,他领导了许多Java API的构建。他在《*有效的Java》*一书中写道:> “在面对许多构造函数参数时考虑构建器”**大多数情况下,该模式用于此用例**。对于此问题,您不需要生成器接口、多个生成器实现或控制器。在我的java示例中,大多数时候你会发现只有一个具体的构建器。然后,UML 变得更加容易:[](http://coding-geek.com/wp-content/uploads/2015/06/builder2.png)在此图中,ConcreteBuilder具有多个函数来创建产品的每个部分(但我只是放了一个,buildPart(),因为我懒惰)。这些函数返回 ConcreteBuilder,以便您可以链接函数调用,例如:builder.buildPart1().buildPart7().createObject()。构建器有一个 createObject() 方法,用于在您不需要添加更多部件时创建产品。总而言之,当您的类具有许多可选参数并且不希望最终得到许多构造函数时,构建器模式是一个不错的选择。虽然这个模式不是为这个问题而设计的,但它大部分时间都用于这个问题(至少在Java中)。## 真实示例Java API中最常见的例子是[StringBuilder](https://docs.oracle.com/javase/8/docs/api/java/lang/StringBuilder.html)。使用它,您可以创建一个临时字符串,向其追加新字符串,完成后,您可以创建一个真正的String对象(这是不可变的)。```javaStringBuilder sBuilder = new StringBuilder();String example = sBuilder.append("this").append(" is"). append(" an").append(" example").toString();System.out.println(example);```# 结论您现在应该对创建模式有了更好的了解。如果您需要记住一件事,那就是使用单个实例而不是单例。请记住生成器模式(Joshua Bloch的版本),如果您正在处理可选参数,它可能会很有用。