暂时未有相关云产品技术能力~
@ConfigurationProperties源码分析@ConfigurationProperties主要作用就是将prefix属性指定的前缀配置项的值绑定到这个JavaBean上 ,通过指定的前缀,来绑定配置文件中的配置,通过如下源码可以看出, 如果你想绑定和验证一些外部属性,可以将它添加到类定义或@Configuration类中的@Bean方法上。标注在类上@Data //使用该注解需要导入Lombok依赖 @Component @ConfigurationProperties(prefix = "userinfo") public class UserInfo { private String userId; private String name; }application.yml文件配置内容userInfo: userId: 1001 name: lucy接下来通过控制器方法来返回这个对象,查看数据是否绑定@RestController public class HelloController { @Autowired private UserInfo userInfo; @GetMapping("/user") public UserInfo getUserInfo(){ return userInfo; } }标注在方法上上面源码中说到可以将它添加到@Configuration类中的@Bean方法上,所以下面来看看标注在方法上是如何使用的!比如我们用到druid数据源的操作,这个数据源是属于第三方的,所以我们不能操作源码,不能再源码里面找到它的对象来给他添加注解,但我们可以在yml文件中给他配置,来拿到它的属性。首先添加所需的依赖<dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.2.11</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency>接着在yml文件中配置数据源spring: datasource: druid: driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://localhost:3306/test?serverTimezone=UTC username: root password: root创建一个配置类,然后在类方法上添加注解,并通过prefix绑定数据@SpringBootConfiguration public class DatasourceConfig { @Bean @ConfigurationProperties(prefix = "spring.database.druid") public DataSource database(){ return new DruidDataSource(); } }通过控制器方法来检验绑定效果@RestController public class HelloController { @Autowired private DataSource dataSource; @GetMapping("/datasource") public void getDataSource(){ System.out.println(dataSource); } }访问http://localhost/datasource,查看控制台输出!!!松散绑定我们在使用 @ConfigurationProperties注解的时候,@ConfigurationProperties(prefix = “userinfo”),这里给prefix的属性值与yml文件中的属性名称不一致,但是依旧绑定成功了!原因是什么呢?这就需要提到Spring的松散绑定属性规则。因此使用以下方式书写都能与类的属性名称匹配。userInfo: userId: 1001 # 驼峰命名方式 #user_id: 1002 #下划线方式 #user-id: 1003 #烤肉串方式 #USER_ID: 1004 # 常量方式 name: lucycd需要注意的是,prefix的属性值必须全部为小写,就像下图所示,就会报错:前缀必须是规范形式。运行程序,也会在控制台提示你:配置属性名称“userInfo”无效;无效字符:“I”数据校验Spring Boot中有很多配置文件,配置文件中我们可以自定义一些对应的属性值。那么这些属性值是否合法呢?我们如何来校验?在Java中有一种JSR303规范,我们可以针对一些对应的数值来进行校验。按照规范来进行书写,如果不符合要求就说明校验失败,反之,则成功!SpringBoot也给出了强大的数据校验功能,可以有效的避免此类问题的发生。在JAVA EE的JSR303规范中给出了具体的数据校验标准,开发者可以根据自己的需要选择对应的校验框架,此处使用Hibernate提供的校验框架来作为实现进行数据校验。导入验证包和校验实现包<dependency> <groupId>javax.validation</groupId> <artifactId>validation-api</artifactId> </dependency> <dependency> <groupId>org.hibernate.validator</groupId> <artifactId>hibernate-validator</artifactId> </dependency>添加注解@Validated给属性添加验证规则@Data @Component @ConfigurationProperties(prefix = "userinfo") @Validated public class UserInfo { @Max(value = 1000,message = "userid超出范围了!") @Min(value = 0,message = "userid不能小于0!") private String userId; @Size(min = 2,max = 5,message = "name长度应该在2-5之间") private String name; }这里我们先输入不符合规范的数据,来验证数据是否能够校验成功userInfo: userId: 1001 name: lucycd编写一个控制器方法来检验@RestController public class HelloController { @Autowired private UserInfo userInfo; @GetMapping("/user") public UserInfo getUserInfo(){ return userInfo; } }运行程序,查看控制台,校验成功!!!同样,将数据修改为符合规则的数据,程序就可以成功运行了!!!当然,这里的校验规则还有许多,@NotNull、@NotEmpty、@Email等等,可以根据实际情况选择合适的注解。
日志介绍记录日志的好处1. 记录应用系统曰志主要有三个原因:记录操作轨迹、监控系统运行状况、回溯系统故障。记录操作行为及操作轨迹数据,可以数据化地分析用户偏好,有助于优化业务逻辑,为用户提供个性化的服务。例如,通过 access.log 记录用户的操作频度和跳转链接,有助于分析用户的后续行为。2. 全面有效的日志系统有助于建立完善的应用监控体系,由此工程师可以实时监控系统运行状况,及时预警,避免故障发生。监控系统运行状况,是指对服务器使用状态,如内存、CPU 等使用情况,应用运行情况,如响应时间QPS等交互状态;应用错误信息,如空指针、SQL异常等的监控。例如,在CPU使用率大于60%,四核服务器中load 大于4时发出报警,提醒工程师及时处理,避免发生故障。3. 当系统发生线上问题时,完整的现场日志有助于工程师快速定位问题。例如当系统内存溢出时,如果日志系统记录了问题发生现场的堆信息,就可以通过这个曰志分析是什么对象在大量产生并且没有释放内存,回溯系统故障,从而定位问题。日志框架门面设计模式是面向对象设计模式中的一种,日志框架采用的就是这种模式,类似JDBC的设计理念。它只提供一套接口规范,自身不负责日志功能的实现。目的是让使用者不需要关注底层具体是哪个日志库来负责日志打印及具体的使用细节等。目前用得最为广泛的是slf4j日志门面是一种门面设计模式,为了提高程序的扩展性给的一套类似于JDBC的接口,只需设定规则,具体的细节都留给对应实现日志库负责实现日志的相关功能,主流日志库有三个,分别是log4j、log-jdk (java.util.logging.Logger)、logback是最晚出现的,与log4j同一个作者,是log4j的升级版且本身实现了slf4j的接口。日志适配器老工程直接使用日志库API完成日志打印,要改成业界标准的门面模式(如slf4j+logback),但是老工程代码打印日志地方太多难以改动,这时就需要一个适配器来完成从旧日志库的API到slf4j的路由,这样在不改动原有代码的情况下也能使用slf4j来统一管理日志(如:log4j-over-slf4j),后续自由替换具体日志库也不成问题。日志体系在开发的时候不应该直接使用日志实现类,应该使用日志的抽象层。。具体参考SLF4J官方下图是SLF4J结合各种日志框架的官方示例,从图中可以清晰的看出SLF4J API永远作为日志的门面,直接应用与应用程序中。spring boot的底层spring-boot-starter-logging可以看出,它依赖3个日志框架:slf4j、Logback和Log4j2。它们的区别是:logback和log4j是日志实现框架,就是实现怎么记录日志的。slf4j-api提供了java所有日志框架的简单规范和标准(日志的门面设计模式)说白了就是一个日志API(没有实现类),它不能单独使用;故必须结合logback和Log4j2日志框架注意:spring Boot采用了slf4j+logback的组合形式,Spring Boot也提供对JUL、log4j2.Logback提供了默认配置注意:由于每一个日志的实现框架都有自己的配置文件,所以在使用SLF4j之后,配置文件还是要使用实现日志框架的配置文件。日志的使用日志级别和格式从上面的分析,发现 Spring Boot默认已经使用了SLF4J+LogBack 。所以我们在不进行任何额外操作的情况下就可以使用SLF4J + Logback 进行日志输出。日志的级别分为6种:TRACE:运行堆栈信息,使用率低DEBUG:程序员调试代码使用INFO:记录运维过程数据WARN:记录运维过程报警数据ERROR:记录错误堆栈信息FATAL:灾难信息,合并计入ERROR编写测试类测试package com.example.springboot.controller; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class HelloController { //获取日志对象 Logger Logger logger = LoggerFactory.getLogger(HelloController.class); @GetMapping("/hello") public String hello(){ logger.debug("debug..."); logger.info("info..."); logger.warn("warn..."); logger.error("error..."); return "Hello SpringBoot!"; } }application.ymlserver: port: 80 # 设置日志级别会给root根节点设置,代表整体应用的级别 #logging: # level: # root: debug # 设置组的日志级别或包的日志级别 logging: group: mygroup: com.example.springboot.controller level: root: warn # 给组设置日志级别 mygroup: debug获取日志对象导入Lombok依赖<dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency>添加@Slf4j注解package com.example.springboot.controller; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @Slf4j @RestController public class HelloController { @GetMapping("/hello") public String hello(){ log.debug("debug..."); log.info("info..."); log.warn("warn..."); log.error("error..."); return "Hello SpringBoot!"; } }哪里需要用到日志,那里添加注解即可日志格式Spring Boot默认输出的日志格式如下:日期和时间:毫秒精度,易于排序日志级别:ERROR、WARN、INFO、DEBUG或TRACE进程标识— :用于区分实际日志开始的分隔符线程名称:在方括号内记录器名称:通常为源类名称日志消息日志格式配置# 修改日志级别 logging: # 设置日志格式 pattern: #控制台输出格式 #格式化输出: %d :表示日期 %thread:表示线程名 # %-5level:级别从左显示5个字符宽度 %msg :日志消息 %n :是换行符 console: "[console]==%d{yyyy-MM-dd HH:mm:ss:SSS} [%thread] %-5level %logger - %msg%n" # 文件输出格式 file: "[file]===%d{yyyy-MM-dd HH:mm:ss:SSS} [%thread] %-5level %logger - %msg%n"日志格式配置信息如下:%d或者%date:指定日志的日期。默认是ISO8601的标准日期,相当于yyyy-MM-dd HH:mm:ss:SSS%level:指定日志的级别: Trace > Debug > Info> Warn> Error%logger:指定日志输出的包名+类名,{n}可以限定长度,比如: %logger{50}%M:指定日志发生时的方法名%L:指定日志调用时所在的行。线下运行的时候不建议使用此参数,因为获取代码的行号对性能有损耗%m或者%msg:表示日志的输出的内容%n :日志是否换行%thread:打印线程的名字彩色编码输出如果您的终端支持ANSI,则使用颜色输出来提高可读性。您可以设置spring.output.ansi.enabled为支持的值以覆盖自动检测。使用%clr转换字配置颜色编码。在最简单的形式中,转换器根据日志级别为输出着色,如以下所示:%clr(%5p)下表描述了日志级别到颜色的映射:或者,您可以通过将其作为转换选项提供来指定应使用的颜色或样式。例如,要使文本变黄,请使用以下设置:%clr(%d{yyyy-MM-dd HH:mm:ss:SSS}){yello}支持的颜色有:blue cyan faint green magenta red yellow因此我们可以自己编写日志的输出格式如下:logging: pattern: console: "%d %clr(%p){green} ---[%16t] %m%n"自定义日志输出日志不能仅显示在控制台上,要把日志记录到文件中,方便后期维护查阅。对于日志文件的使用存在各种各样的策略,例如每日记录,分类记录,报警后记录等。这里主要研究日志文件如何记录。logging: file: name: tomcat.log虽然使用上述格式可以将日志记录下来了,但是面对线上的复杂情况,一个文件记录肯定是不能够满足运维要求的,通常会每天记录日志文件,同时为了便于维护,还要限制每个日志文件的大小。下面给出日志文件的常用配置方式:logging: logback: rollingpolicy: max-file-size: 2MB file-name-pattern: server.%d{yyyy-MM-dd}.%i.log以上格式是基于logback日志技术设置每日日志文件的设置格式,要求容量到达2MB以后就转存信息到第二个文件中。文件命名规则中的%d标识日期,%i是一个递增变量,用于区分日志文件。替换日志框架查看官方文档,可以看到我们可以在 Log4j 和 logging 之间二选一。官方是这样描述Log4j的:An alternative to spring-boot-starter-logging翻译过来就是:spring-boot-starter-logging的替代方案。所以下面来展示如何将 logging 替换为 Log4j第一步:排除spring-boot-starter-logging依赖<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-logging</artifactId> </exclusion> </exclusions> </dependency>第二步:添加spring-boot-starter-Log4j2依赖<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-log4j2</artifactId> </dependency>
前言快速入门程序编写完了,我们发现springBoot程序开发比spring程序编写起来容易的多。配置简洁,依赖关系简单,启动运行容易。那么结下了我们我们就要思考一下入门程序中的这些功能是怎么实现的。接下来我们从以下几个方面研究:SpringBoot的启动依赖启动器starter有什么作用启动引导类是怎么运行的内置的tomcat服务器原理pom.xml文件分析我们应用配置第一个就是依赖,这个依赖的作用到底是什么我们仔细来分析一下。项目中的pom.xml中继承了一个坐标<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.7.3</version> <relativePath/> <!-- lookup parent from repository --> </parent>注意:这里parent的坐标被工程继承了,相当于这是一个父类,我们创建的工程是一个子类,用到了父类的东西。打开spring-boot-starter-parent之后,发现他又继承了一个坐标。继续打开spring-boot-dependencies之后,发现该文件中主要定义了两组信息,分别是各种依赖的版本号和所有依赖的坐标信息,并对声明的版本号做了一个引用。我们打开发现这里有两千多行,所有能配置的版本基本都包含了。由于Spring Boot工程使用到了maven的聚合工程,所以这里我们可以认为spring-boot-dependencies就是父工程,子工程就是我们自己的项目。当我们子工程中使用<parent>继承父类之后,所有的版本就都由父类决定了。可以看到子工程当中我们没有声明版本号,是因为所有的版本都由父类决定,这样做的好处是什么:解决了版本冲突。不同模块、不同功能之间使用的版本是不一样的,因此spring boot就为我们将所有的版本统一化了。启动器starterSpringBoot官方给出了好多个starter的定义,方便我们使用,而且名称都是如下格式命名规则: spring-boot-starter-技术名称starter定义了使用某种技术时对于依赖的固定搭配格式,也是一种最佳解决方案,使用starter可以帮助开发者减少依赖配置项目中的pom.xml定义了使用springMVC技术,但是并没有写SpringMVC的坐标,而是添加了一个名字中包含starter的依赖<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>在spring-boot-starter-web中又定义了若干个具体依赖的坐标<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> <version>2.7.3</version> <scope>compile</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-json</artifactId> <version>2.7.3</version> <scope>compile</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> <version>2.7.3</version> <scope>compile</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>5.3.22</version> <scope>compile</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.3.22</version> <scope>compile</scope> </dependency> </dependencies>然后你可以继续打开任意一个依赖,发现里面又会包含一些依赖。我们可以发现,这个starter中又包含了若干个坐标,其实就是使用SpringMVC开发通常都会使用到Json,使用json又离不开这里面定义的这些坐标,看来还真是方便,SpringBoot把我们开发中使用的东西能用到的都给提前做好了。你仔细看完会发现,里面有一些你没用过的。的确会出现这种过量导入的可能性,没关系,可以通过maven中的排除依赖剔除掉一部分。不过你不管它也没事,大不了就是过量导入呗。总结:使用starter可以帮开发者快速配置依赖关系。以前写依赖3个坐标的,现在写导入一个就搞定了,就是加速依赖配置的。启动引导类目前程序运行的入口就是SpringBoot工程创建时自带的那个类了,带有main方法的那个类,运行这个类就可以启动springBoot工程的运行@SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }Spring Boot本身就是为了加速Spring程序而开发的,而Spring程序运行的基础是需要创建自己的Spring容器对象,并将所有的对象管理在容器里面,这时候你可能会疑惑,Spring boot加速了Spring程序,那spring boot中有没有容器呢,或者说这个容器还存在吗?答案是存在,我们可以修改上面的代码来验证一下。@SpringBootApplication public class Application { public static void main(String[] args) { ConfigurableApplicationContext context = SpringApplication.run(Application.class, args); Application bean = context.getBean(Application.class); System.out.println(bean); } }这里的ConfigurableApplicationContext继承了ApplicationContext,说明他就是一个容器,拿到容器之后再通过getBean()来拿到bean,打印在控制台。可以看到控制台打印出来了对应的地址,说明它就是c从容器中拿到的一个对象,说明spring boot中存在一个容器,而SpringApplication.run(Application.class, args);就是运行Spring Boot容器,@SpringBootApplication注解接下来我们看一下@SpringBootApplication注解根据箭头打开注解可以看到有Spring Boot配置以及自动配置,自动配置包等等,因此当程序加了@SpringBootApplication注解,他就会扫描这个类所在包下和子包下面的所有类。并且他还是spring boot的配置类,所以spring的一些配置文件就不用写了,配置类自动搞定了。总结一下:这个启动引导类,创建了一个容器并将该容器运行起来,做了一个配置类,里面有一些自动配置的东西,然后我们创建的对象就可以放在引导类的包以及子包中,就可以扫描到了。内置的服务器我们在创建spring boot项目时,会勾选需要的依赖,然后导入对应的starter<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>内置的tomcat服务器来研究几个问题这个服务器在什么位置定义的这个服务器是怎么运行的这个服务器如果想换怎么换内嵌Tomcat定义位置说到定义的位置,我们可以通过两种方式来查看:第一种第二种tomcat运行原理tomcat本身就是一个Java项目,那么可不可以将tomcat中的一些东西直接放回项目中,让他变成一个Java代码?当然可以,这里spring boot将tomcat以jar包的形式放到spring容器中让他变成一种对象,然后运行这个对象,把我们的项目启动起来,这就是它能够启动并且运行的原理。修改服务器如果我们不想用他提供的版本能不能修改呢?可以,我们可以通过<exclusions>标签来排除提供的版本,或者内置服务器,然后在再重新添加依赖来达到修改服务器版本或者更换服务器的目的。<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> </exclusion> </exclusions> </dependency>当添加<exclusions>标签之后,就把内置的tomcat的服务器排除了,这时候你在启动项目,在控制台就不会看到tomcat相关的信息了,并且网页也无法访问了。添加服务器这时候你再添加你想要的使用的服务器,只需添加对应的坐标即可<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> </dependency>同样在控制台就会看到服务器相关的信息了,并且网页也可以正常访问了。不过这样就显得有点多此一举了,好好的东西不用,非要自己搞这些,所以还是建议使用内置的服务器。更换内嵌服务器那我们是否可以换个服务器呢?必须的嘛。根据SpringBoot的工作机制,用什么技术,加入什么依赖就行了。SpringBoot提供了3款内置的服务器tomcat(默认): apache出品,粉丝多,应用面广,负载了若干较重的组件jetty:更轻量级,负载性能远不及tomcatundertow:负载性能勉强跑赢tomcatI想用哪个,加个坐标就OK。前提是把tomcat排除掉,因为tomcat是默认加载的。<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jetty</artifactId> </dependency>现在就已经成功替换了web服务器,它的核心思想就是加入对应的坐标就可以了。
1、邮箱设置1.1 启用客户端POP3/SMTP服务首先我们需要一个邮箱账号,我这里选择的是新浪邮箱,注册并登录,这里比较简单,只需打开设置,找到客户端,然后开启POP3/SMTP服务即可。2、Spring Email2.1 导入jar包<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-mail</artifactId> <version>2.7.2</version> </dependency>2.2 邮箱参数设置导入包之后,我们还需要在程序当中给邮箱做一些参数的配置,我们要想在程序中用这个邮箱,那就需要把邮箱的账号、密码、访问链接以及协议等等都需要配置好。如果之后想换邮箱,只需要改配置文件就好了,切记不要把邮箱在Java程序中写死,下面就来配置一下参数。spring: # MailProperties 邮箱设置 mail: host: smtp.sina.com # 声明邮箱域名 port: 465 # 邮箱端口 username: xxxxxx@sina.com # 邮箱账号 password: be157646ac7d3754 # 邮箱授权码 protocol: smtps # 协议 # 发送邮件时采用ssl安全连接 properties: mail: smtp: ssl: enable: true2.3 使用 JavaMailSender 发送邮件接下来我们就可以写代码来发送邮件了,Spring Email 发送邮件的核心是JavaMailSender ,我们通过调用这个接口来发送邮件。我们这里写一个工具类,然后把发送邮件的整个逻辑流程封装起来,可以反复使用。@Component public class MailClient { // 声明一个logger,用来记录日志 private static final Logger logger = LoggerFactory.getLogger(MailClient.class); @Autowired private JavaMailSender mailSender; // 发件人 @Value("${spring.mail.username}") private String from; // to:收件人 subject:邮件标题 content:邮件内容 public void sendMail(String to,String subject,String content) { try { MimeMessage message = mailSender.createMimeMessage(); // 构建 MimeMessage 的内容 MimeMessageHelper helper = new MimeMessageHelper(message); helper.setFrom(from); helper.setTo(to); helper.setSubject(subject); helper.setText(content, true); // true表示支持html文本 mailSender.send(helper.getMimeMessage()); } catch (MessagingException e) { logger.error("发送邮件失败:" + e.getMessage()); } } }发送一份邮件需要发件人、收件人、邮件标题。邮件内容,而通过服务器发邮件,发件人是固定的,所以我们通过@Value将其注入进来。2.4 简单测试配置好之后我们就可以编写一个测试类来测试一下是否能够发送邮件吧@SpringBootTest public class MailTests { @Autowired private MailClient mailClient; @Test public void testTextMail() { mailClient.sendMail("1973707416@qq.com","测试邮件","Welcome!!!"); } }3、模板引擎3.1 使用 Thymeleaf 发送HTML邮件最后,我们来看一下如何使用Thymeleaf 模板引擎来发送HTML格式邮件,这样就邮件可以包含更加丰富的内容了。首先,我们编写一个简单的邮件模板demo.html<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>邮件示例</title> </head> <body> <p>亲爱的, <span style="color:red;" th:text="${username}"></span>!</p> <p>最近怎么样?我希望你的生活有点变化,至少是每当你歇班的时候不要一个人呆在宿舍里睡大觉,应该有一个帅哥哥陪着你逛商场,当你看到一件你想买又一直不舍得买的东西时,刚好他的兜里有足够的钱,逛完商场陪你吃晚饭,然后为你买一大包零食再把你送回宿舍。这种待遇如果恋爱的时候不好好享受一下,结婚后恐怕就很难再找到感觉了。</p> <p>XX,一定要嫁给一个真心实意爱你的男人,嫁给一个品行好的男人,或许他会与你心中的白马王子有一定的差距,但在今后的生活中,你会感觉到,他对你的感情是你无穷无尽的幸福宝藏,有这座宝藏在心中,你会比任何一个女人都幸福。</p> <p>...</p> <p>爱你的男孩</p> </body> </html>接着同样写一个测试方法@SpringBootTest public class MailTests { @Autowired private MailClient mailClient; @Autowired private TemplateEngine templateEngine; @Test public void testHtmlMail() { Context context = new Context(); context.setVariable("username","XX"); // 调用templateEngine的process方法生成动态网页 // 并且需要将模板文件的位置以及数据传给他 String content = templateEngine.process("/mail/demo", context); // 这里可以将生成的网页打印在控制台查看一下 System.out.println(content); // 发送邮件 mailClient.sendMail("1973707416@qq.com","写给心爱女孩的一封信",content); } }控制台输出结果:邮件发送成功页面总的来说,发送邮件是一个比较简单的技术。但她不是!!!
快速排序 通过一趟排序将待排元素分成独立的两部分,其中一部分为比基准数小的元素,另一部分则是比基准数大的元素。然后对这两部分元素再按照前面的算法进行排序,直到每一部分的元素都只剩下一个。本质上来看,快速排序应该算是在冒泡排序基础上的递归分治法。算法原理从数列中挑出一个元素作为基准点重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面然后基准值左右两边,重复上述步骤通过递归把基准值元素左右两侧的数组排序,排完之后,整个数组就排序完成了图解问题描述:给定一个无序排列的数组 nums,使其能够按照有序输出示例:输入: nums = [4,3,1,2,9,6], 输出: nums = [1,2,3,4,6,9]图解如下:Java代码实现核心代码public class QuickSort { //比较 v 是否小于 w public static boolean less(Comparable v,Comparable w){ return v.compareTo(w) < 0; } //数组元素交换位置 private static void swap(Comparable[] a,int i,int j){ Comparable temp; temp = a[i]; a[i] = a[j]; a[j] = temp; } //排序 public static void sort(Comparable[] a){ int l = 0; int h = a.length - 1; sort(a,l,h); } private static void sort(Comparable[] a,int l,int h){ if (h <= l) return; //对数组进行分组(左右两个数组) // i 表示分组之后基准值的索引 int i = partition(a, l, h); //让左边的数组有序 sort(a,l,i - 1); //让有边的数组有序 sort(a,i + 1,h); } public static int partition(Comparable[] a,int l,int h){ //确定基准值 Comparable key = a[l]; //定义两个指针 int left = l; int right = h + 1; //切分 while (true){ //从右向左扫描,移动right指针找一个比基准值小的元素,找到就停止 while (less(key,a[--right])){ if (right == l) break; } //从左向右扫描,移动left指针找一个比基准值大的元素,找到就停止 while (less(a[++left],key)){ if (left == h) break; } if (left>=right){ break; }else { swap(a,left,right); } } //交换基准值 swap(a,l,right); return right; } }public class QuickSortTest { public static void main(String[] args) { Integer[] arr = {3,1,2,4,9,6}; QuickSort.sort(arr); System.out.println(Arrays.toString(arr)); } } //排序前:{3,1,2,4,9,6} //排序后:{1,2,3,4,6,9}运行结果:算法分析时间复杂度 快速排序的最佳情况就是每一次取到的元素都刚好平分整个数组,由于快速排序用到了递归调用,因此计算其时间复杂度也需要用到递归算法来计算。T[n] = 2T[n/2] + f(n);此时时间复杂度是O(nlogn)。最坏的情况,则和冒泡排序一样,每次比较都需要交换元素,此时时间复杂度是O(n^2)。因此,快速排序的时间复杂度为:O(nlogn)。空间复杂度 空间复杂度主要是递归造成的栈空间的使用,最佳情况是,递归树的深度为log2n,此时空间复杂度为O(logn),最坏情况,则需要进行n‐1递归调用,此时空间复杂度为 O(n)。因此,快速排序的空间复杂度为: O(logn)。
一、折半插入排序 折半插入排序(Binary Insertion Sort)是对直接插入排序算法的一种改进。每次找到一个数插入到前面有序的序列中,但是要用折半查找找到其位置!二、算法原理 折半插入排序与直接插入排序算法原理相同。不同之处在于,每次将一个元素插入到前面有序的序列中时,是使用折半查找来确定要插入的位置。具体操作步骤是先取已经排序的序列的中间元素,与待插入的元素进行比较,如果中间元素的值大于待插入的元素,那么待插入的数据属于数组的前半部分,否则属于后半部分。重复操作,不断缩小范围,知道确定要插入的位置。三、代码实现题目描述:给定一个无序的数组,利用折半插入排序将该数组按升序排列。操作步骤:认定array[0]为有序区,其余部分为无序区,同时令 i = 1; 将array[i]与有序区中的中间元素比较,同时通过折半查找找到插入的位置插入; 重复上述操作,直到元素全部插入完成。 public class Sort { public static void main(String[] args) { // 待排序的数组 int[] array = {15,2,6,54,36,1,0,13,-5,68,79}; binaryInsertSort(array); // 显示排序后的结果。 System.out.print("排序后: "); for(int i = 0; i < array.length; i++) { System.out.print(array[i] + " "); } } static void binaryInsertSort(int[] array) { for(int i = 1; i < array.length; i++) { int temp = array[i]; int low = 0; int high = i - 1; //折半查找 while(low <= high) { //取中间点 int mid = (low + high) / 2; if(temp < array[mid]) { high = mid - 1; } else { low = mid + 1; } } for(int j = i; j >= low + 1; j--) { array[j] = array[j - 1]; } array[low] = temp; } } }四、算法分析时间复杂度 折半插入排序仅减少了关键字的比较次数,而记录的移动次数不变。因此,折半插入排序的时间复杂度仍然为O(n^2)。空间复杂度 折半插入排序所需附加存储空间和直接插入排序相同,只需要一个记录的辅助空间,所以空间复杂度为O(1)。
二分查找 二分查找也称折半查找(Binary Search),它是一种效率较高的查找方法,可以在数据规模的对数时间复杂度内完成查找。是一种在有序数组中查找某一特定元素的搜索算法。算法思路 以升序数列为例,比较目标元素与数列中间位置的元素的大小,如果目标元素比中间位置的元素大,则继续在数列的后半部分中进行二分查找;如果目标元素比中间位置的元素小,则在数列的前半部分进行比较;如果相等,则找到了元素的位置。每次比较的数列长度都会是之前数列的一半,直到找到相等元素的位置或者最终没有找到目标元素。图解给定一个有序的升序排列的数组 nums=[-1,0,2,5,8,12,18,38,43,46]然后在该数组中找到目标值 target = 12。图解如下:力扣原题704.二分查找题目描述:给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。示例 1:输入: nums = [-1,0,3,5,9,12], target = 9 输出: 4 解释: 9 出现在 nums 中并且下标为 4示例 2:输入: nums = [-1,0,3,5,9,12], target = 2 输出: -1 解释: 2 不存在 nums 中因此返回 -1解题思路:根据题意得出该数组为有序数组,这也是使用二分查找的前提条件。定义两个指针分别指向数组的首尾两个元素;找到数组的中间值mid;如果nums[mid] < target,则 target 位于数组的后半部分,反之nums[mid] > target在前半部分;重复上一步操作,直到nums[mid] = target,说明找到target,返回下标即可。Java代码实现:class Solution { public int search(int[] nums, int target) { int left = 0,right = nums.length - 1; while(left <= right) { // 循环条件 int mid = left + (right - left) / 2; if(nums[mid] == target){ return mid; } else if (nums[mid] < target) { left = mid + 1; } else { right = mid - 1; } } return -1; // 找不到则返回-1 } }复杂度分析:时间复杂度:O(logn),其中 n 是数组的长度。空间复杂度:O(1)。
✈️Thymeleaf介绍首先,先贴一个官网地址,Thymeleaf官网:https://www.thymeleaf.org/Thymeleaf 是什么?官网:Thymeleaf is a modern server-side Java template engine for both web and standalone environments.翻译:Thymeleaf 是一个现代的服务器端Java模板引擎,适用于web和独立环境。HTML templates written in Thymeleaf still look and work like HTML, letting the actual templates that are run in yourapplication keep working as useful design artifacts.用Thymeleaf编写的HTML模板的外观和工作方式仍然类似HTML,让运行在您的应用程序中的实际模板继续作为有用的设计工件工作。Thymeleaf 是一个 XML/XHTML/HTML5 模板引擎,可用于 Web 与非 Web 环境中的应用开发。它是一个开源的 Java 库,基于 Apache License 2.0 许可,由 Daniel Fernández 创建。Thymeleaf 提供了一个用于整合 Spring MVC 的可选模块,在应用开发中,你可以使用 Thymeleaf 来完全代替 JSP,或其他模板引擎,如 Velocity、FreeMarker 等。Thymeleaf 的主要目标在于提供一种可被浏览器正确显示的、格式良好的模板创建方式,因此也可以用作静态建模。你可以使用它创建经过验证的 XML 与 HTML 模板。相对于编写逻辑或代码,开发者只需将标签属性添加到模板中即可。✈️为什么选择thymeleaf静态html嵌入标签属性,浏览器可以直接打开模板文件非常适合前后端的独立开发SpringBoot官方推荐的模板💬举个栗子th:text="${msg}"是一个动态标签,当传递了msg这个数据,页面就会渲染这个标签,如果没有传递这个参数,就会显示原本的网页结构。<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <p th:text="${msg}"></p> </body> </html>✈️Thymeleaf简单表达式变量表达式::${…}选择变量表达式:*{…}消息表达式:#{…}链接表达式:@{…}片段表达式:~{…}✈️Thymeleaf的常用属性💬准备创建一个springboot项目,然后添加依赖文件pom.xml<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency>在使用 Thymeleaf 之前,首先要在页面的 html 标签中声明名称空间。xmlns:th="http://www.thymeleaf.org"💬th:text、th:utextth:text创建一个控制器方法,将信息传输给页面@Controller public class ThymeleafController { @GetMapping("/index") public String index(Model model){ model.addAttribute("msg","Hello Thymeleaf!!!"); return "index"; } }创建一个html页面用来接收显示信息<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <p th:text="${msg}"></p> </body> </html>当然我们也可以通过右击鼠标或Ctrl+U查看一下网页源码,可以看到这里是把表达式的值直接放在了标签里面!如果你想把传输到页面的数据通过html标签的方式加粗或着其他操作,就需要使用th:utext来显示文本!!!格式如下:model.addAttribute("msg","<b>欢迎访问Thymeleaf</b>");th:utext<p th:utext="${msg}"></p>扩展:拼接字符串<!--方式一:--> <p th:text="${msg}+官网"></p> <!--方式二:--> <p th:text="|${msg}官网|"></p>💬th:object创建一个对象@Data public class People { private String name; private Integer age; private Integer sex; private Boolean isVip; private Date createTime; private List<String> tags; }新建一个方法,将people对象传给页面@Controller public class ThymeleafController { @GetMapping("/index") public String index(Model model){ People people = new People(); people.setName("Thymeleaf"); people.setAge(20); people.setSex(1); // 1表示男 2表示女 people.setIsVip(true); people.setCreateTime(new Date()); people.setTags(Arrays.asList("Java","Go","Vue")); model.addAttribute("people",people); return "index"; } }现在,如果让你把people对象的所有信息显示到页面上你会怎么做?我们刚才学习了th:text,所以你可能会像下面这样的方式来显示!<p th:text="${people.name}"></p> <p th:text="${people.age}"></p> <p th:text="${people.sex}"></p> <p th:text="${people.isVip}"></p> <p th:text="${people.createTime}"></p> <p th:text="${people.tags}"></p>这么写有问题吗?No problem!但这不是最好的方式,下面再来看一种新的方式,它的格式是这样的:th:object<div th:object="${people}"> <p th:text="*{name}"></p> <p th:text="*{age}"></p> <p th:text="*{sex}"></p> <p th:text="*{isVip}"></p> <p th:text="*{createTime}"></p> <p th:text="*{tags}"></p> </div>这种方式用到了thymeleaf为我们提供的th:object属性以及变量表达式*{...}💬th:value比如设置文本框的内容为people的name<input type="text" th:value="${people.name}">💬th:if、th:unlessth:if:控制标签是否显示,if表达式的值为 true 则显示,否则不显示;th:unless:与th:if相反,表达式为fakse时显示,反之,不显示。th:if<div th:if="${true}">显示</div> <!--th:if="${false}" 此时就不会生成这个div标签-->举个栗子: 判断people是否为会员,是,就在页面显示VIP,不是则不显示。<div th:if="${people.isVip}">VIP</div>我们在这里传参时people.setIsVip(true);传的参数为true,下面看效果:th:unless<div th:unless="${true}">不显示</div>💬th:switch、th:casepeople的sex为1则显示男,2 则显示女,*则显示保密。使用方式如下:<div th:switch="${people.sex}"> <p th:case="1">男</p> <p th:case="2">女</p> <p th:case="*">保密</p> </div>注意:* 要放在最后,切记!切记!切记!💬th:eachth:each通过循环迭代显示people的tags信息<ul> <li th:each="tag:${people.tags}" th:text="${tag}"></li> </ul>将当前迭代的最后一个颜色设置为红色<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Title</title> <style> .active{ color: red; } </style> </head> <body> <ul> <li th:each="tag,iterStat:${people.tags}" th:text="${tag}" th:classappend="${iterStat.last}?active"> </li> </ul> </body> </html>th:each属性状态变量iterStat状态变量iterStat包含以下数据:index属性,当前迭代索引,从0开始count属性,当前迭代索引,从1开始size属性,迭代变量中的元素总数current属性,每次迭代的iter变量。even/odd布尔属性,当前迭代是偶数还是奇数first属性,当前迭代是否是第一个,布尔值。last布尔属性,当前迭代是否是最后一个,布尔值。💬th:hrefindex.css .app{ height: 100px; width:100px; background-color: aqua; }通过外链的方式连接index.css文件<link rel="stylesheet" th:href="@{index.css}"> <a th:href="@{https://www.baidu.com/}">百度一下</a>✈️内联表达式[[…]]相当于th:text,即会将HTML代码转义[(…)]相当于th:utext,不会转义HTML代码<div>[[${people.age}]]</div> <div>[(${msg})]</div>在JavaScript中使用内联表达式<script th:inline="javascript"> const people= /*[[${people}]]*/{}; console.log(people) </script>✈️基本对象💬#ctx:上下文对象${#ctx.request} ${#ctx.response} ${#ctx.session} ${#ctx.servletContext}💬请求/会话属性${session.xxx} ${application.xxx} ${#request.getAttribute('xxx')}
🪐Docker镜像操作镜像是Docker的三大组件之一。Docker运行容器前需要本地存在对应的镜像,如果本地不存在,Docker会从镜像仓库下载。下面主要从以下几个方面来了解docker镜像:从仓库获取镜像管理本地仓库的镜像Docker命令使用获取命令行帮助信息直接在命令行内输入docker命令后敲回车📌查找镜像我们可以从 Docker Hub网站来搜索镜像,Docker Hub网址为: https://hub.docker.com/我们也可以使用docker search命令来搜索镜像。比如我们需要一个tomcat的镜像来作为我们的web服务。我们可以通过docker search命令搜索tomcat来寻找适合我们的镜像。举个栗子docker search tomcatNAME: 镜像名称;DESCRIPTION: 镜像描述;OFFICIAL: 是否 Docker 官方发布;STARS: 点赞数;AUTOMATED: 自动构建。📌镜像列表想要查看已经下载的镜像,可以使用docker images ls命令[root@localhost ~]# docker images # docker images ls其中列表包含了仓库名、版本号(标签)、镜像ID、创建时间以及镜像大小 。镜像ID则是镜像的唯一标识,一个镜像可以对应多个标签。因此,如果两个镜像拥有相同的ID,说明它们对应的是同一个镜像。镜像体积如果仔细观察,会注意到,这里标识的所占用空间和在Docker Hub上看到的镜像大小不同。这是因为Docker Hub中显示的体积是压缩后的体积。在镜像下载和上传过程中镜像是保持着压缩状态的,因此 DockerHub所显示的大小是网络传输中更关心的流量大小。而docker image ls 显示的是镜像下载到本地后,展开的大小,准确说,是展开后的各层所占空间的总和,因为镜像到本地后,查看空间的时候,更关心的是本地磁盘空间占用的大小。另外一个需要注意的问题是,docker image ls列表中的镜像体积总和并非是所有镜像实际硬盘消耗。由于Docker镜像是多层存储结构,并且可以继承、复用,因此不同镜像可能会因为使用相同的基础镜像,从而拥有共同的层。由于Docker使用Union FS,相同的层只需要保存一份即可,因此实际镜像硬盘占用空间很可能要比这个列表镜像大小的总和要小的多。📌获取镜像之前提到过,Docker Hub上有大量的高质量的镜像可以用,这里我们就说一下怎么获取这些镜像。从 Docker镜像仓库获取镜像的命令是docker pull其命令格式为:docker pull [选项] [Docker Registry 地址[:端口号]/] 镜像名称[:版本号]具体的选项可以通过docker pull --help命令看到,这里我们说一下镜像名称的格式。 - Docker 镜像仓库地址:―地址的格式一般是〈<域名/IP>[:端口号]。默认地址是 Docker Hub。 -仓库名:这里的仓库名是两段式名称,即<用户名>/<软件名>。 对于Docker Hub,如果不给出用户名,则默认为library,也就是官方镜像。举个栗子我们需要一个tomcat的镜像来作为我们的web服务,就可以通过 docker pull 获取镜像。# docker pull tomcat:版本号 docker pull tomcat # 不指定版本号,默认获取latest版本📌删除镜像删除本地镜像的命令是docker rmi 其命令格式为:docker rmi [选项] <镜像1> [<镜像2>...]用镜像ID、镜像名、摘要删除镜像其中,镜像ID可以是部分镜像ID、完整镜像ID、镜像名或者镜像摘要如果要删除本地的镜像,可以使用 docker rmi 命令docker rmi 镜像ID # 删除本地指定镜像删除镜像之前必须确认该镜像没有被任何容器使用删除所有镜像# 查看本地所有镜像的 IMAGE ID docker images -q # 根据镜像ID删除所有镜像 docker rmi `docker images -q`📌保存镜像备份本地仓库的镜像1、用 save 命令将本地镜像保存到当前目录下docker save -o 导出的路径及名称 镜像名称 # 举个栗子 docker save -o tomcat.tar 镜像名称2、将本地目录下的镜像备份文件导入到本地的docker仓库# 命令1: docker load -i tomcat.tar # 命令2: docker load < tomcat.tar具体操作步骤如下:首先保存镜像,然后删除该镜像。然后将刚才的备份文件导入本地docker仓库,之后就可以继续使用 tomcat 了!修改镜像名称docker tag 镜像id 新镜像名称:版本号 # 举个栗子 docker tag fe helloworld:1🪐Docker服条相关命令启动docker服务:systemctl start docker停止docker服务:systemctl stop docker重启docker服务:systemctl restart docker查看docker服务状态:systemctl status docker设置开机启动docker服务:systemctl enable docker
Docker镜像加速器鉴于国内网络问题,后续拉取Docker镜像十分缓慢,我们可以需要配置加速器来解决使用Docker 的时候,需要经常从官方获取镜像,但是由于显而易见的网络原因,拉取镜像的过程非常耗时,严重影响使用Docker 的体验。因此 DaoCloud推出了加速器工具解决这个难题,通过智能路由和缓存机制,极大提升了国内网络访问Docker Hub 的速度,目前已经拥有了广泛的用户群体,并得到了Docker官方的大力推荐。如果您是在国内的网络环境使用Docker,那么 Docker加速器一定能帮助到您。Docker官方和国内很多云服务商都提供了国内加速器服务,例如:Docker官方提供的中国registry mirror阿里云加速器Daocloud 加速器国内有许多公司都提供了docker 加速镜像,比如:阿里云,腾讯云,网易云,以下以阿里云为例官方加速器Docker官方加速器配置:https://docs.docker.com/registry/recipes/mirror/通过命令查看:在/etc/docker/daemon.json中写入以下内容(如果文件不存在就创建一个){ "registry-mirrors": ["https://<my-docker-mirror-host>"] }一定要保证该文件符合 json 规范,否则Docker就不能启动重启Docker:sudo systemctl daemon-reload sudo systemctl restart docker阿里云加速器配置阿里云加速器登录阿里云官网 >> 打开控制台 >> 搜索镜像服务 >> 打开容器镜像服务点击镜像加速器,就可以看到属于你自己的加速器地址,以及配置方法配置镜像加速器通过修改daemon配置文件/etc/docker/daemon.json来使用加速器只需复制阿里云官方提供的代码,粘贴到虚拟机上即可sudo mkdir -p /etc/docker sudo tee /etc/docker/daemon.json <<-'EOF' { "registry-mirrors": ["https://mu7j1zdf.mirror.aliyuncs.com"] } EOF sudo systemctl daemon-reload sudo systemctl restart docker检查加速器是否生效配置加速器之后,我们可以通过 cat 命令查看 /etc/docker/daemon.json文件,能看到阿里云加速器已配置成功,也可以通过命令docker info,来查看镜像地址是否匹配,匹配则说明配置成功!
一、Docker架构Docker 使用客户端-服务器 (C/S) 架构模式,使用远程API来管理和创建Docker容器。Docker 容器通过 Docker 镜像来创建。1.1 基本概念docker包括三个基本概念:镜像(Image):Docker镜像(Image) ,就相当于是一个root文件系统。比如官方镜像ubuntu:16.04就包含了完整的一套Ubuntu16.04最小系统的root文件系统。容器(Container):镜像(lmage)和容器(Container)的关系,就像是面向对象程序设计中的类和对象一样,镜像是静态的定义,容器是镜像运行时的实体。容器可以被创建、启动、停止、删除、暂停等。仓库(Repository) :仓库可看成一个代码控制中心,用来保存镜像。Docker的运行离不开这三员大将的支持。也有人会误以为,Docker就是容器。但Docker不是容器,而是管理容器的引擎。1.2 Docker引擎docker引擎组件的流程如下图所示:容器与镜像的关系类似于面向对象编程中的对象与类。一个类可以new很多个对象,同样,一个镜像也可以创建许多容器。Docker 主机(Host):一个物理机或虚拟机,用于运行Docker服务进程和容器,也称为宿主机,node节点。Docker 服务端(Server):Docker守护进程,运行docker容器。Docker 客户端(Client):客户端使用docker 命令或其他工具调用docker API与Docker的守护进程通信。Docker 仓库(Registry): 保存镜像的仓库,可以理解为代码控制中心的代码仓库。官方仓库: https://hub.docker.com/,提供了庞大的镜像集合供使用,也可以搭建私有仓库harbor。Docker 镜像(Images):镜像可以理解为创建docker实例使用的模板。Docker 容器(Container): 容器是从镜像生成对外提供服务的一个或一组服务。Docker Machine:Docker Machine是一个简化Docker安装的命令行工具,通过一个简单的命令行即可在相应的平台上安装Docker。Docker镜像是一个特殊的文件系统,除了提供容器运行时所需的程序、库、资源、配置等文件外,还包含了一些为运行时准备的一些配置参数(如匿名卷、环境变量、用户等)。镜像不包含任何动态数据,其内容在构建之后也不会被改变。二、Docker的中央仓库Docker仓库用来保存镜像的,可以理解为代码控制中的代码仓库。Docker官方的中央仓库,这个仓库是镜像最全的,但下载速度较慢 https://hub.docker.com/国内的镜像网站:网易蜂巢、daoCloud、https://c.163yun.com/hub#/homehttps://hub.daocloud.io/ (推荐)在公司内部会采用私服的方式拉取镜像。三、Docker安装Docker 运行在 CentOS 7 上,要求系统为64位、系统内核版本为 3.10 以上。可以通过uname -r 命令查看你当前的内核版本。Docker 在原来的基础上分为两个版本:Docker CE和 Docker EE。Docker CE是社区免费版,Docker EE是付费企业版,安全。下面介绍Docker CE的安装。开始安装1.确保 yum 包更新到最新。yum update2. 下载关于Docker的依赖环境,yum-util提供yun-config-manager功能,另外两个是devicemapper驱动依赖yum -y install yum-utils device-mapper-persistent-data lvm23. 设置一个下载docker的镜像源# 源1:阿里云源 yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo # 源2:官方提供 yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo4. 更新 yum 缓存yum makacache fast5. 安装dockeryum -y install docker-ce6. 启动,并设置为开机自动启动# 启动Docker服务 systemctl start docker # 设置开机自动启动 systemctl enable docker7. 测试# 测试 docker run hello-world8. 验证安装是否成功(有client和service两部分表示docker安装启动都成功了)# 查看docker版本,验证是否安装成功 docker version四、Docker卸载执行下面的命令来删除Docker CEsudo yum remove docker-ce sudo rm -rf /var/lib/docker
前言如果现在有人问你会docker吗,结果你反过来问他Docker是什么?都没听过,那么你就太out了,所以赶紧学起来。今天我们就保持着对docker的疑问和好奇,一起来看看docker到底是什么东东!!一、容器技术1.1 历史技术在很久之前,如果我们要部署一个APP,需要准备一台物理服务器,然后在这台物理服务器上面安装一个操作系统(Windows、Linux),在操作系统里部署application。 那么这么做有什么问题呢?部署非常慢准备服务器,买服务器,将服务器部署到机房里面,再安装操作系统,之后再安装APP。而安装App可能比较复杂,需要一些系统环境的依赖。成本非常高部署app需要购买一个服务器,尽管APP非常小。资源浪费APP非常小,导致内存或cpu用不完。难于迁移或扩展如果要迁移APP,我们就需要额外准备一台物理服务器,安装好操作系统,再把这个APP一步步部署上去;扩展时,我们可能需要购买一个更好的服务器,然后迁移过去之后,再对APP进行扩展。可能会被限定硬件厂商限定刚开始选定了一个硬件厂商,之后想把这个APP迁移到其他平台上面,就需要对这个APP做很多的改变。这种模式有这么多问题,而随着技术的发展,出现了一种解决该问题的新技术——虚拟化技术1.2 虚拟化技术虚拟化技术的实现方式:在原先的物理服务器上面,通过Hypervisor,然后去做物理资源的虚拟化(物理资源:CPU、内存、硬盘),之后在之上安装操作系统,也就是虚拟机。如果我们整个物理机底层物理资源非常丰富,我们可以做一个物理资源的限定和调度,从而实现物理资源利用率的最大化。一个物理机可以部署多个APP每个APP可以独立运行在一个虚拟机里虚拟机的优点资源池 一个物理机的资源分配到了不同的虚拟机里,硬件资源最大化的利用。容易扩展 加物理机或虚拟机容易云化 亚马逊AWS、微软、谷歌、腾讯、阿里云等现在大多数公司都把业务部署在云上面,基本都使用了虚拟化技术,既然虚拟化技术这里牛,为什么还要去学习容器化技术呢? 是因为虚拟化技术具有局限性。虚拟化的局限性每一个虚拟机都是一个完整的操作系统,Guest OS通常会占用不少的硬件资源。为了应用系统运行的性能,还要给其分配更多的资源,当虚拟机数量增多时,操作系统本身消耗的资源势必成倍增多。1.3 容器为什么会出现?一款产品从开发到上线,一般都会有开发环境,测试环境,运行环境。开发和线上代码(同一套代码)。开发人员开发了一套软件,并在开发中测试发现没有问题,然而运维人员部署运行这个软件,发现跑不了,出现了各种问题(启动参数、环境问题、漏配了参数)等问题。这就导致开发和运维人员之间产生了矛盾。开发人员在开发环境将代码跑通,但是到了上线的时候就崩了。还要重新检查软件、依赖、运行环境、操作系统等等,这大大降低了效率。因此,容器出现了,容器的英文名是container,集装箱,它是一种标准,我们通过集装箱去运输,不管里面是什么东西。只要按照这个标准打包app,这个打包好的container是可以运行在任何环境上。容器解决了什么问题?解决了开发和运维之间的矛盾在开发和运维之间搭建了一个桥梁,是实现devops的最佳解决方案。1.4 什么是容器?对软件和其依赖的标准化打包应用之间相互隔离共享同一个OS Kernel可以运行在很多主流操作系统上1.5 容器和虚拟机的区别实现原理技术不同虚拟机是用来进行硬件资源划分的完美解决方案,利用的是硬件虚拟化技术,通过Hypervisor层来实现对资源的彻底隔离;容器则是操作系统级别的虚拟化,利用的是内核的Cgroup和Namespace特性,仅仅是进程本身就可以互相隔离。使用的资源方面不同Docker容器与主机共享操作系统内核,不同容器之间可以共享部分系统资源,因此,更加轻量级,消耗的资源更少;虚拟机会独占分配给自己的资源,各个虚拟机之间完全隔离,更重量级,同样消耗的资源也更多。应用场景不同虚拟机——不考虑资源消耗且需要资源完全隔离Docker容器——隔离进程且需要运行大量进程实例二、认识一下Docker(面向百度学习)2.1 docker的由来在2010年,在美国旧金山有一个默默无闻的小公司,是由几个年轻人创立的叫dotCloud。dotCloud公司主要提供的是基于 PaaS(Platform as a Service,平台即服务)平台为开发者或开发商提供技术服务,并提供的开发工具和技术框架。底层技术上,dotCloud 平台利用了 Linux 的 LXC 容器技术。为了方便创建和管理这些容器,dotCloud 基于 Google 公司推出的 Go 语言开发了一套内部工具,之后被命名为 Docker。Docker 就是这样诞生的。2.2 什么是dockerDocker是一个开源的应用容器引擎,基于Go语言并遵从Apache2.0协议开源。Docker可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中,然后发布到任何流行的Linux机器上,也可以实现虚拟化。容器是完全使用沙箱机制,相互隔离容器性能开销极低。简单来说:docker是一种容器技术,解决软件跨环境迁移的问题2.3 docker的思想如同 Docker 的 Logo 一样,Docker 的思想来源于集装箱。集装箱:将所有需要的内容放到不同的集装箱中,谁需要这些环境直接拿到这个集装箱即可!标准化:运输的标准化:docker有一个码头,所有上传的集装箱都放在了这个码头上,当谁需要某一个环境,就直接去搬运这个集装箱就可以了。命令的标准化:Docker提供了一些列的命令,帮助我们去获取集装箱等等操作。提供了REST的API:衍生出了很多的图形化界面,Rancher。隔离性: Docker在运行集装箱内的内容时,会在Linux的内核中,单独的开辟一片空间,这片空间不会影响到其他程序。2.4 为什么用docker快速部署:短时间内可以部署成百上千个应用,更快速交付到线上高效虚拟化:不需要额外hypervisor支持,基于linux内核实现应用虚拟化,相比虚拟机大幅提高性能和效率节省开支:提高服务器利用率,降低IT支出简化配置:将运行环境打包保存至容器,使用时直接启动即可环境统一:将开发,测试,生产的应用运行环境进行标准化和统一,减少环境不一样带来的各种问题快速迁移和扩展:可实现跨平台运行在物理机、虚拟机、公有云等环境,良好的兼容性可以方便将应用从A宿主机迁移到B宿主机,甚至是A平台迁移到B平台更好的实现面向服务的架构:一个容器只运行一个应用,实现分布的应用模型,可以方便的进行横向扩展,符合开发中的高内聚、低耦合要求,减少不同服务之间的相互影响。2.5 docker能干什么?2.6 docker的应用场景Web 应用的自动化打包和发布。自动化测试和持续集成、发布。在服务型环境中部署和调整数据库或其他的后台应用。从头编译或者扩展现有的 OpenShift 或 Cloud Foundry 平台来搭建自己的 PaaS 环境。最后Docker是一个用于开发,发布和运行应用程序的开放平台。Docker使您能够将应用程序与基础架构分开,从而可以快速交付软件。借助Docker,您可以以与管理应用程序相同的方式来管理基础架构。通过利用Docker的方法来快速交付,测试和部署代码,您可以大大减少编写代码和在生产环境中运行代码之间的延迟。
前言MyBatis-Plus(简称 MP)是一个 MyBatis (opens new window)的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。今天我们主要通过一个简单的案例来体会MyBatis-Plus功能的强大之处。一、创建数据库1、添加数据表CREATE TABLE user ( id BIGINT(20) NOT NULL COMMENT '主键ID', name VARCHAR(30) NULL DEFAULT NULL COMMENT '姓名', age INT(11) NULL DEFAULT NULL COMMENT '年龄', email VARCHAR(50) NULL DEFAULT NULL COMMENT '邮箱', PRIMARY KEY (id) );2、填充数据表INSERT INTO user (id, name, age, email) VALUES (1, 'Jone', 18, 'test1@baomidou.com'), (2, 'Jack', 20, 'test2@baomidou.com'), (3, 'Tom', 28, 'test3@baomidou.com'), (4, 'Sandy', 21, 'test4@baomidou.com'), (5, 'Billie', 24, 'test5@baomidou.com');二、整合MyBatis-Plus1、新建springboot工程具体步骤可以参考spring boot工程的创建2、导入对应的starter<dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.2</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <!--lombok :getter/setter方法以及构造器的生成 --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency>3、 添加配置#配置dataSource spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/test?serverTimezone=UTC username: root password: root #配置日志,方便查看sql语句 mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl4、创建实体类@Data @NoArgsConstructor @AllArgsConstructor public class User { @TableId(type = IdType.AUTO) private Long id; private String name; private Integer age; private String email; }5、定义数据层接口,继承BaseMapper@Component public interface UserMapper extends BaseMapper<User> { //这里不需要写任何代码 //MyBatis-Plus已经给我们准备好了,直接拿来用即可 }三、CRUD的实现1、添加@SpringBootTest @MapperScan("com.example.dao") //扫描数据层接口所在的包 class MybatisPlusApplicationTests { @Autowired private UserMapper userMapper; @Test public void testInsert(){ User user = new User(); user.setName("aaa"); user.setAge(3); user.setEmail("88888888@qq.com"); int insert = userMapper.insert(user); System.out.println(insert); } }2、修改@Test public void testUpdate(){ User user = new User(); user.setId(6L); user.setName("dada"); user.setEmail("22222222@qq.com"); int i = userMapper.updateById(user); System.out.println(i); }3、删除3.1 通过id删除单条数据这里我们删除 id 为 6 的用户// 测试通过id单个删除 @Test public void testDeleteById(){ userMapper.deleteById(1L); }3.2 通过id删除多条数据删除之前我们先多插入几条数据,方便测试// 测试通过id批量删除 @Test public void testDeleteBatchIds(){ userMapper.deleteBatchIds(Arrays.asList(8L,9L,10L)); }3.3 通过map批量删除我们再添加一条数据,删除数据表中年龄等于 18 岁的用户// 测试通过map批量删除 @Test public void testDeleteByMap(){ HashMap<String, Object> map = new HashMap<>(); map.put("age","18"); userMapper.deleteByMap(map); }4、查询4.1 通过id查询单条数据// 测试通过id查询 @Test public void testSelectById(){ User user = userMapper.selectById(2); System.out.println(user); }4.2 查询所有数据@Test public void selectList() { List<User> list = userMapper.selectList(null); list.forEach(System.out::println); }
内部配置文件加载顺序Spring Boot程序启动时,会从以下位置加载配置文件:项目根目录:当前项目下的/config目录下项目根目录:当前项目的根目录下classpath:classpath的/config目录下classpath:classpath的根目录下加载顺序为上面的排列顺序,高优先级配置文件的属性会生效注意:优先级高的配置文件只覆盖优先级低的配置文件中的重复项。低级配置文件的独有项仍然有效。目录结构如下:测试:测试方法: 通过配置Tomcat的端口号来检测他们的优先级顺序测试步骤:classpath的根目录下,即resources下的application.yml在该位置的配置文件中设置Tomcat的端口号为8081,启动该项目classpath的/config目录下,即resources中config目录下的application.yml在该位置的配置文件中设置Tomcat的端口号为8082,重新启动该项目当前项目的根目录在该位置的配置文件中设置Tomcat的端口号为8083,重新启动该项目当前项目下的/config目录下在该位置的配置文件中设置Tomcat的端口号为8084,重新启动该项目测试结果: 这四个位置的配置文件的优先级从低到高与测试的顺序一致。外部配置文件加载顺序通过指定配置spring.config.location来改变默认配置,一般在项目已经打包后,我们可以通过指令来加载外部文件的配置:java -jar xxx.jar --spring.config.location=e://Java/application.yml改变环境变量时,可以通过修改外部配置文件来实现,不需重新打包项目。当然如果你觉得在命令行指定外部配置文件的位置太麻烦,那么我再告诉你种方法,那就是在你想启动的项目jar包所在的文件夹下新建一个application.yml配置文件,或者在该文件夹下新建一个config的文件夹并在config文件夹下新建一个application.yml配置文件。这时候该项目就会自动读取该配置文件,如果两个同时存在,他们也是有优先级的,config文件下的yml文件是优先于与jar包同级的yml文件。更详细的介绍可以查看Spring Boot 中文文档
Spring Boot ProfilesProfile的是配置文件的意思,我们在开发Spring Boot应用时,通常同一个项目会被安装到不同的环境,而不同的环境又需要不同的配置。比如:开发环境,应用需要连接一个可供调试的数据库单机进程生产环境,应用需要使用正式发布的数据库,通常是高可用的集群测试环境,应用只需要使用内存式的模拟数据库其中数据库地址、服务器端口等等配置都不同,如果每次打包时,都要修改配置文件,那么就会非常麻烦。Spring框架提供了profile的管理功能,我们可以使用profile功能来区分不同环境的配置。然后可以通过激活、指定参数等方式快速动态的切换环境。profile配置方式1) 多文件方式新建多个配置文件,命名格式:application-环境名.ymlapplication-dev.yml 开发环境server: port: 8081 # 给配置文件起名字,方便主配置文件引用 spring: config: activate: on-profile: dev application-pro.yml生产环境server: port: 8082 spring: config: activate: on-profile: pro application-test.yml测试环境server: port: 8083 spring: config: activate: on-profile: test2) yml多文档方式该方式只需要一个application.yml配置文件即可,在配置文件中使用 — (三个横杠)来分隔不同的环境配置--- server: port: 8081 spring: profiles: dev --- server: port: 8082 spring: profiles: pro --- server: port: 8083 spring: profiles: test ---profile激活方式1) 配置文件在yml配置文件中配置:spring: profiles: active: test2) 虚拟机参数在VM options指定:-Dspring.profiles.active -dev紧接着启动该项目,你会在控制台看到此时的环境就是你刚才设置的开发环境,覆盖了我们在配置文件中的激活配置3) 命令行参数第一种:--spring.profiles.active=pro第二种:java -jar xxx.jar --spring.profiles.active =test使用maven打包项目,打开该jar包所在目录,接着启动该项目,不会可以参考这个>>>Spring Boot的启动方式
Spring Boot数据获取简单来说,数据获取就是从application.yml配置文件中拿到相对应的值然后交给对应的实体类对象的属性。SpringBoot 提供了以下3种注解来获取数据:@Value@Environment@ConfigurationProperties1. @Value当我们需要获取配置文件中的某一个数据时,就可以通过 @Value 注解来获取。(使用时标注在实体类的属性上)application.yml person: name: lucy age: 18 hobby: - basketball - music - dance name1: tony msg1: 'hello \n springboot!' msg2: "hello \n SpringBoot!"@Value("${name1}") String name1; @Value("${person.name}") String name; @Value("${person.age}") int age; @Value("${person.hobby[0]}") String hobby; @Value("${msg1}") String msg1; @Value("${msg2}") String msg2;输出结果如下:2. @Environment引入Environment通过getProperty方法来获取数据@RestController public class HelloController { @Autowired private Environment environment; @RequestMapping("/hello") public String hello(){ System.out.println("name1:"+ environment.getProperty("name1")); System.out.println("name:"+ environment.getProperty("person.name")); System.out.println("age:"+ environment.getProperty("person.age")); System.out.println("hobby:"+ environment.getProperty("person.hobby[1]")); System.out.println("msg1:"+ environment.getProperty("msg1")); System.out.println("msg2:"+ environment.getProperty("msg2")); return "Hello Spring Boot!"; } }获取结果:3. @ConfigurationProperties@ConfigurationProperties主要作用就是将prefix属性指定的前缀配置项的值绑定到这个JavaBean上,需要和@Component或者@Configuration一起使用才能生效。(使用时标注在实体类上)创建一个Person.java的实体类@Component @ConfigurationProperties(prefix = "person") public class Person { private String name; private int age; private String[] hobby; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String[] getHobby() { return hobby; } public void setHobby(String[] hobby) { this.hobby = hobby; } @Override public String toString() { return "Person{" + "name='" + name + '\'' + ", age=" + age + ", hobby=" + Arrays.toString(hobby) + '}'; } }当出现下面的错误时,需要在pom.xml文件中添加下面的依赖(没有就忽略)<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> </dependency>编写一个控制器方法@RestController public class HelloController { @Autowired private Person person; @RequestMapping("/person") public Person person(){ return person; } }启动该项目,访问地址:http://localhost:8081/person
配置文件分类SpringBoot是基于约定的,所以很多配置都有默认值,但如果想使用自己的配置替换默认配置的话,就可以使用application.properties或者application.yml(application.yaml)进行配置,其文件名是固定的。推荐使用yml文件格式其中,application.yml 是一种使用 YAML 语言编写的文件,它与 application.properties 一样,可以在 Spring Boot 启动时被自动读取,修改 Spring Boot 自动配置的默认值。默认配置文件名称:application在同一级目录下优先级为:properties > yml > yaml下面我们通过修改内置Tomcat的端口号来具体看一下他们的优先级首先配置application.yaml,然后运行主程序启动类server: port: 8081访问测试:http://localhost:8081/hello配置application.yml,配置端口号为8082,接着重新启动server: port: 8082在这里可以看到Tomcat的端口号为8082,可以说明 yml 的优先级高于 yaml接着再次访问测试:http://localhost:8081/hello,发现无法访问,但端口号改为8082就可以访问了最后在application.properties配置文件中,配置Tomcat的端口号为8083,重新启动server.port=8083测试访问http://localhost:8081/hello和http://localhost:8082/hello都无法访问,只有端口号是8083才可以访问至此,我们就可以得出他们在同级目录中的优先级顺序为:properties > yml > yamlyaml基本语法(1)概念YAML是一种直观的能够被电脑识别并且可读性高的数据数据序列化格式。并且容易被人阅读,容易和脚本语言交互的,可以被支持YAML库的不同的编程语言程序导入。(2) 语法特点大小写敏感数据数值前必须要有空格,作为分隔符使用缩进表示层级关系,但不能使用Tab键,只能用空格缩进的空格数目不重要,只要相同层级的元素左侧对齐即可‘’#" 表示注释,从这个字符一直到行尾,都会被解析器忽略(3) 数据类型对象(map): 键值对的集合person: name: lucy age: 18 # 行内写法 person: {name: lucy,age: 18}数组: 一组按次序排列的值address: - beijing - shanghai # 行内写法 address: [beijing,shanghai]纯量: 单个的、不可再分的值在 YAML 中,使用key: value的形式表示一对键值对(value前面的空格不能丢)msg: 'hello \n springboot' #单引忽略转义字符输出结果:hello \n springbootmsg: "hello \n springboot" # 双引号识别转义字符输出结果:hellospringboot
Spring Boot的目录结构1、pom.xml文件指定了一个父工程:指定当前工程为SpringBoot,帮助我们声明了starter依赖的版本。项目的元数据:包名,项目名,版本号。指定了properties信息:指定了java的版本为1.8导入依赖:默认情况导入spring-boot-starter,spring-boot-starter-test,插件: spring-boot-maven-plugin2、src目录-src -main -java -包名 主启动程序类.java -resources -static #存储静态资源 -templates #存储视图页面 application.properties #Spring Boot提供的配置文件 -test #用于测试Spring Boot的启动方式方式一:运行主启动程序类中的main方法即可方式二:使用maven命令来运行SpringBoot工程命令:mvn spring-boot:run方式三:使用jar包的方式来运行:将需要运行的项目打包通过命令java -jar jar文件来运行
计算机组成原理与体系结构一、数据的表示二、计算机结构三、计算机体系结构分类——Flynn四、CISC与RISC五、流水线六、Cache一、数据的表示进制转换1. R进制转十进制使用按权展开法(幂的指数与该位和小数点的距离有关,小数点左边为正数的值,反之为负数,以小数点左边的第一位记为0)例:二进制 10100.01=1*2^4+1 *2^2+1 *2^-2 七进制 604.01=6 *7^2 +4 * 7^0 +1 * 7^-22. 十进制转R进制使用短除法(用十进制数除R取余,直到余数为零,最后将余数从下到上排列)3.二进制转八进制与十六进制(1)二进制转八进制:从小数点开始,向左或向右每3位为一组用一位八进制数的数字表示,不足3位的要用0补足3位,就得到一个八进制数。(2)二进制转十六进制:从小数点位置开始,向左或向右每四位二进制划分一组(不足四位数可补0),然后写出每一组二进制数所对应的十六进制数码。10001110转化为八进制是216 转化为十六进制是8E原码、反码、补码、移码原码:就是符号位加上真值的绝对值,即用第一位表示符号,其余位表示值。比如如果是8位二进制: [+1]原 = 0000 0001 [-1]原 = 1000 0001反码:正数的反码是其本身,负数的反码是在其原码的基础上,符号位不变,其余各个位取反。 [+1] = [00000001]原 = [00000001]反 [-1] = [10000001]原 = [11111110]反补码:正数的补码就是其本身,负数的补码是在其原码的基础上,符号位不变,其余各位取反,最后加一。 [+1] = [00000001]原 = [00000001]反 = [00000001]补 [-1] = [10000001]原 = [11111110]反 = [11111111]补移码:只需要将它的补码的符号位取反。浮点数运算浮点数运算即科学计数法,表示方法为:N=M*R^e(其中,M称为尾数,e是指数,R为基数)。二、计算机结构三、计算机体系结构分类——Flynn四、CISC与RISC五、流水线1.概念:流水线是指在程序执行时多条指令重叠进行操作的一种准并行处理实现技术。各种部件同时处理是针对不同指令而言的,他们可同时为多条指令的不同部分进行工作,以提高各部件的利用率和指令的平均执行速度。2.指令执行的流程:——>取指——>分析——>执行——>3.流水线计算 流水线周期为执行时间最长的一段 流水线计算公式:1条指令执行时间+(指令条数-1)*流水线周期理论公式:(t1+t2+……+tk)+(n-1)*△t实践公式: (k+n-1)*△t4.流水线的吞吐率计算流水线的吞吐率是指在单位时间内流水线所完成的任务数量或输出的结果数量。计算公式:TP=指令条数/流水线执行时间流水线最大吞吐率:TPmax=1/△t5.流水线的加速比计算完成同样一批任务,不使用流水线所用的时间与使用流水线所用的时间之比称为流水线的加速比。计算公式:S=不使用流水线执行时间/使用流水线执行时间6.流水线的效率流水线的效率是指流水线的设备利用率。在时空图上,流水线的效率定义为n个任务占用的时空区与k个流水段总的时空区之比。计算公式:E=n个任务占用的时空区/k个流水段总的时空区六、Cache1.功能:提高CPU数据输入输出的速率,突破冯·诺伊曼瓶颈,即CPU与存储系统间数据传送带宽限制。2.在计算机的存储系统体系中,cache是访问速度最快的层次。3.在使用cache改善系统性能的依据是程序的局部性原理。
详细的Git使用教程前言一、Git是什么?二、Git工作区、暂存区、Git 仓库三、Git的使用1.安装2.设置用户信息3.创建版本库4.git基本操作5.git分支管理前言本文介绍了Git学习的基础内容一、Git是什么?Git是一款免费、开源的分布式版本控制系统,用于敏捷高效地处理任何或小或大的项目版本管理。Git 是 Linus Torvalds为了帮助管理 Linux 内核开发而开发的一个开放源码的版本控制软件。二、Git工作区、暂存区、Git 仓库git工作区工作区是对项目的某个版本独立提取出来的内容。就是你在电脑里能看到的目录。暂存区(stage)暂存区是一个文件,保存了下次将要提交的文件列表信息,一般在 Git 仓库目录中。 按照 Git的术语叫做“索引”,不过一般说法还是叫“暂存区”。gti仓库Git 仓库目录是 Git 用来保存项目的元数据和对象数据库的地方。 这是 Git 中最重要的部分,从其它计算机克隆仓库时,复制的就是这里的数据。三、Git的使用1.安装下载地址:git下载双击开始安装,一直点击next完成安装,鼠标右击桌面出现如下就安装完成了2.设置用户信息安装完 Git 之后,要做的第一件事就是设置你的用户名和邮件地址。 这一点很重要,因为每一个 Git 提交都会使用这些信息,它们会写入到你的每一次提交中,不可更改:$ git config --global user.name "yourname" $ git config --global user.email "123456789@qq.com"配置完成后可以使用以下命令查看$ git config --list3.创建版本库$ mkdir mygit $ cd mygit //切换到新建的文件夹下 #初始化仓库 $ git init之后在该目录下会出现一个.git文件夹,这个是Git来跟踪管理版本库的。4.git基本操作1. 创建一个文件$ touch test.txt2. 将文件添加到缓存区$ git add test.txt3. 将文件提交到仓库//引号里面本次提交的说明 $ git commit -m "first commit"4. 查看当前状态//查看当前版本状态 $ git status5. 查看历史记录//git log显示你所的提交日志 $ git log6. 回退历史版本$ git resetHEAD 表示当前版本,而 HEAD~ 表示 HEAD 的上一个版本,HEAD~~则是上上个版本,如果表示上10个版本,则可以用HEAD~10来表示$ git reset --hard HEAD~–hard : 回退版本库、暂存区、工作区。(这里是回到初始状态,所以修改过的也就没了)–mixed: 回退版本库、暂存区。(mixed为git reset的默认参数)–soft: 回退版本库。7. 查看历史命令(版本号)$ git reflog d9d27d4 (HEAD -> master) HEAD@{0}: reset: moving to HEAD d9d27d4 (HEAD -> master) HEAD@{1}: commit (initial): first commit8. 版本对比在已经存在的文件中添加内容$ git diff warning: LF will be replaced by CRLF in a.txt. The file will have its original line endings in your working directory diff --git a/a.txt b/a.txt //表示对比的是存放在暂存区域和工作目录的a.txt index e69de29..190a180 100644 //表示对应文件的版本号 --- a/a.txt //表示该文件是旧文件 +++ b/a.txt //表示该文件是新文件 @@ -0,0 +1 @@ +123 //表示新添加的内容9. 撤销修改$ git checkout -- README.md就是把没暂存的干掉,或者说,丢弃工作区,回到到暂存状态。10.删除文件$ git rm a.txt rm 'a.txt'11.重命名文件git mv 旧文件名 新文件名$ git mv b.txt c.txt5.git分支管理1.分支的创建与切换git branch 分支名 //创建分支 $ git branch feature1 //查看分支 $ git log commit 59707b02146659f41e9012bf6623e60dfcae05e4 (HEAD -> master, feature1) //会看到版本号后面多了个(HEAD -> master, feature1) //切换分支 $ git checkout feature1 Switched to branch 'feature1'2. 合并分支git merge 分支名 //切换分支 19737@LAPTOP-3TQGGV4N MINGW64 /d/Users/19737/Desktop/mymenu/git (master) $ git checkout feature1 Switched to branch 'feature1' //查看所有文件 $ ls 123.txt c.txt test.txt //随意修改任意一个文件 //切回主分支 $ git checkout master Switched to branch 'master' //合并分支 $ git merge feature1 Updating 59707b0..f2ec55e Fast-forward b.txt => 123.txt | 0 c.txt | 1 + 2 files changed, 1 insertion(+) rename b.txt => 123.txt (100%) create mode 100644 c.txt3. 删除分支git branch -d 分支名 $ git branch -d feature1 Deleted branch feature1 (was f2ec55e). //如果试图删除未合并的分支,使用下面的命令强制删除 $ git branch -D 分支名4. 变基rebase将本地多次的commit合并成一个commit。// 合并前两次的commit $ git rebase -i head~~5. 消除冲突//新建一个分支 $ git branch feature2 $ git checkout feature2 Switched to branch 'feature2' //修改test.txt文件 $ vi test.txt $ git add . $ git commit -m "feature2" //切换到master分支 $ git checkout master Switched to branch 'master' //同样对test.txt进行修改 $ vi test.txt $ git add . $ git commit -m "master" //合并分支feature2会看到 hello world! Hello Git! <<<<<<< HEAD 123 ======= 123456789 >>>>>>> feature2 //Git用<<<<<<< ======= >>>>>>>标记出不同分支的内容 //我们修改之后在提交就可以了 $ git merge feature2 Auto-merging test.txt Merge made by the 'ort' strategy. test.txt | 1 + 1 file changed, 1 insertion(+)
Linux下安装MySQL5.7(使用rpm安装)1.首先在 /usr/local/目录下新建一个文件夹 mysql2.切换到mysql目录下(1)下载**//下载安装包** [root@localhost mysql]# wget https://dev.mysql.com/get/Downloads/MySQL-5.7/mysql-5.7.22-1.el7.x86_64.rpm-bundle.tar //**解压在mysql文件夹内** [root@localhost mysql]# tar -xf mysql-5.7.22-1.el7.x86_64.rpm-bundle.tar //**安装依赖包** [root@localhost mysql]# yum -y install make gcc-c++ cmake bison-devel ncurses-devel libaio-devel net-tools **由于Centos7开始自带的数据库是mariadb,所以需要卸载系统中的mariadb组件,才能安装mysql的组件** [root@localhost mysql]# rpm -qa | grep mariadb mariadb-libs-5.5.68-1.el7.x86_64 [root@localhost mysql]# yum -y remove mariadb-libs //卸载 (2)安装[root@localhost mysql]# rpm -ivh mysql-community-common-5.7.22-1.el7.x86_64.rpm 警告:mysql-community-common-5.7.22-1.el7.x86_64.rpm: 头V3 DSA/SHA1 Signature, 密钥 ID 5072e1f5: NOKEY 准备中... ################################# [100%] 正在升级/安装... 1:mysql-community-common-5.7.22-1.e################################# [100%] [root@localhost mysql]# rpm -ivh mysql-community-libs-5.7.22-1.el7.x86_64.rpm 警告:mysql-community-libs-5.7.22-1.el7.x86_64.rpm: 头V3 DSA/SHA1 Signature, 密钥 ID 5072e1f5: NOKEY 准备中... ################################# [100%] 正在升级/安装... 1:mysql-community-libs-5.7.22-1.el7################################# [100%] [root@localhost mysql]# rpm -ivh mysql-community-libs-compat-5.7.22-1.el7.x86_64.rpm 警告:mysql-community-libs-compat-5.7.22-1.el7.x86_64.rpm: 头V3 DSA/SHA1 Signature, 密钥 ID 5072e1f5: NOKEY 准备中... ################################# [100%] 正在升级/安装... 1:mysql-community-libs-compat-5.7.2################################# [100%] [root@localhost mysql]# rpm -ivh mysql-community-client-5.7.22-1.el7.x86_64.rpm 警告:mysql-community-client-5.7.22-1.el7.x86_64.rpm: 头V3 DSA/SHA1 Signature, 密钥 ID 5072e1f5: NOKEY 准备中... ################################# [100%] 正在升级/安装... 1:mysql-community-client-5.7.22-1.e################################# [100%] [root@localhost mysql]# rpm -ivh mysql-community-server-5.7.22-1.el7.x86_64.rpm 警告:mysql-community-server-5.7.22-1.el7.x86_64.rpm: 头V3 DSA/SHA1 Signature, 密钥 ID 5072e1f5: NOKEY 准备中... ################################# [100%] 正在升级/安装... 1:mysql-community-server-5.7.22-1.e################################# [100%] (3)启动[root@localhost mysql]# systemctl start mysqld [root@localhost mysql]# systemctl enable mysqld [root@localhost mysql]# systemctl status mysqld(4)查找密码并登录[root@localhost mysql]# grep "password" /var/log/mysqld.log // 前往日志文件查找临时密码 2022-01-17T14:15:30.044799Z 1 [Note] A temporary password is generated for root@localhost: VihNdp7u+ff; //密码用引号包住,因为有特殊符号 [root@localhost mysql]# mysql -uroot -p"VihNdp7u+ff;"(5)重新设置密码格式mysql> set global validate_password_policy=0; Query OK, 0 rows affected (0.00 sec) mysql> set global validate_password_length=1; Query OK, 0 rows affected (0.00 sec) mysql> set password for root@localhost=password('root'); //设置密码 Query OK, 0 rows affected, 1 warning (0.00 sec) mysql> flush privileges; //刷新 Query OK, 0 rows affected (0.00 sec) mysql> exit //退出 Bye(6)用新密码重新登录[root@localhost mysql]# mysql -uroot -proot mysql: [Warning] Using a password on the command line interface can be insecure. Welcome to the MySQL monitor. Commands end with ; or \g. Your MySQL connection id is 3 Server version: 5.7.22 MySQL Community Server (GPL) Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved. Oracle is a registered trademark of Oracle Corporation and/or its affiliates. Other names may be trademarks of their respective owners. Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. mysql>至此MySQL就安装完成了
一、设计模式是什么?设计模式的本质提高软件的维护性,通用性和扩展性,并降低软件的复杂度。设计模式并不局限于某种语言,java,php,c++都有设计模式。设计模式的意义在于:可以使人们更加简单、方便的复用成功的设计和体系结构。二、什么是 GoF四人帮?在 1994 年,由 GoF 四人合作出版了一本名为 《设计模式 - 可复用的面向对象软件基础》的书,该书首次提到了软件开发中设计模式的概念。四位作者合称 GOF(四人帮,全拼 Gang of Four)。他们所提出的设计模式主要是基于以下的面向对象设计原则。对接口编程而不是对实现编程。优先使用对象组合而不是继承。别说,还是有点帅的!!!三、设计模式的设计原则设计模式原则,其实就是程序员在编程时,应当遵守的原则,常用的七大原则有以下七种四、设计模式的分类设计模式分为三种类型,共 23种:Creational(创建型):与对象创建有关Structural(结构型):处理类和对象的组合Behavioral(行为型):描述类或对象如何交互及如何分配职责创建型模式:单例模式、抽象工厂模式、原型模式、建造者模式、工厂模式。结构型模式:适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式。行为型模式:模版方法模式、命令模式、访问者模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式、状态模式、策略模式、职责链模式。
1.拦截器1.1 拦截器的定义在Spring MVC中使用拦截器,需要对拦截器类进行定义和配置。一般拦截器可以通过两种方式类定义。一种是通过实现HandlerInterceptor接口或继承该接口的实现类来定义;一种是通过实现WebRequestInterceptor接口或继承该接口的实现类来定义。下面我们以实现HandlerInterceptor接口的方式来自定义拦截器类:@Component public class MyFirstInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("MyFirstInterceptor=====================>preHandle"); return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("MyFirstInterceptor=====================>postHandle"); } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("MyFirstInterceptor=====================>afterCompletion"); } }可以看出自定义的拦截器类实现了HandlerInterceptor接口,并重写了接口中的三个方法。1.2 拦截器的方法拦截器有3个回调方法:preHandle: 预处理回调方法,实现处理器的预处理,第三个参数为响应的处理器返回值:true表示继续向下执行(如调用下一个拦截器);false表示中断后续操作,不会继续调用其他的拦截器和控制器类的方法执行。postHandle: 后处理回调方法,实现处理器的后处理,在渲染视图之前,此时我们可以通过modelAndView对模型数据进行处理或对视图进行处理。afterCompletion: 整个请求处理完成,即在视图渲染结束之后执行,可以通过该方法进行一些资源清理,类似于trycatchfinally中的finally,但仅调用处理器执行链中preHandle返回true的拦截器才会执行。1.3 拦截器的配置想要使自定义的拦截器类生效,还需要在Spring MVC的配置文件中进行配置。我们通过<mvc:interceptors>元素来配置一组拦截器,若要拦截DispatcherServlet处理的所有请求,则如下面配置:<!--配置拦截器--> <mvc:interceptors> <!--1.对DispatcherServlet处理的所有请求进行拦截--> <bean class="com.it.interceptor.MyFirstInterceptor"/> <ref bean="myFirstInterceptor"/> </mvc:interceptors>当然,我们也可以通过<mvc:interceptors>的子元素\<mvc:interceptor\>来定义指定路径的拦截器,他会对指定路径下的请求生效。<mvc:interceptors> <mvc:interceptor> <!--配置拦截器作用的路径,path的属性值“/**”表示拦截所有路径--> <mvc:mapping path="/**"/> <!--配置拦截请求中不需要拦截的请求--> <mvc:exclude-mapping path="/interceptor"/> <ref bean="myFirstInterceptor"/> </mvc:interceptor> </mvc:interceptors>注意:\<mvc:interceptor\>元素中的子元素的配置顺序不能变。1.4 定义多个拦截器1.4.1 添加另一个拦截器@Component public class MySecondInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("MySecondInterceptor=================>preHandle"); return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("MySecondInterceptor==================>postHandle"); } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("MySecondInterceptor=============>afterCompletion"); } }1.4.2 配置拦截器<mvc:interceptors> <ref bean="myFirstInterceptor"/> <ref bean="mySecondInterceptor"/> </mvc:interceptors>可以看到如下图所示的执行顺序:从上面能够看到,若每个拦截器的preHandle()都返回true,则preHandle()会按照配置的顺序执行,而postHandle()和afterComplation()会按照配置的反序执行。若某个拦截器的preHandle()返回了false,那么preHandle()返回false和它之前的拦截器的preHandle()都会执行,而postHandle()都不执行,返回false的拦截器之前的拦截器的afterComplation()会执行。也就是已经通过的拦截器的afterCompletion会执行。1.4.1 示例比如:我们将第二个拦截器的preHandle方法的返回值改为false,可以看到preHandle方法都执行了,postHandle没有执行,而afterComplation只执行了第一个拦截器的。1.5 拦截器的执行流程1.5.1 单个拦截器执行流程单个拦截器,程序会首先执行拦截器类中的preHandle()方法,若该方法返回true,则继续向下执行处理器中的方法,否则中断;处理完请求之后会执行postHandle()方法,然后再通过DispatcherServlet请求处理之后,最后执行afterComplation()方法。1.5.2 多拦截器执行流程多个拦截器同时工作时,每个拦截器的preHandle()方法都会按照配置的顺序执行,而postHandle()和afterComplation()会按照配置的反序执行。
1. 文件上传1.1 文件上传介绍文件上传是通过表单的形式提交给服务器的,因此,实现文件的上传,就需要一个提供上传的表单,而这个表单则必须满足以下三个条件。form表单的method属性为postform表单的enctype属性为multipart/form-data使用<input type="file" name="filename"/>为文件上传输入框示例代码如下:<form action="${pageContext.request.contextPath}/fileUpload" method="post" enctype="multipart/form-data"> <input type="file" name="photo" multiple> <input type="submit" value="上传"/> </form>注意:使用 multiple 属性,则可以同时选择多个文件上传。enctype=multipart/form-data:该属性表明发送的请求体的内容是多表单元素的,通俗点讲,就是有各种各样的数据,可能有二进制数据,也可能有表单数据。当使用该属性时,浏览器就会采用二进制流的方式来处理表单数据,服务器端就会对文件上传的请求进行解析处理。1.2 文件上传实现1.2.1 添加依赖Spring NVC 的文件上传需要依赖Apache Commons FileUpload的组件,即需要添加支持文件上传的依赖。<!-- https://mvnrepository.com/artifact/commons-fileupload/commons-fileupload --> <dependency> <groupId>commons-fileupload</groupId> <artifactId>commons-fileupload</artifactId> <version>1.3.3</version> </dependency>1.2.2 配置上传解析器Spring MVC 为文件的上传提供了直接的支持,而这个支持是通过MultipartResolver对象实现。MultipartResolver是一个接口,需要他的实现类CommonsMultipartResolver来完成文件上传。而在Spring MVC中使用该对象,只需在配置文件中定义一个MultipartResolver接口的Bean即可。<!-- 配置文件上传解析器,将上传的文件封装为CommonsMultipartFile --> <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <property name="defaultEncoding" value="UTF-8"/> </bean>注意: 配置CommonsMultipartResolver时必须指定该bean的id为multipartResolver。MultipartResolver的属性:属性名作用maxUploadSize上传文件的最大长度maxInMemorySize缓存中的最大尺寸resolveLazily推迟文件解析defaultEncoding默认编码格式1.2.3 通过表单上传<form action="${pageContext.request.contextPath}/fileUpload" method="post" enctype="multipart/form-data"> <input type="file" name="photo" multiple> <input type="submit" value="上传"/> </form>1.2.4 创建控制器类@Controller public class FileUploadController { @RequestMapping("/fileUpload") public String testFileUpload(MultipartFile photo, HttpSession session) throws IOException { String filename = photo.getOriginalFilename(); ServletContext servletContext = session.getServletContext(); String realPath = servletContext.getRealPath("photo"); File file = new File(realPath); //判断realPath对应的路径是否存在 if (!file.exists()){ //不存在就创建 file.mkdir(); } String finalPath = realPath + File.separator + filename; photo.transferTo(new File(finalPath)); return "success"; } }文件上传位置:1.2.5 上传演示查看上传结果,可以看到上传成功了!!!2. 文件下载2.1文件下载的实现文件下载就是将文件服务器中的文件下载到本地。2.1.1 客服端创建超链接需要先在文件下载目录中添加一个a.jpg文件<a href="${pageContext.request.contextPath}/fileDownload">下载a.jpg</a>2.1.2 创建控制器类使用Spring MVC提供的文件下载方法进行文件下载,Spring MVC为我们提供了一个ResponseEntity类型的对象来实现文件的下载。@Controller public class FileDownloadController { @RequestMapping("/fileDownload") public ResponseEntity<byte[]> testFileDownload(HttpSession session) throws IOException { //获取ServletContext对象 ServletContext servletContext = session.getServletContext(); //文件的真实路径 String realPath = servletContext.getRealPath("static/img/a.jpg"); //创建输入流 InputStream inputStream = new FileInputStream(realPath); //创建字节数组 byte[] bytes = new byte[inputStream.available()]; //将流读到字节数组中 inputStream.read(bytes); //创建HttpHeaders对象,设置响应头信息 MultiValueMap<String,String> headers = new HttpHeaders(); //设置下载方式和下载文件的名称 attachment表示以附件的形式下载 headers.add("Content-Disposition","attachment;filename=a.jpg"); //设置响应状态码 HttpStatus status = HttpStatus.OK; //创建ResponseEntity对象 ResponseEntity<byte[]> responseEntity = new ResponseEntity<>(bytes,headers,status); //关闭输入流 inputStream.close(); return responseEntity; } }2.1.3 下载演示可以看到跟我们平时下载文件是一样的。
1.JSON数据交互1.1 JSON概述JSON 是一种轻量级的数据交换格式,是一种理想的数据交互语言,它易于阅读和编写,同时也易于机器解析和生成。JSON有两种数据结构:对象结构数组结构1.1.1 对象结构对象结构是由花括号括起来的逗号分割的键值对(key:value)构成。对象结构的语法格式是:{ "key1 ":"value 1", "key2 ":"value 2", "key3 ":"value 3"}注意:key 必须是字符串,value 可以是字符串, 数字, 对象, 数组, 布尔值或 null。key 和 value 中使用冒号(:)分割。每个 key/value 对使用逗号(,)分割。1.1.2 数组结构数组结构是由中括号包裹逗号分隔的值的列表组成。数组结构的语法格式是:[ "value1","value2","value3" ]注意:JSON 中数组值必须是字符串, 数字, 对象, 数组, 布尔值或 null。java转换为json 的过程一般会称为 “序列化”json转换为java 的过程一般会称为 “反序列化”json的属和字符串值必须要用双引号 "" 不能用单引1.2 JSON数据转换为实现浏览器与控制器类之间的数据交互,Spring提供了一个HttpMessageConverter接口来实现该工作。Spring为HttpMessageConverter接口提供了很多实现类,来对不同类型的数据进行信息转换。而Spring MVC 默认处理JSON格式请求响应的实现类是MappingJackson2HttpMessageConverter,使用此类对数据进行转换。数据转换操作步骤:导入jackson依赖<dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.13.2.2</version> </dependency>在SpringMVC的核心配置文件中开启mvc的注解驱动,此时在HandlerAdaptor中会自动装配一个消息转换器:MappingJackson2HttpMessageConverter,可以将响应到浏览器的Java对象转换为Json格式的字符串<mvc:annotation-driven/> <mvc:default-servlet-handler/>创建一个User类,用于封装User类型的请求参数public class User { private Integer id; private String username; private String password; public User() { } public User(Integer id, String username, String password) { this.id = id; this.username = username; this.password = password; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } } 在控制器方法上使用@ResponseBody注解进行标识@RequestMapping("/testJson") @ResponseBody public User testJson(){ return new User(001,"jack","123456"); }将Java对象直接作为控制器方法的返回值返回,就会自动转换为json格式的字符串2. HttpMessageConverterHttpMessageConverter,报文信息转换器,将请求报文转换为Java对象,或将Java对象转换为响应报文。HttpMessageConverter提供了两个注解@RequestBody和@ResponseBody,在JSON格式转换中也是非常重要的。2.1 @RequestBody@RequestBody 可以获取请求体,需要在控制器方法设置一个形参,使用@RequestBody进行标识,就会将请求体中的数据绑定到方法的形参中。 @RequestMapping("/testRequestBody") public String testRequestBody(@RequestBody String requestBody){ System.out.println("requestBody:" + requestBody); return "success"; }表单提交:<form action="${pageContext.request.contextPath}/testRequestBody" method="post"> 用户名:<input type="text" name="username"><br> 密码:<input type="password" name="password"><br> <input type="submit" value="测试"><br> </form>返回结果:requestBody:username=abc&password=1232.2 @ResponseBody@ResponseBody 标识当前的控制器方法,直接将该方法的返回值作为响应报文的响应体响应到浏览器@RequestMapping("/testResponseBody") @ResponseBody public String testResponseBody(){ return "success"; }通过超链接测试:<a href="${pageContext.request.contextPath}/testResponseBody">测试testResponseBody</a>
1. RESTful是什么?REST 是Roy Fielding博士在2000年他的博士论文中提出来的。它是一种针对网络应用的设计和开发方式,可以降低开发的复杂性,提高系统的可伸缩性。2. 传统风格与RESTful风格对比2.1 传统风格如果是原来的架构风格,需要发送四个请求,分别是?查询用户:http://localhost:8080/springmvc/selectuser?id=1GET增加用户: http://localhost:8080/springmvc/insertuser POST修改用户: http://localhost:8080/springmvc/updateuser PUT删除用户: http://localhost:8080/springmvc/deleteuser?id=1 DELETE2.1 RESTful风格按照传统方式发送请求的时候比较麻烦,需要定义多种请求,而RESTful在HTTP协议中,有不同的发送请求的方式,分别是GET、POST、PUT和DELETE方式,分别对应查询、修改、添加和删除操作。我们如果能让不同的请求方式表示不同的请求类型就可以简化我们的查询。查询用户: http://localhost:8080/springmvc/user/1 GET 查询查询多个用户: http://localhost:8080/springmvc/user GET添加用户: http://localhost:8080/springmvc/user POST 添加修改用户: http://localhost:8080/springmvc/user PUT 修改删除用户:http://localhost:8080/springmvc/user DELETE 删除注意:RESTful风格中的URL不存在动词形式的路径,如selectuser表示查询用户,是一个动词,要改为名词user。3. RESTful的实现RESTful 风格提倡URL地址使用统一的风格设计,各单词之间用斜杠分开。3.1 GET、POST方式3.1.1 创建控制器类@Controller public class UserController { @RequestMapping(value = "/user", method = RequestMethod.GET) public String getAllUser(){ System.out.println("查询所有用户信息"); return "success"; } @RequestMapping(value = "/user/{id}",method = RequestMethod.GET) public String getUserById(){ System.out.println("根据用户ID查询用户信息"); return "success"; } @RequestMapping(value = "/user",method = RequestMethod.POST) public String insertUser(String username,String password){ System.out.println("添加用户信息:" + username + ","+ password); return "success"; } }3.1.2 创建一个jsp页面通过超链接的方式进行测试<a href="${pageContext.request.contextPath}/user">查询全部</a><br> <a href="${pageContext.request.contextPath}/user/1">根据id查询信息</a><br> <form action="${pageContext.request.contextPath}/user" method="post"> 用户名:<input type="text" name="username"><br> 密码:<input type="password" name="password"><br> <input type="submit" value="添加"><br> </form>运行之后可以在控制台正常输出3.2 PUT、DELETE方式一切看起来都非常美好,但是大家需要注意了,我们在发送请求的时候只能发送post或者get,没有办法发送put和delete请求,那么应该如何处理呢?下面开始进入代码环节:3.2.1 编写控制器方法@RequestMapping(value = "/user",method = RequestMethod.PUT) public String updateUser(String username,String password){ System.out.println("修改用户信息:" + username + ","+ password); return "success"; } @RequestMapping(value = "/user",method = RequestMethod.DELETE) public String deleteUser(String username,String password){ System.out.println("删除用户信息:" + username + ","+ password); return "success"; }3.2.2 配置HiddenHttpMethodFilter在web.xml文件中配置HiddenHttpMethodFilter过滤器来处理put和delete请求方式<filter> <filter-name>HiddenHttpMethodFilter</filter-name> <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class> </filter> <filter-mapping> <filter-name>HiddenHttpMethodFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>3.2.3 编写jsp页面处理put和delete请求方式注意事项:请求方式必须为: post请求参数必须为:_method<form action="${pageContext.request.contextPath}/user" method="post"> <input name="_method" value="put" type="hidden"/> 用户名:<input type="text" name="username"><br> 密码:<input type="password" name="password"><br> <input type="submit" value="修改"><br> </form> <form action="${pageContext.request.contextPath}/user" method="post"> <input name="_method" value="delete" type="hidden"/> 用户名:<input type="text" name="username"><br> 密码:<input type="password" name="password"><br> <input type="submit" value="删除"><br> </form>测试结果:3.3 Http405 解决方法在处理put和delete请求方式时,可能会遇到这种情况:控制台能够正常输出,但是浏览器会报405错误解决办法:1.加入 @ResponseBody 注解。2.请求先转给一个Controller,再返回jsp页面。注意:注解添加位置在控制器方法处
1. 简单实例在Spring MVC的控制器方法中,常见的返回类型有ModelAndView、String、void。其中ModelAndView类型中可以添加Model数据并指定视图;String类型的返回值可以跳转试图,但是不能携带数据;void类型主要在异步请求时使用,只返回数据,不跳转视图。 由于ModelAndView类型未能实现数据与视图之间的解耦,通常返回类型会选择String,那么用String作为返回值类型时, 如何将数据带入视图页面呢?这就需要Model参数类型来实现了,通过该参数类型,就可以添加需要在视图中显示的信息。控制器方法如下:@RequestMapping("/modelTest") public String modelTest(Model model){ model.addAttribute("msg","这是一个Spring MVC 程序!"); return "success"; }String类型除了返回上述代码的视图页面外,还可以进行重定向与请求转发2. forward请求转发视图在发送请求的时候,可以通过forward:来实现转发的功能forward:也可以由一个请求跳转到另外一个请求控制器方法如下:@RequestMapping("/forwardTest") public String forwardTest(){ return "forward:/modelTest"; }注意:当控制器方法中所设置的视图名称以"forward:"为前缀时,创建InternalResourceView视图,此时的视图名称不会被SpringMVC配置文件中所配置的视图解析器解析,而是会将前缀"forward:"去掉,剩余部分作为最终路径通过转发的方式实现跳转。3. redirect重定向视图redirect :重定向的路径控制器方法如下:@RequestMapping("/redirectTest") public String redirectTest(){ return "redirect:/modelTest"; }注意:当控制器方法中所设置的视图名称以"redirect:"为前缀时,创建RedirectView视图,此时的视图名称不会被SpringMVC配置文件中所配置的视图解析器解析,而是会将前缀"redirect:"去掉,剩余部分作为最终路径通过重定向的方式实现跳转。4. 重定向和转发的区别4.1 转发由服务器的页面进行跳转,不需要客户端重新发送请求:特点如下:1、地址栏的请求不会发生变化,显示的还是第一次请求的地址2、请求的次数,有且仅有一次请求3、请求域中的数据不会丢失4、根目录:localhost:8080/项目地址/,包含了项目的访问地址4.2 重定向在浏览器端进行页面的跳转,需要发送两次请求(第一次是人为的,第二次是自动的)特点如下:1、地址栏的地址发生变化,显示最新发送请求的地址2、请求次数:2次3、请求域中的数据会丢失,因为是不同的请求4、根目录:localhost:8080/ 不包含项目的名称4.3 对比区别转发(forward)重定向(redirect)根目录包含项目访问地址没有项目访问地址地址栏不会发生变化会发生变化跳转位置服务器端浏览器端请求域中的数据不会丢失会丢失
@RequestMapping前言我们在实际的开发当中,一个控制器中不一定只有一个方法,而这些方法都是用来处理请求的,那么怎样才能将请求与处理方法一一对应呢,当然是通过 RequestMapping 注解来处理这些映射请求,也就是通过它来指定控制器可以处理哪些URL请求。@RequestMapping@RequestMapping注解是一个用来处理请求地址映射的注解,可用于映射一个请求或一个方法,可以用在类或方法上。1.@RequestMapping可以标注的位置1.1 标注在方法上用于方法上,表示在类的父路径下追加方法上注解中的地址将会访问到该方法@Controller public class RequestMappingController { @RequestMapping("/testRequest") public String testRequest(){ return "success"; } }此时请求映射所映射的请求的请求路径为:http://http://localhost:8080/springmvc_study02/testRequest注意:springmvc_study02是项目名称1.2 标注在类和方法上用于类上,表示类中的所有响应请求的方法都是以该地址作为父路径。@Controller @RequestMapping("/hello") public class RequestMappingController { @RequestMapping("/testRequest") public String testRequest(){ return "success"; } }此时请求映射所映射的请求的请求路径为:http://localhost:8080/springmvc_study02/hello/testRequest注意:当你在类上添加RequestMapping注解后,如果要请求映射,就意味着请求要先映射到标注类的位置,然后再映射到该类的方法上。如果不加就会出现如下错误2. @RequestMapping的属性我们可以先查看一下源码:按住Ctrl键,鼠标点击RequestMapping注解就可以看到下面的界面在源码中我们可以看到@RequestMapping的所有属性,那么接下来就一起看一看这些属性2.1 @RequestMapping的value属性*@RequestMapping 的 value 属性必须设值;@RequestMapping 的 value 属性是通过当前请求的请求地址来匹配请求;从上面的源码中可以看到value属性是一个字符串类型的数组,因此说明可以将多个请求映射到一个方法上,只需要给 value 来指定一个包含多个路径的数组。设置value属性的值:@Controller public class RequestMappingController { @RequestMapping(value = {"/testRequest","/test"}) public String testRequest(){ return "success"; } }在浏览器中输入下面路径进行测试:注意:从上面两张图我们能够看到,这时的请求映射所映射的请求的请求路径为选择value数组中的任意一个都可以。2.2 @RequestMapping的method属性*@RequestMapping的method属性是通过当前请求的请求方式来匹配请求;浏览器向服务器发送请求,请求方式有很多GET、HEAD、POST、PUT、PATCH、DELETE、OPTIONS、TRACE。可以使用 method 属性来约束请求方式。设置请求方式为get:@Controller public class RequestMappingController { @RequestMapping(value = "/testRequest",method = RequestMethod.GET) public String testRequest(){ return "success"; } }以post方式请求:<form th:action="@{/testRequest}" method="post"> <input type="submit"> </form>请求结果:注意:我们在@RequestMapping注解里加一个方法限定:method = RequestMethod.GET,则请求必须是get的,否则就会发生以上的错误。映射方法中明确要求请求方式为get,所以post方式不被允许,只有修改为get,才能够请求成功,如果要想两种方式都支持,只需在@RequestMapping注解的method属性中添加另一种方式即可,中间用英文逗号隔开。扩展:@GetMapping:处理get方式请求的映射@PostMapping:处理post方式请求的映射@PutMapping:处理put方式请求的映射@DeleteMapping:处理delete方式请求的映射@GetMapping就相当于@RequestMapping(method=RequestMethod.GET),它会将get映射到特定的方法上。使用方式:@GetMapping(value = "/testRequest")2.3 @RequestMapping的params属性@RequestMapping的params属性是通过当前请求的请求参数来匹配请求;@RequestMapping的params属性是一个字符串类型的数组,可以通过下面四种表达是来设置匹配关系"param":要求请求映射的请求必须为包含 param的请求参数"!param":要求请求映射的请求是不能包含param的请求参数"param=value":要求请求映射的请求必须包含 param 的请求参数,且 param 参数的值必须为 value"param!=value": 要求请求映射的请求是必须包含 param 的请求参数,其值不能为 value。设置params的属性值为username:@RequestMapping(value = "/test",params = "username") public String test(){ return "success"; }请求结果:注意:我们设置了params属性,就意味着该请求映射的请求必须包含username才能够请求成功。当我们传入参数用http://localhost:8080/springmvc_study02/test?username路径来访问,我们来看看结果:当给params属性设置多个属性值时,必须同时满足才能够请求成功,否则会出现下面的错误2.4 @RequestMapping的headers属性@RequestMapping的headers属性是通过当前请求的请求头信息来匹配请求;@RequestMapping的headers属性是一个字符串类型的数组,可以通过下面四种表达是来设置匹配关系"header":要求请求映射的请求必须为包含 header的请求头信息"!header":要求请求映射的请求必须为不包含 header的请求头信息"header=value":要求请求映射的请求必须为包含 header的请求头信息,并且header的值必须为value"header!=value":要求请求映射的请求必须为包含 header的请求头信息,并且header的值必须不是value设置请求头信息: @RequestMapping(value = "/test",headers = "Host = localhost:8081") public String test(){ return "success"; }请求结果:注意:如果当前请求不满足headers属性,此时页面就会显示404错误,即资源未找到。学完这个法宝,是不是感觉非常有意思呢,那还不赶紧去试试。
原代码// This file is auto-generated, don't edit it. Thanks. import com.aliyun.dysmsapi20170525.Client; import com.aliyun.tea.*; import com.aliyun.dysmsapi20170525.*; import com.aliyun.dysmsapi20170525.models.*; import com.aliyun.teaopenapi.*; import com.aliyun.teaopenapi.models.*; public class Sample { public static void main(String[] args_) throws Exception { Config config = new Config() //这里修改为我们上面生成自己的AccessKey ID .setAccessKeyId("LTAI5tLdwwPpCrJbzMdTdQ7") //这里修改为我们上面生成自己的AccessKey Secret .setAccessKeySecret("jnP9no9KhtsE4kVbqbV40JKCksCqy3"); // 访问的域名 config.endpoint = "dysmsapi.aliyuncs.com"; Client client = new Client(config); SendSmsRequest sendSmsRequest = new SendSmsRequest() .setSignName("阿里云短信测试")//短信签名 .setTemplateCode("SMS_154950909")//短信模板 .setPhoneNumbers("157xxxxxxxx")//这里填写接受短信的手机号码 .setTemplateParam("{\"code\":\"1234\"}");//验证码 // 复制代码运行请自行打印 API 的返回值 client.sendSms(sendSmsRequest); } }优化添加maven依赖 <!-- mybatis--> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.9</version> </dependency> <!--spring的ioc相关--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.1.6.RELEASE</version> </dependency> <!--spring的jdbc相关--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.1.6.RELEASE</version> </dependency> <!--阿里云短信依赖--> <dependency> <groupId>com.aliyun</groupId> <artifactId>dysmsapi20170525</artifactId> <version>2.0.9</version> </dependency> <!--单元测试--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <!--spring整合单元测试--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.1.6.RELEASE</version> </dependency> <dependency> <groupId>com.aliyun.oss</groupId> <artifactId>aliyun-sdk-oss</artifactId> <version>3.10.2</version> </dependency>创建config.properties配置文件在原代码中,我们用到的AccessKey ID、AccessKey Secret、域名。短信签名以及短信模板都是固定不变的,因此我们可以将它们拿出来放到一个配置文件中。#aliyun短信配置参数 aliyun.accessKeyId=LTAI5tLdwwPpCrJbzMdTdQ7w aliyun.accessKeySecret=jnP9no9KhtsE4kVbqbV40JKCksCqy2 aliyun.endpoint=dysmsapi.aliyuncs.com aliyun.signName=阿里云短信测试 aliyun.templateCode=SMS_154950909创建applicationContext.xml配置文件在配置文件中配置注解自动扫描,以及加载外部配置文件<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"> <!-- 注解扫描 --> <context:component-scan base-package="com.it"/> <!--加载外部配置--> <context:property-placeholder location="classpath:config.properties"/> </beans>封装工具类package com.it.sms; 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.beans.factory.annotation.Value; import org.springframework.stereotype.Component; @Component //创建当前类的对象 public class MessageTemplate { @Value("${aliyun.accessKeyId}") private String accessKeyId; @Value("${aliyun.accessKeySecret}") private String accessKeySecret; @Value("${aliyun.endpoint}") private String endpoint; @Value("${aliyun.signName}") private String signName; @Value("${aliyun.templateCode}") private String templateCode; public void sendMessage(String phone,String code) throws Exception { Config config = new Config() // 您的AccessKey ID .setAccessKeyId(accessKeyId) // 您的AccessKey Secret .setAccessKeySecret(accessKeySecret); // 访问的域名 config.endpoint = endpoint; Client client = new Client(config); SendSmsRequest sendSmsRequest = new SendSmsRequest() .setSignName(signName) .setTemplateCode(templateCode) .setPhoneNumbers(phone) .setTemplateParam("{\"code\":\""+code+"\"}"); // 复制代码运行请自行打印 API 的返回值 SendSmsResponse sendSmsResponse = client.sendSms(sendSmsRequest); SendSmsResponseBody body = sendSmsResponse.getBody(); System.out.println("短信发送结果:"+body.toString());//打印结果 } } 测试类package com.it.sms; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import static org.junit.Assert.*; //spring整合单元测试 @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:applicationContext.xml") public class MessageTemplateTest { @Autowired private MessageTemplate messageTemplate; @Test public void sendMessage() { try { messageTemplate.sendMessage("17809523930","1024"); } catch (Exception e) { e.printStackTrace(); } } }
如何实现短信验证码功能1.准备工作1.1 注册 阿里云 账号,并完成实名认证。找到短信服务功能。1.2 开通短信服务1.3 签名与模板阿里云不支持个人申请签名和模板,这里可以不申请,可以先使用阿里云提供的免费的测试签名与模板1.4 获取AccessKey生成AccessKey查看Secret,得到 AccessKey ID 与 AccessKey Secret(后面会用到)1.5 打开短信控制台,通过API测试功能打开快速学习页面,点击API发送测试按钮单击绑定测试手机号,在对话框输入接收测试短信的手机号和获取的验证码点击调用API发送短信点击发起调用,将通过该平台直接调用发送接口发送短信这时手机就会收到测试短信2.Java SDK 示例2.1 添加maven依赖官方提供的版本创建一个maven项目,将下面代码添加到pom.xml文件中<dependency> <groupId>com.aliyun</groupId> <artifactId>dysmsapi20170525</artifactId> <version>2.0.9</version> </dependency>2.2 阿里云官方配置创建一个Sample 类复制官方提供的代码,进行一些修改,如下:// This file is auto-generated, don't edit it. Thanks. import com.aliyun.dysmsapi20170525.Client; import com.aliyun.tea.*; import com.aliyun.dysmsapi20170525.*; import com.aliyun.dysmsapi20170525.models.*; import com.aliyun.teaopenapi.*; import com.aliyun.teaopenapi.models.*; public class Sample { public static void main(String[] args_) throws Exception { Config config = new Config() //这里修改为我们上面生成自己的AccessKey ID .setAccessKeyId("LTAI5tLdwwPpCrJbzMdTdQ7") //这里修改为我们上面生成自己的AccessKey Secret .setAccessKeySecret("jnP9no9KhtsE4kVbqbV40JKCksCqy3"); // 访问的域名 config.endpoint = "dysmsapi.aliyuncs.com"; Client client = new Client(config); SendSmsRequest sendSmsRequest = new SendSmsRequest() .setSignName("阿里云短信测试")//短信签名 .setTemplateCode("SMS_154950909")//短信模板 .setPhoneNumbers("157xxxxxxxx")//这里填写接受短信的手机号码 .setTemplateParam("{\"code\":\"1234\"}");//验证码 // 复制代码运行请自行打印 API 的返回值 client.sendSms(sendSmsRequest); } }运行此代码,就会收到如下的短信:【阿里云短信测试】您正在使用阿里云短信测试服务,体验验证码是:1234,如非本人操作,请忽略本短信!
1.创建maven工程项目总体部署2.部署MyBatis框架2.1 添加mybatis依赖<!-- MyBatis依赖 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.27</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.7</version> </dependency>2.2 新建mybatis-config.xml配置文件<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> </configuration>2.3 添加Lombok、junit依赖<dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.22</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.13.1</version> <scope>test</scope> </dependency>3.部署Spring框架3.1添加依赖<!-- Spring依赖 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.3.18</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>5.3.18</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.3.18</version> </dependency>3.2 创建applicationContext.xml配置文件<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"> </beans>4.添加Spring整合MyBatis的依赖* <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>2.0.7</version> </dependency>5.配置数据源(Druid)5.1 添加druid依赖<!-- druid依赖 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.2.8</version> </dependency>5.2 创建druid.properties文件druid.driver=com.mysql.cj.jdbc.Driver druid.url=jdbc:mysql://localhost:3306/ssm?characterEncoding=utf-8 druid.username=root druid.password=root ## 连接池参数 druid.pool.init=1 druid.pool.minIdle=3 druid.pool.maxActive=20 druid.pool.maxWait=300005.3 在Spring配置文件中配置DruidDataSource <!--加载druid.properties文件--> <context:property-placeholder location="classpath:druid.properties"/> <!--数据源DataSource的创建--> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="${druid.driver}"/> <property name="url" value="${druid.url}"/> <property name="username" value="${druid.username}"/> <property name="password" value="${druid.password}"/> <property name="initialSize" value="${druid.pool.init}"/> <property name="minIdle" value="${druid.pool.minIdle}"/> <property name="maxActive" value="${druid.pool.maxActive}"/> <property name="maxWait" value="${druid.pool.maxWait}"/> </bean>6.创建SqlSessionFactory<!--MyBatis的SqlSessionFactory对象的创建--> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <!--配置数据源--> <property name="dataSource" ref="dataSource"/> <!--配置Mapper文件路径--> <property name="mapperLocations" value="classpath:mappers/*Mapper.xml"/> <!--配置需要定义别名的实体类的包--> <property name="typeAliasesPackage" value="com.gyh.pojo"/> <!--配置MyBatis的配置文件--> <property name="configLocation" value="classpath:mybatis-config.xml"/> </bean>7. 创建Mapper<!--加载dao包中的所有接口,通过sqlSessionFactory获取sqlSession对象,然后创建所有的dao接口并存储在Spring容器中--> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/> <property name="basePackage" value="com.gyh.dao"/> </bean>8.使用示例8.1创建一个user表并插入几条数据id设置为自动递增8.2 创建一个User类@Data @NoArgsConstructor @AllArgsConstructor public class User { private int userId; private String userName; private String userPwd; }8.3 创建一个接口在该接口中声明一个查询user的方法public interface UserDao { public List<User> selectUser(); }8.4 创建UserMapper.xml文件namespace绑定UserDao接口,编写sql语句时id要与接口中的方法名一致。当namespace绑定接口后,就可以不用写接口实现类,Mybatis就会通过该绑定自动找到对应要执行的SQL语句。<?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.gyh.dao.UserDao"> <resultMap id="userMap" type="User"> <id column="id" property="userId"/> <result column="username" property="userName"/> <result column="password" property="userPwd"/> </resultMap> <select id="selectUser" resultMap="userMap"> select * from user </select> </mapper>8.5 编写测试类@Test public void selectUsersTest(){ ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); //UserDao userDao = context.getBean("userDao", UserDao.class); UserDao userDao = (UserDao) context.getBean("userDao"); List<User> users = userDao.selectUser(); for (User user:users) { System.out.println(user); } }运行结果:
一、AOP概念面向切面编程:它是面向对象编程(OOP)的一种延伸和补充,是基于OOP基础之上新的编程思想,OOP面向的主要对象是类,而AOP面向的主要对象是切面,在处理日志、安全管理、事务管理等方面有非常重要的作用。Spring的关键组件之一是AOP框架。虽然Spring IoC容器不依赖于AOP(意味着如果不想使用AOP,就不需要使用AOP),但是AOP补充了Spring IoC,提供了一个非常强大的中间件解决方案。通俗点说的话就是在程序运行期间, 在不修改原有代码的情况下,增强跟核心业务没有关系的公共功能代码到之前写好的方法中的指定位置,这种编程的方式就叫AOP。二、AOP优点AOP的使用,使开发人员在编写业务逻辑时可以专注于核心业务,而不去过多关注业务逻辑的实现,这样不仅提高了开发效率,还增强了代码的可维护性。当前使用的AOP框架有Spring AOP和AspectJ:Spring AOP是用纯Java实现的。不需要特殊的编译过程。Spring AOP不需要控制类加载器的层次结构,因此适合在servlet容器或应用服务器中使用。AspectJ是一个基于Java语言的AOP框架,AspectJ扩展了Java语言,提供了一个专门的编译器,在编译时提供横向代码的织入。Spring提供了简单而强大的方式来编写定制方面,通过使用基于XML的方法或者@AspectJ注释样式。这两种风格都提供了完全类型化的建议和AspectJ切入点语言的使用,同时仍然使用Spring AOP进行编织。AOP在Spring框架中用于:提供声明式企业服务。最重要的服务是声明式事务管理.让用户实现自定义方面,用AOP补充他们对OOP的使用。三、AOP术语Aspect(切面):指关注点模块化,这个关注点可能会横切多个对象。在具体应用中,切面通常指封装的用于横行插入系统功能的类。在Spring AOP中,切面可以使用通用类基于模式的方式来实现。Join point(连接点):在程序执行过程中某个j阶段点,例如某个方法调用的时间点或者异常的抛出。在Spring AOP中,一个连接点就是指一个方法的调用。Pointcut(切入点): 指切面与程序流程的交叉点,即需要处理的连接点。通常在程序中,切入点指的是类或者方法名。Advice(通知/增强处理): AOP框架在特定的切入点执行的增强处理,即在定义好的切入点处所要执行的代码。即切面类中的方法,他是切面的具体实现。Target Object(目标对象):被一个或者多个切面所通知的对象。也被称作被通知(advised)对象。既然Spring AOP是通过运行时代理实现的,那么这个对象永远是一个被代理(proxied)的对象。Proxy(代理): AOP框架创建的对象。在Spring中,AOP代理可以是JDK动态代理或CGLIB代理。Weaving(织入):把切面连接到其它的应用程序类型或者对象上,并创建一个被被通知的对象的过程。四、AOP的通知类型前置通知(Before advice): 在连接点之前运行但无法阻止执行流程进入连接点的通知。后置返回通知(After returning advice):在连接点正常完成后执行的通知。后置异常通知(After throwing advice): 在方法抛出异常退出时执行的通知。后置通知(总会执行)(After (finally) advice): 当连接点退出的时候执行的通知(无论是正常返回还是异常退出)。环绕通知(Around Advice):环绕连接点的通知,例如方法调用。这是最强大的一种通知类型。在目标方法前后实施增强。
一、静态代理静态代理,代理类和被代理的类实现了同样的接口,只能代理特定的类。定义一个接口public interface Service { public void delete(); public void update(); public void insert(); }被代理类public class StudentServiceImpl implements Service { @Override public void delete() { System.out.println("删除学生信息!!!"); } @Override public void update() { System.out.println("修改学生信息!!!"); } @Override public void insert() { System.out.println("添加学生信息!!!"); } }代理类public class StaticProxy implements Service { private Service service; public StaticProxy(Service service) { this.service = service; } public void delete() { open(); service.delete(); commit(); } public void update() { open(); service.update(); commit(); } public void insert() { open(); service.insert(); commit(); } public void open(){ System.out.println("开启事务!!!"); } public void commit(){ System.out.println("提交事务!!!"); } }4.测试类public class Test { public static void main(String[] args) { //被代理类 StudentServiceImpl student = new StudentServiceImpl(); //被代理类的代理对象 StaticProxy proxy = new StaticProxy(student); //通过代理对象调用被代理类的方法 proxy.delete(); } }运行结果:被代理类只需负责自己特定的业务,而代理类则负责业务的扩展,比如执行被代理类的方法前要开启事务,执行之后要提交事务,我们就不需要给每个方法都添加开启事务和提交事务。代理类就会负责完成这些工作。静态代理的优缺点:优点:被代理类只需负责核心业务业务逻辑的扩展更加方便通用代码放到代理类中,提高了代码的复用性缺点:被代理类太多,就会导致工作量变大,开发效率降低二、动态代理动态代理,由AOP框架动态生成的一的对象,对象可以作为目标对象使用。动态代理有两种方式:JDK动态代理CGLIB代理2.1 JDK动态代理基于接口的动态代理,只能为实现了接口的类动态代理对象创建一个接口并创建一个它的实现类并重写它的方法。创建一个类实现InvocationHandler接口,并重写invoke方法public class JdkDynamicProxy implements InvocationHandler { //被代理的对象 private Object object; public JdkDynamicProxy(Object object) { this.object = object; } //产生代理对象,返回代理对象 public Object getProxy(){ //1.获取被代理对象的类加载器 ClassLoader classLoader = object.getClass().getClassLoader(); //2.获取被代理对象实现的所有接口 Class<?>[] interfaces = object.getClass().getInterfaces(); //3.创建代理对象 //classloader:类加载器来定义代理类 //interfaces:代理类实现的接口列表 //this:调度方法调用的调用处理函数 Object o = Proxy.newProxyInstance(classLoader, interfaces,this); return o; } //处理代理实例,返回结果 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { log(method.getName()); Object invoke = method.invoke(object,args); return invoke; } //定义一个打印日志的方法 public void log(String msg){ System.out.println("执行了"+ msg +"方法!"); } }测试public class DynamicProxyTest { public static void main(String[] args) { //创建被代理类对象 StudentServiceImpl studentService = new StudentServiceImpl(); //创建代理对象,产生的代理对象可以强转成被代理对象实现的接口类型 JdkDynamicProxy jdkDynamicProxy = new JdkDynamicProxy(studentService); Service proxy = (Service) jdkDynamicProxy.getProxy(); //使用代理对象调方法,不会执行调用的方法,而是进入到创建代理对象时指定的invoke方法 //调用的方法作为一个参数,传给invoke方法 proxy.delete(); } }运行结果:2.2 CGLib代理使用JDK动态代理的对象必须是实现了一个或多个接口的,如果要对没有实现接口的类创建代理对象,就要使用CGLIB代理。基于类的动态代理—CGlibCGLib是一个高性能开源的代码生成包,因在Spring的核心包中已包含CGLib所需要的包,所以不再需要添加依赖。创建一个StudentServiceImpl 类,并添加增删改方法。创建一个类,实现MethodInterceptor接口,并重写intercept方法。/** * CGLIB代理 */ public class CGLibDynamicProxy implements MethodInterceptor { private Object object; public CGLibDynamicProxy(Object object) { this.object = object; } //创建并返回代理对象 //该代理对象是通过被代理类的子类来创建的 public Object getProxy(){ Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(object.getClass()); enhancer.setCallback(this); return enhancer.create(); } @Override public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { log(method.getName()); Object invoke = method.invoke(object, args); return invoke; } //定义一个打印日志的方法 public void log(String msg){ System.out.println("执行了"+ msg +"方法!"); } }测试public class DynamicProxyTest { public static void main(String[] args) { //创建被代理类对象 StudentServiceImpl studentService = new StudentServiceImpl(); //创建代理对象,产生的代理对象可以强转成被代理类类型 CGLibDynamicProxy cgLibDynamicProxy = new CGLibDynamicProxy(studentService); StudentServiceImpl proxy = (StudentServiceImpl) cgLibDynamicProxy.getProxy(); //使用代理对象调方法,不会执行调用的方法,而是进入到创建代理对象时指定的intercept方法 //将调用的方法以及方法中的参数传给intercept方法 proxy.insert(); } }运行结果:动态代理的优点:被代理类只需负责核心业务;业务逻辑的扩展更加方便;通用代码放到代理类中,提高了代码的复用性;一个动态代理 , 一般代理某一类业务;一个动态代理可以代理多个类,代理的是接口。
1.创建maven工程2.添加Spring依赖在maven项目的pom.xml文件中添加一下代码<dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.3.18</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>5.3.18</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> <version>5.3.18</version> </dependency>3.创建Spring配置文件在Resources目录下创建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 https://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="..." class="..."> <!-- collaborators and configuration for this bean go here --> </bean> <bean id="..." class="..."> <!-- collaborators and configuration for this bean go here --> </bean> <!-- more bean definitions go here --> </beans>4.创建实体类public class Student { private String id; private String name; private String sex; private int age; public String getId() { return id; } public void setId(String id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "Student{" + "id='" + id + '\'' + ", name='" + name + '\'' + ", sex='" + sex + '\'' + ", age=" + age + '}'; } }5.配置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 https://www.springframework.org/schema/beans/spring-beans.xsd"> <!--将指定类配置给Spring,创建Bean实例--> <bean id="student" class="com.gyh.ioc.pojo.Student"></bean> </beans>6.测试public class Test { public static void main(String[] args) { //初始化Spring容器并加载配置文件 ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); //通过getBean方法获取指定的Bean,获取之后需要进行强制类型转换 Student student = (Student) context.getBean("student"); System.out.println(student); } }运行结果:这里可以看到,我们没有使用new关键字来创建对象,而是通过Spring成功获取了Student的实现类对象,这就是SpringIoC容器的工作机制。通过配置文件给对象的属性赋值<bean id="student" class="com.gyh.ioc.pojo.Student"> <property name="id" value="1001"/> <property name="name" value="Spring"/> <property name="sex" value="男"/> <property name="age" value="18"/> </bean>运行结果因此我们不仅可以通过配置文件创建实现类的对象,还可以给对象的属性赋值。
1.添加日志依赖<dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency>2.添加日志配置文件#声明日志的输出级别及输出方式 log4j.rootLogger=DEBUG,stdout # MyBatis logging configuration... # MyBatis 日志配置 log4j.logger.org.mybatis.example.BlogMapper=TRACE # Console output... #声明日志的输出位置在控制台输出 log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.layout=org.apache.log4j.PatternLayout #定义日志的打印格式 %t 表示线程名称 %5p表示输出日志级别 %n表示换行 log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n
2022年09月
2022年08月
2022年07月