从源码解析-Spring-JDBC-异常抽象

初入学习 JDBC 操作数据库,想必大家都写过下面的代码: 数据库为:H2 如果需要处理特定 SQL 异常,比如 SQL 语句错误,这个时候我们应该怎么办? 查看 SQLException 源码,我们可以发现两个重要的方法。 SQLException.getErrorCode:返回数据库特定的错误码,由数据库厂商制定,不同厂商错误码不同。如重复主键错误码在 MySQL 中是 1062,而在 Oracle 中却是 1。 SQLException.getSQLState:返回 XOPEN 或 SQL:2003 制定的错误码规范。数据库厂商会将不同错误消息映射成同一个错误码 所以我们可以根据 SQLException.getErrorCode 处理相应的数据库异常。 由于数据库厂商错误码不相同,这就导致如果我们更换数据库,上面判断逻辑就必须重写。 下面我们使用 Spring 操作数据库。 Spring 操作数据库 使用 Spring 之后,我们不再需要强制捕获异常。如果 SQL 语句运行存在异常,Spring 会抛出其内置特定的异常。如上面 SQL 语句异常将会抛出 BadSqlGrammarException。除了这个异常之外,Spring 还定义很多数据库异常。 每个 Spring 数据库异常的基类都是 DataAccessException。由于 DataAccessException 继承自 RuntimeException,所以在这类异常无需强制捕获。 在 Spring 中使用 SQLExceptionTranslator 进行异常转换,默认的转换规则会根据 SQLException.getErrorCode 返回的错误码进行相应的转换。 下面我们从源码分析转换过程。 实现细节调试 JdbcTemplate 的源码。 可以看到这里捕获了 SQLException,转换之后再将其抛出。 ...

May 25, 2019 · 1 min · jiezi

spring-aop第6篇spring-aop-实战

基本概念说到spring aop大家都知道是面向切面编程,本文就不在啰嗦的介绍什么是面向切面编程,本文重点是编码过程中如何使用spring aop,首先要理解一下几个概念。 切面(Aspect)切面简单理解就是一个类,在这个类里面定义了通知与切点。 通知(advice)spring aop中的五种通知方式: @Before:前置通知,在目标方法被调用之前调用通知功能@After:后置通知,在目标方法执行结束后,无论执行结果如何都执行通知定义的任务@After-returning:后置通知,在目标方法执行结束后,如果执行成功,则执行通知定义的任务@After-throwing:异常通知,如果目标方法执行过程中抛出异常,则执行通知定义的任务@Around:环绕通知,在目标方法执行前和执行后,都需要执行通知定义的任务。切点(PointCut)切点就是告诉程序要在执行哪些类、方法时执行我们自定义的各种通知。切点如何定义呢?我们通常是使用 Aspectj 的切点表达式语言来定义切点。所以需要了解一下 spring aop 所支持的 Aspectj 切点表达式。

May 25, 2019 · 1 min · jiezi

小说搜索站快速搭建1架构图

技术栈 模板 thymeleaf框架 Spring Boot 2数据库 Mongodb缓存 Redis搜索 聚合源站搜索框架图 仅展示交流使用:免费小说阅读网

May 25, 2019 · 1 min · jiezi

深入理解控制反转IoC和依赖注入DI

容器,字面上理解就是装东西的东西。常见的变量、对象属性等都可以算是容器。一个容器能够装什么,全部取决于你对该容器的定义。当然,有这样一种容器,它存放的不是文本、数值,而是对象、对象的描述(类、接口)或者是提供对象的回调,通过这种容器,我们得以实现许多高级的功能,其中最常提到的,就是 “解耦” 、“依赖注入(DI)”。本文就从这里开始。IoC 容器, laravel 的核心Laravel 的核心就是一个 IoC 容器,根据文档,称其为“服务容器”,顾名思义,该容器提供了整个框架中需要的一系列服务。作为初学者,很多人会在这一个概念上犯难,因此,我打算从一些基础的内容开始讲解,通过理解面向对象开发中依赖的产生和解决方法,来逐渐揭开“依赖注入”的面纱,逐渐理解这一神奇的设计理念。 本文一大半内容都是通过举例来让读者去理解什么是 IoC(控制反转) 和 DI(依赖注入),通过理解这些概念,来更加深入。更多关于 laravel 服务容器的用法建议阅读文档即可。 IoC 容器诞生的故事讲解 IoC 容器有很多的文章,我之前也写过。但现在我打算利用当下的灵感重新来过,那么开始吧。 超人和超能力,依赖的产生!面向对象编程,有以下几样东西无时不刻的接触:接口、类还有对象。这其中,接口是类的原型,一个类必须要遵守其实现的接口;对象则是一个类实例化后的产物,我们称其为一个实例。当然这样说肯定不利于理解,我们就实际的写点中看不中用的代码辅助学习。 怪物横行的世界,总归需要点超级人物来摆平。我们把一个“超人”作为一个类, class Superman {}我们可以想象,一个超人诞生的时候肯定拥有至少一个超能力,这个超能力也可以抽象为一个对象,为这个对象定义一个描述他的类吧。一个超能力肯定有多种属性、(操作)方法,这个尽情的想象,但是目前我们先大致定义一个只有属性的“超能力”,至于能干啥,我们以后再丰富: class Power { /** * 能力值 */ protected $ability; /** * 能力范围或距离 */ protected $range; public function __construct($ability, $range) { $this->ability = $ability; $this->range = $range; }}这时候我们回过头,修改一下之前的“超人”类,让一个“超人”创建的时候被赋予一个超能力: class Superman{ protected $power; public function __construct() { $this->power = new Power(999, 100); }}这样的话,当我们创建一个“超人”实例的时候,同时也创建了一个“超能力”的实例,但是,我们看到了一点,“超人”和“超能力”之间不可避免的产生了一个依赖。 所谓“依赖”,就是 “我若依赖你,我就不能离开你”。在一个贯彻面向对象编程的项目中,这样的依赖随处可见。少量的依赖并不会有太过直观的影响,我们随着这个例子逐渐铺开,让大家慢慢意识到,当依赖达到一个量级时,是怎样一番噩梦般的体验。当然,我也会自然而然的讲述如何解决问题。 ...

May 24, 2019 · 4 min · jiezi

面试官谈谈Spring中都用到了那些设计模式

我自己总结的Java学习的系统知识点以及面试问题,已经开源,目前已经 41k+ Star。会一直完善下去,欢迎建议和指导,同时也欢迎Star: https://github.com/Snailclimb...JDK 中用到了那些设计模式?Spring 中用到了那些设计模式?这两个问题,在面试中比较常见。我在网上搜索了一下关于 Spring 中设计模式的讲解几乎都是千篇一律,而且大部分都年代久远。所以,花了几天时间自己总结了一下,由于我的个人能力有限,文中如有任何错误各位都可以指出。另外,文章篇幅有限,对于设计模式以及一些源码的解读我只是一笔带过,这篇文章的主要目的是回顾一下 Spring 中的设计模式。 Design Patterns(设计模式) 表示面向对象软件开发中最好的计算机编程实践。 Spring 框架中广泛使用了不同类型的设计模式,下面我们来看看到底有哪些设计模式? 控制反转(IoC)和依赖注入(DI)IoC(Inversion of Control,控制翻转) 是Spring 中一个非常非常重要的概念,它不是什么技术,而是一种解耦的设计思想。它的主要目的是借助于“第三方”(Spring 中的 IOC 容器) 实现具有依赖关系的对象之间的解耦(IOC容易管理对象,你只管使用即可),从而降低代码之间的耦合度。IOC 是一个原则,而不是一个模式,以下模式(但不限于)实现了IoC原则。 Spring IOC 容器就像是一个工厂一样,当我们需要创建一个对象的时候,只需要配置好配置文件/注解即可,完全不用考虑对象是如何被创建出来的。 IOC 容器负责创建对象,将对象连接在一起,配置这些对象,并从创建中处理这些对象的整个生命周期,直到它们被完全销毁。 在实际项目中一个 Service 类如果有几百甚至上千个类作为它的底层,我们需要实例化这个 Service,你可能要每次都要搞清这个 Service 所有底层类的构造函数,这可能会把人逼疯。如果利用 IOC 的话,你只需要配置好,然后在需要的地方引用就行了,这大大增加了项目的可维护性且降低了开发难度。关于Spring IOC 的理解,推荐看这一下知乎的一个回答:https://www.zhihu.com/questio... ,非常不错。 控制翻转怎么理解呢? 举个例子:"对象a 依赖了对象 b,当对象 a 需要使用 对象 b的时候必须自己去创建。但是当系统引入了 IOC 容器后, 对象a 和对象 b 之前就失去了直接的联系。这个时候,当对象 a 需要使用 对象 b的时候, 我们可以指定 IOC 容器去创建一个对象b注入到对象 a 中"。 对象 a 获得依赖对象 b 的过程,由主动行为变为了被动行为,控制权翻转,这就是控制反转名字的由来。 ...

May 23, 2019 · 4 min · jiezi

springboot2x集成swagger

集成swaggerpom包配置<dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.9.2</version></dependency><!-- swagger-ui --><dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>${swagger.version}</version></dependency>添加Swagger配置文件@Configuration@EnableSwagger2public class SwaggerConfig { /** * 创建一个Docket对象 * 调用select()方法, * 生成ApiSelectorBuilder对象实例,该对象负责定义外漏的API入口 * 通过使用RequestHandlerSelectors和PathSelectors来提供Predicate,在此我们使用any()方法,将所有API都通过Swagger进行文档管理 * @return */ @Bean public Docket createRestApi() { return new Docket(DocumentationType.SWAGGER_2) .apiInfo(apiInfo()) .select() .apis(RequestHandlerSelectors.any()) .paths(PathSelectors.any()) .build(); } private ApiInfo apiInfo() { return new ApiInfoBuilder() //标题 .title("Spring Boot中使用Swagger2构建RESTful APIs") //简介 .description("") //服务条款 .termsOfServiceUrl("") //作者个人信息 .contact(new Contact("chenguoyu","","chenguoyu_sir@163.com")) //版本 .version("1.0") .build(); }}如果不想将所有的接口都通过swagger管理的话,可以将RequestHandlerSelectors.any()修改为RequestHandlerSelectors.basePackage() 配置静态访问资源@Configurationpublic class WebMvcConfig implements WebMvcConfigurer { @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { // 解决 swagger-ui.html 404报错 registry.addResourceHandler("/swagger-ui.html").addResourceLocations("classpath:/META-INF/resources/"); }}到这里为止swagger就已经配置完了,可以启动项目,然后访问如下链接即可http://localhost:9000/swagger... ...

May 23, 2019 · 1 min · jiezi

关于springboot里面的事务回滚的简单记录

最近自己在写一个小的项目,写的时候才发现自己会的东西太少了,总是遇到各种各样的坑。今天主要记录一下自己在写数据库存储的时候想到要是出现错误,是不是要回滚数据库的操作呀!然后就百度并实践了一下,得出下面的结论: 第一、需要在service方法上添加注解: @Transactional(rollbackFor = Exception.class)第二、如果你没有用try catch去捕获异常的话,那么只需要加上这个注解就可以了,如果你捕获异常了但catch里面只是打印或者返回了异常信息,没有手动抛出RuntimeException异常。那么这个时候你就需要在catch里面添加一个手动回滚的机制了。 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();这样就OK了,当然也可以通过AOP去批量实现这种效果,只是暂时我还没有研究明白,所以就先记录这个最简单的了。后期补上。。。

May 22, 2019 · 1 min · jiezi

springcloud框架的简单搭建消费服务基于feign

上一篇文章主要介绍了如何搭建一个简单的springcloud框架。不过,我们搭建好框架就是为了消费它使用它,那么这篇文章就来看看如何去消费使用我们之前搭建起来的服务吧!首先本文是基于上一篇文章进行的。 一、Feign简介Feign是Netflix开发的声明式、模板化的HTTP客户端, Feign可以帮助我们更快捷、优雅地调用HTTP API。 二、启动程序继续用上一节的工程, 启动eureka-server,端口为8080; 启动eureka-client两次,端口分别为8081 、8082. 三、创建一个feign的服务我们首先要新建一个spring-boot工程,取名为serice-feign。 这次我们要选择三个依赖: 对应的pom.xml文件内容为: <?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.5.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.zhouxiaoxi</groupId> <artifactId>eureka-feign</artifactId> <version>0.0.1-SNAPSHOT</version> <name>eureka-feign</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> <spring-cloud.version>Greenwich.SR1</spring-cloud.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build></project>接下来我们就需要配置application.yml文件了: ...

May 22, 2019 · 1 min · jiezi

Spring-BootSpring-BootHelloWorld

Spring Boot——入门spring boot简化了spring的开发,是J2EE一站式解决方案。 Spring Boot 的优缺点优点快速创建独立运行的服务,与主流框架集成。使用嵌入式Serverlet容器,应用无需达成war包。starters自动依赖与版本控制。大量的自动配置,简化开发,支持自定义配置。无需xml配置,开箱即用。准生产环境的运行时应用监控。云计算的天然集成。缺点入门容易,精通难;因为很多事情是spring boot自动完成的。 微服务martin flow 《Microservices》 martin flow 《微服务》中文翻译 实例讲解Hello Worldmain函数所在的HelloWorld类 /** * @SpringBootApplication 用来告诉程序这是一个 spring boot 应用 */ @SpringBootApplication public class Helloworld { public static void main(String[] args) { // 启动Spring应用 SpringApplication.run(Helloworld.class, args); } }controller类 @Controllerpublic class HelloController { @ResponseBody @RequestMapping("/hello") public String hello(){ return "Hello world"; }}Pom文件<!-- spring boot 所有spring boot starter的父项目 是真正管理spring boot应用中所有依赖版本 是spring boot的版本仲裁中心(决定依赖库的版本) 以后我们导入依赖默认是不需要写版本的(但是那些没有在父项目中管理的依赖自然是需要声明版本号的)--> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.3.RELEASE</version> </parent> <dependencies> <!--开发web应用需要的库--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies> <!--将应用打包为jar包--> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>Spring Boot 主程序分析 /** * @SpringBootApplication 用来告诉程序这是一个 spring boot 应用 */ @SpringBootApplication public class Helloworld { public static void main(String[] args) { // 启动Spring应用 SpringApplication.run(Helloworld.class, args); } }@SpringBootApplication 注解标注在某个类上的时候,说明这个类是SpringBoot的主配置类,SpringBoot就应该运行这个类的main方法来启动SpringBoot应用。该注解为组合注解,由以下注解组成: ...

May 22, 2019 · 2 min · jiezi

springcloud框架的简单搭建服务注册中心

发现很多招聘都需要会springcloud,所以最近在学习springcloud。必须要从零开始用IDEA搭建一下这个springcloud框架,本文是采用Eureka作为服务注册与发现的组件。 先用IDEA创建一个MAVEN项目。 下一步需要填写GroupId和ArtifactId,这里按照自己的习惯去填写就好了。 下一步选择项目位置和项目名称点击完成就可以了。在该MAVEN项目下新建一个模块,这里就需要新建的是springboot了。 这里选择的是spring initializr进行springboot的创建。 还是要对Group和Artifact进行设置。 点击下一步之后,我们需要选择Cloud Discovery下的Eureka Server,点击下一步然后点击完成就可以了。现在我们需要对服务中心eureka-server进行一些相关的配置了。 打开它的pom.xml文件,我们看到是这样的: <?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.5.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.zhouxiaoxi</groupId> <artifactId>eureka-server</artifactId> <version>0.0.1-SNAPSHOT</version> <name>eureka-server</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> <spring-cloud.version>Greenwich.SR1</spring-cloud.version> </properties> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build></project>然后我们需要启动一个服务注册中心,这需要在springboot工程的启动application类上加一个注解@EnableEurekaServer: ...

May 21, 2019 · 2 min · jiezi

Java设计模式综合运用责任链模式进阶

1 责任链模式现存缺点由于责任链大多数都是不纯的情况,本案例中,只要校验失败就直接返回,不继续处理接下去责任链中的其他校验逻辑了,故而出现如果某个部分逻辑是要由多个校验器组成一个整理的校验逻辑的话,则此责任链模式则显现出了它的不足之处了。(责任链模式的具体运用以及原理请参见笔者github wiki 2 责任链模式) 2 改进方式2.1 引入适配器模式关于接口适配器模式原理以及使用场景请参见笔者github wiki 12 适配器模式 。 2.2 引入接口默认方法事例代码请参见工程 design-patterns-business中的 defaultmethod包下的代码。2.2.1 概念java8引入了一个 default medthod 使用 default 关键字Spring 4.2支持加载在默认方法里声明的bean2.2.2 优点用来扩展已有的接口,在对已有接口的使用不产生任何影响的情况下,添加扩展。 比如我们已经投入使用的接口需要拓展一个新的方法,在Java8以前,如果为一个使用的接口增加一个新方法,则我们必须在所有实现类中添加该方法的实现,否则编译会出现异常。如果实现类数量少并且我们有权限修改,可能会工作量相对较少。如果实现类比较多或者我们没有权限修改实现类源代码,这样可能就比较麻烦。而默认方法则解决了这个问题,它提供了一个实现,当没有显示提供其他实现时就采用这个实现,这样新添加的方法将不会破坏现有代码。默认方法的另一个优势是该方法是可选的,子类可以根据不同的需求Override默认实现。 例如,我们定义一个集合接口,其中有增、删、改等操作。如果我们的实现类90%都是以数组保存数据,那么我们可以定义针对这些方法给出默认实现,而对于其他非数组集合或者有其他类似业务,可以选择性复写接口中默认方法。2.2.3 使用原则”类优先” 原则 若一个接口中定义了一个默认方法,而另外一个父类或接口中又定义了一个同名的方法时选择父类中的方法:如果一个父类提供了具体的实现,那么接口中具有相同名称和参数的默认方法会被忽略。接口冲突原则 如果一个父接口提供一个默认方法,而另一个接口也提供了一个具有相同名称和参数列表的方法(不管方法是否是默认方法),那么必须覆盖该方法来解决冲突。4.3 项目演示3.1 逻辑梳理由于系统业务需求的变更,目前有两种业务需求, 一种就是按照原来的文件上传需求,上传过程中需要校验操作,只要校验失败了,就直接返回失败信息,整个文件都不需要做处理了。(参见之前的文章 Java设计模式综合运用(门面+模版方法+责任链+策略))另一种就是校验的逻辑都一样,但是如果校验失败的情况,需要继续往下执行,记录一下失败id即可,即需要把失败的记录也保存到数据库中,比如约束字段不符合约束规则的情况,则把此字段置空,然后继续执行其他校验器逻辑即可。(本节需要讨论的问题)3.2 实现方案根据第2节的改进方式可以知道,我们有两种方式改进以上逻辑。 3.2.1 采用适配器模式代码参见2.1版本的,地址为:https://github.com/landy8530/...若采用适配器模式,此处我们会采用接口适配器模式。 接口需要多增加一个不用链式调用的校验方法,定义如下, /** * 业务校验统一接口,增加了接口的默认方法实现,这样可以更加方便且自由选择实现接口的哪些方法。 * @author landyl * @create 10:32 AM 05/09/2018 * @version 2.0 * @since 1.0 */public interface Validator<R extends RequestDetail,F extends RequestFile> { /** * 需要引入责任链的时候,则采用此方法 * @param detail * @param chain * @return * @throws BusinessValidationException */ String doValidate(R detail, F file, ValidatorChain chain) throws BusinessValidationException; /** * 不需要责任链的时候,则可以直接调用此方法的实现即可 * @param detail * @return * @throws BusinessValidationException */ boolean doValidate(R detail, F file) throws BusinessValidationException;}适配器类定义如下抽象类, ...

May 21, 2019 · 2 min · jiezi

Java-设计模式综合运用门面模版方法责任链策略工厂方法

在上一篇文章Java设计模式综合运用(门面+模版方法+责任链+策略)中,笔者写了一篇门面模式、模版方法、责任链跟策略模式的综合运用的事例文章,但是后来笔者发现,在实现策略模式的实现上,发现了一个弊端:那就是如果在后续业务发展中,需要再次增加一个业务策略的时候,则需要再次继承AbstractValidatorHandler类(详情请参见上篇文章),这样就会造成一定的类膨胀。今天我利用注解的方式改造成动态策略模式,这样就只需要关注自己的业务类即可,无需再实现一个类似的Handler类。本文也同步发布至简书,地址:https://www.jianshu.com/p/b86...1. 项目背景1.1 项目简介在公司的一个业务系统中,有这样的一个需求,就是根据不同的业务流程,可以根据不同的组合主键策略进行动态的数据业务查询操作。在本文中,我假设有这样两种业务,客户信息查询和订单信息查询,对应以下枚举类: /** * 业务流程枚举 * @author landyl * @create 11:18 AM 05/07/2018 */public enum WorkflowEnum { ORDER(2), CUSTOMER(3), ; ....}每种业务类型都有自己的组合主键查询规则,并且有自己的查询优先级,比如客户信息查询有以下策略: customerIdrequestIdbirthDate+firstName以上仅是假设性操作,实际业务规则比这复杂的多 1.2 流程梳理主要业务流程,可以参照以下简单的业务流程图。 1.2.1 查询抽象模型 1.2.2 组合主键查询策略 1.2.3 组合主键查询责任链 2. Java注解简介注解的语法比较简单,除了@符号的使用之外,它基本与Java固有语法一致。 2.1 元注解JDK1.5提供了4种标准元注解,专门负责新注解的创建。 注解说明@Target表示该注解可以用于什么地方,可能的ElementType参数有:<br/>CONSTRUCTOR:构造器的声明<br/>FIELD:域声明(包括enum实例)<br/>LOCAL_VARIABLE:局部变量声明<br/>METHOD:方法声明<br/>ACKAGE:包声明<br/>PARAMETER:参数声明<br/>TYPE:类、接口(包括注解类型)或enum声明@Retention表示需要在什么级别保存该注解信息。可选的RetentionPolicy参数包括:<br/>SOURCE:注解将被编译器丢弃<br/>CLASS:注解在class文件中可用,但会被VM丢弃<br/>RUNTIME:JVM将在运行期间保留注解,因此可以通过反射机制读取注解的信息。@Document将注解包含在Javadoc中@Inherited允许子类继承父类中的注解2.2 自定义注解定义一个注解的方式相当简单,如下代码所示: @Target({ElementType.METHOD,ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Inherited@Documented//使用@interface关键字定义注解public @interface Description { /* * 注解方法的定义(其实在注解中也可以看做成员变量)有如下的规定: * 1.不能有参数和抛出异常 * 2.方法返回类型只能为八种基本数据类型和字符串,枚举和注解以及这些类型构成的数组 * 3.可以包含默认值,通过default实现 * 4.如果只有一个方法(成员变量),最好命名为value */ String value(); int count() default 1; //默认值为1}注解的可用的类型包括以下几种:所有基本类型、String、Class、enum、Annotation、以上类型的数组形式。元素不能有不确定的值,即要么有默认值,要么在使用注解的时候提供元素的值。而且元素不能使用null作为默认值。注解在只有一个元素且该元素的名称是value的情况下,在使用注解的时候可以省略“value=”,直接写需要的值即可。 2.3 使用注解如上所示的注解使用如下: /** * @author landyl * @create 2018-01-12:39 PM *///在类上使用定义的Description注解@Description(value="class annotation",count=2)public class Person { private String name; private int age; //在方法上使用定义的Description注解 @Description(value="method annotation",count=3) public String speak() { return "speaking..."; }}使用注解最主要的部分在于对注解的处理,那么就会涉及到注解处理器。从原理上讲,注解处理器就是通过反射机制获取被检查方法上的注解信息,然后根据注解元素的值进行特定的处理。 ...

May 18, 2019 · 2 min · jiezi

Java设计模式综合运用动态代理Spring-AOP

本文也同步发布至简书,地址:https://www.jianshu.com/p/f70...AOP设计模式通常运用在日志,校验等业务场景,本文将简单介绍基于Spring的AOP代理模式的运用。 1. 代理模式1.1 概念代理(Proxy)是一种提供了对目标对象另外的访问方式,即通过代理对象访问目标对象。这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能。这里使用到编程中的一个思想:不要随意去修改别人已经写好的代码或者方法,如果需改修改,可以通过代理的方式来扩展该方法。 1.2 静态代理静态代理在使用时,需要定义接口或者父类,被代理对象与代理对象一起实现相同的接口或者是继承相同父类。 1.3 动态代理1.3.1 JDK代理JDK动态代理有以下特点:1.代理对象,不需要实现接口2.代理对象的生成,是利用JDK的API,动态的在内存中构建代理对象(需要我们指定创建代理对象/目标对象实现的接口的类型)3.动态代理也叫做:JDK代理,接口代理 1.3.2 CGLib代理Cglib代理,也叫作子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能的扩展。 JDK的动态代理有一个限制,就是使用动态代理的对象必须实现一个或多个接口,如果想代理没有实现接口的类,就可以使用Cglib实现。Cglib是一个强大的高性能的代码生成包,它可以在运行期扩展java类与实现java接口。它广泛的被许多AOP的框架使用,例如Spring AOP和synaop,为他们提供方法的interception(拦截)。Cglib包的底层是通过使用一个小而块的字节码处理框架ASM来转换字节码并生成新的类。不鼓励直接使用ASM,因为它要求你必须对JVM内部结构包括class文件的格式和指令集都很熟悉。2. Spring AOP2.1 Spring AOP原理AOP实现的关键在于AOP框架自动创建的AOP代理,AOP代理主要分为静态代理和动态代理,静态代理的代表为AspectJ;而动态代理则以Spring AOP为代表。本文以Spring AOP的实现进行分析和介绍。 Spring AOP使用的动态代理,所谓的动态代理就是说AOP框架不会去修改字节码,而是在内存中临时为方法生成一个AOP对象,这个AOP对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法。 Spring AOP中的动态代理主要有两种方式,JDK动态代理和CGLIB动态代理。JDK动态代理通过反射来接收被代理的类,并且要求被代理的类必须实现一个接口。JDK动态代理的核心是InvocationHandler接口和Proxy类。 如果目标类没有实现接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态的生成某个类的子类,注意,CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。 注意:以上片段引用自文章Spring AOP的实现原理,如有冒犯,请联系笔者删除之,谢谢!Spring AOP判断是JDK代理还是CGLib代理的源码如下(来自org.springframework.aop.framework.DefaultAopProxyFactory): @Overridepublic AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException { if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) { Class<?> targetClass = config.getTargetClass(); if (targetClass == null) { throw new AopConfigException("TargetSource cannot determine target class: " + "Either an interface or a target is required for proxy creation."); } if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) { return new JdkDynamicAopProxy(config); } return new ObjenesisCglibAopProxy(config); } else { return new JdkDynamicAopProxy(config); }}由代码发现,如果配置proxyTargetClass = true了并且目标类非接口的情况,则会使用CGLib代理,否则使用JDK代理。 ...

May 18, 2019 · 3 min · jiezi

项目笔记1通过在线制图工具绘制阿里云部署图

title: 通过在线制图工具绘制阿里云部署图最近做一个项目是关于采集指纹的系统,先给大家简单介绍一下项目的主要功能: 该项目主要是做一个采集婴幼儿的手掌指纹和掌纹的客户端,并且通过服务端接口保存手掌指纹到阿里云oss存储中。同时后台提供管理功能,对采集人员,系统角色权限管理,同时提供婴幼儿的手指指纹图片的查看和分析功能。 系统分为三个子系统: 指纹采集客户端程序(client)指纹采集接口应用服务(通过springboot 框架开发的Restful Api方式 (client restful api)指纹采集后台管理应用前端页面 (Ant Design Vue Admin )指纹采集后台应用接口服务器 (通过Spring Boot框架开发,用到Redis, JWT,mybatis等技术)由于客户要求使用阿里云服务作为应用部署的基本设施,按照客户的要求绘制阿里云系统部署架构图。 最后讨论后的部署建议方案: 为了保证数据传输的安全建议采用https SSL证书方式。用户请求通过负载均衡SLB分发到阿里云的ESC上。后端web服务器采用多台ECS(至少2台)负载用户请求。数据库采用阿里云的Mysql版本,通过设置好数据库的备份和安全OSS存在被采集人指纹信息。使用redis作为数据缓存中心。短信接口建议采用阿里云平台短信网关服务。在此使用了一个网上在线制图网站Freedgo Design 其访问地址为: https://www.freedgo.com. freedgo Design 是一个多种类型图表的在线绘制软件,让您创建 阿里云架构图 腾讯云架构图 Oracle云架构图 AWS系统部署图 软件架构图, UML,BPMN,ERD,流程图,UX设计图,ANT DESIGN,思维导图,图表。可以做到注册用户免费使用。 具体绘制步骤如下:打开https://www.freedgo.com,先点...,Freedgo Design提供邮箱、微信、QQ、微博等多种注册方式。注册成功后,点击 开始制作 按钮,然后就进入制图工具页面进行绘制。选择菜单文件-> 从类型中新建 -> 云架构 -> 阿里云左侧图标库中选择所需要的web服务器,数据库,redis等等图标安装业务逻辑绘制在线架构图。最终的绘制效果如下图:

May 16, 2019 · 1 min · jiezi

3分钟干货之对spring进行定制化功能扩展

可以选择如下一些扩展点: ▌1.BeanFactoryPostProcessor是beanFactory后置处理器,支持在bean factory标准初始化完成后,对bean factory进行一些额外处理。在讲context初始化流程时介绍过,这时所有的bean的描述信息已经加载完毕,但是还没有进行bean初始化。例如前面提到的PropertyPlaceholderConfigurer,就是在这个扩展点上对bean属性中的占位符进行替换。 ▌2.BeanDefinitionRegistryPostProcessor 它扩展自BeanFactoryPostProcessor,在执行BeanFactoryPostProcessor的功能前,提供了可以添加bean definition的能力,允许在初始化一般bean前,注册额外的bean。例如可以在这里根据bean的scope创建一个新的代理bean。 ▌3.BeanPostProcessor 提供了在bean初始化之前和之后插入自定义逻辑的能力。与BeanFactoryPostProcessor的区别是处理的对象不同,BeanFactoryPostProcessor是对beanfactory进行处理,BeanPostProcessor是对bean进行处理。 注:上面这三个扩展点,可以通过实现Ordered和PriorityOrdered接口来指定执行顺序。实现PriorityOrdered接口的processor会先于实现Ordered接口的执行。 ▌4.ApplicationContextAware 可以获得ApplicationContext及其中的bean,当需要在代码中动态获取bean时,可以通过实现这个接口来实现。 ▌5.InitializingBean 可以在bean初始化完成,所有属性设置完成后执行特定逻辑,例如对自动装配对属性进行验证等等。 ▌6.DisposableBean 用于在bean被销毁前执行特定的逻辑,例如做一些回收工作等。 ▌7.ApplicationListener 用来监听spring的标准应用事件或者自定义事件。

May 15, 2019 · 1 min · jiezi

从零入门系列4Sprint-Boot-之-WEB接口设计实现

文章系列【从零入门系列-0】Sprint Boot 之 Hello World【从零入门系列-1】Sprint Boot 之 程序结构设计说明【从零入门系列-2】Sprint Boot 之 数据库实体类【从零入门系列-3】Sprint Boot 之 数据库操作类)前言前一章简述了已经实现了对数据库的增删改查以及复杂查询的功能,这一步将对相应的功能方法封装成WEB接口,对外提供WEB接口服务。 控制层类设计及测试控制层的角色是负责对访问路由到处理过程的关联映射和封装,在这里我们队Book新建一个控制类即可,在文件夹Controller上右键,New->Java Class新建BookController类。作为控制类,该类需要使用@Controller注解使之能够被识别为控制类对象,在这里我们使用@RestController,该注解包含了@Controller,相当于@Controller+@ResponseBody两个注解的结合,适合返回Json格式的控制器使用。 类定义@RestController@RequestMapping(path = "/library")public class BookController { @Autowired private BookJpaRepository bookJpaRepository; @Autowired private BookService bookService;}考虑到数据库的操作需要用到BookJpaRepository和BookService,这里首先声明这两个属性,并使用@Autowired注解自动装配。 在类上使用@RequestMapping(path = "/library")注解后,定义了该类的路径都是/library开始,可以统一接口路径,避免重复书写。 新增接口/*** 新增书籍* @param name* @param author* @param image* @return*/@PostMapping("/save")public Map<String, Object> save(@RequestParam String name, @RequestParam String author, @RequestParam String image){ Book book = new Book(); Map<String, Object> rsp = new HashMap<>(); book.setName(name); book.setAuthor(author); book.setImage(image); bookJpaRepository.save(book); rsp.put("data", book); rsp.put("code", "0"); rsp.put("info", "成功"); return rsp;}使用@PostMapping表示接口只接受POST请求,WEB接口路径为/library/save,该接口返回的是一个Map类型对象,但是由于类使用@RestController注解后,使得返回结果会自动转换成Json字符串格式。 ...

May 15, 2019 · 4 min · jiezi

19051501记录一次日常犯的错

Parameter 'array' not found. Available parameters are [collection, list]莫名其妙,今天写代码遇到个低级错误,困扰了好久,测试突然给提了个缺陷,说业务逻辑有问题于是,就启动了缺陷排查的流程1.问题复现   根据问题复现步骤,确实发现业务逻辑不对2.代码排查   根据代码排查,业务逻辑确实写了,对表的更新3.日志排查   根据日志排查,发现新增的代码并没有执行,而且,也没有报错。随后就进行了纠结(现在都想敲死自己,应该不用纠结,在编辑器debug跑一遍,问题就暴露出来了)。4.解决问题   先使用单测,跑了一遍对应的方法,发现确实没有问题,所以怀疑,是因为MOCK掉的DAO方法,抛了一个异常,然后没有显式的抛出来,所以就手动debug启动了下应用,就是POSTMAN测试,果然,报错如下: nested exception is org.apache.ibatis.binding.BindingException: Parameter 'array' not found. Available parameters are [collection, list]这里是因为,在mybaits传集合参数,进行循环时,一定要指定集合类型,目前mybaits对List集合和Array集合,是不同,需要在循环时指定对应的集合,如果使用类似于Long[] 等进行传参时,一定要指定collection="array",如果使用List进行传参时,需要指定collection="list",否则就会抛异常。至于为什么在服务器上没有抛异常出来,很可能是被框架给吃掉了,需要进一步排查。

May 15, 2019 · 1 min · jiezi

SpringBoot系列教程应用篇之借助Redis搭建一个简单站点统计服务

判断一个网站值不值钱的一个重要标准就是看pv/uv,那么你知道pv,uv是怎么统计的么?当然现在有第三方做的比较完善的可以直接使用,但如果让我们自己来实现这么一个功能,应该怎么做呢? 本篇内容较长,源码如右 ➡️ https://github.com/liuyueyi/spring-boot-demo/tree/master/spring-case/124-redis-sitecount <!-- more --> I. 背景及需求为了看看我的博客是不是我一个人的单机游戏,所以就想着统计一下总的访问量,每日的访问人数,哪些博文又是大家感兴趣的,点击得多的; 因此就萌发了自己撸一个pv/uv统计的服务,当然我这个也不需要特别完善高大上,能满足我自己的基本需要就可以了 希望统计站点(域名)总访问次数希望统计站点总的访问人数,当前访问者在访问人数中的排名(即这个ip是所有访问ip中的第多少位访问的这个站点)每个子页面都有访问次数,访问总人数,当前ip访问的排名统计同一个ip,同一天内访问同一个子页面,pv次数只加1次;隔天之后,再次访问pv+1II. 方案设计前面的背景和需求,可以说大致说明了我们要做个什么东西,以及需要注意哪些事项,再进行方案设计的过程中,则需要对需求进行详细拆解 1. 术语说明前面提到了pv,uv,在我们的实际实现中,会发现这个服务中对于pv,uv的定义和标准定义并不是完全一致的,下面进行说明 a. pvpage viste, 每个页面的访问次数,在本服务中,我们的pv指的是总量,即从开始接入时,到现在总的访问次数 但是这里有个限制: 一个合法的ip,一天之内pv统计次数只能+1次 根据ip进行区分,因此需要获取访问者ip同一天内,这个ip访问相同的URI,只能算一次有效pv;第二天之后,再次访问,则可以再算一次有效pvb. hot前面的pv针对ip进行了限制,一个ip同一天的访问,只能计算一次,大部分情况下这种统计并没有什么问题,但是如果一个文章写得特别有参考意义,导致有人重复的看,仔细的看,换着花样的刷新看,这个时候统计下总的访问次数是不是也挺好的 因此在这个服务中,引入了hot(热度)的概念,对于一个uri而言,只要一次点击,hot+1 c. uvunique visitor, 这个就是统计URI的访问ip数 2. 流程图通过前面三个术语的定义,我们的操作流程就相对清晰了,我们的服务接收一个IP和URI,然后操作对应的pv,uv,hot并返回 首先判断这个ip是否为第一次访问这个URI是,则pv+1, uv+1, hot+1否,表示之前访问过,uv就不能变了 判断是否今天第一次访问是,今天访问过,那么pv不变,hot+1否,之前访问过,今天没有,pv可以+1, hot+1对应的流程图如下 3. 数据结构流程清晰之后,接下来就需要看下pv,uv,hot三个数据怎么存了 a. pvpv保存的就是访问次数,与ip无关,所以kv存储就可以满足我们的需求了,这里的key为uri,value则保存pv的值 b. hothot和pv类似,同样用kv可以满足要求 c. uvuv这里有两个数据,一个是uv总数,要给是这个ip的访问排名,redis中有个zset数据结构正好就可以做这个 zset数据结构中,我们定义value为ip,score为ip的排名,那么uv就是最大的score了 d. 结构图 4. 方案设计流程清晰,结构设计出来之后,就可以进入具体的方案设计环节了,在这个环节中,我们引入一个app的维度,这样我们的服务就可以通用了; 每个使用者都申请一个app,那么这个使用者的请求的所有站点统计数据,都关联到这个app上,这样也有利于后续统计了 a. 接口API引入了app之后,结合前面的两个参数ip + URI,我们的请求参数就清晰了 @Datapublic class VisitReqDTO { /** * 应用区分 */ private String app; /** * 访问者ip */ private String ip; /** * 访问的URI */ private String uri;}然后我们返回的数据,pv + uv + rank + hot,所以返回的基础VO如下 ...

May 14, 2019 · 7 min · jiezi

SpringBoot整合MybatisPlus的简单教程简单整合

最近在研究springboot,顺便就会看看数据库连接这一块的知识 ,所以当我发现有通用Mapper和MybatisPlus这两款网络上比较火的简化mybatis开发的优秀软件之后。就都想试一下,看看哪一款比较适合自己。先创建一个springboot的项目,可以参考我之前的文章Spring Boot 的简单教程(一) Spring Boot 项目的创建。创建好springboot之后就需要整合mybatis和mybatis-plus了。 打开pom.xml文件,将最新的mybatis相关的包都引用进来。 <!-- 这是mysql的依赖 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <!-- 这是lombok的依赖 --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <!-- 这是mybatis-plus依赖 --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.1.1</version> </dependency> <!-- 这是mybatis-plus的代码自动生成器 --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-generator</artifactId> <version>3.1.1</version> </dependency> <!-- 这是模板引擎依赖 --> <dependency> <groupId>org.freemarker</groupId> <artifactId>freemarker</artifactId> <version>2.3.28</version> </dependency>需要对application.yml进行相关的配置。 #端口号 server: port: 8088 #数据库的配置信息 spring: datasource: url: jdbc:mysql://localhost:3306/*** #自己的数据库名称 username: root password: 123456 mybatis: #开启驼峰命名法 configuration: map-underscore-to-camel-case: true mybatis-plus: # xml地址 mapper-locations: classpath:mapper/*Mapper.xml # 实体扫描,多个package用逗号或者分号分隔 type-aliases-package: *** #自己的实体类地址 configuration: # 这个配置会将执行的sql打印出来,在开发或测试的时候可以用 log-impl: org.apache.ibatis.logging.stdout.StdOutImpl自动生成模块的方法,在相应的位置上添加上自己的一些包名就可以运行生成相应的Entity、Mapper、Mapper XML、Service、Controller 等各个模块的代码。public class CodeGenerator { /** * <p> * 读取控制台内容 * </p> */ public static String scanner(String tip) { Scanner scanner = new Scanner(System.in); StringBuilder help = new StringBuilder(); help.append("请输入" + tip + ":"); System.out.println(help.toString()); if (scanner.hasNext()) { String ipt = scanner.next(); if (StringUtils.isNotEmpty(ipt)) { return ipt; } } throw new MybatisPlusException("请输入正确的" + tip + "!"); } public static void main(String[] args) { // 代码生成器 AutoGenerator mpg = new AutoGenerator(); // 全局配置 GlobalConfig gc = new GlobalConfig(); String projectPath = System.getProperty("user.dir"); gc.setOutputDir(projectPath + "/src/main/java"); gc.setAuthor("jobob"); gc.setOpen(false); // gc.setSwagger2(true); 实体属性 Swagger2 注解 mpg.setGlobalConfig(gc); // 数据源配置 DataSourceConfig dsc = new DataSourceConfig(); dsc.setUrl("jdbc:mysql://localhost:3306/***?useUnicode=true&useSSL=false&characterEncoding=utf8"); // dsc.setSchemaName("public"); dsc.setDriverName("com.mysql.cj.jdbc.Driver"); dsc.setUsername("root"); dsc.setPassword("***"); mpg.setDataSource(dsc); // 包配置 PackageConfig pc = new PackageConfig(); //这里有个模块名的配置,可以注释掉不用。// pc.setModuleName(scanner("模块名")); pc.setParent("com.zhouxiaoxi.www"); mpg.setPackageInfo(pc); // 自定义配置 InjectionConfig cfg = new InjectionConfig() { @Override public void initMap() { // to do nothing } }; // 如果模板引擎是 freemarker String templatePath = "/templates/mapper.xml.ftl"; // 如果模板引擎是 velocity// String templatePath = "/templates/mapper.xml.vm"; // 自定义输出配置 List<FileOutConfig> focList = new ArrayList<>(); // 自定义配置会被优先输出 focList.add(new FileOutConfig(templatePath) { @Override public String outputFile(TableInfo tableInfo) { // 自定义输出文件名 , 如果你 Entity 设置了前后缀、此处注意 xml 的名称会跟着发生变化!! return projectPath + "/src/main/resources/mapper/"// + + pc.getModuleName() + 如果放开上面的模块名,这里就有一个模块名了 + "/" + tableInfo.getEntityName() + "Mapper" + StringPool.DOT_XML; } }); /* cfg.setFileCreate(new IFileCreate() { @Override public boolean isCreate(ConfigBuilder configBuilder, FileType fileType, String filePath) { // 判断自定义文件夹是否需要创建 checkDir("调用默认方法创建的目录"); return false; } }); */ cfg.setFileOutConfigList(focList); mpg.setCfg(cfg); // 配置模板 TemplateConfig templateConfig = new TemplateConfig(); // 配置自定义输出模板 //指定自定义模板路径,注意不要带上.ftl/.vm, 会根据使用的模板引擎自动识别 // templateConfig.setEntity("templates/entity2.java"); // templateConfig.setService(); // templateConfig.setController(); templateConfig.setXml(null); mpg.setTemplate(templateConfig); // 策略配置 StrategyConfig strategy = new StrategyConfig(); //数据库表映射到实体的明明策略 strategy.setNaming(NamingStrategy.underline_to_camel); //数据库表字段映射到实体的命名策略, 未指定按照 naming 执行 strategy.setColumnNaming(NamingStrategy.underline_to_camel); //自定义继承的Entity类全称,带包名// strategy.setSuperEntityClass("***"); strategy.setEntityLombokModel(true); strategy.setRestControllerStyle(true); //自定义继承的Controller类全称,带包名// strategy.setSuperControllerClass("***"); strategy.setInclude(scanner("表名,多个英文逗号分割").split(",")); //自定义基础的Entity类,公共字段(可添加更多)// strategy.setSuperEntityColumns("id"); //驼峰转连字符 strategy.setControllerMappingHyphenStyle(true); //表前缀// strategy.setTablePrefix(pc.getModuleName() + "_"); mpg.setStrategy(strategy); mpg.setTemplateEngine(new FreemarkerTemplateEngine()); mpg.execute(); }}在生成的controller里面添加对应的方法启动就可以正常进行访问了。 ...

May 14, 2019 · 2 min · jiezi

从零入门系列2Sprint-Boot-之-数据库实体类

文章系列【从零入门系列-0】Sprint Boot 之 Hello World【从零入门系列-1】Sprint Boot 之 程序结构设计说明前言本篇文章开始代码实践,系统设计从底向上展开,因此本篇先介绍如何实现数据库表实体类的设计实现。 SpringBoot数据库的持久层框架主要分为两种架构模式,即以JDBC Template为代表的SQL类和以Spring Data JPA为代表的ORM对象类。其中: Spring Data JPA 是 Spring 基于 ORM 框架、JPA 规范的基础上封装的一套JPA应用框架,可使开发者用极简的代码即可实现对数据的访问和操作。它提供了包括增删改查等在内的常用功能,且易于扩展!学习并使用 Spring Data JPA 可以极大提高开发效率!spring data jpa让我们解脱了DAO层的操作,基本上所有CRUD都可以依赖于它来实现,自己写个仓储接口后继承JpaRepository即可实现最基本的增删改查功能!在使用@Entity进行实体类的持久化操作,当JPA检测到我们的实体类当中有@Entity 注解的时候,会在数据库中生成关联映射对应的表结构信息,因此针对本项目情况最底层的设计实现一个@Entity注解的书籍对象定义即可。 项目开始前,先按上一篇文章【从零入门系列-1】Sprint Boot 之 程序结构设计说明后台程序结构建立相对应的目录: 控制层:前端路由和后端处理关系处理,目录:Controller数据服务层:自定义对数据库的访问操作方法,目录:Service数据访问层:实现通用的数据库访问功能,SpringData的JPA方案,目录:Dao数据实体层:定义数据库表的属性方法,目录:Domain根据结构,我们需要在Domain目录下编写项目表实体类,右键Domain文件夹,New->Java Class。 编写实体类1.新建Book类 package com.arbboter.demolibrary.Domain; import javax.persistence.Entity; import javax.persistence.Table; /** * @Entity 注解该类为数据库表实体类,JPA可自动扫描识别到 * @Table 注解数据表信息,其中name指定表名 */ @Entity @Table(name = "library_book") public class Book{ }注意添加的类需要使用@Entity注解,其中@Table是可选的,默认不配置生成的表名和类名相同,如果上述类中不使用@Table,那么本类对应的表名为book。 2.添加表字段信息 public class Book{ /** * ID,唯一主键,按Alt+Enter可以快速导入引入 */ @Id @GeneratedValue private Integer id; /** * 书名 */ private String name; /** * 作者 */ private String author; /** * 封面 */ private String image; }@Id注解用于声明一个实体类的属性映射为数据库的主键列,该属性通常置于属性声明语句之前,可与声明语句同行,也可写在单独行上。 ...

May 14, 2019 · 2 min · jiezi

SpringSpring相关面试题总结

Spring是什么 Spring是一个一站式轻量级的开源框架Spring Bean的三种配置方式:xml、注解和Java @Configurationpublic class BeanConfig { @Bean public BeanFactory beanFactory(){ return new BeanFactoryImpl(); }}Spring的核心: 控制反转(IoC)和面向切面(AOP)Spring中AOP动态代理的两种实现方式: JDK动态代理和CGLIB动态代理Spring的优点: Spring将对象之间的依赖关系交由框架处理,减低组件的耦合性;Spring对于主流的应用框架提供了非常方便的集成支持;Spring提供的AOP功能,方便进行面向切面的编程,许多不容易用传统OOP实现的功能可以通过AOP轻松应付;Spring框架设计精妙,Spring源码是经典的学习范例Spring的七大组成模块: Spring Core:核心类库,提供IOC服务;Spring Context:提供框架式的Bean访问方式,以及企业级功能(JNDI、定时任务等);Spring AOP:AOP服务;Spring DAO:对JDBC的抽象,简化了数据访问异常的处理;Spring ORM:对现有的ORM框架的支持;Spring Web:提供了基本的面向Web的综合特性,例如多方文件上传;Spring MVC:提供面向Web应用的Model-View-Controller实现Spring事务的实现方式和实现原理: Spring事务的本质其实就是数据库对事务的支持没有数据库的事务支持,spring是无法提供事务功能的。真正的数据库层的事务提交和回滚是通过binlog或者redo log实现的。

May 14, 2019 · 1 min · jiezi

DOClever安装以及使用介绍

DOClever被赞为目前最好用的接口管理平台,强大之处在哪?试他一试。一、什么是DOClever?官网地址:http://doclever.cn/controller...DOClever与目前postman、swagger不同之处在于,不仅仅能满足接口文档开发、测试、数据mock等,还更轻量级,也对postman、swagger、RAP支持导入。注意:以下来自官网拷贝!(^▽^) DOClever是一个可视化免费开源的接口管理工具 ,可以分析接口结构,校验接口正确性, 围绕接口定义文档,通过一系列自动化工具提升我们的协作效率。DOClever前后端全部采用了javascript来作为我们的开发语言,前端用的是vue+element UI,后端是express+mongodb,这样的框架集成了高并发,迭代快的特点,保证系统的稳定可靠。主要特性:• 可以对接口信息进行编辑管理,支持 get,post,put,delete,patch 五种方法,支持 https 和 https 协议,并且支持 query,body,json,raw,rest,formdata 的参数可视化编辑。同时对 json 可以进行无限层次可视化编辑。并且,状态码,代码注入,markdown 文档等附加功能应有尽有。• 接口调试运行,可以对参数进行加密,从 md5 到 aes 一应俱全,返回参数与模型实时分析对比,给出不一致的地方,找出接口可能出现的问题。如果你不想手写文档,那么试试接口的数据生成功能,可以对接口运行的数据一键生成文档信息。• mock 的无缝整合,DOClever 自己就是一个 mock 服务器,当你把接口的开发状态设置成已完成,本地 mock 便会自动请求真实接口数据,否则返回事先定义好的 mock 数据。• 支持 postman,rap,swagger 的导入,方便你做无缝迁移,同时也支持 html 文件的导出,方便你离线浏览!• 项目版本和接口快照功能并行,你可以为一个项目定义 1.0,1.1,1.2 版本,并且可以自由的在不同版本间切换回滚,再也不怕接口信息的遗失,同时接口也有快照功能,当你接口开发到一半或者接口需求变更的时候,可以随时查看之前编辑的接口信息。• 自动化测试功能,目前市面上类似平台的接口自动化测试大部分都是伪自动化,对于一个复杂的场景,比如获取验证码,登陆,获取订单列表,获取某个特定订单详情这样一个上下文关联的一系列操作无能为力。而 DOClever 独创的自动化测试功能,只需要你编写极少量的 javascript 代码便可以在网页里完成这样一系列操作,同时,DOClever 还提供了后台定时批量执行测试用例并把结果发送到团队成员邮箱的功能,你可以及时获取接口的运行状态。• 团队协作功能,很多类似的平台这样的功能是收费的,但是 DOClever 觉得好东西需要共享出来,你可以新建一个团队,并且把团队内的成员都拉进来,给他们分组,给他们分配相关的项目以及权限,发布团队公告等等。二、DOClever环境依赖以及使用DOClever的使用,依赖nodejs和MongoDB,注意,这里的安装都是在windows系统上!(^▽^)1、安装nodejs去官网下载nodejs:https://nodejs.org/en/download/ 选择windows版本64位下载,下载完成后双击msi文件安装 至此,安装完成!win+r 输入cmd 表示安装成功!!(^▽^)PS:如果想配置环境变量等,可以参考此文:https://www.cnblogs.com/liuqi... 2、安装MongoDB去官网下载MongoDB:https://www.mongodb.com/downl... 选择windows版本64位下载,下载完成后双击msi文件安装 选择自定义路径 ...

May 12, 2019 · 1 min · jiezi

springboot整合quarzt实现动态定时任务

实现定时任务的几种方式:1.使用linux的crontab 优点: 1.使用方式很简单,只要在crontab中写好 2.随时可以修改,不需要重启服务器 缺点: 1.分布式的系统中不好使用,只能一台台机器去修改 2.分是最小的时间单位,秒级的不能使用2.使用spring自带的ScheduledExecutor 优点: cronExpression比crontab的更强大一些支持到秒,性能更好 缺点: 修改了cronExpression的重启服务器,否则不生效3. 使用JDK自带的Timer优点: 轻量级,执行速度快缺点:分布式系统不好使用.而且不能指定时间执行,只能按某个频次来执行4.使用quarzt优点:1.可适用于分布式系统,quartz可支持集群模式2.修改了定时任务无须重启服务器(这只是我个人想到的一些优缺点,网友有其他看法可以留言说下)你好! 这是你第一次使用 Markdown编辑器 所展示的欢迎页。如果你想学习如何使用Markdown编辑器, 可以仔细阅读这篇文章,了解一下Markdown的基本语法知识。 整合步骤:我们现在知道了quartz有这么优秀,该怎么整合到项目中呢?笔者接下来将实现一个通过http接口调用来触发动态定时任务的一个小功能.笔者使用的环境:jdk:1.8.0_162;springboot:1.5.10.RELEASE1.引入需要的jar包,在pom文件中加入quartz的jar包和spring支持quartz的jar <dependency> <groupId>org.quartz-scheduler</groupId> <artifactId>quartz</artifactId> <version>2.3.1</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId> </dependency>2.配置调度器的bean,这里spring实现了三个工厂类,SchedulerFactoryBean,CronTriggerBean,JobDetailBean,使用注解的方式将这三个类交给spring管理.一般看网上的资料都是这三个类,都交给spring管理,可以参考这篇文章[这篇文章]。(https://blog.csdn.net/liuchua...。而我这里定时任务的触发是要通过接口的方式来触发,所以只用实现以下SchedulerFactoryBean的调度器即可。如果读者不是很明白这几个类是干嘛的,可以看下quartz使用的文章。我这里简单说下:scheduler:任务的调度器,job:具体的任务类,trigger:触发器,任务什么时候执行是由它决定的。就是说时间人物做什么,scheduler就是主语的人物,trigger是时间,job是做什么事。 @Configurationpublic class SchedulerConfig { /** * attention: * Details:定义quartz调度工厂 */ @Bean(name = "scheduler") public SchedulerFactoryBean schedulerFactory() { SchedulerFactoryBean bean = new SchedulerFactoryBean(); // 用于quartz集群,QuartzScheduler 启动时更新己存在的Job bean.setOverwriteExistingJobs(true); // 延时启动,应用启动1秒后 bean.setStartupDelay(1); return bean; }}3.具体任务类job,必须实现quartz的job类,这个也可以去实现spring的QuartJobBean(spring对job类的实现)是一样的,或者还有一种方式就是MethodInvokingJobDetailFactoryBean,这个类里面可以设置什么类的什么方法来执行这个任务,会更灵活一些: @Slf4jpublic class ScheduleTaskJob implements Job { @Override public void execute(JobExecutionContext context) throws JobExecutionException { log.info("任务执行了......"); }}4.http的接口来触发该调度程序: ...

May 12, 2019 · 1 min · jiezi

Spring-boot-redis-cache-的-key

在数据库查询中我们往往会使用增加缓存来提高程序的性能,@Cacheable 可以方便的对数据库查询方法加缓存。本文主要来探究一下缓存使用的key。 搭建项目数据库 mysql> select * from t_student;+----+--------+-------------+| id | name | grade_class |+----+--------+-------------+| 1 | Simone | 3-2 |+----+--------+-------------+1 row in set (0.01 sec)spring boot 配置 #jpaspring.jpa.hibernate.ddl-auto=nonespring.datasource.url=jdbc:mysql://127.0.0.1:3306/praticespring.datasource.username=rootspring.datasource.password=123456#redisspring.redis.host=localhostspring.redis.lettuce.pool.maxActive=5spring.redis.lettuce.pool.maxIdle=5#cachespring.cache.cache-names=Cachespring.cache.redis.time-to-live=300000数据访问层 public interface StudentDao extends CrudRepository<Student, Long> { @Cacheable(value = "Cache") @Override Optional<Student> findById(Long aLong); @Cacheable(value = "Cache") Optional<Student> findByName(String name);}启动调用数据访问层方法观察 @Overridepublic void run(ApplicationArguments args) throws Exception { Optional<Student> optional = studentDao.findById(1L); System.out.println(optional);}当默认使用 @Cacheable(value = "Cache") 的时候查看 redis 中缓存的 key ...

May 12, 2019 · 2 min · jiezi

装饰器代理模式与Spring-AOP

引言翻开to-do,注解认证中答应大家要讲解代理模式。 正好遇到了一道这样的题:抛开Spring来说,如何自己实现Spring AOP? 就喜欢这样的题,能把那些天天写增删改查从来不思考的人给PK下去,今天就和大家一切学习代理模式与Spring AOP。 代理与装饰器场景描述代理,即替代之意,可替代所有功能,即和原类实现相同的规范。 代理模式和装饰器模式很像,之前的装饰器讲的不是很好,这里换个例子再讲一遍。 宁静的午后,来到咖啡馆,想喝一杯咖啡。基础实现给你一个咖啡接口: public interface Coffee { /** * 打印当前咖啡的原材料,即咖啡里有什么 */ void printMaterial();}一个默认的苦咖啡的实现: public class BitterCoffee implements Coffee { @Override public void printMaterial() { System.out.println("咖啡"); }}默认的点餐逻辑: public class Main { public static void main(String[] args) { Coffee coffee = new BitterCoffee(); coffee.printMaterial(); }}点一杯咖啡。 装饰器模式优雅的服务生把咖啡端了上来,抿了一口,有些苦。 想加点糖,对服务生说:“您好,请为我的咖啡加些糖”。 /** * 糖装饰器,用来给咖啡加糖 */public class SugarDecorator implements Coffee { /** * 持有的咖啡对象 */ private final Coffee coffee; public SugarDecorator(Coffee coffee) { this.coffee = coffee; } @Override public void printMaterial() { System.out.println("糖"); this.coffee.printMaterial(); }}然后服务生就拿走了我的咖啡,去使用SugarDecorator为咖啡加糖,最后把加好糖的咖啡给我。 ...

May 11, 2019 · 2 min · jiezi

短信验证

在本周的项目中用到了一个手机短信验证的功能,虽然代码都是已经写好了的,自己只是拿来就用,但事后还是得学习一下思路的。 短信验证整体思路主要流程如下 基础功能还是比较简单的毕竟发短信用到是现成的接口,本项目用到的短信验证接口的网站是这个 /** * 批量发送短信 * * @param phoneNumbers * @param message 短信内容 * @return 成功200 ,不成功400(短信验证错误或未传入发送手机号) * @throws IOException */ @Override public Integer sentMessage(Set<String> phoneNumbers, String message) throws IOException { HttpClient client = new HttpClient(); PostMethod post = new PostMethod(sOpenUrl); // 在头文件中设置转码 post.addRequestHeader("Content-Type", ContentType); // 注册的用户名 NameValuePair[] data = {new NameValuePair("action", "sendOnce"), // 注册成功后,登录网站使用的密钥 new NameValuePair("ac", account), // 手机号码 new NameValuePair("authkey", authkey), new NameValuePair("cgid", cgid.toString()), new NameValuePair("c", message), new NameValuePair("m", String.join(",", phoneNumbers))}; // 设置短信内容 post.setRequestBody(data); client.executeMethod(post); post.releaseConnection(); return post.getStatusCode(); }小难点主要的难点我认为主要就是:怎么保存已经发送的验证码并判断是否失效。在本项目中是直接通过一个服务中的hashMap把验证码与手机号的信息直接存到内存中,毕竟本项目同时注册人数不可能太多,而几个字符串内存还是承受的住的。 ...

May 10, 2019 · 1 min · jiezi

Spring-Security-初始化流程详解

最近在整合微服务OAuth 2认证过程中,它是基于Spring Security之上,而本人对Spring Security架构原理并不太熟悉,导致很多配置搞不太清楚,遂咬牙啃完了Spring Security核心源码,花了差不多一星期,总体上来说,其代码确实比较晦涩,之前在学习Apache Shiro框架之前也曾经在相关论坛里了解过,相比Spring Security,Apache Shiro真的是相当轻量,代码研读起来容易很多,而Spring Security类继承结构复杂,大量使用了其所谓Builder和Configuer模式,其代码跟踪过程很痛苦,遂记录下,分享给有需要的人,由于本人能力有限,在文章中有不对之处,还请各位执教,在此谢谢各位了。 本人研读的Spring Security版本为:5.1.4.RELEASE Spring Security在3.2版本之后支持Java Configuration,即:通过Java编码形式配置Spring Security,可不再依赖XML文件配置,本文采用Java Configuration方式。 在Spring Security官方文档中有一个最简配置例子: import org.springframework.beans.factory.annotation.Autowired;import org.springframework.context.annotation.*;import org.springframework.security.config.annotation.authentication.builders.*;import org.springframework.security.config.annotation.web.configuration.*;@Configuration@EnableWebSecuritypublic class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { auth .inMemoryAuthentication() .withUser("user").password("password").roles("USER"); }}我们先不要看其它内容,先关注注解@EnableWebSecurity,它是初始化Spring Security的入口,打开其源码如下: @Retention(value = java.lang.annotation.RetentionPolicy.RUNTIME)@Target(value = { java.lang.annotation.ElementType.TYPE })@Documented@Import({ WebSecurityConfiguration.class, SpringWebMvcImportSelector.class, OAuth2ImportSelector.class })@EnableGlobalAuthentication@Configurationpublic @interface EnableWebSecurity { /** * Controls debugging support for Spring Security. Default is false. * @return if true, enables debug support with Spring Security */ boolean debug() default false;}该注解类通过@Configuration和@Import配合使用引入了一个配置类(WebSecurityConfiguration)和两个ImportSelector(SpringWebMvcImportSelector,OAuth2ImportSelector),我们重点关注下WebSecurityConfiguration,它是Spring Security的核心,正是它构建初始化了所有的Bean实例和相关配置,下面我们详细分析下。 ...

May 10, 2019 · 5 min · jiezi

Spring中Enable功能的使用

@Enable** 注解,一般用于开启某一类功能。类似于一种开关,只有加了这个注解,才能使用某些功能。 spring boot 中经常遇到这样的场景,老大让你写一个定时任务脚本、开启一个spring缓存,或者让你提供spring 异步支持。你的做法肯定是 @EnableScheduling+@Scheduled,@EnableCaching+@Cache,@EnableAsync+@Async 立马开始写逻辑了,但你是否真正了解其中的原理呢?之前有写过一个项目,是日志系统,其中要提供spring 注解支持,简化配置,当时就是参考以上源码的技巧实现的。 1 原理先来看@EnableScheduling源码 @Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Import(SchedulingConfiguration.class)@Documentedpublic @interface EnableScheduling {}可以看到这个注解是一个混合注解,和其他注解的唯一区别就是多了一个@Import注解 通过查询spring api文档 Indicates one or more @Configuration classes to import. Provides functionality equivalent to the <import/> element in Spring XML. Allows for importing @Configuration classes, ImportSelector and ImportBeanDefinitionRegistrar implementations, as well as regular component classes (as of 4.2; analogous to AnnotationConfigApplicationContext.register(java.lang.Class<?>...)). 表示要导入的一个或多个@Configuration类。 提供与Spring XML中的<import />元素等效的功能。 允许导入@Configuration类,ImportSelector和ImportBeanDefinitionRegistrar实现,以及常规组件类(从4.2开始;类似于AnnotationConfigApplicationContext.register(java.lang.Class <?> ...))。可以看出,通过这个注解的作用是导入一些特定的配置类,这些特定类包括三种 @Configuration 注解的类实现ImportSelector接口的类实现ImportBeanDefinitionRegistrar接口的类1.1 @Configuration注解类先来看看导入@Configuration注解的例子,打开SchedulingConfiguration类 ...

May 7, 2019 · 3 min · jiezi

软件与服务对比分析对象存储的发展历程下

导语在《从“软件”到“服务”——【对象存储】的发展历程(上)》中,我们和大家在对象存储大规模普及之前,大量的数据存储和处理是怎么实现的。但这些方案大都专注于解决其中一类问题,缺少足够的普适性。那么对象存储出现后,究竟解决了什么问题?优势又为何呢?1.软件 V.S 服务跟上一篇提到的各个软件相比,对象存储与之最大的区别不在于实现的机制,而在于形态从软件到服务的一个大飞跃。在这儿我想用一个可能不太恰当的比喻来说这事——传统的体重计。 不管是电子的还是机械的,他只是一个工具,我们评价的标准更多是价格、准确度、易用性、量程。而互联网的体重计,能帮你记录你的体重变化曲线,你关心的可能更多是数据联动、可视化、以及根据你的体重给出的建议。当然,如果你真的对减肥有强烈的需求,那么找一个合适的教练,由教练来指导你的减肥流程才合适,而互联网体重计只是教练手中的一个工具而已。 服务跟软件相比,有几个大的不同点: 首先是自由演化——对象存储的客户可以只关心SLA,并不关心你实现SLA的手段,所以演化更自由;其次是使用服务完全不用关心运维问题——运维问题完全交给服务提供商来解决;最后是采用服务形态后,业务的架构方式更灵活——比如控制面和数据面分离更轻松等。作为一个部署在云端的服务,它的接口和实现是分离的,也就是说我可以在保持接口不变的情况下持续演化实现。我们可以想象一下第一次在 AWS S3 上传的数据还有一些直到今天也没有删除,但这些数据可能已经经历了很多代的硬盘(毕竟硬盘的寿命一般也就3-5年),以及很多代的存储引擎了。这也是与传统存储软件不同的地方,传统存储软件如果大幅度更改了架构,那么通常是以一个新的存储软件的形式来出现。 背后的原因至少有两点: 不同存储引擎之间的平滑过渡和数据迁移难度很高,耗时很长,风险也很大,对于软件的使用者来讲根本不愿承受这些风险;每个存储引擎都有自己的优缺点,如果是服务的提供方,还能接受新引擎的缺点,也能通过硬件或者使用其他方式来弥补,但作为软件来使用的话,部分喜欢老版本优点的用户会长期停留在老版本,这经常会导致社区的分裂。2.传统存储 V.S 对象存储存储类的软件运维永远是一个问题,磁盘的寿命一般是3~5年,在3副本的情况下,1PB存储需要300块10TB硬盘,5年总共260周,也就是说,平均每周都要进行一次以上的硬盘更换操作。而采用对象存储,对应的麻烦一般交给服务供应商来解决。服务供应商一般会选择将坏盘留在机架上,等服务器到期后一次性销毁,来降低运维成本。此外,为了避免单机架、单AZ(Available Zone,可用区,一般一个AZ对应一个机房,两个AZ之间间距不低于20km,且不高于100km)故障导致数据不可用,一般还会采用一些反亲和策略,比如同一数据的多个副本,放在3个机架上,并且至少两个不同的AZ来存储。 跟运维相关的采购负担也是使用存储软件的一个大难题,在很多业务刚开始推广时,并不知道需要多少存储和上传带宽。如果按照上限准备,势必造成大量的浪费。如果准备不足,一旦存储用满,客户无法上传,就是影响运营的超大事故。而使用对象存储,这些问题都不再存在。 采用对象存储后,我们可以更方便地引入控制流和数据流分离。以一个UGC(User-generated content,用户生成内容,比如抖音、快手)类型的图片或者短视频网站为例,如果控制流和数据流不分离,那么为了提供用户访问网站的体验,我们需要租赁优质的多线BGP机房,这类机房的带宽成本非常昂贵,而图片和短视频的带宽需求巨大(主要是上传所需的带宽和CDN回源所需的带宽),造成总成本过高。如果把图片/短视频相关的上传和CDN都挪到对象存储服务商,只把控制相关的部分保留在昂贵的多线BGP机房。首先是上传基本免费,如果租赁同一个服务商的CDN,CDN回源费用也可以打折,而上传、下载的质量保证则由服务商去做保证,在获得足够质量的同时能大幅度节省费用。由于对象存储一般提供各类回调功能和转码功能,所以你原有的功能需求一般也能通过架构微调来满足。 除此之外,对象存储服务还能提供完全无缝的迁移方案,利用镜像存储等功能,可以做到在迁移时,终端用户完全无感知。比如从原始存储站点A迁移到对象存储B,一般步骤如下: 当然实际情况可能会更复杂,比如还涉及到图片转码等功能的迁移。 3.对象存储在中国的特色扩展图片和音视频处理算是很有中国特色的一个对象存储的扩展了,这也跟中国的对象存储发展跟富媒体网站的兴起时间重叠有一定的关系。基于对象存储的富媒体处理的好处不仅仅在于简化了使用流程,免除了客户自己维护图片转码集群负担,还大幅度降低了图片相关的安全风险。众所周知,图片相关的 libjpeg, libpng 等库是安全漏洞的重灾区,UGC类的业务很难避免黑客上传恶意图片来攻击。对象存储的供应商能使用的手段也不仅仅是紧盯CVE及时升级,还包括了使用容器来加固转码引擎,定期清理容器来避免APT攻击等手段。 综上所述,采用对象存储,跟采用存储软件相比,最主要的收益来自于运维负担、采购风险转移给了对象存储供应商,其次的收益还包括更灵活的架构于使用方式。其实对象存储的功能还有很多,如果对象存储兼容常用的S3 协议的话,对应的生态也很强大,不仅有大量的工具,常见的框架一般也有S3的支持。推荐阅读从“软件”到“服务”——【对象存储】的发展历程(上) 免费试用*点击“免费试用”免费领取京东云10GB对象存储额度

May 7, 2019 · 1 min · jiezi

服务治理Spring-Cloud-Eureka上

服务治理:Spring Cloud Eureka(上)Netflix Eureka是由Netflix开源的一款基于REST的服务治理组件,包括Eureka Server及Eureka Client。由于种种原因,Eureka 2.x版本已经冻结开发,目前最新版本是2018年8月份发布的1.9.4版本。Spring Cloud Eureka是Pivotal公司为Netflix Eureka整合于Spring Cloud生态系统提供的版本。1. 服务发现1.1 Eureka简介Eureka是Netflix公司提供的开源服务发现组件(现已闭源),最新版本是1.9.4,该组件提供的服务发现可以为负载均衡、failover等提供支持。Eureka包括Eureka Server和Eureka Client。Eureka Server提供REST服务,Eureka Clinet多数是使用Java编写的客户端(Eureka Client可以使用其他语言编写,比如Node.js或.NET),用于简化和Eureka Server的交互。1.2 Eureka Server简单案例所有工程使用Spring Cloud的新版Greenwich.SR1和Maven构建。 1.2.1 创建Spring Cloud Eureka Server工程pom.xml内容如下: <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.4.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>watermelon.cloud</groupId> <artifactId>eureka-server</artifactId> <version>0.0.1-SNAPSHOT</version> <name>eureka-server</name> <description>Spring Cloud Eureka Server</description> <properties> <java.version>1.8</java.version> <spring-cloud.version>Greenwich.SR1</spring-cloud.version> </properties> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>Finchley版本之后,Eureka的depenecy片段稍微有点不同 ...

May 6, 2019 · 2 min · jiezi

SpringMVC方法四种类型返回值总结你用过几种

SpringMVC 现在算是 Java 领域的一个基础性框架了,很多人天天用,可是对于 SpringMVC 方法的返回值,你又是否完全清楚呢?今天松哥就来和大家聊一聊 SpringMVC 中四种不同类型的返回值,看看有没有 get 到你的知识盲点? 1. ModelAndView以前前后端不分的情况下,ModelAndView 应该是最最常见的返回值类型了,现在前后端分离后,后端都是以返回 JSON 数据为主了。后端返回 ModelAndView 这个比较容易理解,开发者可以在 ModelAndView 对象中指定视图名称,然后也可以绑定数据,像下面这样: @RequestMapping("/book")public ModelAndView getAllBook() { ModelAndView mv = new ModelAndView(); List<Book> books = new ArrayList<>(); Book b1 = new Book(); b1.setId(1); b1.setName("三国演义"); b1.setAuthor("罗贯中"); books.add(b1); Book b2 = new Book(); b2.setId(2); b2.setName("红楼梦"); b2.setAuthor("曹雪芹"); books.add(b2); //指定数据模型 mv.addObject("bs", books); mv.setViewName("book");//指定视图名 return mv;}返回 ModelAndView ,最常见的两个操作就是指定数据模型+指定视图名 。 2. Void返回值为 void 时,可能是你真的没有值要返回,也可能是你有其他办法,松哥将之归为如下四类,大伙来看下。 2.1 没有值如果确实没有返回值,那就返回 void ,但是一定要注意,此时,方法上需要添加 @ResponseBody 注解,像下面这样: ...

May 6, 2019 · 2 min · jiezi

Spring项目本地环境搭建

0x01 依赖环境安装JDK 1.8,并配置环境变量安装Gradle 4.4.1,并配置环境变量GitIntellJ IDEA0x02 下载源码到本地,导入IDEAhttps://github.com/spring-projects/spring-framework这里建议fork到自己仓库,便于翻看源码添加注释0x03 构建编译顺序1.直接导入IDEA2.选择本地的gradle目录环境,点击完成 * Where:Build file 'D:\code\framework\spring-framework\spring-beans\spring-beans.gradle' line: 28* What went wrong:A problem occurred evaluating project ':spring-beans'.> No such property: immutableValues for class: org.gradle.api.internal.tasks.DefaultTaskDependency注释掉:spring-beans.gradle: 28, 29行* Where:Build file 'D:\code\framework\spring-framework\spring-test\spring-test.gradle' line: 103* What went wrong:A problem occurred evaluating project ':spring-test'.> Could not find method useJUnitPlatform() for arguments [spring_test_f5fp54pu50zhhzke93j1kdcu1$_run_closure4$_closure13@61e2341b] on task ':spring-test:testJUnitJupiter' of type org.gradle.api.tasks.testing.Test.注释掉:spring-test.gradle: 102-135行2.将阿里云的库添加到build.gradle 151行3.等待项目Jar包下载完maven { url "http://maven.aliyun.com/nexus/content/groups/public" }4.进入spring-core目录下,执行gradle build命令将spring-core项目打包,要不然cglib报错0x04 构建异常合集Could not resolve io.spring.gradle:propdeps-plugin:0.0.9.RELEASE.org.gradle.api.ProjectConfigurationException: A problem occurred configuring root project 'spring'. at org.gradle.configuration.project.LifecycleProjectEvaluator.addConfigurationFailure(LifecycleProjectEvaluator.java:94) at org.gradle.configuration.project.LifecycleProjectEvaluator.doConfigure(LifecycleProjectEvaluator.java:66) at org.gradle.configuration.project.LifecycleProjectEvaluator.access$100(LifecycleProjectEvaluator.java:34) at org.gradle.configuration.project.LifecycleProjectEvaluator$ConfigureProject.run(LifecycleProjectEvaluator.java:110) at org.gradle.internal.progress.DefaultBuildOperationExecutor$RunnableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:336) at org.gradle.internal.progress.DefaultBuildOperationExecutor$RunnableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:328) at org.gradle.internal.progress.DefaultBuildOperationExecutor.execute(DefaultBuildOperationExecutor.java:199) at org.gradle.internal.progress.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:110) at org.gradle.configuration.project.LifecycleProjectEvaluator.evaluate(LifecycleProjectEvaluator.java:50) at org.gradle.api.internal.project.DefaultProject.evaluate(DefaultProject.java:666) at org.gradle.api.internal.project.DefaultProject.evaluate(DefaultProject.java:135) at org.gradle.execution.TaskPathProjectEvaluator.configure(TaskPathProjectEvaluator.java:35) at org.gradle.execution.TaskPathProjectEvaluator.configureHierarchy(TaskPathProjectEvaluator.java:60) at org.gradle.configuration.DefaultBuildConfigurer.configure(DefaultBuildConfigurer.java:38) at org.gradle.initialization.DefaultGradleLauncher$ConfigureBuild.run(DefaultGradleLauncher.java:249) at org.gradle.internal.progress.DefaultBuildOperationExecutor$RunnableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:336) at org.gradle.internal.progress.DefaultBuildOperationExecutor$RunnableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:328) at org.gradle.internal.progress.DefaultBuildOperationExecutor.execute(DefaultBuildOperationExecutor.java:199) at org.gradle.internal.progress.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:110) at org.gradle.initialization.DefaultGradleLauncher.configureBuild(DefaultGradleLauncher.java:167) at org.gradle.initialization.DefaultGradleLauncher.doBuildStages(DefaultGradleLauncher.java:126) at org.gradle.initialization.DefaultGradleLauncher.getConfiguredBuild(DefaultGradleLauncher.java:104) at org.gradle.internal.invocation.GradleBuildController$2.call(GradleBuildController.java:87) at org.gradle.internal.invocation.GradleBuildController$2.call(GradleBuildController.java:84) at org.gradle.internal.work.DefaultWorkerLeaseService.withLocks(DefaultWorkerLeaseService.java:152) at org.gradle.internal.invocation.GradleBuildController.doBuild(GradleBuildController.java:100) at org.gradle.internal.invocation.GradleBuildController.configure(GradleBuildController.java:84) at org.gradle.tooling.internal.provider.runner.ClientProvidedBuildActionRunner.run(ClientProvidedBuildActionRunner.java:64) at org.gradle.launcher.exec.ChainingBuildActionRunner.run(ChainingBuildActionRunner.java:35) at org.gradle.launcher.exec.ChainingBuildActionRunner.run(ChainingBuildActionRunner.java:35) at org.gradle.tooling.internal.provider.ValidatingBuildActionRunner.run(ValidatingBuildActionRunner.java:32) at org.gradle.launcher.exec.RunAsBuildOperationBuildActionRunner$1.run(RunAsBuildOperationBuildActionRunner.java:43) at org.gradle.internal.progress.DefaultBuildOperationExecutor$RunnableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:336) at org.gradle.internal.progress.DefaultBuildOperationExecutor$RunnableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:328) at org.gradle.internal.progress.DefaultBuildOperationExecutor.execute(DefaultBuildOperationExecutor.java:199) at org.gradle.internal.progress.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:110) at org.gradle.launcher.exec.RunAsBuildOperationBuildActionRunner.run(RunAsBuildOperationBuildActionRunner.java:40) at org.gradle.tooling.internal.provider.SubscribableBuildActionRunner.run(SubscribableBuildActionRunner.java:51) at org.gradle.launcher.exec.InProcessBuildActionExecuter.execute(InProcessBuildActionExecuter.java:47) at org.gradle.launcher.exec.InProcessBuildActionExecuter.execute(InProcessBuildActionExecuter.java:30) at org.gradle.launcher.exec.BuildTreeScopeBuildActionExecuter.execute(BuildTreeScopeBuildActionExecuter.java:39) at org.gradle.launcher.exec.BuildTreeScopeBuildActionExecuter.execute(BuildTreeScopeBuildActionExecuter.java:25) at org.gradle.tooling.internal.provider.ContinuousBuildActionExecuter.execute(ContinuousBuildActionExecuter.java:80) at org.gradle.tooling.internal.provider.ContinuousBuildActionExecuter.execute(ContinuousBuildActionExecuter.java:53) at org.gradle.tooling.internal.provider.ServicesSetupBuildActionExecuter.execute(ServicesSetupBuildActionExecuter.java:57) at org.gradle.tooling.internal.provider.ServicesSetupBuildActionExecuter.execute(ServicesSetupBuildActionExecuter.java:32) at org.gradle.tooling.internal.provider.GradleThreadBuildActionExecuter.execute(GradleThreadBuildActionExecuter.java:36) at org.gradle.tooling.internal.provider.GradleThreadBuildActionExecuter.execute(GradleThreadBuildActionExecuter.java:25) at org.gradle.tooling.internal.provider.ParallelismConfigurationBuildActionExecuter.execute(ParallelismConfigurationBuildActionExecuter.java:43) at org.gradle.tooling.internal.provider.ParallelismConfigurationBuildActionExecuter.execute(ParallelismConfigurationBuildActionExecuter.java:29) at org.gradle.tooling.internal.provider.StartParamsValidatingActionExecuter.execute(StartParamsValidatingActionExecuter.java:69) at org.gradle.tooling.internal.provider.StartParamsValidatingActionExecuter.execute(StartParamsValidatingActionExecuter.java:30) at org.gradle.tooling.internal.provider.SessionFailureReportingActionExecuter.execute(SessionFailureReportingActionExecuter.java:59) at org.gradle.tooling.internal.provider.SessionFailureReportingActionExecuter.execute(SessionFailureReportingActionExecuter.java:44) at org.gradle.tooling.internal.provider.SetupLoggingActionExecuter.execute(SetupLoggingActionExecuter.java:45) at org.gradle.tooling.internal.provider.SetupLoggingActionExecuter.execute(SetupLoggingActionExecuter.java:30) at org.gradle.launcher.daemon.server.exec.ExecuteBuild.doBuild(ExecuteBuild.java:67) at org.gradle.launcher.daemon.server.exec.BuildCommandOnly.execute(BuildCommandOnly.java:36) at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:122) at org.gradle.launcher.daemon.server.exec.WatchForDisconnection.execute(WatchForDisconnection.java:37) at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:122) at org.gradle.launcher.daemon.server.exec.ResetDeprecationLogger.execute(ResetDeprecationLogger.java:26) at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:122) at org.gradle.launcher.daemon.server.exec.RequestStopIfSingleUsedDaemon.execute(RequestStopIfSingleUsedDaemon.java:34) at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:122) at org.gradle.launcher.daemon.server.exec.ForwardClientInput$2.call(ForwardClientInput.java:74) at org.gradle.launcher.daemon.server.exec.ForwardClientInput$2.call(ForwardClientInput.java:72) at org.gradle.util.Swapper.swap(Swapper.java:38) at org.gradle.launcher.daemon.server.exec.ForwardClientInput.execute(ForwardClientInput.java:72) at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:122) at org.gradle.launcher.daemon.server.exec.LogAndCheckHealth.execute(LogAndCheckHealth.java:55) at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:122) at org.gradle.launcher.daemon.server.exec.LogToClient.doBuild(LogToClient.java:62) at org.gradle.launcher.daemon.server.exec.BuildCommandOnly.execute(BuildCommandOnly.java:36) at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:122) at org.gradle.launcher.daemon.server.exec.EstablishBuildEnvironment.doBuild(EstablishBuildEnvironment.java:82) at org.gradle.launcher.daemon.server.exec.BuildCommandOnly.execute(BuildCommandOnly.java:36) at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:122) at org.gradle.launcher.daemon.server.exec.StartBuildOrRespondWithBusy$1.run(StartBuildOrRespondWithBusy.java:50) at org.gradle.launcher.daemon.server.DaemonStateCoordinator$1.run(DaemonStateCoordinator.java:295) at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:63) at org.gradle.internal.concurrent.ManagedExecutorImpl$1.run(ManagedExecutorImpl.java:46) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) at org.gradle.internal.concurrent.ThreadFactoryImpl$ManagedThreadRunnable.run(ThreadFactoryImpl.java:55) at java.lang.Thread.run(Thread.java:748)Caused by: org.gradle.api.internal.artifacts.ivyservice.DefaultLenientConfiguration$ArtifactResolveException: Could not resolve all files for configuration ':classpath'. at org.gradle.api.internal.artifacts.configurations.DefaultConfiguration.rethrowFailure(DefaultConfiguration.java:918) at org.gradle.api.internal.artifacts.configurations.DefaultConfiguration.access$1600(DefaultConfiguration.java:116) at org.gradle.api.internal.artifacts.configurations.DefaultConfiguration$ConfigurationFileCollection.getFiles(DefaultConfiguration.java:892) at org.gradle.api.internal.artifacts.configurations.DefaultConfiguration.getFiles(DefaultConfiguration.java:404) at org.gradle.api.internal.artifacts.configurations.DefaultConfiguration_Decorated.getFiles(Unknown Source) at org.gradle.api.internal.file.AbstractFileCollection.iterator(AbstractFileCollection.java:68) at org.gradle.internal.classpath.DefaultClassPath.<init>(DefaultClassPath.java:48) at org.gradle.api.internal.initialization.DefaultScriptClassPathResolver.resolveClassPath(DefaultScriptClassPathResolver.java:39) at org.gradle.api.internal.initialization.DefaultScriptHandler.getScriptClassPath(DefaultScriptHandler.java:72) at org.gradle.plugin.use.internal.DefaultPluginRequestApplicator.defineScriptHandlerClassScope(DefaultPluginRequestApplicator.java:204) at org.gradle.plugin.use.internal.DefaultPluginRequestApplicator.applyPlugins(DefaultPluginRequestApplicator.java:140) at org.gradle.configuration.DefaultScriptPluginFactory$ScriptPluginImpl.apply(DefaultScriptPluginFactory.java:179) at org.gradle.configuration.BuildOperationScriptPlugin$1.run(BuildOperationScriptPlugin.java:61) at org.gradle.internal.progress.DefaultBuildOperationExecutor$RunnableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:336) at org.gradle.internal.progress.DefaultBuildOperationExecutor$RunnableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:328) at org.gradle.internal.progress.DefaultBuildOperationExecutor.execute(DefaultBuildOperationExecutor.java:199) at org.gradle.internal.progress.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:110) at org.gradle.configuration.BuildOperationScriptPlugin.apply(BuildOperationScriptPlugin.java:58) at org.gradle.configuration.project.BuildScriptProcessor.execute(BuildScriptProcessor.java:41) at org.gradle.configuration.project.BuildScriptProcessor.execute(BuildScriptProcessor.java:26) at org.gradle.configuration.project.ConfigureActionsProjectEvaluator.evaluate(ConfigureActionsProjectEvaluator.java:34) at org.gradle.configuration.project.LifecycleProjectEvaluator.doConfigure(LifecycleProjectEvaluator.java:64) ... 84 moreCaused by: org.gradle.internal.resolve.ModuleVersionResolveException: Could not resolve io.spring.gradle:propdeps-plugin:0.0.9.RELEASE.Required by: project : at org.gradle.api.internal.artifacts.ivyservice.ivyresolve.RepositoryChainComponentMetaDataResolver.resolveModule(RepositoryChainComponentMetaDataResolver.java:103) at org.gradle.api.internal.artifacts.ivyservice.ivyresolve.RepositoryChainComponentMetaDataResolver.resolve(RepositoryChainComponentMetaDataResolver.java:63) at org.gradle.api.internal.artifacts.ivyservice.resolveengine.ComponentResolversChain$ComponentMetaDataResolverChain.resolve(ComponentResolversChain.java:93) at org.gradle.api.internal.artifacts.ivyservice.clientmodule.ClientModuleResolver.resolve(ClientModuleResolver.java:48) at org.gradle.api.internal.artifacts.ivyservice.resolveengine.graph.builder.ComponentState.resolve(ComponentState.java:157) at org.gradle.api.internal.artifacts.ivyservice.resolveengine.graph.builder.ComponentState.getMetaData(ComponentState.java:168) at org.gradle.api.internal.artifacts.ivyservice.resolveengine.graph.builder.EdgeState.calculateTargetConfigurations(EdgeState.java:134) at org.gradle.api.internal.artifacts.ivyservice.resolveengine.graph.builder.EdgeState.attachToTargetConfigurations(EdgeState.java:105) at org.gradle.api.internal.artifacts.ivyservice.resolveengine.graph.builder.DependencyGraphBuilder.attachToTargetRevisionsSerially(DependencyGraphBuilder.java:239) at org.gradle.api.internal.artifacts.ivyservice.resolveengine.graph.builder.DependencyGraphBuilder.resolveEdges(DependencyGraphBuilder.java:229) at org.gradle.api.internal.artifacts.ivyservice.resolveengine.graph.builder.DependencyGraphBuilder.traverseGraph(DependencyGraphBuilder.java:143) at org.gradle.api.internal.artifacts.ivyservice.resolveengine.graph.builder.DependencyGraphBuilder.resolve(DependencyGraphBuilder.java:109) at org.gradle.api.internal.artifacts.ivyservice.resolveengine.DefaultArtifactDependencyResolver.resolve(DefaultArtifactDependencyResolver.java:90) at org.gradle.api.internal.artifacts.ivyservice.DefaultConfigurationResolver.resolveGraph(DefaultConfigurationResolver.java:146) at org.gradle.api.internal.artifacts.ivyservice.ShortCircuitEmptyConfigurationResolver.resolveGraph(ShortCircuitEmptyConfigurationResolver.java:73) at org.gradle.api.internal.artifacts.ivyservice.ErrorHandlingConfigurationResolver.resolveGraph(ErrorHandlingConfigurationResolver.java:66) at org.gradle.api.internal.artifacts.configurations.DefaultConfiguration$4.run(DefaultConfiguration.java:483) at org.gradle.internal.progress.DefaultBuildOperationExecutor$RunnableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:336) at org.gradle.internal.progress.DefaultBuildOperationExecutor$RunnableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:328) at org.gradle.internal.progress.DefaultBuildOperationExecutor.execute(DefaultBuildOperationExecutor.java:199) at org.gradle.internal.progress.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:110) at org.gradle.api.internal.artifacts.configurations.DefaultConfiguration.resolveGraphIfRequired(DefaultConfiguration.java:474) at org.gradle.api.internal.artifacts.configurations.DefaultConfiguration.resolveToStateOrLater(DefaultConfiguration.java:459) at org.gradle.api.internal.artifacts.configurations.DefaultConfiguration.access$1700(DefaultConfiguration.java:116) at org.gradle.api.internal.artifacts.configurations.DefaultConfiguration$ConfigurationFileCollection.getSelectedArtifacts(DefaultConfiguration.java:901) at org.gradle.api.internal.artifacts.configurations.DefaultConfiguration$ConfigurationFileCollection.getFiles(DefaultConfiguration.java:889) ... 103 moreCaused by: org.gradle.internal.resolve.ModuleVersionResolveException: Could not resolve io.spring.gradle:propdeps-plugin:0.0.9.RELEASE. at org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ErrorHandlingModuleComponentRepository$ErrorHandlingModuleComponentRepositoryAccess.resolveComponentMetaData(ErrorHandlingModuleComponentRepository.java:129) at org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ComponentMetaDataResolveState.process(ComponentMetaDataResolveState.java:66) at org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ComponentMetaDataResolveState.resolve(ComponentMetaDataResolveState.java:58) at org.gradle.api.internal.artifacts.ivyservice.ivyresolve.RepositoryChainComponentMetaDataResolver.findBestMatch(RepositoryChainComponentMetaDataResolver.java:138) at org.gradle.api.internal.artifacts.ivyservice.ivyresolve.RepositoryChainComponentMetaDataResolver.findBestMatch(RepositoryChainComponentMetaDataResolver.java:119) at org.gradle.api.internal.artifacts.ivyservice.ivyresolve.RepositoryChainComponentMetaDataResolver.resolveModule(RepositoryChainComponentMetaDataResolver.java:92) ... 128 moreCaused by: org.gradle.api.resources.ResourceException: Could not get resource 'https://repo.spring.io/plugins-release/io/spring/gradle/propdeps-plugin/0.0.9.RELEASE/propdeps-plugin-0.0.9.RELEASE.pom'. at org.gradle.internal.resource.ResourceExceptions.failure(ResourceExceptions.java:74) at org.gradle.internal.resource.ResourceExceptions.getFailed(ResourceExceptions.java:57) at org.gradle.api.internal.artifacts.repositories.resolver.DefaultExternalResourceArtifactResolver.downloadByCoords(DefaultExternalResourceArtifactResolver.java:138) at org.gradle.api.internal.artifacts.repositories.resolver.DefaultExternalResourceArtifactResolver.downloadStaticResource(DefaultExternalResourceArtifactResolver.java:95) at org.gradle.api.internal.artifacts.repositories.resolver.DefaultExternalResourceArtifactResolver.resolveArtifact(DefaultExternalResourceArtifactResolver.java:65) at org.gradle.api.internal.artifacts.repositories.resolver.ExternalResourceResolver.parseMetaDataFromArtifact(ExternalResourceResolver.java:216) at org.gradle.api.internal.artifacts.repositories.resolver.MavenResolver.parseMetaDataFromArtifact(MavenResolver.java:170) at org.gradle.api.internal.artifacts.repositories.resolver.MavenResolver.parseMetaDataFromArtifact(MavenResolver.java:65) at org.gradle.api.internal.artifacts.repositories.resolver.ExternalResourceResolver.resolveStaticDependency(ExternalResourceResolver.java:193) at org.gradle.api.internal.artifacts.repositories.resolver.MavenResolver.doResolveComponentMetaData(MavenResolver.java:145) at org.gradle.api.internal.artifacts.repositories.resolver.ExternalResourceResolver$RemoteRepositoryAccess.resolveComponentMetaData(ExternalResourceResolver.java:467) at org.gradle.api.internal.artifacts.ivyservice.ivyresolve.CachingModuleComponentRepository$ResolveAndCacheRepositoryAccess.resolveComponentMetaData(CachingModuleComponentRepository.java:363) at org.gradle.api.internal.artifacts.ivyservice.ivyresolve.BaseModuleComponentRepositoryAccess.resolveComponentMetaData(BaseModuleComponentRepositoryAccess.java:50) at org.gradle.api.internal.artifacts.ivyservice.ivyresolve.memcache.InMemoryCachedModuleComponentRepository$CachedAccess.resolveComponentMetaData(InMemoryCachedModuleComponentRepository.java:95) at org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ErrorHandlingModuleComponentRepository$ErrorHandlingModuleComponentRepositoryAccess.resolveComponentMetaData(ErrorHandlingModuleComponentRepository.java:126) ... 133 moreCaused by: org.gradle.internal.resource.transport.http.HttpRequestException: Could not HEAD 'https://repo.spring.io/plugins-release/io/spring/gradle/propdeps-plugin/0.0.9.RELEASE/propdeps-plugin-0.0.9.RELEASE.pom'. at org.gradle.internal.resource.transport.http.HttpClientHelper.performRequest(HttpClientHelper.java:96) at org.gradle.internal.resource.transport.http.HttpClientHelper.performRawHead(HttpClientHelper.java:72) at org.gradle.internal.resource.transport.http.HttpClientHelper.performHead(HttpClientHelper.java:76) at org.gradle.internal.resource.transport.http.HttpResourceAccessor.getMetaData(HttpResourceAccessor.java:65) at org.gradle.internal.resource.transfer.DefaultExternalResourceConnector.getMetaData(DefaultExternalResourceConnector.java:63) at org.gradle.internal.resource.transfer.AccessorBackedExternalResource.getMetaData(AccessorBackedExternalResource.java:201) at org.gradle.internal.resource.BuildOperationFiringExternalResourceDecorator$1.call(BuildOperationFiringExternalResourceDecorator.java:61) at org.gradle.internal.resource.BuildOperationFiringExternalResourceDecorator$1.call(BuildOperationFiringExternalResourceDecorator.java:58) at org.gradle.internal.progress.DefaultBuildOperationExecutor$CallableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:350) at org.gradle.internal.progress.DefaultBuildOperationExecutor$CallableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:340) at org.gradle.internal.progress.DefaultBuildOperationExecutor.execute(DefaultBuildOperationExecutor.java:199) at org.gradle.internal.progress.DefaultBuildOperationExecutor.call(DefaultBuildOperationExecutor.java:120) at org.gradle.internal.resource.BuildOperationFiringExternalResourceDecorator.getMetaData(BuildOperationFiringExternalResourceDecorator.java:58) at org.gradle.internal.resource.transfer.DefaultCacheAwareExternalResourceAccessor$1.create(DefaultCacheAwareExternalResourceAccessor.java:102) at org.gradle.internal.resource.transfer.DefaultCacheAwareExternalResourceAccessor$1.create(DefaultCacheAwareExternalResourceAccessor.java:82) at org.gradle.cache.internal.ProducerGuard$AdaptiveProducerGuard.guardByKey(ProducerGuard.java:97) at org.gradle.internal.resource.transfer.DefaultCacheAwareExternalResourceAccessor.getResource(DefaultCacheAwareExternalResourceAccessor.java:82) at org.gradle.api.internal.artifacts.repositories.resolver.DefaultExternalResourceArtifactResolver.downloadByCoords(DefaultExternalResourceArtifactResolver.java:129) ... 145 moreCaused by: java.net.SocketTimeoutException: Read timed out at java.net.SocketInputStream.socketRead0(Native Method) at java.net.SocketInputStream.socketRead(SocketInputStream.java:116) at java.net.SocketInputStream.read(SocketInputStream.java:171) at java.net.SocketInputStream.read(SocketInputStream.java:141) at sun.security.ssl.InputRecord.readFully(InputRecord.java:465) at sun.security.ssl.InputRecord.read(InputRecord.java:503) at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:973) at sun.security.ssl.SSLSocketImpl.readDataRecord(SSLSocketImpl.java:930) at sun.security.ssl.AppInputStream.read(AppInputStream.java:105) at org.apache.http.impl.io.SessionInputBufferImpl.streamRead(SessionInputBufferImpl.java:139) at org.apache.http.impl.io.SessionInputBufferImpl.fillBuffer(SessionInputBufferImpl.java:155) at org.apache.http.impl.io.SessionInputBufferImpl.readLine(SessionInputBufferImpl.java:284) at org.apache.http.impl.conn.DefaultHttpResponseParser.parseHead(DefaultHttpResponseParser.java:140) at org.apache.http.impl.conn.DefaultHttpResponseParser.parseHead(DefaultHttpResponseParser.java:57) at org.apache.http.impl.io.AbstractMessageParser.parse(AbstractMessageParser.java:261) at org.apache.http.impl.DefaultBHttpClientConnection.receiveResponseHeader(DefaultBHttpClientConnection.java:165) at org.apache.http.impl.conn.CPoolProxy.receiveResponseHeader(CPoolProxy.java:167) at org.apache.http.protocol.HttpRequestExecutor.doReceiveResponse(HttpRequestExecutor.java:272) at org.apache.http.protocol.HttpRequestExecutor.execute(HttpRequestExecutor.java:124) at org.apache.http.impl.execchain.MainClientExec.execute(MainClientExec.java:271) at org.apache.http.impl.execchain.ProtocolExec.execute(ProtocolExec.java:184) at org.apache.http.impl.execchain.RetryExec.execute(RetryExec.java:88) at org.apache.http.impl.execchain.RedirectExec.execute(RedirectExec.java:110) at org.apache.http.impl.client.InternalHttpClient.doExecute(InternalHttpClient.java:184) at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:82) at org.gradle.internal.resource.transport.http.HttpClientHelper.performHttpRequest(HttpClientHelper.java:148) at org.gradle.internal.resource.transport.http.HttpClientHelper.performHttpRequest(HttpClientHelper.java:126) at org.gradle.internal.resource.transport.http.HttpClientHelper.executeGetOrHead(HttpClientHelper.java:103) at org.gradle.internal.resource.transport.http.HttpClientHelper.performRequest(HttpClientHelper.java:94) ... 162 moreA problem occurred configuring root project 'spring'.> Could not resolve all files for configuration ':classpath'. > Could not resolve io.spring.gradle:propdeps-plugin:0.0.9.RELEASE. Required by: project : > Could not resolve io.spring.gradle:propdeps-plugin:0.0.9.RELEASE. > Could not get resource 'https://repo.spring.io/plugins-release/io/spring/gradle/propdeps-plugin/0.0.9.RELEASE/propdeps-plugin-0.0.9.RELEASE.pom'. > Could not GET 'https://repo.spring.io/plugins-release/io/spring/gradle/propdeps-plugin/0.0.9.RELEASE/propdeps-plugin-0.0.9.RELEASE.pom'. > Read timed out* Try:Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. Run with --scan to get full insights.* Get more help at https://help.gradle.org

May 6, 2019 · 3 min · jiezi

转载Spring-Boot-设置项目名后静态文件相对路径问题

原博客地址:https://blog.csdn.net/qq_2928... 出现问题的原因server.servlet.context-path=testDemospring.mvc.static-path-pattern=/static/**定义项目名和静态资源路径后发现,templates中html 中引用的css,js的相对路径出现异常在上面目录中,index.html 通过相对路径引用css href="../static/xxx" 就获取不到了在没有定义 server.servlet.context-path=testDemo 的前href="../static/xxx" 这样写是没有问题的在设置项目名后,使用相对路径的时候就会缺少项目名,从而获取不到静态资源 解决方案使用绝对路径修改路径,将href="../static/xxx" 改成href="static/xxx"使用spring thymeleaf的th:src或者th:href属性改变标签的链接路径,如 <link rel="stylesheet" th:href="@{/pace/themes/blue/pace-theme-flash.css}>但这3种方案,编译器无法识别路径,导致编写代码无提示,这就很难受了,下面两种方案以解决编译器无法提示的问题 同样使用spring thymeleaf的th:src或者th:href,并且写的时候再加多src,href 供编译识别(推荐) <link rel="stylesheet" href="../static/pace/themes/blue/pace-theme-flash.css" th:href="@{/pace/themes/blue/pace-theme-flash.css}">不使用thymeleaf或者不想每个css,js引入都多写一次路径,在html head添加<base href="XXX/">标签,这样../就不会丢失项目名,而是去掉了XXX/<base href="XXX/"><link rel="stylesheet" href="../static/pace/themes/blue/pace-theme-flash.css">

May 5, 2019 · 1 min · jiezi

微服务与Spring-Cloud概述

微服务与Spring Cloud随着互联网的快速发展, 云计算近十年也得到蓬勃发展, 企业的IT环境和IT架构也逐渐在发生变革,从过去的单体应用架构发展为至今广泛流行的微服务架构。 微服务是一种架构风格, 能给软件应用开发带来很大的便利,但是微服务的实施和落地会面临很大的挑战, 因此需要一套完整的微服务解决方案。 在Java领域,Spring框架的出现给Java企业级软件开发带来 了福音, 提高了开发效率。 在2014年底,Spring团队推出Spring Cloud, 目标使其成为Java 领域微服务架构落地的标准,发展至今,Spring Cloud已经成为Java领域落地微服务架构的完整解决方案, 为企业IT架构变革保驾护航。微服务架构概述1.应用架构的发展应用是可独立运行的程序代码, 提供相对完善的业务功能。 目前软件架构有三种架构类型, 分别是业务架构、应用架构、技术架构。 它们之间的关系是业务架构决定应用架构, 技术架构支撑应用架构。 架构的发展历程是从单体架构、分布式架构、SOA架构再到微服务架构。 1.1 单体架构单体架构在Java领域可以理解为一个Java Web应用程序,包含表现层、业务层、数据访问层,从controller到service再到dao,就像一条单行道,从头一路走到底,没有任何业务的拆分,开发完毕之后就是一个超级大型的War包部署。简单的单体架构示例图如下: 这种开发方式对于大型应用来说非常复杂,也有“单体地狱”的称号。我们来说说单体架构的优缺点:单体架构的优点: 易于开发:开发人员使用当前开发工具在短时间内就可以开发出单体应用。易于测试:因为不需要依赖其他接口,测试可以节约很多时间。易于部署:你只需要将目录部署在运行环境中即可。单体架构的缺点: 灵活度不够:如果程序有任何修改, 修改的不只是一个点, 而是自上而下地去修改,测试时必须等到整个程序部署完后才能看出效果。 在开发过程可能需要等待其他开发 人员开发完成后才能完成部署,降低了团队的灵活性。降低系统的性能:原本可以直接访问数据库但是现在多了一层。 即使只包含一个功能点, 也需要在各个层写上代码。系统启动慢:一个进程包含了所有业务逻辑, 涉及的启动模块过多, 导致系统的启动 时间延长。系统扩展性比较差:增加新东西的时候不能针对单个点增加, 要全局性地增加。 牵一 发而动全身。1.2 分布式架构分布式架构就是在传统的单体架构的基础上,按照业务垂直切分,每个应用都是单体架构,通过API相互调用。 分布式架构的优缺点:优点: 依赖解耦理解清晰缺点: 进程间调用的可靠性低实现技术复杂1.3 SOA架构SOA(Service-Oriented Architecture)是指面向服务的架构,面向服务的架构是一种软件体系结构, 其应用程序的不同组件通过网络上的通信协议向其他组件提供服务或消费服务,所以也是一种分布式架构。简单来说,SOA是不同业务建立不同 的服务, 服务之间的数据交互粗粒度可以通过服务接口分级, 这样松散耦合提高服务的可重用性,也让业务逻辑变得可组合, 并且每个服务可以根据使用情况做出合理的分布式部署,从而让服务变得规范,高性能,高可用。 SOA架构中有两个主要角色:服务提供者(Provider)和服务消费者(Consumer)。 阿里开源的Dubbo是SOA的典型实现。SOA架构的优缺点:优点: 把模块拆分,使用接口通信,降低模块之间的耦合度把项目拆分成若干子项目,不同团队负责不同的子项目增加功能时只需要增加一个子项目,调用其他系统的接口即可可灵活地进行分布式部署缺点: 系统之间交互需要远程通信接口开发增加工作量1.4 微服务架构微服务架构在某种程度上是SOA架构继续发展的下一步,微服务的概念最早源千Martin Flower的《Microservice》。总体来讲,微服务是一种架构风格,对于一个大型复杂的业务系统,它的业务功能可以拆分为多个相互独立的微服务,各个服务之间是松耦合的,通过各种远程协议进行同步/异步通信,各微服务均可被独立部署、扩/缩容以及服务升/降级。 2. 微服务解决方案现今微服务架构十分火爆,而采用微服务构建系统也会带来更清晰的业务划分和可扩展性。支持微服务的技术栈也是多种多样。这里主要介绍两种实现微服务的解决方案: 2.1 基于Spring Cloud的微服务解决方案基于Spring Cloud的微服务解决方案也有人称为“Spring系微服务”,Spring Cloud的技术选型是中立的,Spring Cloud框架提供微服务落地方案主要有以下三种: ...

May 5, 2019 · 1 min · jiezi

springboot六springboot与webflux结合初探

spring-cloud-gateway 的ReactorHttpHandlerAdapter这几天看了看spring-cloud-gateway的请求处理流程,因为之前一直用的springboot1.x和spring4,一开始对spring-cloud-gateway的处理流程有点懵逼,找不到入口,后来跟了代码,在网上找了点资料,发现spring-cloud-gateway的入口在ReactorHttpHandlerAdapter的apply方法 public class ReactorHttpHandlerAdapter implements BiFunction<HttpServerRequest, HttpServerResponse, Mono<Void>> { private static final Log logger = HttpLogging.forLogName(ReactorHttpHandlerAdapter.class); private final HttpHandler httpHandler; public ReactorHttpHandlerAdapter(HttpHandler httpHandler) { Assert.notNull(httpHandler, "HttpHandler must not be null"); this.httpHandler = httpHandler; } @Override public Mono<Void> apply(HttpServerRequest reactorRequest, HttpServerResponse reactorResponse) { NettyDataBufferFactory bufferFactory = new NettyDataBufferFactory(reactorResponse.alloc()); try { ReactorServerHttpRequest request = new ReactorServerHttpRequest(reactorRequest, bufferFactory); ServerHttpResponse response = new ReactorServerHttpResponse(reactorResponse, bufferFactory); if (request.getMethod() == HttpMethod.HEAD) { response = new HttpHeadResponseDecorator(response); } return this.httpHandler.handle(request, response) .doOnError(ex -> logger.trace(request.getLogPrefix() + "Failed to complete: " + ex.getMessage())) .doOnSuccess(aVoid -> logger.trace(request.getLogPrefix() + "Handling completed")); } catch (URISyntaxException ex) { if (logger.isDebugEnabled()) { logger.debug("Failed to get request URI: " + ex.getMessage()); } reactorResponse.status(HttpResponseStatus.BAD_REQUEST); return Mono.empty(); } }}该方法的作用就是把接收到的HttpServerRequest或者最终需要返回的HttpServerResponse,包装转换为ReactorServerHttpRequest和ReactorServerHttpResponse。 ...

May 5, 2019 · 2 min · jiezi

适合新手的spring-cloud入门教程

就和 springboot 是 web 应用的脚手架一样, springcloud 是分布式和集群应用的脚手架。 但是并不是所有的同学都有接触过分布式和集群,所以为了让学习曲线变得缓和,站长按照如下顺序展开 springcloud 教程的讲解: 先来个单体架构的应用,里面既没有分布式,也没有集群。 基于这个单体架构,分析其弊端,引入微服务,集群和分布式的概念。 一般说来做一个springcloud项目都会有多个子项目,这里就涉及到使用 maven 创建父子(聚合)项目的概念。很多同学之前也没有接触过这个,为了让后面学习更顺滑,也在这里做了 maven 父子项目教程,分别提供了 eclipse 版本 和 idea 版本。 springcloud 是由一个一个的微服务组成, 而这些微服务都是在注册中心管理起来的。所以这里我们就会做注册中心的开发。 有了注册中心,我们就可以发布真正提供服务的微服务了。 springcloud 里面的一个核心内容是微服务之间的彼此调用,所以我们会先演示 ribbon 方式的视图微服务调用数据微服务。 7. 然后再学习主流的 Feign 方式  微服务之间的调用关系是需要被掌握的,于是我们学习服务链路追踪 集群里有多个实例,当发生改变的时候,必须重新部署,这样维护成本比较高。为了降低维护成本,我们引入了分布式配置服务的概念。 被调用的服务不一定100% 可用,当发生不可用的时候怎么办呢?我们会使用断路器。 断路器什么时候起作用了?微服务的可用度如何?这些都应该被纳入监控,所以我们会学习对单个微服务的短路监控以及集群里多个微服务的聚合监控。 微服务有很多个,分别处于不同的ip地址,使用不同的端口。这让访问者难以记忆,为了方便访问,我们引入了网关,这样访问者似乎就意识不到微服务的存在了一般。 在这个系列教材里,微服务有很多个,端口也有很多个,担心学员被端口号搞混淆了,于是把这些端口号都做了整理,方便梳理思路。 教程地址:http://how2j.cn/p/1628

May 4, 2019 · 1 min · jiezi

Spring笔记03AOP

1. AOP1.1 AOP介绍1.1.1 什么是AOP在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP(面向对象编程)的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。AOP采取横向抽取机制,取代了传统纵向继承体系重复性代码。如下图所示:经典应用:事务管理、性能监视、安全检查、缓存 、日志等。Spring AOP使用纯Java实现,不需要专门的编译过程和类加载器,在运行期通过代理方式向目标类织入增强代码。AspectJ是一个基于Java语言的AOP框架,从Spring2.0开始,Spring AOP引入对Aspect的支持,AspectJ扩展了Java语言,提供了一个专门的编译器,在编译时提供横向代码的织入。1.1.2 AOP实现原理aop底层将采用代理机制进行实现接口+实现类时:Spring采用JDK的动态代理Proxy只有实现类时:Spring采用cglib字节码增强。这种底层属于继承增强。1.1.3 AOP术语【掌握】Target :目标类,需要被代理的类。本例中如:UserDaoJoinpoint(连接点) :所谓连接点是指那些可能被拦截到的点。在spring中,这些点指的是方法,因为spring只支持方法类型的连接点。本例中如:UserDao的所有的方法PointCut 切入点 :所谓切入点是指我们要对哪些Joinpoint进行拦截,即已经被增强的连接点。例如:save()Advice :通知/增强,增强的代码。例如:checkPri()所谓通知是指拦截到Joinpoint之后所要做的事情就是通知,通知分为前置通知、后置通知、异常通知、最终通知、环绕通知(即切面要完成的功能)。Weaving(织入) :是指把通知/增强advice应用到目标对象target来创建新的代理对象proxy的过程。spring采用动态代理织入,而AspectJ采用编译期织入和类装在期织入。Proxy :代理类,一个类被AOP织入增强后,就产生一个结果代理类。Aspect(切面) : 是切入点Pointcut和通知Advice(引介)的结合。Introduction(引介) :引介是一种特殊的通知,在不修改类代码的前提下,Introduction 可以在运行期为类动态地添加一些方法或Field。 小结:一个线是一个特殊的面。 一个切入点和一个通知,组成成一个特殊的面。 详解如图01: 详解如图02: 1.2 AOP的底层实现(了解)动态代理 JDK动态代理:只能对实现了接口的类产生代理Cglib动态代理(第三方代理技术):对没有实现接口的类产生代理对象,生成子类对象。代理知识点参考:https://www.cnblogs.com/itzho...1.2.1 JDK动态代理准备工作:新建web项目,不需要导包代理对象UserDao package com.itzhouq.spring.demo1;public interface UserDao { public void save(); public void update(); public void find(); public void delete();}实现类 package com.itzhouq.spring.demo1;public class UserDaoImpl implements UserDao { @Override public void save() { System.out.println("保存用户"); } @Override public void update() { System.out.println("更新用户"); } @Override public void find() { System.out.println("查找用户"); } @Override public void delete() { System.out.println("删除用户"); }}使用JDK产生UserDao的代理类 package com.itzhouq.spring.demo1; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; /* * 使用JDK动态代理对UserDao产生代理 */ public class JDKProxy implements InvocationHandler { // 将被增强的对象传递到代理总 private UserDao userDao; public JDKProxy(UserDao userDao) { this.userDao = userDao; } /* * 产生UserDao代理的方法 */ public UserDao createProxy() { UserDao userDaoProxy = (UserDao) Proxy.newProxyInstance( userDao.getClass().getClassLoader(), userDao.getClass().getInterfaces(), this); return userDaoProxy; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 判断方法名是不是save if("save".equals(method.getName())) { // 增强 System.out.println("权限校验=============="); return method.invoke(userDao, args); } return method.invoke(userDao, args); } }测试类 ...

May 3, 2019 · 5 min · jiezi

BeanFactory

/** * BeanFactory是Spring Bean容器的根接口,是bean容器的基本客户端视图。其他接口类似ListableBeanFactory和ConfigurableBeanFactory可以用于特定的用途。 * 此接口由包含许多bean定义的对象实现,每个bean定义由String名称唯一标识。 * 根据bean定义,工厂将返回包含对象的独立实例(Prototype设计模式),或者单例共享实例(Singleton设计模式的高级替代,其中实例是工厂作用域的单例)。 * 将返回哪种类型的实例取决于bean工厂配置:API是相同的。 从Spring 2.0开始,根据具体的应用程序上下文(例如Web环境中的“request”和“session”作用域),可以使用更多的作用域。 * 这种方法的重点是BeanFactory是应用程序组件的中央注册表,并集中应用程序组件的配置(例如,不再需要单个对象读取属性文件)。 * 请注意,通常最好依靠依赖注入(“推送”配置),通过setter或构造函数来配置应用程序对象,而不是像BeanFactory查找一样使用任何形式的“拉”配置。 Spring的依赖注入功能是使用这个BeanFactory接口及其子接口实现的。 * 通常,BeanFactory将加载存储在配置源(例如XML文档)中的bean定义,并使用code org.springframework.beans包来配置bean。但是,实现类可以直接在Java代码中直接返回它创建的Java对象。对如何存储定义没有限制:LDAP,RDBMS,XML,属性文件等。鼓励实现类支持bean之间的引用(依赖注入)。 * 与ListableBeanFactory中的方法相反,如果是HierarchicalBeanFactory,则此接口中的所有操作也将检查父工厂。如果在此工厂实例中找不到bean,则会询问直接父工厂。此工厂实例中的Bean应该覆盖任何父工厂中同名的Bean。 * * Bean工厂实现应尽可能支持标准bean生命周期接口。 完整的初始化方法及其标准顺序是: * BeanNameAware.setBeanName() * BeanClassLoaderAware.setBeanClassLoader() * BeanFactoryAware.setBeanFactory() * EnvironmentAware.setEnvironment() * EmbeddedValueResolverAware.setEmbeddedValueResolver() * ResourceLoaderAware.setResourceLoader() 仅适用于在应用程序上下文中运行时 * ApplicationEventPublisherAware.setApplicationEventPublisher() 仅适用于在应用程序上下文中运行时 * MessageSourceAware.setMessageSource() 仅适用于在应用程序上下文中运行时 * ApplicationContextAware.setApplicationContext() 仅适用于在应用程序上下文中运行时 * ServletContextAware.setServletContext() 仅适用于在应用程序上下文中运行时 * BeanPostProcessors.postProcessBeforeInitialization()和InitializingBean.afterPropertiesSet() 自定义初始化方法定义 * BeanPostProcessors.postProcessAfterInitialization() * * 关闭bean工厂时,以下生命周期方法适用: * DestructionAwareBeanPostProcessors.postProcessBeforeDestruction() * DisposableBea.destroy 自定义的销毁方法定义 */public interface BeanFactory { /** * 用于取消引用FactoryBean实例,并将其与FactoryBean创建的bean区分开来。例如,如果名为myJndiObject的bean是FactoryBean,则获取&myJndiObject将返回工厂,而不是工厂返回的实例。 */ String FACTORY_BEAN_PREFIX = "&"; /** * 返回指定bean的实例,该实例可以是共享的或独立的。此方法允许Spring BeanFactory用作Singleton或Prototype设计模式的替代。 * 在Singleton bean的情况下,调用者可以保留对返回对象的引用。将别名转换回相应的规范bean名称。将询问父工厂是否在此工厂实例中找不到bean。 * @param 命名要检索的bean的名称 * @return bean的一个实例 * @throws 如果没有具有指定名称的bean,抛出NoSuchBeanDefinitionException * @throws 如果无法获取bean,抛出BeansException */ Object getBean(String name) throws BeansException; /** * 返回指定bean的实例,该实例可以是共享的或独立的。 * 与getBean(String)的行为相同,但如果bean不是所需类型,则通过抛出BeanNotOfRequiredTypeException来提供类型安全性的度量。这意味着在转换结果正确时不能抛出ClassCastException,就像#getBean(String)一样。将别名转换回相应的规范bean名称。 将询问父工厂是否在此工厂实例中找不到bean。 * @param 命名要检索的bean的名称 * @param bean要匹配的类型,可以是接口或是超类 * @return 一个bean实例 * @throws 如果没有具有指定名称的bean,抛出NoSuchBeanDefinitionException * @throws 如果bean不是要求的类型,抛出BeanNotOfRequiredTypeException * @throws 如果bean不能创建,抛出BeansException */ <T> T getBean(String name, Class<T> requiredType) throws BeansException; /** * 返回指定bean的实例,该实例可以是共享的或独立的。 * 允许指定显式构造函数参数/工厂方法参数,覆盖bean定义中指定的默认参数(如果有)。 * @param 命名要检索的bean的名称 * @param args 使用显式参数创建bean实例时使用的参数(仅在创建新实例时应用,而不是在检索现有实例时应用) * @return 一个bean实例 * @throws 如果没有具有指定名称的bean,抛出NoSuchBeanDefinitionException * @throws 如果已经给出了参数但受影响的bean不是Prototype,抛出BeanDefinitionStoreException * @throws 如果bean不能创建,抛出BeansException */ Object getBean(String name, Object... args) throws BeansException; /** * 返回唯一匹配给定对象类型的bean实例(如果有)。此方法进入ListableBeanFactory按类型查找区域,但也可以根据给定类型的名称转换为常规的按名称查找。要跨越多组bean进行更广泛的检索操作,请使用 ListableBeanFactory和/或BeanFactoryUtils。 * @param bean要匹配的类型,可以是接口或是超类 * @return 匹配所需类型的单个bean的实例 * @throws 如果没有找到给定类型的bean,抛出NoSuchBeanDefinitionException * @throws 如果找到多个给定类型的bean,抛出NoUniqueBeanDefinitionException * @throws 如果无法创建bean,抛出BeansException */ <T> T getBean(Class<T> requiredType) throws BeansException; /** * 返回指定bean的实例,该实例可以是共享的或独立的。 * 允许指定显式构造函数参数/工厂方法参数,覆盖bean定义中指定的默认参数(如果有)。此方法进入ListableBeanFactory按类型查找区域,但也可以转换为传统的按名称查找 基于给定类型的名称。对于跨bean集的更广泛的检索操作,请使用ListableBeanFactory和/或BeanFactoryUtils。 * @param requiredType bean要匹配的类型,可以是接口或是超类 * @param args 使用显式参数创建bean实例时使用的参数(仅在创建新实例时应用,而不是在检索现有实例时应用) * @return 一个bean实例 * @throws 如果没有找到给定类型的bean,抛出NoSuchBeanDefinitionException * @throws 如果已经给出了参数但受影响的bean不是Prototype,抛出BeanDefinitionStoreException * @throws 如果无法创建bean,抛出BeansException * @since 4.1 */ <T> T getBean(Class<T> requiredType, Object... args) throws BeansException; /** * 返回指定bean的提供程序,允许对实例进行惰性按需检索,包括可用性和唯一性选项。 * @param requiredType bean要匹配的类型,可以是接口或是超类 * @return 相应的提供者句柄 */ <T> ObjectProvider<T> getBeanProvider(Class<T> requiredType); /** * 返回指定bean的提供程序,允许对实例进行惰性按需检索,包括可用性和唯一性选项。 * @param requiredType bean要匹配的类型。可以是泛型类型声明。请注意,此处不支持集合类型,与反射注入点相反。要以编程方式检索与特定类型匹配的bean列表,请在此处将实际bean类型指定为参数,然后使用 ObjectProvider.orderedStream()或其延迟流/迭代选项。 * @return 相应的提供者句柄 */ <T> ObjectProvider<T> getBeanProvider(ResolvableType requiredType); /** * 此bean工厂是否包含bean定义或具有给定名称的外部注册的singleton实例? * 如果给定的名称是别名,它将被转换回相应的规范bean名称。如果此工厂是分层的,将询问任何父工厂是否在这个工厂实例中找不到bean。如果找到匹配给定名称的bean定义或单例实例,则此方法将返回true。 * 无论命名bean定义是具体的还是抽象的,lazy还是eager的,作用域与否。 因此,请注意此方法的true返回值不一定表示getBean将能够获取同名的实例。 * @param name 查询的bean的名称 * @return 给定名称的bean是否存在 */ boolean containsBean(String name); /** * 该Bean是否是共享的单例? 也就是说, getBean()方法总是返回相同的实例? * 注意:返回false的此方法不能清楚地表明是独立的实例。它表示非单例实例,也可以对应于作用域的bean。使用isPrototype操作显式检查是否是独立的实例。将别名转换回相应的规范bean名称。将询问父工厂是否在此工厂实例中找不到bean。 * @param name 查询的bean的名称 * @return 该bean是否有单例实例 * @throws 如果没有给定名称的Bean,抛出NoSuchBeanDefinitionException */ boolean isSingleton(String name) throws NoSuchBeanDefinitionException; /** * 该bean是否是Prototype? 也就是说,getBean总会返回独立实例吗? * 注意:返回false的此方法不能清楚地指示单个对象。它表示非独立实例,也可以对应于范围内的bean。使用isSingleton操作显式检查共享单例实例。将别名转换回相应的规范bean名称。将询问父工厂是否在此工厂实例中找不到bean。 * @param name 查询的bean的名称 * @return 这个bean是否总是提供独立的实例 * @throws 如果没有给定名称的Bean,抛出NoSuchBeanDefinitionException */ boolean isPrototype(String name) throws NoSuchBeanDefinitionException; /** * 检查具有给定名称的bean是否与指定的类型匹配。更具体地说,检查对给定名称的getBean调用是否将返回可分配给指定目标类型的对象。将别名转换回相应的规范bean的名称.将询问父工厂是否在此工厂实例中找不到bean。 * @param name 查询bean的名称 * @param typeToMatch 要匹配的类型 * @return 如果Bean类型匹配,返回true;如果bean类型不匹配或不确定,返回false。 * @throws 如果没有给定名称的Bean,抛出NoSuchBeanDefinitionException */ boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException; /** * 检查具有给定名称的bean是否与指定的类型匹配。更具体地说,检查对给定名称的getBean调用是否将返回可分配给指定目标类型的对象。将别名转换回相应的规范bean的名称.将询问父工厂是否在此工厂实例中找不到bean。 * @param name 查询bean的名称 * @param typeToMatch 要匹配的类型 * @return 如果Bean类型匹配,返回true;如果bean类型不匹配或不确定,返回false。 * @throws 如果没有给定名称的Bean,抛出NoSuchBeanDefinitionException */ boolean isTypeMatch(String name, Class<?> typeToMatch) throws NoSuchBeanDefinitionException; /** * 确定给定名称的bean的类型。 进一步来说,确定getBean方法为给定bean名称返回的对象类型。对于FactoryBean,返回FactoryBean创建的对象类型,由FactoryBean.getObjectType()公开。将别名转换回相应的规范bean名称。将询问父工厂是否在此工厂实例中找不到bean。 * @param name 查询的bean的名称 * @return bean的类型, 或者不可确定返回null。 * @throws 如果没有给定名称的Bean,抛出NoSuchBeanDefinitionException */ @Nullable Class<?> getType(String name) throws NoSuchBeanDefinitionException; /** * 返回给定bean名称的别名(如果有)。所有这些别名在getBean调用中使用时指向同一个bean。 * 如果给定名称是别名,则对应原始bean名称和其他别名( 如果有的话)将返回,原始bean名称是数组中的第一个元素。将询问父工厂是否在此工厂实例中找不到bean。 * @param name 用于检查别名的bean名称 * @return 别名,如果没有,则为空数组 */ String[] getAliases(String name);}

May 3, 2019 · 2 min · jiezi

AliasRegistry

/** * 用来管理别名的公共接口 */public interface AliasRegistry { /** * 注册别名 * 如果别名已经存在不会覆盖原来的,并抛出IllegalStateException。 */ void registerAlias(String name, String alias); /** * 删除别名 * 如果没有找到对应的别名,抛出IllegalStateException。 */ void removeAlias(String alias); /** * 判断给定的名称是否被定义为别名 */ boolean isAlias(String name); /** * 返回给定名称的别名,如果定义了。 */ String[] getAliases(String name);}

May 3, 2019 · 1 min · jiezi

SimpleAliasRegistry

public class SimpleAliasRegistry implements AliasRegistry { protected final Log logger = LogFactory.getLog(getClass()); //别名集合 private final Map<String, String> aliasMap = new ConcurrentHashMap<>(16); @Override public void registerAlias(String name, String alias) { Assert.hasText(name, "'name' must not be empty"); Assert.hasText(alias, "'alias' must not be empty"); //对别名集合进行同步 synchronized (this.aliasMap) { //如果别名等于名称,从别名集合中删除别名 if (alias.equals(name)) { this.aliasMap.remove(alias); if (logger.isDebugEnabled()) { logger.debug("Alias definition '" + alias + "' ignored since it points to same name"); } } else { //判断别名是否注册过 String registeredName = this.aliasMap.get(alias); //如果给指定的名称注册过相同的别名,就直接返回。 if (registeredName != null) { if (registeredName.equals(name)) { // An existing alias - no need to re-register return; } //别名是否允许被覆盖 if (!allowAliasOverriding()) { throw new IllegalStateException("Cannot define alias '" + alias + "' for name '" + name + "': It is already registered for name '" + registeredName + "'."); } if (logger.isDebugEnabled()) { logger.debug("Overriding alias '" + alias + "' definition for registered name '" + registeredName + "' with new target name '" + name + "'"); } } //循环检测 checkForAliasCircle(name, alias); //注册别名 this.aliasMap.put(alias, name); if (logger.isTraceEnabled()) { logger.trace("Alias definition '" + alias + "' registered for name '" + name + "'"); } } } } /** * 返回别名是否允许覆盖 */ protected boolean allowAliasOverriding() { return true; } /** * 判断给定的名称是否已经注册给定的别名 */ public boolean hasAlias(String name, String alias) { for (Map.Entry<String, String> entry : this.aliasMap.entrySet()) { String registeredName = entry.getValue(); if (registeredName.equals(name)) { String registeredAlias = entry.getKey(); if (registeredAlias.equals(alias) || hasAlias(registeredAlias, alias)) { return true; } } } return false; } /** * 移除别名 */ @Override public void removeAlias(String alias) { //对别名集合进行同步 synchronized (this.aliasMap) { String name = this.aliasMap.remove(alias); if (name == null) { throw new IllegalStateException("No alias '" + alias + "' registered"); } } } /** * 判断给定的名称是否是别名 */ @Override public boolean isAlias(String name) { return this.aliasMap.containsKey(name); } /** * 获取给定名称的所有别名 */ @Override public String[] getAliases(String name) { List<String> result = new ArrayList<>(); synchronized (this.aliasMap) { retrieveAliases(name, result); } return StringUtils.toStringArray(result); } /** * 获得给定名称的所有别名 */ private void retrieveAliases(String name, List<String> result) { this.aliasMap.forEach((alias, registeredName) -> { if (registeredName.equals(name)) { result.add(alias); retrieveAliases(alias, result); } }); } /** * */ public void resolveAliases(StringValueResolver valueResolver) { Assert.notNull(valueResolver, "StringValueResolver must not be null"); synchronized (this.aliasMap) { Map<String, String> aliasCopy = new HashMap<>(this.aliasMap); aliasCopy.forEach((alias, registeredName) -> { String resolvedAlias = valueResolver.resolveStringValue(alias); String resolvedName = valueResolver.resolveStringValue(registeredName); if (resolvedAlias == null || resolvedName == null || resolvedAlias.equals(resolvedName)) { this.aliasMap.remove(alias); } else if (!resolvedAlias.equals(alias)) { String existingName = this.aliasMap.get(resolvedAlias); if (existingName != null) { if (existingName.equals(resolvedName)) { // Pointing to existing alias - just remove placeholder this.aliasMap.remove(alias); return; } throw new IllegalStateException( "Cannot register resolved alias '" + resolvedAlias + "' (original: '" + alias + "') for name '" + resolvedName + "': It is already registered for name '" + registeredName + "'."); } checkForAliasCircle(resolvedName, resolvedAlias); this.aliasMap.remove(alias); this.aliasMap.put(resolvedAlias, resolvedName); } else if (!registeredName.equals(resolvedName)) { this.aliasMap.put(alias, resolvedName); } }); } } /** * */ protected void checkForAliasCircle(String name, String alias) { if (hasAlias(alias, name)) { throw new IllegalStateException("Cannot register alias '" + alias + "' for name '" + name + "': Circular reference - '" + name + "' is a direct or indirect alias for '" + alias + "' already"); } } /** * */ public String canonicalName(String name) { String canonicalName = name; // Handle aliasing... String resolvedName; do { resolvedName = this.aliasMap.get(canonicalName); if (resolvedName != null) { canonicalName = resolvedName; } } while (resolvedName != null); return canonicalName; }}

May 3, 2019 · 3 min · jiezi

SingletonBeanRegistry

/** * 以接口的方式,定义注册表共享bean实例。可以被org.springframework.beans.factory.BeanFactory实现类实现,用统一的方式暴露它们的单例。 */public interface SingletonBeanRegistry { /** * 基于给定的bean名称,在bean注册表中注册给定的存在的对象作为单例。 * 给定的实例应该完全初始化,注册表不会执行任何初始化回调(特别地,不会调用InitializingBean的afterPropertiesSet的方法)。给定的实例不会接收任何破坏回调(像DisposableBean的destroy方法)。 * 在完整的BeanFactory中运行时:如果bean应该接收初始化和/或销毁回调,则注册bean定义而不是现有实例。 * 通常在注册表配置期间调用,但也可用于单例的运行时注册。 因此,注册表实现应该同步单例访问; 如果它支持BeanFactory对单例的延迟初始化,它将无论如何都必须这样做。 * @param beanName 实例名称 * @param singletonObject 存在的单例对象 */ void registerSingleton(String beanName, Object singletonObject); /** * 返回在给定名称下注册的(原始)单例对象。 * 只检查已经实例化的单例;但是不返回没有实例化的单例bean对象。 * 这个方法的主要目的是去访问手动注册的单例。 * 也可以用来以原始方式访问已经创建的bean定义定义的单例。注意:此查找方法不知道FactoryBean前缀或别名。在获取单例之前,需要首先解析规范bean名称实例。 * @param beanName 查找的bean的名称 * @return 返回注册的单例对象,或者Null。 * @return the registered singleton object, or {@code null} if none found */ @Nullable Object getSingleton(String beanName); /** * 检查注册表中是否包含给定名称的单例实例。 * 只检查已经实例化的单例,对没有实例化的单例bean定义不返回true。 * 这个方法的主要目的是去检查手动注册的单例。 * 也可以用来检查一个单例是否由已经创建的bean定义所定义的。 * 调用ListableBeanFactory的containsBeanDefinition方法检查一个bean工厂是否包含给定名字的bean定义。调用containsBeanDefinition和containsSingleton回答一个指定的bean工厂是否包括给定名称的本地bean实例。 * 使用BeanFactory的containsBean进行常规检查,bean工厂是否知道具有给定名称的Bean(无论是手动注册的单例bean或是由bean定义创建的)。也检查祖先工厂。 * 注意:此查找方法不知道FactoryBean前缀或别名。在检查单例状态之前,您需要首先解析规范bean名称。 * @param beanName 查找的bean的名称 * @return 返回bean工厂是否包含给定名称的单例实例。 */ boolean containsSingleton(String beanName); /** * 返回在此注册表中注册的单例bean的名称。仅检查已实例化的单例; 不返回尚未实例化的单例bean定义的名称。此方法的主要目的是检查手动注册的单例。也可以被用来检查由已经创建的bean定义所定义的单例。 * @return 返回名字集合作为字符串数组 */ String[] getSingletonNames(); /** * 返回在此注册表中注册的单例bean的数量。仅检查已实例化的单例;不计算尚未实例化的单例bean定义。此方法的主要目的是检查手动注册的单例。也可以被用来计算由已经创建的bean定义所定义的单例的数量。 * @return 返回单例bean的数量 */ int getSingletonCount(); /** * 返回此注册表使用的单例互斥锁(适用于外部协作者)。 * @return 返回互斥对象,不返回Null。 * @since 4.2 */ Object getSingletonMutex();}

May 3, 2019 · 1 min · jiezi

从零开始搭建SSM框架Spring-Spring-MVC-Mybatis

最近在回顾和总结一些技术,想到了把之前比较火的 SSM 框架重新搭建出来,作为一个小结,同时也希望本文章写出来能对大家有一些帮助和启发,因本人水平有限,难免可能会有一些不对之处,欢迎各位大神拍砖指教,共同进步。 本文章示例使用 IntelliJ IDEA 来开发,JDK 使用 11 版本,其余各框架和技术基本上使用了文章撰写当时的最新版本。 好的,下面直接进入正题。 打开 IntelliJ IDEA,File > New > Project > Maven,选中“Create from archetype”,然后再选中“org.apache.maven.archetypes:maven-archetype-webapp”: Next,输入项目的“GroupId”、“ArtifactId”和Version: Next,指定“Maven home directory”等配置: Next,修改Project Name: Finish,打开项目,添加一些必要的目录,最终项目框架目录图如下: 修改pom.xml文件,指定各依赖和插件的版本等信息: <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <java.version>11</java.version> <maven.compiler.source>11</maven.compiler.source> <maven.compiler.target>11</maven.compiler.target> <spring.version>5.1.6.RELEASE</spring.version> <junit.version>4.12</junit.version> <lombok.version>1.18.6</lombok.version> <mybatis-plus.version>3.1.1</mybatis-plus.version> <freemarker.version>2.3.28</freemarker.version> <druid.version>1.1.16</druid.version> <jsqlparser.version>2.0</jsqlparser.version> <mysql-connector.version>8.0.16</mysql-connector.version> <jstl-api.version>1.2</jstl-api.version> <servlet-api.version>4.0.1</servlet-api.version> <jsp-api.version>2.3.3</jsp-api.version> <springfox-swagger.version>2.9.2</springfox-swagger.version> <commons-lang3.version>3.9</commons-lang3.version> <jackson.version>2.9.8</jackson.version> <mapstruct.version>1.3.0.Final</mapstruct.version> <log4j.version>2.11.2</log4j.version> <slf4j.version>1.7.26</slf4j.version> <clean.plugin.version>3.1.0</clean.plugin.version> <resources.plugin.version>3.1.0</resources.plugin.version> <compiler.plugin.version>3.8.0</compiler.plugin.version> <surefire.plugin.version>3.0.0-M3</surefire.plugin.version> <war.plugin.version>3.2.2</war.plugin.version> <install.plugin.version>3.0.0-M1</install.plugin.version> <deploy.plugin.version>3.0.0-M1</deploy.plugin.version></properties>在<dependencyManagement>标签里面管理各依赖的版本号: <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>${spring.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>${junit.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>${lombok.version}</version> <scope>provided</scope> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus</artifactId> <version>${mybatis-plus.version}</version> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-generator</artifactId> <version>${mybatis-plus.version}</version> <scope>test</scope> <optional>true</optional> </dependency> <dependency> <groupId>org.freemarker</groupId> <artifactId>freemarker</artifactId> <version>${freemarker.version}</version> <scope>test</scope> <optional>true</optional> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>${druid.version}</version> </dependency> <dependency> <groupId>com.github.jsqlparser</groupId> <artifactId>jsqlparser</artifactId> <version>${jsqlparser.version}</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>${mysql-connector.version}</version> </dependency> <dependency> <groupId>javax.servlet.jsp.jstl</groupId> <artifactId>jstl-api</artifactId> <version>${jstl-api.version}</version> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>${servlet-api.version}</version> <scope>provided</scope> </dependency> <dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>javax.servlet.jsp-api</artifactId> <version>${jsp-api.version}</version> <scope>provided</scope> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>${springfox-swagger.version}</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>${springfox-swagger.version}</version> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>${commons-lang3.version}</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>${jackson.version}</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-annotations</artifactId> <version>${jackson.version}</version> <scope>compile</scope> </dependency> <dependency> <groupId>org.mapstruct</groupId> <artifactId>mapstruct</artifactId> <version>${mapstruct.version}</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>${slf4j.version}</version> </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-slf4j-impl</artifactId> <version>${log4j.version}</version> </dependency> </dependencies></dependencyManagement>添加项目依赖: ...

May 3, 2019 · 8 min · jiezi

如何在低版本的-Spring-中快速实现类似自动配置的功能

感谢您的阅读,本文由 杨斌的博客 版权所有。如若转载,请注明出处:杨斌的博客(https://y0ngb1n.github.io/a/c...) 在 Spring 4 后才引入了 @Conditional 等条件注解,它是 Spring Boot 中实现自动配置的最大功臣!那么问题来了:如果我们还在使用 Spring 3.x 的老版本,这时候要怎么实现一个自动配置呢?代码托管于 GitHub,欢迎 Star 需求和问题核心的诉求现存系统,不打算重构Spring 版本为 3.x,也不打算升级版本和引入 Spring Boot期望能够在少改代码的前提下实现功能增强比如说: 希望能够给全站统一添加上日志记录(如:RPC 框架 Web 调用的摘要信息、数据库访问层的摘要信息),这个其实是个通用的功能。我们引用了一些基础设施,并想对这些基础设施的功能作进一步的增强,这时候就应该从框架的层面来解决这个问题。面临的问题3.x 的 Spring 没有条件注解因为没有条件注解,所以我们不清楚在什么时候 需要/不需要 配置这些东西 无法自动定位需要加载的自动配置此时我们没有办法像 Spring Boot 的自动配置那样让框架自动加载我们的配置,我们要使用一些别的手段让 Spring 可以加载到我们定制的这些功能。 核心解决思路条件判断通过 BeanFactoryPostProcessor 进行判断Spring 为我们提供了一个扩展点,我们可以通过 BeanFactoryPostProcessor 来解决条件判断的问题,它可以让我们在 BeanFactory 定义完之后、Bean 的初始化之前对我们这些 Bean 的定义做一些后置的处理。可以在这个时候对我们的 Bean 定义做判断,看看当前 存在/缺少 哪些 Bean 的定义,还可以增加一些 Bean 的定义 —— 加入一些自己定制的 Bean。 配置加载编写 Java Config 类引入配置类 通过 component-scan通过 XML 文件 import可以考虑编写自己的 Java Config 类,并把它加到 component-scan 里面,然后想办法让现在系统的 component-scan 包含我们编写的 Java Config 类;也可以编写 XML 文件,如果当前系统使用 XML 的方式,那么它加载的路径上是否可以加载我们的 XML 文件,如果不行就可以使用手动 import 这个文件。 ...

May 3, 2019 · 3 min · jiezi

Spring笔记02注解IOC

1. Spring整合连接池1.1 Spring整合C3P0在工程中导入c3p0连接池需要的包com.springsource.com.mchange.v2.c3p0-0.9.1.2.jarc3p0的硬编码方式 @Test //自己new对象,自己设置属性 public void test() throws Exception { ComboPooledDataSource dataSource = new ComboPooledDataSource(); //设置驱动 dataSource.setDriverClass("com.mysql.jdbc.Driver"); //设置地址 dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/hibernate"); //设置用户名 dataSource.setUser("root"); //设置密码 dataSource.setPassword("2626"); //获取链接池连接对象 Connection con = dataSource.getConnection(); System.out.println(con); //com.mchange.v2.c3p0.impl.NewProxyConnection@26ba2a48 }Spring整合c3p0连接池配置文件 <!-- c3p0 --> <bean id="C3P0" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="com.mysql.jdbc.Driver"></property> <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/hibernate"></property> <property name="user" value="root"></property> <property name="password" value="2626"></property> </bean>测试 @Test //Spring的IOC+DI替代以上硬编码的方式 public void test2() throws SQLException { ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); DataSource dataSource = (DataSource) context.getBean("C3P0"); Connection con = dataSource.getConnection(); System.out.println(con); //com.mchange.v2.c3p0.impl.NewProxyConnection@52aa2946 }1.2 Spring整合DBCP导入DBCP连接池需要的包com.springsource.org.apache.commons.dbcp-1.2.2.osgi.jar和com.springsource.org.apache.commons.pool-1.5.3.jarDBCP硬编码方式 ...

May 2, 2019 · 2 min · jiezi

Cglib-和-Mica-Bean-copy-生成字节码对比

1. 前言距离上上篇【mica cglib 增强——【01】cglib bean copy 介绍】 已经过去一个月八一天。 距离上一篇【Java Bean Copy 性能大比拼】 已过去一个月零一天。 督促自己早日完成整个系列的文章,今天我将带领大家从字节码的层面来分析。 注:对于java 字节码感兴趣的朋友也可以阅读 《Java虚拟机规范》,Oracle 官方也有英文原版的 pdf可供下载。 2. Bean 模型我们列举2个模型 User 和 UserVo,注意:birthday 字段类型不一样(敲黑板)。 2.1 User@Datapublic class User { private Integer id; private String name; private Integer age; private LocalDateTime birthday;}2.2 UserVo@Datapublic class UserVo { private String name; private Integer age; private String birthday;} 3. Cglib Bean copy 字节码分析 3.1 配置 Cglib debug 模式在第一篇【mica cglib 增强——【01】cglib bean copy 介绍】我们提到可以设置 cglib 源码生成目录。 ...

April 30, 2019 · 3 min · jiezi

Spring笔记01下载概述监听器

1.Spring介绍1.1 Spring概述Spring是一个开源框架,Spring是于2003 年兴起的一个轻量级的Java 开发框架,由Rod Johnson 在其著作Expert One-On-One J2EE Development and Design中阐述的部分理念和原型衍生而来。它是为了解决企业应用开发的复杂性而创建的。Spring使用基本的JavaBean来完成以前只可能由EJB完成的事情。然而,Spring的用途不仅限于服务器端的开发。从简单性、可测试性和松耦合的角度而言,任何Java应用都可以从Spring中受益。简单来说,Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器框架。 1.2 Spring好处方便解耦,简化开发 Spring就是一个大工厂,专门负责生成Bean,可以将所有对象创建和依赖关系维护由Spring管理。通过Spring提供的IoC容器,可以将对象间的依赖关系交由Spring进行控制,避免硬编码所造成的过度程序耦合。用户也不必再为单例模式类、属性文件解析等这些很底层的需求编写代码,可以更专注于上层的应用。AOP变成的支持 Spring提供面向切面编程,可以方便的实现对程序进行权限拦截,运行监控等功能。通过Spring的AOP功能,方便进行面向切面的编程,许多不容易用传统OOP实现的功能可以通过AOP轻松应付。声明式事务的支持 只需要通过配置就可以完成对事务的管理,而无需手动编程方便程序的测试 Spring对Junit4支持,可以通过注解方便的测试Spring程序方便集成各种优秀框架 Spring不排斥各种优秀的开源框架,其内部提供了对各种优秀框架的支持,如Struts、Hibernate、Mybatis、Quartz等降低JavaEE API的使用难度 对JavaEE开发中一些难用的API(JDBC、JavaMail、远程调用等),都提供了封装,使这些API应用难度大大降低Java源码是经典学习范例 Spring的源代码设计精妙、结构清晰、匠心独用,处处体现着大师对Java设计模式灵活运用以及对Java技术的高深造诣。它的源代码无意是Java技术的最佳实践的范例。1.3 Spring结构体系Spring框架是一个分层架构,它包含一系列的功能要素并被分为大约20个模块。这些模块分为Core Container、DataAccess/Integration、Web、AOP、Instrumentation和测试部分。如下图所示: 1.4 在项目中的架构web层:Struts,SpringMVCdao层:Hibernate,Mybatis 1.5 程序的耦合和解耦程序的耦合 我们开发种,会写很多的类,而有些类之间不可避免地产生依赖关系,这种依赖关系称为耦合。有些依赖是必须的,有些依赖关系可以通过优化代码来解除。比如:/** * 客户的业务层实现类 */public class CustomerServiceImpl implements ICustomerService { private ICustomerDao customerDao = new CustomerDaoImpl(); }上面的代码表示:业务调用持久层,并且此时业务再依赖持久层的接口和实现类。如果此时没有持久层实现类,编译将不能通过。这种依赖关系就是我们可以通过优化代码解决的。再比如:下面的代码种,我们的类依赖MySQL的具体驱动类,如果这狮虎更换了数据库品牌,我们需要改源码来修修改数据库驱动,这显然不是我们想要的。public class JdbcDemo1 { /** * JDBC操作数据库的基本入门中存在什么问题? * 导致驱动注册两次是个问题,但不是严重的。 * 严重的问题:是当前类和mysql的驱动类有很强的依赖关系。 * 当我们没有驱动类的时候,连编译都不让。 * 那这种依赖关系,就叫做程序的耦合 * * 我们在开发中,理想的状态应该是: * 我们应该尽力达到的:编译时不依赖,运行时才依赖。 * * @param args * @throws Exception */ public static void main(String[] args) throws Exception { //1.注册驱动 //DriverManager.registerDriver(new com.mysql.jdbc.Driver()); Class.forName("com.mysql.jdbc.Driver"); //2.获取连接 //3.获取预处理sql语句对象 //4.获取结果集 //5.遍历结果集 }解决程序耦合的思路 ...

April 30, 2019 · 6 min · jiezi

配置-spring-boot-2X-支持-prometheus-metrics

前言实际项目中,提供metrics接口,对接公司的监控系统,增加服务的可观察性,是一个基本的要求。在spring boot 1.X 中集成prometheus metrics,非常简单。但是spring boot 2.X 颇费周折。因为prometheus官方提供的prometheus-client-java不兼容spring boot 2.X 。需要借助micrometer。 步骤1:引入所需的包 在pom.xml文件中增加如下: <!-- Spring boot actuator to expose metrics endpoint --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <!-- Micormeter core dependecy --> <dependency> <groupId>io.micrometer</groupId> <artifactId>micrometer-core</artifactId> </dependency> <dependency> <groupId>io.micrometer</groupId> <artifactId>micrometer-registry-prometheus</artifactId> </dependency>2: 增加相关配置 在 application.yml中增加如下设置: management: endpoints: web: exposure: include: ["metrics","prometheus"] endpoint: metrics: enabled: true prometheus: enabled: true metrics: export: prometheus: enabled: truePS: 如果想获取其他的metrics,可以设置include: ["*"] 3:运行查看metrics 运行项目,访问 http://localhost:8090/actuator,可看到如下: ...

April 30, 2019 · 4 min · jiezi

mapper4与springboot的简单整合

最近自己在网上搜索一些关于mapper4的教程,一直都没有找到简单明了的,所以就只能自己写一篇初级入门的mapper4与当下最火的springboot的整合。1.首先我们需要用IDEA工具新建一个springboot的项目。 Group和Artfact需要自己进行填写,否则就是默认的。 选择Web和MySQL 然后点击下一步完成就好了。 项目建好之后的结构如下所示,需要将application.properties改名为application.yml。 2.需要在maven里面添加相关的依赖。<!-- 添加通用 Mapper 提供的 starter --><dependency> <groupId>tk.mybatis</groupId> <artifactId>mapper-spring-boot-starter</artifactId> <version>2.1.5</version></dependency><!-- 添加lombok插件 --><dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.6</version> <scope>provided</scope></dependency>3.application配置文件进行相关设置。#端口号server: port: 8088spring: #数据库连接数据配置 datasource: url: jdbc:mysql://localhost:3306/mapper-test username: root password: 123456mybatis: #驼峰命名法 configuration: map-underscore-to-camel-case: true #配置mybatis的全局配置文件 mapper-locations: classpath:mapping/*.xml#sql语句的打印logging: level: com: mapper4: www: debug4.需要在Spring Boot 的启动类上用@MapperScan 注解进行配置。@tk.mybatis.spring.annotation.MapperScan(basePackages = "扫描包") 5.新建一个Girl的实体类,并将其放到entity包中。 用lombok的@Data注解,这样就可以省略掉get/set等方法。 6.新建一个GirlMapper接口类,并将其放入到mapper包中。 继承BaseMapper<实体类>类。 7.新建一个GirlController类,将其放到controller中。 写一个根据id查询数据的方法。 8.用postman进行接口的调用你就会发现可以成功的查询出相关的数据了。拓展如果你想要自己写一些sql语句进行查询,不想使用mapper4自带的方法的话,那你就需要自己写一个*mapper.xml。这里我们简单的写一个*mapper.xml进行查询。其实我们在application.yml里面已经进行了相关的配置了。 这样程序就会自动的去这个目录下面去扫描相关的xml进行关联了。 我们需要在resources里面新建一个mapping文件夹,里面来存放我们写的*mapper.xml文件 需要在GirlMapper.xml里面添加一个新的查询SQL。 在GirlMapper类中添加这个方法,然后就可以在GirlController里面进行调用了。 在GirlController里面添加相关的方法。 进行测试就可以了,发现也是可以的,至此我们就完成了springboot与mapper4的简单集成。

April 30, 2019 · 1 min · jiezi

Spring-Cloud-Zuul-1x的websocket支持实践

一、组件Spring Cloud Netflix Edgware.SR3spring-cloud-starter-zuul 1.3.5.RELEASEnginx 1.14.0Vue + SockJS + STOMP 二、实现• 下游服务添加websocket支持,包括依赖、配置和使用• zuul添加spring-cloud-netflix-zuul-websocket依赖• zuul配置文件添加如下配置:下游使用websocket的服务和对应的websocket设置zuul: ws: brokerages: 下游使用websocket的服务名: end-points: /endpoint brokers: /brokers destination-prefixes:• zuul添加websocket配置类@Configurationpublic class WebsokcetConfig { @Bean public ZuulPropertiesResolver zuulPropertiesResolver() { return wsBrokerage -> discoveryClient.getInstances(wsBrokerage.getId()).stream().findAny() .orElseGet(() -> new DefaultServiceInstance("", "", 0, false)) .getUri().toString(); } @Bean public WebSocketHttpHeadersCallback webSocketHttpHeadersCallback() { return userAgentSession -> new WebSocketHttpHeaders() {{ add("Authorization", new CustomBasicAuthRequestInterceptor(username, password).encodeBase64()); }}; }}• 前端使用sockJs和stomp连接网关的websocketfunction connect() { var socket = new SockJS('http://zuul的地址/endpoint', null, { 'transports': ['websocket'] }); stompClient = Stomp.over(socket); stompClient.connect({}, function (frame) { setConnected(true); console.log('Connected: ' + frame); // 订阅用户消息通知 stompClient.subscribe("/brokers",handleNotification); }); function handleNotification(message) { showGreeting(message); }}• nginx添加对websocket的支持 location /gateway { //添加如下配置 proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $host:$server_port; }

April 30, 2019 · 1 min · jiezi

Spring-Cloud-参考文档Spring-Cloud-Sleuth抽样

Spring Cloud Sleuth抽样采样可用于减少收集和报告的进程外数据,如果未对span进行抽样,则不会增加任何开销(noop)。 抽样是一个前期决策,这意味着报告数据的决定是在trace中的第一个操作中做出的,并且该决策是向下游传播的。 默认情况下,全局抽样器将单个速率应用于所有跟踪的操作,Tracer.Builder.sampler控制此设置,默认为跟踪每个请求。 声明性抽样某些应用程序需要根据java方法的类型或注解进行采样。 大多数用户使用框架拦截器来自动执行此类策略,以下示例显示了内部可能如何工作: @Autowired Tracer tracer;// derives a sample rate from an annotation on a java methodDeclarativeSampler<Traced> sampler = DeclarativeSampler.create(Traced::sampleRate);@Around("@annotation(traced)")public Object traceThing(ProceedingJoinPoint pjp, Traced traced) throws Throwable { // When there is no trace in progress, this decides using an annotation Sampler decideUsingAnnotation = declarativeSampler.toSampler(traced); Tracer tracer = tracer.withSampler(decideUsingAnnotation); // This code looks the same as if there was no declarative override ScopedSpan span = tracer.startScopedSpan(spanName(pjp)); try { return pjp.proceed(); } catch (RuntimeException | Error e) { span.error(e); throw e; } finally { span.finish(); }}定制抽样根据操作的不同,末可能希望应用不同的策略,例如,你可能不希望跟踪对静态资源(如图像)的请求,或者你可能希望跟踪对新api的所有请求。 ...

April 30, 2019 · 1 min · jiezi

Spring-IOC源码深度解析

IOC的核心就是代码入口就在AbstractApplictionContext public void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { // 在刷新容器前进行一些准备工作,例如设置容器的激活状态,校验容器环境所必须的启动参数 prepareRefresh(); // 刷新内部的BeanFactory,获得一个新鲜的BeanFactory,这里面主要是读取XML文件,将其转换为BeanDefinition ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); // 对BeanFactory进行进一步的完善,包括注册应用上下文感知以及监听器感知的BeanPostProcessor, // 先注册一些系统的环境Bean prepareBeanFactory(beanFactory); try { // 给子类在初始化BeanFactory重写修改变动beanFactory的权利 postProcessBeanFactory(beanFactory); // 调用BeanFactoryPostProcessor,可以修改beanFactory,与上面不同的是,这里是类似插件的形式,耦合度更低 invokeBeanFactoryPostProcessors(beanFactory); // 提前注册BeanPostProcessor,用于后期提供代理等功能 registerBeanPostProcessors(beanFactory); // 初始化消息源,用于国际化 initMessageSource(); // 初始化应用事件广播器,用于广播应用上下文事件 initApplicationEventMulticaster(); // Initialize other special beans in specific context subclasses. onRefresh(); // 为应用事件广播器初始化监听器(ApplicationListener) registerListeners(); // 实例化并注册所有非懒加载的bean finishBeanFactoryInitialization(beanFactory); // 刷新容器后的额外工作,初始化生命周期执行器,发布容器刷新完毕的应用上下文事件 finishRefresh(); } catch (BeansException ex) { if (logger.isWarnEnabled()) { logger.warn("Exception encountered during context initialization - " + "cancelling refresh attempt: " + ex); } // Destroy already created singletons to avoid dangling resources. destroyBeans(); // Reset 'active' flag. cancelRefresh(ex); // Propagate exception to caller. throw ex; } finally { // 清除掉不再需要的缓存,节省空间 resetCommonCaches(); } } }未完待续。。。 ...

April 29, 2019 · 1 min · jiezi

开源-js-在线编程答题系统

前言:开源一套javascript的在线编程答题系统。技术架构:前端: Vue后端: Spring Boot数据库: Mysql持久层框架: Mybatis缓存存储: redis项目部署: docker部分截图: 项目演示: http://xcoding.me 项目前端: https://github.com/Zo3i/CodeJsFront 项目后端: https://github.com/Zo3i/CodeJsSystem 线上部署安装dockerwget https://raw.githubusercontent.com/Zo3i/OCS/master/docker/dockerInstall.sh && chmod +x dockerInstall.sh && ./dockerInstall.sh安装gityum install git一键部署wget https://raw.githubusercontent.com/Zo3i/CodeJsSystem/0040aa0b50f950f6bac160b81dced0a260ddac0b/web/src/main/docker/dockerRun.sh && chmod +x dockerRun.sh && ./dockerRun.sh完成部署访问前端:ip: 80 访问后端: ip: 8090/jsweb 账号/密码: system/admin

April 29, 2019 · 1 min · jiezi

XUpdate-整套的Android全量版本更新解决方案

XUpdate是一套基于Android的全量版本更新整体解决方案。他除了提供了Android SDK外,还附带了Spring Boot搭建的后台服务以及Vue.js编写的后台管理界面。 ## 为什么选择XUpdate使用简单,只需一行代码即可完成版本更新功能。功能强大,兼容Android6.0、7.0、8.0,支持静默更新和自动更新,支持国际化。扩展性强,可自定义请求API接口、提示弹窗、下载服务等。搭建简单,只需提供json内容即可支持版本更新。配套齐全,默认提供了后台服务和管理界面。资源链接使用说明文档XUpdate版本更新库后台服务后台管理界面效果演示Android版本更新界面 后台管理界面 下载体验

April 29, 2019 · 1 min · jiezi

Spring-Cloud-参考文档Spring-Cloud-Sleuth特性

Spring Cloud Sleuth特性将trace和span ID添加到Slf4J MDC,因此你可以在日志聚合器中从给定的trace或span提取所有日志,如以下示例日志中所示: 2016-02-02 15:30:57.902 INFO [bar,6bfd228dc00d216b,6bfd228dc00d216b,false] 23030 --- [nio-8081-exec-3] ...2016-02-02 15:30:58.372 ERROR [bar,6bfd228dc00d216b,6bfd228dc00d216b,false] 23030 --- [nio-8081-exec-3] ...2016-02-02 15:31:01.936 INFO [bar,46ab0d418373cbc9,46ab0d418373cbc9,false] 23030 --- [nio-8081-exec-4] ...请注意MDC中的[appname,traceId,spanId,exportable]条目: spanId:发生的特定操作的ID。appname:记录span的应用程序的名称。traceId:包含span的延迟图的ID。exportable:是否应将日志导出到Zipkin,你希望什么时候span不能导出?如果要在Span中包装某些操作并将其写入日志中。提供对常见分布式追踪数据模型的抽象:trace、span(形成DAG)、annotation和键值annotation,Spring Cloud Sleuth基于HTrace,但与Zipkin(Dapper)兼容。Sleuth记录时间信息以帮助进行延迟分析,通过使用sleuth,你可以查明应用程序中的延迟原因。Sleuth不进行过多的日志记录,并且不会导致生产应用程序崩溃,为此,Sleuth: 在带内传播有关你的调用图的结构数据,在带外休息。包括层的自定义插装,比如HTTP。包括用于管理卷的采样策略。可以向Zipkin系统报告用于查询和可视化。仪器从Spring应用程序中常见的入口和出口点(servlet过滤器、异步端点、rest模板,调度操作,消息通道,Zuul过滤器和Feign客户端)。Sleuth包含默认逻辑,用于跨HTTP或消息传递边界连接trace,例如,HTTP传播适用于与Zipkin兼容的请求headers。Sleuth可以在进程之间传播上下文(也称为baggage),因此,如果你在Span上设置baggage元素,则会通过HTTP或消息传递向下发送到其他进程。提供创建或继续span以及通过annotations添加标记和日志的方法。如果spring-cloud-sleuth-zipkin位于类路径上,则应用程序会生成并收集与Zipkin兼容的trace,默认情况下,它通过HTTP将它们发送到localhost上的Zipkin服务器(端口9411),你可以通过设置spring.zipkin.baseUrl来配置服务的位置。 如果你依赖spring-rabbit,你的应用程序会将trace发送到RabbitMQ代理而不是HTTP。如果你依赖spring-kafka,并设置spring.zipkin.sender.type:kafka,你的应用程序会将trace发送到Kafka代理而不是HTTP。 spring-cloud-sleuth-stream已弃用,不应再使用。Spring Cloud Sleuth兼容OpenTracing。 如果使用Zipkin,请通过设置spring.sleuth.sampler.probability来配置导出的span概率(默认值:0.1,即10%),否则,你可能会认为Sleuth没有工作,因为它忽略了一些span。始终设置SLF4J MDC,并且logback用户可以根据前面显示的示例立即在日志中看到trace和span ID,其他日志记录系统必须配置自己的格式化程序才能获得相同的结果,默认值如下:logging.pattern.level设置为%5p [${spring.zipkin.service.name:${spring.application.name:-}},%X{X-B3-TraceId:-},%X{X-B3-SpanId:-},%X{X-Span-Export:-}](这是Logback用户的Spring Boot特性),如果你不使用SLF4J,则不会自动应用此模式。Brave介绍从版本2.0.0开始,Spring Cloud Sleuth使用Brave作为追踪库,为方便起见,在此处嵌入了Brave的部分文档。在绝大多数情况下,你只需使用Sleuth提供的Brave中的Tracer或SpanCustomizer bean,下面的文档概述了Brave是什么以及它是如何工作的。Brave是一个用于捕获和报告关于分布式操作的延迟信息到Zipkin的库,大多数用户不直接使用Brave,他们使用库或框架,而不是代表他们使用Brave。 此模块包含一个追踪器,用于创建和连接span,对潜在分布式工作的延迟进行建模,它还包括通过网络边界传播trace上下文的库(例如,使用HTTP头)。 追踪最重要的是,你需要一个brave.Tracer,配置为向Zipkin报告。 以下示例设置通过HTTP(而不是Kafka)将trace数据(spans)发送到Zipkin: class MyClass { private final Tracer tracer; // Tracer will be autowired MyClass(Tracer tracer) { this.tracer = tracer; } void doSth() { Span span = tracer.newTrace().name("encode").start(); // ... }}如果你的span包含一个名称长度超过50个字符,则该名称将被截断为50个字符,你的名称必须明确而具体,大名称会导致延迟问题,有时甚至会引发异常。追踪器创建并连接span,对潜在分布式工作的延迟进行建模,它可以采用抽样来减少进程中的开销,减少发送到Zipkin的数据量,或两者兼而有之。 ...

April 28, 2019 · 3 min · jiezi

Spring-Data-Jpa-自动生成表结构

想在部署的时候随应用的启动而初始化数据脚本,这不就是Spring Data Jpa中的自动生成表结构,听起来特别简单,不就是配置Hibernate的ddl-auto嘛,有什么好说的,是个人都知道。当初我也是这样认为,实际操作了一把,虽然表是创建成功了,但是字段注释,字符集以及数据库引擎都不对,没想到在这些细节上翻车了。 毕竟开翻的车还要自己扶起来,于是在这记录一下。 注:本文中使用的Spring Data Jpa版本为2.1.4.RELEASE 以MySQL为例,我这边举个例子: import com.fasterxml.jackson.annotation.*;import org.hibernate.annotations.*;import org.springframework.data.annotation.*;import javax.persistence.*;import javax.persistence.Entity;import javax.persistence.Id;import java.math.BigDecimal;@Entity@javax.persistence.Table(name = "basic_city")@org.hibernate.annotations.Table(appliesTo = "basic_city", comment = "城市基本信息")public class CityDO { @Id @GenericGenerator(name = "idGenerator", strategy = "uuid") @GeneratedValue(generator = "idGenerator") @Column(name = "CITY_ID", length = 32) private String cityId; @Column(name = "CITY_NAME_CN", columnDefinition = "VARCHAR(255) NOT NULL COMMENT '名称(中文)'") private String cityNameCN; @Column(name = "CITY_NAME_EN", columnDefinition = "VARCHAR(255) NOT NULL COMMENT '名称(英文)'") private String cityNameEN; @Column(name = "LONGITUDE", precision = 10, scale = 7) private BigDecimal longitude; @Column(name = "LATITUDE", precision = 10, scale = 7) private BigDecimal latitude; @Column(name = "ELEVATION", precision = 5) private Integer elevation; @Column(name = "CITY_DESCRIPTION", length = 500) private String cityDescription; // 构造方法及get/set方法省略}@javax.persistence.Table 修改默认ORM规则,属性name设置表名;@org.hibernate.annotations.Table 建表时的描述, 属性comment修改表描述;@Id 主键@GenericGenerator 设置主键策略,这里使用了Hibernate的UUID来生成主键;@GeneratedValue 设置主键值,属性generator关联主键策略的name;@Column 修改默认ORM规则;name设置表中字段名称,表字段和实体类属性相同,则该属性可不写;unique设置该字段在表中是否唯一,默认false;nullable是否可为空,默认true;insertable表示insert操作时该字段是否响应写入,默认为true;updatable表示update操作时该字段是否响应修改,默认为true;columnDefinition是自定义字段,可以用这个属性来设置字段的注释;table表示当映射多个表时,指定表的表中的字段,默认值为主表的表名;length是长度,仅对varchar类型的字段生效,默认长度为255;precision表示一共多少位;scale表示小数部分占precision总位数的多少位,例子中两者共同使用来确保经纬度的精度;接下来需要设置数据引擎和字符集,网上大把的继承MySQL5InnoDBDialect,但是这个类已经过期了,我们用MySQL5Dialect来代替: ...

April 28, 2019 · 1 min · jiezi

如何优雅的生成海报动态合成图片

poster-generater⚡⚡⚡海报生成器. 只需要一个简单的 json 配置即可生成你需要的海报... 说明此项目诞生有一段时间了,我本人也一直在使用这个程序,从一开始的 golang 版本,到现在的 java 版本,一路上也得到了许多朋友的支持和鼓励。在 golang 版本的时候,就有朋友跟我说:『我非常喜欢这个项目,我非常希望加入到这个项目中来,但是我是从事JAVA开发的,可不可以弄一个 java 版本的』?加上我在 golang 开发中遇到了一些让我头疼的问题,以及常年写 OO(面向对象) 代码导致的惯性思维,让我在 golang 开发中备受煎熬。所以今年年初的时候我决定把这个项目用 java 实现一遍,如今 java 版本已经发布,添加了许多使用特型,希望给大家带来更好的使用体验。同时也希望有能力的朋友可以和我一起完善这个项目(欢迎PR、issues),让更多的人享受到项目带来的便利。 感谢 sm.ms 提供的图床服务适用场景我希望这个项目用于渲染需要动态合成的图片,例如用户名片(需要动态渲染名字和头像等),而非一经渲染就恒定不变的,例如logo、banner等。虽然 poster-generater 两者都可以实现。 项目愿景希望广大开发者可以不在为海报制作而烦恼。快速找到适合自己的海报,快速集成可扩展、高性能的海报渲染功能。 在线体验点击 在线测试 如果点击 提交测试 后没有反应, 参考如下解决方案,点击加载不安全的脚本即可。 出现该问题的原因是因为 测试地址不是 https 协议,所以 chrome 会给以警告。java 版本功能更丰富基于 java 开发,部署和二次开发更方便图片将上传到 公共 CDN,不占用主机磁盘,且速度更快支持结果缓存,相同的 海报配置 不会重复渲染,一次渲染持续保存,速度更快添加删除结果 API支持自定义字体,运行目录下新建 fonts 文件夹,里面放 ttf 格式字体就行。支持模板图片,减少网络图片加载,运行目录下新建 templates 文件夹,支持多种图片格式。缓存网络图片,减少网络图片加载支持企业定制化开发部署,详情请联系我ps:自定义字体、模板图片、网络图片缓存路径均可配置。具体配置参考 example.application.properties相关资源github 仓库共享海报库sm.ms 免费图床sm.ms 图床 js 插件公开测试地址: http://118.24.86.202:8000 此地址是我的测试机,配置不高,请不要在生产环境中使用下载下载 jar 包 $ wget http://static.janguly.com/poster-2.1-SNAPSHOT.jar你也可以 点击下载克隆代码 ...

April 27, 2019 · 1 min · jiezi

Spring-Cloud-参考文档Spring-Cloud-Sleuth介绍

Spring Cloud Sleuth介绍Spring Cloud Sleuth为Spring Cloud实现了分布式跟踪解决方案。 术语Spring Cloud Sleuth借用了Dapper的术语。 Span:基本工作单元,例如,发送RPC是一个新的span,就像向RPC发送响应一样,Span由span的唯一64位ID标识,另一个64位ID标识其所属的Trace。Span还有其他数据,例如描述、带时间戳的事件、键值annotations(标签),导致它们的span的ID以及进程ID(通常是IP地址)。 span可以启动和停止,它们可以跟踪自己的时间信息,创建span后,必须在将来的某个时刻停止它。 启动Trace的初始span称为root span,该span的ID值等于trace ID。Trace:一组span形成的树状结构,例如,如果运行分布式大数据存储,则可能由PUT请求形成trace。 Annotation:用于及时记录事件的存在,使用Brave工具,不再需要为Zipkin设置特殊的事件来了解客户端和服务器是谁、请求在哪里开始以及在哪里结束,然而,出于学习目的,标记这些事件以突出发生了什么类型的操作。 cs:Client Sent,客户端发起了一个请求,这个annotation表示span的开始。sr:Server Received,服务器端获得了请求并开始处理它,从此时间戳中减去cs时间戳会显示网络延迟。ss:Server Sent,在请求处理完成时注释(当响应被发送回客户端时),从此时间戳中减去sr时间戳会显示服务器端处理请求所需的时间。cr:Client Received,表示span的结束,客户端已成功从服务器端收到响应,从此时间戳中减去cs时间戳会显示客户端从服务器接收响应所需的全部时间。下图显示了系统中的Span和Trace,以及Zipkin annotations: 标记的每种颜色表示一个span(有七个span — 从A到G),请考虑以下标记: Trace Id = XSpan Id = DClient Sent此标记表示当前span的Trace Id设置为X,Span Id设置为D,此外,还发生了Client Sent事件。 下图显示了span的父—子关系: 用途以下部分参考上图中显示的示例。 使用Zipkin进行分布式跟踪这个例子有七个span,如果你进入Zipkin中的trace,你可以在第二个trace中看到这个数字,如下图所示: 但是,如果选择特定trace,则可以看到四个span,如下图所示: 选择特定trace时,你会看到合并的span,这意味着,如果通过Server Received和Server Sent或Client Received和Client Sent annotations向Zipkin发送了两个span,则它们将显示为单个span。在这种情况下,为什么七个和四个span之间存在差异? 一个span来自http:/start span,它具有Server Received(sr)和Server Sent(ss)annotations。两个span来自从service1到service2的http:/foo端点的RPC调用,Client Sent(cs)和Client Received(cr)事件发生在service1端,Server Received(sr)和Server Sent(ss)事件发生在service2端,这两个span形成一个与RPC调用相关的逻辑span。两个span来自从service2到service3的http:/bar端点的RPC调用,Client Sent(cs)和Client Received(cr)事件发生在service2端,Server Received(sr)和Server Sent(ss)事件发生在service3端,这两个span形成一个与RPC调用相关的逻辑span。两个span来自从service2到service4的http:/baz端点的RPC调用,Client Sent(cs)和Client Received(cr)事件发生在service2端,Server Received(sr)和Server Sent(ss)事件发生在service4端,这两个span形成一个与RPC调用相关的逻辑span。因此,如果我们计算物理span,我们有一个来自http:/start,两个来自service1调用service2,两个来自service2调用service3,两个来自service2调用service4,总之,我们总共有七个span。 从逻辑上讲,我们看到四个总Span的信息,因为我们有一个与传入请求到service1相关的span和三个与RPC调用相关的span。 ...

April 25, 2019 · 3 min · jiezi

手写一个springboot错误自动通知starter插件

手写一个springboot错误自动通知starter插件缘起在实际工作当中, 每次线上问题到达开发时间久,线上日志查找问题麻烦,所以想在程序发生错误时直接监听到并将错误信息发送给开发同学,提高客户使用体验 资源https://github.com/lihang1991/exception-spring-boot-starter 使用该jar正在申请发布到中央仓库(地址待完善)maven引用<dependency> <groupId>com.github.lihang1991</groupId> <artifactId>exception-spring-boot-starter</artifactId> <version>1.0.0</version></dependency>配置exception-handle: project-name: com.lihang.exception.client ## 工程名称 email: from: ceshi@163.com # 发送邮箱 to: - ceshi@139.com # 发送到(支持list) cc: - ceshi@139.com # 抄送(支持list)spring: mail: host: smtp.163.com port: 25 password: ceshi123456 username: ceshi@163.com@SpringBootApplication// 切面@EnableExceptionHandle(value = "execution(* com.lihang.exception.client.controller.*.*(..))")public class ExceptionCilentApplication { public static void main(String[] args) { SpringApplication.run(ExceptionCilentApplication.class, args); }}结果展示

April 24, 2019 · 1 min · jiezi

第二讲SpringSpring-MVCSpring-Boot三者之间的区别与联系

Spring Framework的诞生让开发人员的工作从石器时代跨域到了工业时代,你是否还能记起手撸Servlet和JDBC的岁月?,你是否还对Struts1以及Struts2莫名其妙的404错误记忆犹新?从2004年3月Spring 1.0发布到至今,Spring的发展已经走过了15个年头,其创造的价值让人瞩目。今天,带着这样一个背景来梳理一下Spring Framework,Spring MVC和Spring Boot三者之间的区别。 我们使用Spring家族的系列产品这么长时间,不禁会问这样几个问题:Spring Framework是什么?Spring MVC是什么?Spring Boot又是什么?它们被设计出来的目的是什么? 你需要了解的知识在接下来的内容中,将梳理这样几个知识点: Spring Framework基本概述Spring Framework主要解决的问题是什么?Spring MVC基本概述Spring MVC主要解决的问题是什么?Spring Boot主要解决的问题是什么?Spring,Spring MVC和Spring Boot三者之间的区别是什么?Spring Framework 解决了哪些核心问题?当你仔细思考这个问题的时候你会发现,很多地方它都有渗透到,貌似一个Spring就可以撑起开发的半边天,以至于很难一下子回答这个问题。那Spring Framework到底解决了哪些核心问题? Spring Framework最重要也是最核心的特性是依赖注入。所有的Spring模块的核心就是DI(依赖注入)或者IoC(控制反转)。依赖注入或控制反转是Spring Framework最大的特性,当我们正确使用DI(依赖注入)或IoC时,可以开发出一个高内聚低耦合的应用程序,而这一一个低耦合的应用程序可以轻松的对其实施单元测试。这就是Spring Framework解决的最核心的问题。 无依赖注入请考虑这一一个案例:UserAction依赖于UserService来获取用户信息,在没有依赖注入的情况下,我们需要手动在UserAction中实例化一个UserService对象,这样的手工作业意味着UserAction和UserService必须精密的联系在一起,才能正常工作。如果一个Action需要多个Service提供服务,那实例化这些Service将是一个繁重的工作。下面我们给出一个不使用依赖注入的代码片段加以说明: UserService.java public interface UserService{ User profile();}UserServiceImpl.java public class UserServiceImpl implements UserService{ @Override User profile(){ // TODO }}UserAction.java @RestControllerpublic class UserAction{ private UserService userService = new UserServiceImpl(); // other services... @GetMapping("/profile") public User profile(){ return userService.profile(); }}引入依赖注入引入依赖注入将会使整个代码看起来很清爽。为了能够开发出高内聚低耦合的应用程序,Spring Framework为我们做了大量的准备工作。下面我们使用两个简单的注解@Component和@Autowired来实现依赖注入。 @Component : 该注解将会告诉Spring Framework,被此注解标注的类需要纳入到Bean管理器中。@Autowired : 告诉Spring Framework需要找到一与其类型匹配的对象,并将其自动引入到所需要的类中。在接下来的示例代码中,我们会看到Spring Framework将为UserService创建一个Bean对象,并将其自动引入到UserAction中。 ...

April 24, 2019 · 2 min · jiezi

Spring-Cloud-参考文档重试失败的请求

重试失败的请求Spring Cloud Netflix提供了多种方式来发出HTTP请求,你可以使用负载均衡的RestTemplate、Ribbon或Feign。无论你如何选择创建HTTP请求,总是有可能请求失败,请求失败时,你可能希望自动重试请求,要在使用Sping Cloud Netflix时这样做,你需要在应用程序的类路径中包含Spring Retry。当存在Spring Retry时,负载均衡的RestTemplates、Feign和Zuul会自动重试任何失败的请求(假设你的配置允许这样做)。 退避策略默认情况下,重试请求时不使用退避策略,如果要配置退避策略,则需要创建类型为LoadBalancedRetryFactory的bean并覆盖给定服务的createBackOffPolicy方法,如以下示例所示: @Configurationpublic class MyConfiguration { @Bean LoadBalancedRetryFactory retryFactory() { return new LoadBalancedRetryFactory() { @Override public BackOffPolicy createBackOffPolicy(String service) { return new ExponentialBackOffPolicy(); } }; }}配置将Ribbon与Spring Retry一起使用时,可以通过配置某些Ribbon属性来控制重试功能,为此,请设置client.ribbon.MaxAutoRetries、client.ribbon.MaxAutoRetriesNextServer和client.ribbon.OkToRetryOnAllOperations属性,有关这些属性的说明,请参阅Ribbon文档。 启用client.ribbon.OkToRetryOnAllOperations包括重试POST请求,由于请求body的缓冲,POST请求会对服务器的资源产生影响。此外,你可能希望在响应中返回某些状态码时重试请求,你可以通过设置clientName.ribbon.retryableStatusCodes属性列出你希望Ribbon客户端重试的响应码,如以下示例所示: clientName: ribbon: retryableStatusCodes: 404,502你还可以创建类型为LoadBalancedRetryPolicy的bean,并实现retryableStatusCode方法以在给定状态码的情况下重试请求。 Zuul你可以通过将zuul.retryable设置为false来关闭Zuul的重试功能,你还可以通过将zuul.routes.routename.retryable设置为false来逐个路由地禁用重试功能。 上一篇:使用Sidecar支持多语言

April 24, 2019 · 1 min · jiezi

Spring-Cloud-参考文档HTTP客户端

HTTP客户端Spring Cloud Netflix会自动为你创建Ribbon、Feign和Zuul使用的HTTP客户端,但是,你也可以根据需要自定义自己的HTTP客户端,为此,如果使用Apache Http Cient,可以创建ClosableHttpClient类型的bean;如果使用OK Http,可以创建OkHttpClient类型的bean。 创建自己的HTTP客户端时,你还负责为这些客户端实施正确的连接管理策略,不正确地执行此操作可能会导致资源管理问题。维护模式的模块将模块置于维护模式意味着Spring Cloud团队将不再向模块添加新功能,将修复bug和安全问题,还将考虑和审查来自社区的小型拉请求。 Spring Cloud团队打算继续支持这些模块至少一年的时间,从Greenwich发行版开始。 以下Spring Cloud Netflix模块和相应的启动器将进入维护模式: spring-cloud-netflix-archaiusspring-cloud-netflix-hystrix-contractspring-cloud-netflix-hystrix-dashboardspring-cloud-netflix-hystrix-streamspring-cloud-netflix-hystrixspring-cloud-netflix-ribbonspring-cloud-netflix-turbine-streamspring-cloud-netflix-turbinespring-cloud-netflix-zuul这不包括Eureka或并发限制模块。上一篇:重试失败的请求

April 24, 2019 · 1 min · jiezi

Spring-Cloud-参考文档使用Sidecar支持多语言

使用Sidecar支持多语言你是否有希望利用Eureka、Ribbon和Config Server的非JVM语言?Spring Cloud Netflix Sidecar的灵感来自Netflix Prana,它包含一个HTTP API,用于获取给定服务的所有实例(按主机和端口)。你还可以通过嵌入式Zuul代理代理服务调用,该代理从Eureka获取其路由条目,可以通过主机查找或Zuul代理直接访问Spring Cloud Config Server,非JVM应用程序应实现健康检查,以便Sidecar可以向Eureka报告应用程序是启动还是关闭。 要在项目中包含Sidecar,请使用组ID为org.springframework.cloud和工件IDspring-cloud-netflix-sidecar的依赖项。 要启用Sidecar,请使用@EnableSidecar创建Spring Boot应用程序,此注解包括@EnableCircuitBreaker、@EnableDiscoveryClient和@EnableZuulProxy,在与非JVM应用程序相同的主机上运行生成的应用程序。 要配置Sidecar,请将sidecar.port和sidecar.health-uri添加到application.yml,sidecar.port属性是非JVM应用程序侦听的端口,这样Sidecar可以正确地向Eureka注册应用程序,sidecar.health-uri是可在非JVM应用程序上访问的URI,它模仿Spring Boot健康指示器,它应该返回类似于以下内容的JSON文档: health-uri-document { "status":"UP"}以下application.yml示例显示了Sidecar应用程序的示例配置: server: port: 5678spring: application: name: sidecarsidecar: port: 8000 health-uri: http://localhost:8000/health.jsonDiscoveryClient.getInstances()方法的API是/hosts/{serviceId},以下针对/hosts/customers的示例响应返回在不同主机上的两个实例: /hosts/customers [ { "host": "myhost", "port": 9000, "uri": "http://myhost:9000", "serviceId": "CUSTOMERS", "secure": false }, { "host": "myhost2", "port": 9000, "uri": "http://myhost2:9000", "serviceId": "CUSTOMERS", "secure": false }]非JVM应用程序(如果sidecar在端口5678上)可以在http://localhost:5678/hosts/{serviceId}访问此API。 Zuul代理自动将Eureka中已知的每个服务的路由添加到/<serviceId>,因此customers服务可在/customers处获得,非JVM应用程序可以访问位于http://localhost:5678/customers的customer服务(假设sidecar正在侦听端口5678)。 如果配置服务器已在Eureka中注册,则非JVM应用程序可以通过Zuul代理访问它,如果ConfigServer的serviceId是configserver且Sidecar在端口5678上,则可以在http://localhost:5678/configserver上访问它。 非JVM应用程序可以利用Config Server返回YAML文档的能力,例如,调用http://sidecar.local.spring.io:5678/configserver/default-master.yml可能会导致YAML文档类似于以下内容: eureka: client: serviceUrl: defaultZone: http://localhost:8761/eureka/ password: passwordinfo: description: Spring Cloud Samples url: https://github.com/spring-cloud-samples要在使用HTTPs时将健康检查请求设置为接受所有证书,请将sidecar.accept-all-ssl-certificates设置为true。 ...

April 23, 2019 · 1 min · jiezi

Spring-Cloud-参考文档外部配置Archaius

外部配置:ArchaiusArchaius是Netflix客户端配置库,它是所有Netflix OSS组件用于配置的库。 Archaius是Apache Commons Configuration项目的扩展,它允许通过轮询源更改或允许源推送更改到客户端来更新配置,Archaius使用Dynamic<Type>Property类作为属性的句柄,如以下示例所示: Archaius Example class ArchaiusTest { DynamicStringProperty myprop = DynamicPropertyFactory .getInstance() .getStringProperty("my.prop"); void doSomething() { OtherClass.someMethod(myprop.get()); }}Archaius有自己的一组配置文件和加载优先级,Spring应用程序通常不应直接使用Archaius,但仍然需要原生配置Netflix工具。 Spring Cloud有一个Spring Environment Bridge,因此Archaius可以从Spring环境中读取属性,此桥接器允许Spring Boot项目使用常规配置工具链,同时让他们按照文档(大多数情况下)配置Netflix工具。 上一篇:客户端负载均衡器:Ribbon

April 23, 2019 · 1 min · jiezi

Spring-Cloud-参考文档客户端负载均衡器Ribbon

客户端负载均衡器:RibbonRibbon是一个客户端负载均衡器,可以让你对HTTP和TCP客户端的行为进行大量控制,Feign已经使用了Ribbon,因此,如果你使用@FeignClient,此部分也适用。 Ribbon中的一个核心概念是命名客户端,每个负载均衡器都是一组组件的一部分,这些组件一起工作以按需联系远程服务器,并且该集合具有你作为应用程序开发人员提供的名称(例如,通过使用@FeignClient注解)。根据需要,Spring Cloud通过使用RibbonClientConfiguration为每个命名客户端创建一个新的集合作为ApplicationContext,这包含(除其他外)ILoadBalancer、RestClient和ServerListFilter。 如何包含Ribbon要在项目中包含Ribbon,使用组ID为org.springframework.cloud和工件ID为spring-cloud-starter-netflix-ribbon。 自定义Ribbon客户端你可以使用<client>.ribbon.*中的外部属性配置Ribbon客户端的某些,这类似于使用原生Netflix API,但你可以使用Spring Boot配置文件,可以在CommonClientConfigKey(ribbon-core的一部分)中将原生选项作为静态字段进行检查。 Spring Cloud还允许你通过使用@RibbonClient声明其他配置(在RibbonClientConfiguration之上)来完全控制客户端,如以下示例所示: @Configuration@RibbonClient(name = "custom", configuration = CustomConfiguration.class)public class TestConfiguration {}在这种情况下,客户端由RibbonClientConfiguration中已有的组件以及CustomConfiguration(后者通常覆盖前者)中的任何组件组成。 CustomConfiguration类必须是@Configuration类,但要注意它不在@ComponentScan中用于主应用程序上下文,否则,它由所有@RibbonClients共享。如果使用@ComponentScan(或@SpringBootApplication),则需要采取措施以避免包含它(例如,你可以将其放在单独的非重叠包中,或指定要在@ComponentScan中显式扫描的包)。下表显示了Spring Cloud Netflix默认为Ribbon提供的bean: Bean类型Bean名称类名称IClientConfigribbonClientConfigDefaultClientConfigImplIRuleribbonRuleZoneAvoidanceRuleIPingribbonPingDummyPingServerList<Server>ribbonServerListConfigurationBasedServerListServerListFilter<Server>ribbonServerListFilterZonePreferenceServerListFilterILoadBalancerribbonLoadBalancerZoneAwareLoadBalancerServerListUpdaterribbonServerListUpdaterPollingServerListUpdater创建其中一种类型的bean并将其置于@RibbonClient配置(例如下面的FooConfiguration)中,可以覆盖所描述的每个bean,如以下示例所示: @Configurationprotected static class FooConfiguration { @Bean public ZonePreferenceServerListFilter serverListFilter() { ZonePreferenceServerListFilter filter = new ZonePreferenceServerListFilter(); filter.setZone("myTestZone"); return filter; } @Bean public IPing ribbonPing() { return new PingUrl(); }}前面示例中的语句将NoOpPing替换为PingUrl,并提供自定义serverListFilter。 自定义所有Ribbon客户端的默认值可以使用@RibbonClients注解并注册默认配置为所有Ribbon客户端提供默认配置,如以下示例所示: @RibbonClients(defaultConfiguration = DefaultRibbonConfig.class)public class RibbonClientDefaultConfigurationTestsConfig { public static class BazServiceList extends ConfigurationBasedServerList { public BazServiceList(IClientConfig config) { super.initWithNiwsConfig(config); } }}@Configurationclass DefaultRibbonConfig { @Bean public IRule ribbonRule() { return new BestAvailableRule(); } @Bean public IPing ribbonPing() { return new PingUrl(); } @Bean public ServerList<Server> ribbonServerList(IClientConfig config) { return new RibbonClientDefaultConfigurationTestsConfig.BazServiceList(config); } @Bean public ServerListSubsetFilter serverListFilter() { ServerListSubsetFilter filter = new ServerListSubsetFilter(); return filter; }}通过设置属性自定义Ribbon客户端从版本1.2.0开始,Spring Cloud Netflix现在支持通过将属性设置为与Ribbon文档兼容来自定义Ribbon客户端。 ...

April 23, 2019 · 1 min · jiezi

Apache-Shiro-配置-LDAP-验证

通常在根据LDAP进行身份验证时一般进行以下三步: 利用一个LDAP用户的用户名和密码绑定到LDAP服务器。在LDAP中检索一个用户的条目,然后将提供的密码和检索到的LDAP记录中进行验证。根据LDAP提供的记录,再去本系统中查找授权信息。Shiro 提供了DefaultLdapRealm,只做了第二步,根据用户的条目和密码来验证。并不能满足我们的需求,所以肯定是要定制化LdapRealm。 这里使用Spring Ldap 来简化Ldap操作 public class LdapRealm extends AuthorizingRealm { private static final Logger logger = LoggerFactory.getLogger(LdapRealm.class); private LdapTemplate ldapTemplate; @Autowired private UserService userService; @Autowired private RoleService roleService; @Autowired private MenuService menuService; @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { String username = (String) token.getPrincipal(); String password = new String((char[]) token.getCredentials()); try { LdapQuery ldapQuery = LdapQueryBuilder.query().base("DC=example,DC=com").searchScope(SearchScope.SUBTREE) .filter("(sAMAccountName={0})", username); boolean authResult = ldapTemplate.authenticate(ldapQuery.base(), ldapQuery.filter().encode(), password); if (!authResult) { logger.debug("ldap authentication for {} failed", username); return null; } User ldapUser = (User) ldapTemplate.searchForObject(ldapQuery, new LdapUserAttrMapper()); User user = userService.selectUserById(ldapUser.getUserId()); if (user == null) { // 用户名不存在抛出异常 throw new UnknownAccountException(); } if (user.getRemoveFlag()) { // 用户被管理员锁定抛出异常 throw new LockedAccountException(); } SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(user, token.getCredentials(), "LdapRealm"); return authenticationInfo; } catch (Exception e) { logger.error("ldap authentication failed", e.toString()); return null; } } @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { Long userId = ShiroUtils.getUserId(); SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); // 角色加入AuthorizationInfo认证对象 info.setRoles(roleService.selectRoleKeys(userId)); // 权限加入AuthorizationInfo认证对象 info.setStringPermissions(menuService.selectPermsByUserId(userId)); return info; } public LdapTemplate getLdapTemplate() { return ldapTemplate; } public void setLdapTemplate(LdapTemplate ldapTemplate) { this.ldapTemplate = ldapTemplate; }}关键的代码如下,验证用户和获取LDAP用户信息 ...

April 23, 2019 · 2 min · jiezi

Spring-Cloud-参考文档Hystrix超时和Ribbon客户端

Hystrix超时和Ribbon客户端使用包装Ribbon客户端的Hystrix命令时,要确保将Hystrix超时配置为长于配置的Ribbon超时,包括可能进行的任何可能的重试,例如,如果你的Ribbon连接超时为一秒,并且Ribbon客户端可能会重试该请求三次,那么你的Hystrix超时应该略大于三秒。 如何包含Hystrix仪表板要在项目中包含Hystrix仪表板,请使用组ID为org.springframework.cloud和工件ID为spring-cloud-starter-netflix-hystrix-dashboard的启动器。 要运行Hystrix仪表板,请使用@EnableHystrixDashboard注解Spring Boot主类,然后访问/hystrix并将仪表板指向Hystrix客户端应用程序中的单个实例的/hystrix.stream端点。 连接到使用HTTPS的/hystrix.stream端点时,JVM必须信任服务器使用的证书,如果证书不受信任,则必须将证书导入JVM,以便Hystrix仪表板成功连接到流端点。Turbine查看单个实例的Hystrix数据在系统整体运行状况方面不是很有用,Turbine是一个应用程序,它将所有相关的/hystrix.stream端点聚合到一个组合的/turbine.stream中,以便在Hystrix仪表板中使用,从Eureka定位单个实例。运行Turbine需要使用@EnableTurbine注解来注解主类(例如,通过使用spring-cloud-starter-netflix-turbine来设置类路径),Turbine 1 wiki中所有记录的配置属性均适用。唯一的区别是turbine.instanceUrlSuffix不需要前置端口,因为除非turbine.instanceInsertPort=false,否则会自动处理。 默认情况下,Turbine在注册实例上查找/hystrix.stream端点,方法是查找它在Eureka中的hostName和port条目,然后将/hystrix.stream附加到它。如果实例的元数据包含management.port,则使用它来代替/hystrix.stream端点的port值。默认情况下,名为management.port的元数据条目等于management.port配置属性,可以通过以下配置覆盖它:eureka: instance: metadata-map: management.port: ${management.port:8081}turbine.appConfig配置键是turbine用于查找实例的Eureka serviceId列表,然后,turbine流在Hystrix仪表板中使用,其URL类似于以下内容: http://my.turbine.server:8080/turbine.stream?cluster=CLUSTERNAME如果名称是default,则可以省略cluster参数,cluster参数必须与turbine.aggregator.clusterConfig中的条目匹配,从Eureka返回的值是大写的,因此,如果有一个名为customers的应用程序在Eureka注册,则以下示例有效: turbine: aggregator: clusterConfig: CUSTOMERS appConfig: customers如果需要自定义Turbine应使用的集群名称(因为你不希望在turbine.aggregator.clusterConfig配置中存储集群名称),请提供TurbineClustersProvider类型的Bean。 clusterName可以通过turbine.clusterNameExpression中的SPEL表达式进行自定义,其中根用作InstanceInfo的实例,默认值为appName,这意味着Eureka serviceId成为群集键(即,customers的InstanceInfo的appName为CUSTOMERS)。另一个示例是turbine.clusterNameExpression=aSGName,它从AWS ASG名称获取集群名称,以下清单显示了另一个示例: turbine: aggregator: clusterConfig: SYSTEM,USER appConfig: customers,stores,ui,admin clusterNameExpression: metadata['cluster']在前面的示例中,来自四个服务的集群名称是从其元数据映射中提取的,并且应该具有包含SYSTEM和USER的值。 要为所有应用程序使用“default”群集,你需要一个字符串文字表达式(如果它在YAML中,则使用单引号并使用双引号进行转义): turbine: appConfig: customers,stores clusterNameExpression: "'default'"Spring Cloud提供了一个spring-cloud-starter-netflix-turbine,它具有运行Turbine服务器所需的所有依赖关系,要添加Turbine,请创建一个Spring Boot应用程序并使用@EnableTurbine对其进行注解。 默认情况下,Spring Cloud允许Turbine使用主机和端口来允许每个主机、每个集群有多个进程,如果你希望Turbine内置的原生Netflix行为不允许每个主机、每个群集有多个进程(实例ID的键是主机名),请设置turbine.combineHostPort=false。集群端点在某些情况下,了解Turbine中配置了哪些集群可能对其他应用程序有用,为了支持这一点,你可以使用/clusters端点,它将返回所有已配置集群的JSON数组。 GET /clusters [ { "name": "RACES", "link": "http://localhost:8383/turbine.stream?cluster=RACES" }, { "name": "WEB", "link": "http://localhost:8383/turbine.stream?cluster=WEB" }]可以通过将turbine.endpoints.clusters.enabled设置为false来禁用此端点。 Turbine流在某些环境中(例如在PaaS设置中),从所有分布式Hystrix命令中提取指标的经典Turbine模型不起作用,在这种情况下,你可能希望让Hystrix命令将指标推送到Turbine,Spring Cloud通过消息传递实现这一点。要在客户端上实现这一点,请添加依赖spring-cloud-netflix-hystrix-stream和你选择的spring-cloud-starter-stream-*。有关代理以及如何配置客户端凭据的详细信息,请参阅Spring Cloud Stream文档,对于本地代理,这应该是开箱即用的。 在服务器端,创建一个Spring Boot应用程序并使用@EnableTurbineStream注解它,Turbine Stream服务器需要使用Spring Webflux,因此spring-boot-starter-webflux需要包含在你的项目中,默认情况下,在将spring-cloud-starter-netflix-turbine-stream添加到你的应用程序时,会包含spring-boot-starter-webflux。 然后,你可以将Hystrix仪表板指向Turbine Stream Server而不是单独的Hystrix流,如果Turbine Stream在myhost上的端口8989上运行,则将http://myhost:8989放入Hystrix仪表板的流输入字段中,Circuit的前缀是各自的serviceId,后跟一个点(·),然后是Circuit名称。 ...

April 23, 2019 · 1 min · jiezi

SpringBoot高级篇JdbcTemplate之数据插入使用姿势详解

db操作可以说是java后端的必备技能了,实际项目中,直接使用JdbcTemplate的机会并不多,大多是mybatis,hibernate,jpa或者是jooq,然后前几天写一个项目,因为db操作非常简单,就直接使用JdbcTemplate,然而悲催的发现,对他的操作并没有预期中的那么顺畅,所以有必要好好的学一下JdbcTemplate的CURD;本文为第一篇,插入数据 <!-- more --> I. 环境1. 配置相关使用SpringBoot进行db操作引入几个依赖,就可以愉快的玩耍了,这里的db使用mysql,对应的pom依赖如 <dependencies> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency></dependencies>接着就是db的配置信息,下面是连接我本机的数据库配置 ## DataSourcespring.datasource.url=jdbc:mysql://127.0.0.1:3306/story?useUnicode=true&characterEncoding=UTF-8&useSSL=falsespring.datasource.driver-class-name= com.mysql.jdbc.Driverspring.datasource.username=rootspring.datasource.password=2. 测试db创建一个测试db CREATE TABLE `money` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `name` varchar(20) NOT NULL DEFAULT '' COMMENT '用户名', `money` int(26) NOT NULL DEFAULT '0' COMMENT '钱', `is_deleted` tinyint(1) NOT NULL DEFAULT '0', `create_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', PRIMARY KEY (`id`), KEY `name` (`name`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;II. 使用姿势直接引入jdbcTemplate,注入即可,不需要其他的操作 ...

April 23, 2019 · 5 min · jiezi

SpringBoot高级篇JdbcTemplate之数据查询上篇

前面一篇介绍如何使用JdbcTemplate实现插入数据,接下来进入实际业务中,最常见的查询篇。由于查询的姿势实在太多,对内容进行了拆分,本篇主要介绍几个基本的使用姿势 queryForMapqueryForListqueryForObject<!-- more --> I. 环境准备环境依然借助前面一篇的配置,链接如: 190407-SpringBoot高级篇JdbcTemplate之数据插入使用姿势详解 或者直接查看项目源码: https://github.com/liuyueyi/spring-boot-demo/blob/master/spring-boot/101-jdbctemplate 我们查询所用数据,正是前面一篇插入的结果,如下图 II. 查询使用说明1. queryForMapqueryForMap,一般用于查询单条数据,然后将db中查询的字段,填充到map中,key为列名,value为值 a. 基本使用姿势最基本的使用姿势,就是直接写完整的sql,执行 String sql = "select * from money where id=1";Map<String, Object> map = jdbcTemplate.queryForMap(sql);System.out.println("QueryForMap by direct sql ans: " + map);这种用法的好处是简单,直观;但是有个非常致命的缺点,如果你提供了一个接口为 public Map<String, Object> query(String condition) { String sql = "select * from money where name=" + condition; return jdbcTemplate.queryForMap(sql);}直接看上面代码,会发现问题么??? 有经验的小伙伴,可能一下子就发现了sql注入的问题,如果传入的参数是 '一灰灰blog' or 1=1 order by id desc limit 1, 这样输出和我们预期的一致么? b. 占位符替换正是因为直接拼sql,可能到只sql注入的问题,所以更推荐的写法是通过占位符 + 传参的方式 ...

April 23, 2019 · 3 min · jiezi

SpringBoot高级篇JdbcTemplate之数据查询下篇

SpringBoot高级篇JdbcTemplate之数据查询上篇 讲了如何使用JdbcTemplate进行简单的查询操作,主要介绍了三种方法的调用姿势 queryForMap, queryForList, queryForObject 本篇则继续介绍剩下的两种方法使用说明 queryForRowSetquery<!-- more --> I. 环境准备环境依然借助前面一篇的配置,链接如: 190407-SpringBoot高级篇JdbcTemplate之数据插入使用姿势详解 或者直接查看项目源码: https://github.com/liuyueyi/spring-boot-demo/blob/master/spring-boot/101-jdbctemplate 我们查询所用数据,正是前面一篇插入的结果,如下图 II. 查询使用说明1. queryForRowSet查询上篇中介绍的三种方法,返回的记录对应的结构要么是map,要么是通过RowMapper进行结果封装;而queryForRowSet方法的调用,返回的则是SqlRowSet对象,这是一个集合,也就是说,可以查询多条记录 使用姿势也比较简单,如下 public void queryForRowSet() { String sql = "select * from money where id > 1 limit 2"; SqlRowSet result = jdbcTemplate.queryForRowSet(sql); while (result.next()) { MoneyPO moneyPO = new MoneyPO(); moneyPO.setId(result.getInt("id")); moneyPO.setName(result.getString("name")); moneyPO.setMoney(result.getInt("money")); moneyPO.setDeleted(result.getBoolean("is_deleted")); moneyPO.setCreated(result.getDate("create_at").getTime()); moneyPO.setUpdated(result.getDate("update_at").getTime()); System.out.println("QueryForRowSet by DirectSql: " + moneyPO); }}对于使用姿势而言与之前的区别不大,还有一种就是sql也支持使用占位方式,如 // 采用占位符方式查询sql = "select * from money where id > ? limit ?";result = jdbcTemplate.queryForRowSet(sql, 1, 2);while (result.next()) { MoneyPO moneyPO = new MoneyPO(); moneyPO.setId(result.getInt("id")); moneyPO.setName(result.getString("name")); moneyPO.setMoney(result.getInt("money")); moneyPO.setDeleted(result.getBoolean("is_deleted")); moneyPO.setCreated(result.getDate("create_at").getTime()); moneyPO.setUpdated(result.getDate("update_at").getTime()); System.out.println("QueryForRowSet by ? sql: " + moneyPO);}重点关注下结果的处理,需要通过迭代器的方式进行数据遍历,获取每一列记录的值的方式和前面一样,可以通过序号的方式获取(序号从1开始),也可以通过制定列名方式(db列名) ...

April 23, 2019 · 3 min · jiezi

SpringBoot高级篇JdbcTemplate之数据更新与删除

前面介绍了JdbcTemplate的插入数据和查询数据,占用CURD中的两项,本文则将主要介绍数据更新和删除。从基本使用上来看,姿势和前面的没啥两样 <!-- more --> I. 环境准备环境依然借助前面一篇的配置,链接如: 190407-SpringBoot高级篇JdbcTemplate之数据插入使用姿势详解 或者直接查看项目源码: https://github.com/liuyueyi/spring-boot-demo/blob/master/spring-boot/101-jdbctemplate 我们查询所用数据,正是前面一篇插入的结果,如下图 II. 更新使用说明对于数据更新,这里会分为两种进行说明,单个和批量;这个单个并不是指只能一条记录,主要针对的是sql的数量而言 1. update 方式看过第一篇数据插入的童鞋,应该也能发现,新增数据也是用的这个方法,下面会介绍三种不同的使用姿势 先提供一个数据查询的转换方法,用于对比数据更新前后的结果 private MoneyPO queryById(int id) { return jdbcTemplate.queryForObject( "select id, `name`, money, is_deleted as isDeleted, unix_timestamp(create_at) as " + "created, unix_timestamp(update_at) as updated from money where id=?", new BeanPropertyRowMapper<>(MoneyPO.class), id);}a. 纯sql更新这个属于最基本的方式了,前面几篇博文中大量使用了,传入一条完整的sql,执行即可 int id = 10;// 最基本的sql更新String sql = "update money set money=money + 999 where id =" + id;int ans = jdbcTemplate.update(sql);System.out.println("basic update: " + ans + " | db: " + queryById(id));b. 占位sql问好占位,实际内容通过参数传递方式 ...

April 23, 2019 · 3 min · jiezi

Spring Cloud Admin 实战

Spring Cloud Admin简介 Spring Boot Admin 用于监控基于 Spring Boot 的应用,它是在 Spring Boot Actuator 的基础上提供简洁的可视化 WEB UI。Spring Boot Admin 提供了很多功能,如显示 name、id 和 version,显示在线状态,Loggers 的日志级别管理,Threads 线程管理,Environment 管理等。 基于Cairo-SR3 和 Finchley.SR1 在 Spring Boot 项目中,Spring Boot Admin 作为 Server 端,其他的要被监控的应用作为 Client 端,基于这种的配置如下步骤: admin-server@EnableAdminServer@EnableEurekaClient@SpringBootApplicationpublic class AdminServerApplication { public static void main(String[] args) { SpringApplication.run(AdminServerApplication.class, args); }}application.yml: server: port: 5000spring: application: name: admin-servereureka: client: service-url: defaultZone: ${EUREKA_SERVICE_URL:http://localhost:8761}/eureka/pom: <dependencyManagement> <dependencies> <dependency> <groupId>io.spring.platform</groupId> <artifactId>platform-bom</artifactId> <version>Cairo-SR3</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Finchley.SR1</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dependency> <groupId>de.codecentric</groupId> <artifactId>spring-boot-admin-starter-server</artifactId> <version>2.0.1</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>1.8</source> <target>1.8</target> <encoding>UTF-8</encoding> </configuration> </plugin> </plugins> </build>admin-clent@EnableDiscoveryClient@SpringBootApplicationpublic class AdminClientApplication { public static void main(String[] args) { SpringApplication.run(AdminClientApplication.class, args); }}application.yml ...

April 22, 2019 · 2 min · jiezi

Spring webflux 函数式编程web框架

Spring webfluxSpring 5.0 Spring webflux 是一个全新的非堵塞的函数式 Reactive Web 框架,可以用来构建异步的、非堵塞的、事件驱动的服务。springboot2.0发布不久,最近研究了一下springboot2.0的新特性,其中就发现了webflux。 下面是spring-flux的一个demo话不多少上代码使用webflux和MVC的区别就是在artifacId后面加上flux <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.0.RELEASE</version></parent><dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId></dependency>@RestControllerpublic class HelloController { @GetMapping("/hello") public String hello() { return "hello world"; }}在webflux中有Handler和Router 的概念,分别与springmvc中的controllerr和equest mapper相对应,通俗的将就是handler就是真正处理请求的bean,可以在handler中编写处理请求的逻辑,而Router就是如何让请求找到对应的handler中的方法处理,下面我们来实现一个简单的handler和router。@Componentpublic class HelloWorldHandler { public Mono<ServerResponse> helloWorld(ServerRequest request){ return ServerResponse.ok() .contentType(MediaType.TEXT_PLAIN) .body(BodyInserters.fromObject("hello flux")); } }上面是一个简单的handler只相应了一个“hello flux” 字符串!@Configurationpublic class RouterConfig { @Autowired private HelloWorldHandler helloWorldHandler; @Bean public RouterFunction<?> helloRouter() { return RouterFunctions.route(RequestPredicates.GET("/hello"), helloWorldHandler::helloWorld); }}上面是对应的router对应的是匹配一个get方式的/hello请求,然后调用helloWorldHandler中的helloWorld方法向浏览器输出一个文本类型的字符串再来一个例子@Componentpublic class UserHandler { @Autowired private ReactiveRedisConnection connection; public Mono<ServerResponse> getTime(ServerRequest request) { return ServerResponse.ok().contentType(MediaType.TEXT_PLAIN) .body(Mono.just("Now is " + new SimpleDateFormat("HH:mm:ss").format(new Date())), String.class); } public Mono<ServerResponse> getDate(ServerRequest request) { return ServerResponse.ok().contentType(MediaType.TEXT_PLAIN) .body(Mono.just("Today is " + new SimpleDateFormat("yyyy-MM-dd").format(new Date())), String.class); } public Mono<ServerResponse> sendTimePerSec(ServerRequest request) { return ServerResponse.ok().contentType(MediaType.TEXT_EVENT_STREAM) .body(Flux.interval(Duration.ofSeconds(1)).map(l -> new SimpleDateFormat("HH:mm:ss").format(new Date())), String.class); } public Mono<ServerResponse> register(ServerRequest request) { Mono<Map> body = request.bodyToMono(Map.class); return body.flatMap(map -> { String username = (String) map.get("username"); String password = (String) map.get("password"); String hashedPassword = BCrypt.hashpw(password, BCrypt.gensalt()); return connection.stringCommands() .set(ByteBuffer.wrap(username.getBytes()), ByteBuffer.wrap(hashedPassword.getBytes())); }).flatMap(aBoolean -> { Map<String, String> result = new HashMap<>(); ServerResponse serverResponse = null; if (aBoolean){ result.put("message", "successful"); return ServerResponse.ok() .contentType(MediaType.APPLICATION_JSON_UTF8) .body(BodyInserters.fromObject(result)); }else { result.put("message", "failed"); return ServerResponse.status(HttpStatus.BAD_REQUEST) .contentType(MediaType.APPLICATION_JSON_UTF8) .body(BodyInserters.fromObject(request)); } }); } public Mono<ServerResponse> login(ServerRequest request) { Mono<Map> body = request.bodyToMono(Map.class); return body.flatMap(map -> { String username = (String) map.get("username"); String password = (String) map.get("password"); return connection.stringCommands().get(ByteBuffer.wrap(username.getBytes())).flatMap(byteBuffer -> { byte[] bytes = new byte[byteBuffer.remaining()]; byteBuffer.get(bytes, 0, bytes.length); String hashedPassword = null; try { hashedPassword = new String(bytes, "UTF-8"); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } Map<String, String> result = new HashMap<>(); if (hashedPassword == null || !BCrypt.checkpw(password, hashedPassword)) { result.put("message", "账号或密码错误"); return ServerResponse.status(HttpStatus.UNAUTHORIZED) .contentType(MediaType.APPLICATION_JSON_UTF8) .body(BodyInserters.fromObject(result)); } else { result.put("token", "无效token"); return ServerResponse.ok() .contentType(MediaType.APPLICATION_JSON_UTF8) .body(BodyInserters.fromObject(result)); } }); }); }}@Configurationpublic class RouterConfig { @Autowired private HelloWorldHandler helloWorldHandler; @Bean public RouterFunction<?> helloRouter() { return RouterFunctions.route(RequestPredicates.GET("/hello"), helloWorldHandler::helloWorld); } @Autowired private UserHandler userHandler; @Bean public RouterFunction<ServerResponse> timerRouter() { return RouterFunctions.route(RequestPredicates.GET("/time"), userHandler::getTime) .andRoute(RequestPredicates.GET("/date"), userHandler::getDate); } @Bean public RouterFunction<?> routerFunction() { return RouterFunctions.route(RequestPredicates.GET("/hello"), helloWorldHandler::helloWorld) .andRoute(RequestPredicates.POST("/register"), userHandler::register) .andRoute(RequestPredicates.POST("/login"), userHandler::login) .andRoute(RequestPredicates.GET("/times"), userHandler::sendTimePerSec); }}

April 21, 2019 · 2 min · jiezi

spring boot学习(7)— 自定义中的 HttpMessageConverter

在我们开发自己的应用时,有时候,我们可能需要自定义一些自己的数据格式来传输,这时,自定义的数据传输和类的实例之间进行转化就需要统一起来了, Spring MVC 中的 HttpMessageConverter 就派上用场了。 HttpMessageConverter 的声明:public interface HttpMessageConverter<T> { /** * Indicates whether the given class can be read by this converter. * @param clazz the class to test for readability * @param mediaType the media type to read (can be {@code null} if not specified); * typically the value of a {@code Content-Type} header. * @return {@code true} if readable; {@code false} otherwise */ boolean canRead(Class<?> clazz, @Nullable MediaType mediaType); /** * Indicates whether the given class can be written by this converter. * @param clazz the class to test for writability * @param mediaType the media type to write (can be {@code null} if not specified); * typically the value of an {@code Accept} header. * @return {@code true} if writable; {@code false} otherwise */ boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType); /** * Return the list of {@link MediaType} objects supported by this converter. * @return the list of supported media types */ List<MediaType> getSupportedMediaTypes(); /** * Read an object of the given type from the given input message, and returns it. * @param clazz the type of object to return. This type must have previously been passed to the * {@link #canRead canRead} method of this interface, which must have returned {@code true}. * @param inputMessage the HTTP input message to read from * @return the converted object * @throws IOException in case of I/O errors * @throws HttpMessageNotReadableException in case of conversion errors */ T read(Class<? extends T> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException; /** * Write an given object to the given output message. * @param t the object to write to the output message. The type of this object must have previously been * passed to the {@link #canWrite canWrite} method of this interface, which must have returned {@code true}. * @param contentType the content type to use when writing. May be {@code null} to indicate that the * default content type of the converter must be used. If not {@code null}, this media type must have * previously been passed to the {@link #canWrite canWrite} method of this interface, which must have * returned {@code true}. * @param outputMessage the message to write to * @throws IOException in case of I/O errors * @throws HttpMessageNotWritableException in case of conversion errors */ void write(T t, @Nullable MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException;}里面有四个方法: ...

April 21, 2019 · 3 min · jiezi

好书共读 | 国内外互联网技术大牛们都写了哪些书籍?

出自 GitHub 开源组织 Doocs源地址:https://github.com/doocs/tech…后面将会在 GitHub 陆续更新书籍清单,有兴趣的朋友可以一起来维护。书籍是人们获取知识的主要途径。然而,如今的社会太浮躁,不少人不愿意花时间静下心来仔细读书,很多开发人员也是如此。殊不知,书籍沉淀了前人的经验和思考。本项目主要收集国内外各大互联网公司技术大牛们出版的值得一看的书籍,欢迎推荐书籍、完善内容和排版。如果你有比较好的 idea,也欢迎在 issues 上交流。注意,本项目所有书籍资源仅供交流和学习使用,请勿商用。若喜欢且有条件,请购买正版书籍,以便更好地支持原书作者。Common#TitleAuthor(s)AbstractResource1编码:隐匿在计算机软硬件背后的语言[美]Charles Petzold 著左飞 薛佟佟 译讲述计算机工作原理,却并不晦涩难懂。不管你是计算机高手,还是对这个神奇的机器充满敬畏之心的菜鸟,读一读大师的经典作品,必然有所收获。1f7s2码农翻身:用故事给技术加点料刘欣用故事方式讲述软件编程的若干重要领域,侧重于基础性、原理性的知识。2d9sBackend#TitleAuthor(s)AbstractResource1Effective Java 原书第 3 版[美]Joshua Bloch 著俞黎敏 译Java 经典书籍,内容涵盖 Java 9 及以下版本。w2f22码出高效:Java 开发手册杨冠宝(花名:孤尽)高海慧(花名:鸣莎)结合阿里巴巴实践经验与故障案例,与 Java 底层源码解析融会贯通。9yyp3互联网轻量级SSM框架解密:Spring、Spring MVC、MyBatis源码深度剖析李艳鹏 等SSM 框架源码深度剖析。39i14Java 8 In Action 中文版[英]Raoul-Gabriel Urma [意]Mario Fusco [英]Alan Mycroft 著陆明刚 劳佳 译全面介绍 Java8 这个里程碑版本的新特性,包括 Lambdas、流和函数式编程6063Database#TitleAuthor(s)AbstractResource1Redis 设计与实现黄健宏基于 Redis 3.0,内容通俗易懂,可以深入了解 Redis 底层。q2ky2高性能 MySQL 第三版[美] Baron Schwartz 等著 宁海元 等译MySQL 领域极佳之作。0ijqFrontend#TitleAuthor(s)AbstractResourceArchitecture#TitleAuthor(s)AbstractResource1企业 IT 架构转型之道:阿里巴巴中台战略思想与架构实战钟华(花名:古谦)分享阿里巴巴建设共享服务体系的经验和实践。euzo2大型网站系统与 Java 中间件实践曾宪杰(花名:华黎)围绕大型网站和支撑大型网站架构的 Java 中间件的实践展开介绍。hrkh3大型网站技术架构:核心原理与案例分析李智慧梳理大型网站技术发展历程,剖析大型网站技术架构模式,深入讲述大型互联网架构设计的核心原理。8epr4亿级流量网站架构核心技术:跟开涛学搭建高可用高并发系统张开涛总结并梳理了亿级流量网站高可用和高并发原则,通过实例详细介绍了如何落地这些原则。2v7a5逆流而上:阿里巴巴技术成长之路阿里巴巴集团成长集编委会总结阿里巴巴技术团队在基础架构、中间件、数据库、业务开发等领域的经典实践以及对未来的思考。coi5Big Data#TitleAuthor(s)AbstractResource1HBase 不睡觉书杨曦一本让读者看了不会睡着的 HBase 技术书。sjcw

April 19, 2019 · 1 min · jiezi

Spring Cloud 参考文档(声明式REST客户端:Feign)

声明式REST客户端:FeignFeign是一个声明式的Web服务客户端,它使编写Web服务客户端变得更容易,要使用Feign,请创建一个接口并对其进行注解,它具有可插拔的注解支持,包括Feign注解和JAX-RS注解,Feign还支持可插拔编码器和解码器。Spring Cloud增加了对Spring MVC注解的支持,并使用了Spring Web中默认使用的相同HttpMessageConverters,Spring Cloud集成了Ribbon和Eureka,在使用Feign时提供负载均衡的http客户端。如何包含Feign要在项目中包含Feign,请使用包含组名为org.springframework.cloud和工件名为spring-cloud-starter-openfeign的启动器。spring boot应用示例@SpringBootApplication@EnableFeignClientspublic class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); }}StoreClient.java@FeignClient(“stores”)public interface StoreClient { @RequestMapping(method = RequestMethod.GET, value = “/stores”) List<Store> getStores(); @RequestMapping(method = RequestMethod.POST, value = “/stores/{storeId}”, consumes = “application/json”) Store update(@PathVariable(“storeId”) Long storeId, Store store);}在@FeignClient注解中,String值(上面的“stores”)是一个任意客户端名称,用于创建Ribbon负载均衡器,你还可以使用url属性指定URL(绝对值或仅指定主机名),应用程序上下文中bean的名称是接口的完全限定名称,要指定自己的别名值,可以使用@FeignClient注解的qualifier值。上面的Ribbon客户端将想要发现“stores”服务的物理地址,如果你的应用程序是Eureka客户端,那么它将解析Eureka服务注册表中的服务,如果你不想使用Eureka,只需在外部配置中配置服务器列表。覆盖Feign默认值Spring Cloud的Feign支持的核心概念是命名客户端,每个feign客户端都是一个组件集成的一部分,这些组件协同工作以按需联系远程服务器,并且集成有一个名称,作为使用@FeignClient注解的应用程序开发人员可以使用这个名称。Spring Cloud使用FeignClientsConfiguration按需为每个命名客户端创建一个新的集成作为ApplicationContext,这包含(除其他外)feign.Decoder、feign.Encoder和feign.Contract,可以使用@FeignClient注解的contextId属性覆盖该集成的名称。Spring Cloud允许你通过使用@FeignClient声明其他配置(在FeignClientsConfiguration之上)来完全控制feign客户端,例如:@FeignClient(name = “stores”, configuration = FooConfiguration.class)public interface StoreClient { //..}在这种情况下,客户端由FeignClientsConfiguration中已有的组件以及FooConfiguration中的任何组件组成(后者将覆盖前者)。FooConfiguration不需要使用@Configuration注解,但是,如果是,则注意将其从任何包含此配置的@ComponentScan中排除,因为它将成为feign.Decoder、feign.Encoder、feign.Contract等的默认源。这可以通过将其放在任何@ComponentScan或@SpringBootApplication的单独的非重叠包中来避免,也可以在@ComponentScan中明确排除。现在不推荐使用serviceId属性,而是使用name属性。使用@FeignClient注解的contextId属性除了更改ApplicationContext集成的名称,它将覆盖客户端名称的别名,它将用作为该客户端创建的配置bean名称的一部分。以前,使用url属性不需要name属性,现在需要使用name。name和url属性支持占位符。@FeignClient(name = “${feign.name}”, url = “${feign.url}")public interface StoreClient { //..}Spring Cloud Netflix默认为feign(BeanType beanName:ClassName)提供以下bean:Decoder feignDecoder:ResponseEntityDecoder(包装SpringDecoder)Encoder feignEncoder:SpringEncoderLogger feignLogger:Slf4jLoggerContract feignContract:SpringMvcContractFeign.Builder feignBuilder:HystrixFeign.BuilderClient feignClient:如果启用了Ribbon,则它是LoadBalancerFeignClient,否则使用默认的feign客户端。可以通过将feign.okhttp.enabled或feign.httpclient.enabled分别设置为true,并将它们放在类路径上来使用OkHttpClient和ApacheHttpClient feign客户端,你可以通过在使用Apache时提供ClosableHttpClient或在使用OK HTTP时提供OkHttpClient的bean来定制使用的HTTP客户端。Spring Cloud Netflix默认情况下不为feign提供以下bean,但仍然从应用程序上下文中查找这些类型的bean以创建feign客户端:Logger.LevelRetryerErrorDecoderRequest.OptionsCollection<RequestInterceptor>SetterFactory创建其中一种类型的bean并将其放在@FeignClient配置中(如上面的FooConfiguration)允许你覆盖所描述的每个bean,例如:@Configurationpublic class FooConfiguration { @Bean public Contract feignContract() { return new feign.Contract.Default(); } @Bean public BasicAuthRequestInterceptor basicAuthRequestInterceptor() { return new BasicAuthRequestInterceptor(“user”, “password”); }}这将使用feign.Contract.Default替换SpringMvcContract,并将RequestInterceptor添加到RequestInterceptor的集合中。@FeignClient也可以使用配置属性进行配置。application.ymlfeign: client: config: feignName: connectTimeout: 5000 readTimeout: 5000 loggerLevel: full errorDecoder: com.example.SimpleErrorDecoder retryer: com.example.SimpleRetryer requestInterceptors: - com.example.FooRequestInterceptor - com.example.BarRequestInterceptor decode404: false encoder: com.example.SimpleEncoder decoder: com.example.SimpleDecoder contract: com.example.SimpleContract可以以与上述类似的方式在@EnableFeignClients属性defaultConfiguration中指定默认配置,不同之处在于此配置将适用于所有feign客户端。如果你更喜欢使用配置属性来配置所有@FeignClient,则可以使用default feign名称创建配置属性。application.ymlfeign: client: config: default: connectTimeout: 5000 readTimeout: 5000 loggerLevel: basic如果我们同时创建@Configuration bean和配置属性,配置属性将获胜,它将覆盖@Configuration值,但是,如果要将优先级更改为@Configuration,则可以将feign.client.default-to-properties更改为false。如果需要在RequestInterceptor中使用ThreadLocal绑定变量,则需要将Hystrix的线程隔离策略设置为“SEMAPHORE”或在Feign中禁用Hystrix。application.yml# To disable Hystrix in Feignfeign: hystrix: enabled: false# To set thread isolation to SEMAPHOREhystrix: command: default: execution: isolation: strategy: SEMAPHORE如果我们想要创建具有相同名称或URL的多个feign客户端,以便它们指向同一服务器但每个都具有不同的自定义配置,那么我们必须使用@FeignClient的contextId属性,以避免这些配置bean的名称冲突。@FeignClient(contextId = “fooClient”, name = “stores”, configuration = FooConfiguration.class)public interface FooClient { //..}@FeignClient(contextId = “barClient”, name = “stores”, configuration = BarConfiguration.class)public interface BarClient { //..}手动创建Feign客户端在某些情况下,可能需要以使用上述方法无法实现的方式自定义Feign客户端,在这种情况下,你可以使用Feign Builder API创建客户端。下面是一个示例,它创建两个具有相同接口的Feign客户端,但使用单独的请求拦截器配置每个客户端。@Import(FeignClientsConfiguration.class)class FooController { private FooClient fooClient; private FooClient adminClient; @Autowired public FooController(Decoder decoder, Encoder encoder, Client client, Contract contract) { this.fooClient = Feign.builder().client(client) .encoder(encoder) .decoder(decoder) .contract(contract) .requestInterceptor(new BasicAuthRequestInterceptor(“user”, “user”)) .target(FooClient.class, “http://PROD-SVC”); this.adminClient = Feign.builder().client(client) .encoder(encoder) .decoder(decoder) .contract(contract) .requestInterceptor(new BasicAuthRequestInterceptor(“admin”, “admin”)) .target(FooClient.class, “http://PROD-SVC”); }}在上面的示例中,FeignClientsConfiguration.class是Spring Cloud Netflix提供的默认配置。PROD-SVC是客户端将向其发出请求的服务的名称。Feign Contract对象定义了哪些注解和值在接口上是有效的,自动装配的Contract bean提供对SpringMVC注解的支持,而不是默认的Feign原生注解。Feign Hystrix支持如果Hystrix位于类路径并且feign.hystrix.enabled=true,则Feign将使用断路器包装所有方法,返回com.netflix.hystrix.HystrixCommand也可用,这允许你使用反应模式(通过调用.toObservable()或.observe()或异步使用(通过调用.queue())。要在每个客户端的基础上禁用Hystrix支持,请创建一个带有“prototype”范围的vanilla Feign.Builder,例如:@Configurationpublic class FooConfiguration { @Bean @Scope(“prototype”) public Feign.Builder feignBuilder() { return Feign.builder(); }}在Spring Cloud Dalston发布之前,如果Hystrix在类路径上,Feign会默认将所有方法包装在断路器中,Spring Cloud Dalston中更改了此默认行为,转而采用了选择加入方法。Feign Hystrix FallbackHystrix支持回退的概念:在电路打开或出现错误时执行的默认代码路径,要为给定的@FeignClient启用回退,请将fallback属性设置为实现回退的类名,你还需要将实现声明为Spring bean。@FeignClient(name = “hello”, fallback = HystrixClientFallback.class)protected interface HystrixClient { @RequestMapping(method = RequestMethod.GET, value = “/hello”) Hello iFailSometimes();}static class HystrixClientFallback implements HystrixClient { @Override public Hello iFailSometimes() { return new Hello(“fallback”); }}如果需要访问产生回退触发器的原因,可以使用@FeignClient中的fallbackFactory属性。@FeignClient(name = “hello”, fallbackFactory = HystrixClientFallbackFactory.class)protected interface HystrixClient { @RequestMapping(method = RequestMethod.GET, value = “/hello”) Hello iFailSometimes();}@Componentstatic class HystrixClientFallbackFactory implements FallbackFactory<HystrixClient> { @Override public HystrixClient create(Throwable cause) { return new HystrixClient() { @Override public Hello iFailSometimes() { return new Hello(“fallback; reason was: " + cause.getMessage()); } }; }}在Feign中实现回退以及Hystrix回退如何工作都有一定的限制,返回com.netflix.hystrix.HystrixCommand和rx.Observable的方法目前不支持回退。Feign和@Primary当与Hystrix回退一起使用Feign时,ApplicationContext中有相同类型的多个bean,这将导致@Autowired无法工作,因为没有一个明确的bean或一个标记为primary的bean。为了解决这个问题,Spring Cloud Netflix将所有Feign实例标记为@Primary,因此Spring Framework将知道要注入哪个bean,在某些情况下,这可能并不理想,要关闭此行为,请将@FeignClient的primary属性设置为false。@FeignClient(name = “hello”, primary = false)public interface HelloClient { // methods here}Feign继承支持Feign通过单继承接口支持样板api,这允许将通用操作分组为方便的基本接口。UserService.javapublic interface UserService { @RequestMapping(method = RequestMethod.GET, value ="/users/{id}”) User getUser(@PathVariable(“id”) long id);}UserResource.java@RestControllerpublic class UserResource implements UserService {}UserClient.javapackage project.user;@FeignClient(“users”)public interface UserClient extends UserService {}通常不建议在服务器和客户端之间共享接口,它引入了紧耦合,并且实际上也不能以其当前形式使用Spring MVC(方法参数映射不会被继承)。Feign请求/响应压缩你可以考虑为你的Feign请求启用请求或响应GZIP压缩,你可以通过启用以下属性之一来执行此操作:feign.compression.request.enabled=truefeign.compression.response.enabled=trueFeign请求压缩为你提供类似于你为Web服务器设置的设置:feign.compression.request.enabled=truefeign.compression.request.mime-types=text/xml,application/xml,application/jsonfeign.compression.request.min-request-size=2048通过这些属性,你可以选择压缩介质类型和最小请求阈值长度。Feign记录日志为每个创建的Feign客户端创建一个记录器,默认情况下,记录器的名称是用于创建Feign客户端的接口的完整类名,Feign日志记录仅响应DEBUG级别。application.ymllogging.level.project.user.UserClient: DEBUG你可以为每个客户端配置Logger.Level对象,告诉Feign要记录多少,选择是:NONE,没有记录(DEFAULT)。BASIC,仅记录请求方法和URL以及响应状态代码和执行时间。HEADERS,记录基本信息以及请求和响应headers。FULL,记录请求和响应的headers、body和元数据。例如,以下内容将Logger.Level设置为FULL:@Configurationpublic class FooConfiguration { @Bean Logger.Level feignLoggerLevel() { return Logger.Level.FULL; }}Feign @QueryMap支持OpenFeign @QueryMap注解为POJO提供了支持,可用作GET参数映射,不幸的是,默认的OpenFeign QueryMap注解与Spring不兼容,因为它缺少value属性。Spring Cloud OpenFeign提供等效的@SpringQueryMap注解,用于将POJO或Map参数注解为查询参数映射。例如,Params类定义参数param1和param2:// Params.javapublic class Params { private String param1; private String param2; // [Getters and setters omitted for brevity]}以下feign客户端使用@SpringQueryMap注解使用Params类:@FeignClient(“demo”)public class DemoTemplate { @GetMapping(path = “/demo”) String demoEndpoint(@SpringQueryMap Params params);} ...

April 18, 2019 · 2 min · jiezi

嵌套事务及事务不起作用的问题

代码方法示例 @Transactional public void saveAA() { try { //方法A第一次更新数据 mapper.save(); //调用方法B更新数据 this.saveBB(); } catch (Exception e) { throw new RuntimeException(); } } @Transactional public void saveBB(){ try { //方法B第一次更新数据 mapper.save(); int i = 100/0; //方法B第二次更新数据 mapper.save(); } catch (Exception e) { //e.printStackTrace(); throw new RuntimeException(); } }说明: 让事务起作用,遇到错误进行回滚,应该注意的事项:第一种情况:同一个类中 一个方法无嵌套方法 1、如果方法名上加上@Transactional注解,方法内不要用try catch ;如果必须要用try catch ,则catch中必须用throw new RuntimeException()。否则事务不起作用。第二种情况:同一个类中 方法A嵌套方法B1、方法A有@Transactional,方法内都没有try catch,事务起作用。2、方法A有@Transactional和try catch,并且catch中用throw new RuntimeException(),事务起作用。第三种情况:不同类中,方法C嵌套方法B1、方法B上加上@Transactional注解,方法内不要用try catch ;如果必须要用try catch ,则catch中必须用throw new RuntimeException()。否则方法B的事务不起作用。2、方法C上加上@Transactional注解,方法内不要用try catch ;如果必须要用try catch ,则catch中必须用throw new RuntimeException(),此时方法B怎么写都行。否则方法C的事务不起作用。 总结1、要想事务起作用,必须是主方法名上有@Transactional注解,方法体内不能用try catch;如果用try catch,则catch中必须用throw new RuntimeException();2、@Transactional注解应该只被应用到public方法上,不要用在protected、private等方法上,即使用了也将被忽略,不起作用。这是由Spring AOP决定的。3、只有来自外部的方法调用才会呗AOP代理捕捉,类内部方法调用类内部的其他方法,子方法并会不引起事务行为,即使被调用的方法上使用有@Transactional注解。4、类内部方法调用内部的其他方法,被调用的方法体中如果有try catch,则catch中必须用throw new RuntimeException(),否则即使主方法上加上@Transactional注解,如果被调用的子方法出错也不会抛出异常,不会引起事务起作用。 ...

April 18, 2019 · 1 min · jiezi

使用idea创建SpringBoot项目

首先我们打开idea。这里我是用的版本是2019.1.这边项目名选默认的就可以了,项目地址个人建议用一个专门的文件夹进行存放。

April 18, 2019 · 1 min · jiezi

Spring Cloud 参考文档(服务发现:Eureka客户端)

服务发现:Eureka客户端服务发现是基于微服务架构的关键原理之一,尝试手动配置每个客户端或某种形式的约定可能很难做到并且可能很脆弱,Eureka是Netflix Service Discovery服务器和客户端,服务器可以被配置和部署为高可用性,每个服务器将注册服务的状态复制到其他服务器。如何包含Eureka客户端要在项目中包含Eureka Client,请使用组ID为org.springframework.cloud和工件ID为spring-cloud-starter-netflix-eureka-client的启动器。注册Eureka当客户端向Eureka注册时,它会提供有关自身的元数据 — 例如主机、端口、健康指示器URL、主页和其他详细信息,Eureka从属于服务的每个实例接收心跳消息,如果心跳故障超过可配置的时间表,则通常会从注册表中删除该实例。以下示例显示了最小的Eureka客户端应用程序:@SpringBootApplication@RestControllerpublic class Application { @RequestMapping("/") public String home() { return “Hello world”; } public static void main(String[] args) { new SpringApplicationBuilder(Application.class).web(true).run(args); }}请注意,前面的示例显示了一个普通的Spring Boot应用程序,通过在类路径上使用spring-cloud-starter-netflix-eureka-client,你的应用程序会自动向Eureka Server注册,找到Eureka服务器需要进行配置,如以下示例所示:application.ymleureka: client: serviceUrl: defaultZone: http://localhost:8761/eureka/在前面的示例中,“defaultZone”是一个神奇的字符串回退值,它为任何不表示首选项的客户端提供服务URL(换句话说,它是一个有用的默认值)。默认应用程序名称(即服务ID)、虚拟主机和非安全端口(取自Environment)分别是${spring.application.name}、${spring.application.name}和${server.port}。在类路径上使用spring-cloud-starter-netflix-eureka-client使应用程序成为Eureka“实例”(即,它自己注册)和“客户端”(它可以查询注册表以查找其他服务),实例行为由eureka.instance.*配置键驱动,但如果你确保应用程序具有spring.application.name的值(这是Eureka服务ID或VIP的默认值),则默认值很好。有关可配置选项的更多详细信息,请参阅EurekaInstanceConfigBean和EurekaClientConfigBean。要禁用Eureka Discovery Client,可以将eureka.client.enabled设置为false。使用Eureka Server进行身份验证如果其中一个eureka.client.serviceUrl.defaultZone URL中嵌入了凭据,则会自动将HTTP基本身份验证添加到你的eureka客户端(curl样式,如下所示:http://user:password@localhost:8761/eureka)。对于更复杂的需求,你可以创建一个类型为DiscoveryClientOptionalArgs的@Bean并将ClientFilter实例注入其中,所有这些都应用于从客户端到服务器的调用。由于Eureka的限制,无法支持每个服务器基本身份验证凭据,因此仅使用找到的第一个集合。状态页面和健康指示器Eureka实例的状态页面和健康指示器分别默认为/info和/health,它们是Spring Boot Actuator应用程序中有用端点的默认位置,如果使用非默认上下文路径或servlet路径(例如server.servletPath=/custom),则需要更改这些,即使对于Actuator应用程序也是如此,以下示例显示了两个设置的默认值:application.ymleureka: instance: statusPageUrlPath: ${server.servletPath}/info healthCheckUrlPath: ${server.servletPath}/health这些链接显示在客户端使用的元数据中,并在某些情况下用于决定是否向你的应用程序发送请求,因此如果它们准确,则会很有帮助。在Dalston中,还需要在更改管理上下文路径时设置状态和健康检查URL,从Edgware开始删除此要求。注册安全应用程序如果你的应用程序想通过HTTPS联系,你可以在EurekaInstanceConfig中设置两个标志:eureka.instance.[nonSecurePortEnabled]=[false]eureka.instance.[securePortEnabled]=[true]这样做会使Eureka发布显示对安全通信明确偏好的实例信息,对于以这种方式配置的服务,Spring Cloud DiscoveryClient始终返回以https开头的URI,同样,当以这种方式配置服务时,Eureka(本机)实例信息具有安全的健康检查URL。由于Eureka在内部工作的方式,它仍然会发布状态和主页的非安全URL,除非你也明确地覆盖这些URL,你可以使用占位符来配置eureka实例URL,如以下示例所示:application.ymleureka: instance: statusPageUrl: https://${eureka.hostname}/info healthCheckUrl: https://${eureka.hostname}/health homePageUrl: https://${eureka.hostname}/请注意,${eureka.hostname}是一个原生占位符,仅在更高版本的Eureka中可用,你也可以使用Spring占位符实现相同的功能 — 例如,使用${eureka.instance.hostName}。如果你的应用程序在代理后面运行,并且SSL终止在代理中(例如,如果你在Cloud Foundry或其他平台中作为服务运行),然后,你需要确保代理“转发” headers被应用程序拦截和处理。如果嵌入在Spring Boot应用程序中的Tomcat容器具有针对X-Forwarded-* headers的显式配置,则会自动发生,应用程序呈现到自身的链接错误(错误的主机、端口或协议)表明你的配置错误。Eureka的健康检查默认情况下,Eureka使用客户端心跳来确定客户端是否已启动,除非另有说明,否则Discovery Client不会根据Spring Boot Actuator传播应用程序的当前健康检查状态,因此,在成功注册后,Eureka始终宣布应用程序处于“UP”状态,通过启用Eureka健康检查可以更改此行为,从而将应用程序状态传播到Eureka。因此,每个其他应用程序都不会向“UP”以外的状态下的应用程序发送流量,以下示例显示如何为客户端启用健康检查:application.ymleureka: client: healthcheck: enabled: trueeureka.client.healthcheck.enabled=true应该只在application.yml中设置,在bootstrap.yml中设置值会导致不良副作用,例如在Eureka中以UNKNOWN状态注册。如果你需要更多控制健康检查,请考虑实现自己的com.netflix.appinfo.HealthCheckHandler。实例和客户端的Eureka元数据值得花一些时间了解Eureka元数据的工作原理,因此你可以在平台中使用它,有用于信息的标准元数据,如主机名、IP地址、端口号、状态页和健康检查。这些发布在服务注册表中,客户端使用它们以直接的方式联系服务,可以将额外元数据添加到eureka.instance.metadataMap中的实例注册中,并且可以在远程客户端中访问此元数据。通常,除非客户端了解元数据的含义,否则额外元数据不会更改客户端的行为,本文稍后将介绍几种特殊情况,其中Spring Cloud已经为元数据映射赋予了意义。在Cloud Foundry上使用EurekaCloud Foundry有一个全局路由器,因此同一个应用程序的所有实例都具有相同的主机名(具有类似架构的其他PaaS解决方案),这不一定是使用Eureka的障碍。但是,如果你使用路由器(建议甚至强制使用,具体取决于你的平台的设置方式),你需要明确设置主机名和端口号(安全或非安全),以便他们使用路由器。你可能还希望使用实例元数据,以便区分客户端上的实例(例如,在自定义负载均衡器中),默认情况下,eureka.instance.instanceId是vcap.application.instance_id,如以下示例所示:application.ymleureka: instance: hostname: ${vcap.application.uris[0]} nonSecurePort: 80根据在Cloud Foundry实例中设置安全规则的方式,你可以注册并使用主机VM的IP地址进行直接服务到服务调用,Pivotal Web Services(PWS)尚未提供此功能。在AWS上使用Eureka如果计划将应用程序部署到AWS云,则必须将Eureka实例配置为支持AWS,你可以通过自定义EurekaInstanceConfigBean来执行此操作,如下所示:@Bean@Profile("!default")public EurekaInstanceConfigBean eurekaInstanceConfig(InetUtils inetUtils) { EurekaInstanceConfigBean b = new EurekaInstanceConfigBean(inetUtils); AmazonInfo info = AmazonInfo.Builder.newBuilder().autoBuild(“eureka”); b.setDataCenterInfo(info); return b;}更改Eureka实例ID一个vanilla Netflix Eureka实例注册的ID等于其主机名(即每个主机只有一个服务),Spring Cloud Eureka提供合理的默认值,定义如下:${spring.cloud.client.hostname}:${spring.application.name}:${spring.application.instance_id:${server.port}}}一个例子是myhost:myappname:8080。通过使用Spring Cloud,你可以通过在eureka.instance.instanceId中提供唯一标识符来覆盖此值,如以下示例所示:application.ymleureka: instance: instanceId: ${spring.application.name}:${vcap.application.instance_id:${spring.application.instance_id:${random.value}}}使用前面示例中显示的元数据和部署在localhost上的多个服务实例,将随机值插入其中以使实例唯一,在Cloud Foundry中,vcap.application.instance_id会自动填充在Spring Boot应用程序中,因此不需要随机值。使用EurekaClient一旦你拥有一个发现客户端的应用程序,就可以使用它从Eureka Server发现服务实例,一种方法是使用原生com.netflix.discovery.EurekaClient(而不是Spring Cloud DiscoveryClient),如以下示例所示:@Autowiredprivate EurekaClient discoveryClient;public String serviceUrl() { InstanceInfo instance = discoveryClient.getNextServerFromEureka(“STORES”, false); return instance.getHomePageUrl();}不要在@PostConstruct方法或@Scheduled方法中使用EurekaClient(或者可能尚未启动ApplicationContext的任何地方),它在SmartLifecycle中初始化(phase=0),因此最早可以依赖它的是另一个具有更高阶段的SmartLifecycle。没有Jersey的EurekaClient默认情况下,EurekaClient使用Jersey进行HTTP通信,如果你希望避免来自Jersey的依赖项,则可以将其从依赖项中排除,Spring Cloud基于Spring RestTemplate自动配置传输客户端,以下示例显示Jersey被排除在外:<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> <exclusions> <exclusion> <groupId>com.sun.jersey</groupId> <artifactId>jersey-client</artifactId> </exclusion> <exclusion> <groupId>com.sun.jersey</groupId> <artifactId>jersey-core</artifactId> </exclusion> <exclusion> <groupId>com.sun.jersey.contribs</groupId> <artifactId>jersey-apache-client4</artifactId> </exclusion> </exclusions></dependency>原生Netflix EurekaClient的替代品你无需使用原始Netflix EurekaClient,此外,在某种包装后面使用它通常更方便,Spring Cloud通过逻辑Eureka服务标识符(VIP)而不是物理URL支持Feign(REST客户端构建器)和Spring RestTemplate。要使用固定的物理服务器列表配置Ribbon,可以将<client>.ribbon.listOfServers设置为以逗号分隔的物理地址(或主机名)列表,其中<client>是客户端的ID。你还可以使用org.springframework.cloud.client.discovery.DiscoveryClient,它为发现客户端提供简单的API(不特定于Netflix),如以下示例所示:@Autowiredprivate DiscoveryClient discoveryClient;public String serviceUrl() { List<ServiceInstance> list = discoveryClient.getInstances(“STORES”); if (list != null && list.size() > 0 ) { return list.get(0).getUri(); } return null;}为什么注册服务这么慢?作为实例还涉及到注册表的定期心跳(通过客户端的serviceUrl),默认持续时间为30秒,在实例、服务器和客户端在其本地缓存中都具有相同的元数据之前,客户端无法发现服务(因此可能需要3个心跳)。你可以通过设置eureka.instance.leaseRenewalIntervalInSeconds来更改周期,将其设置为小于30的值会加快使客户端连接到其他服务的过程,在生产中,最好坚持使用默认值,因为服务器中的内部计算会对租约续期做出假设。Zones如果你已将Eureka客户端部署到多个区域,你可能希望这些客户端在尝试另一个区域中的服务之前使用同一区域内的服务,要进行此设置,你需要正确配置Eureka客户端。首先,你需要确保将Eureka服务器部署到每个区域,并确保它们彼此对等,有关详细信息,请参阅有关Zones和Regions的部分。接下来,你需要告诉Eureka你的服务所在的区域,你可以使用metadataMap属性执行此操作,例如,如果将service 1部署到zone 1和zone 2,则需要在service 1中设置以下Eureka属性:zone 1的service 1eureka.instance.metadataMap.zone = zone1eureka.client.preferSameZoneEureka = truezone 2的service 1eureka.instance.metadataMap.zone = zone2eureka.client.preferSameZoneEureka = true ...

April 18, 2019 · 1 min · jiezi

Spring Cloud 参考文档(断路器:Hystrix客户端)

断路器:Hystrix客户端Netflix创建了一个名为Hystrix的库,用于实现断路器模式,在微服务架构中,通常有多层服务调用,如以下示例所示:较低级别的服务中的服务故障可能导致级联故障一直到用户,当对特定服务的调用超过circuitBreaker.requestVolumeThreshold(默认值:20个请求)并且在metrics.rollingStats.timeInMilliseconds(默认值:10秒)定义的滚动窗口中,失败百分比大于circuitBreaker.errorThresholdPercentage(默认值:> 50%)时,电路打开,没有调用,在出现错误和开路的情况下,开发人员可以提供回退。拥有一个开放的电路可以停止级联故障,并允许过载或故障服务有时间恢复,回退可以是另一个受Hystrix保护的调用、静态数据或合理的空值,回退可以被链接,以便第一个回退执行一些其他业务调用,而这些业务调用又反过来回退到静态数据。如何包含Hystrix要在项目中包含Hystrix,请使用组ID为org.springframework.cloud和工件ID为spring-cloud-starter-netflix-hystrix的启动器。以下示例显示了具有Hystrix断路器的最小Eureka服务器:@SpringBootApplication@EnableCircuitBreakerpublic class Application { public static void main(String[] args) { new SpringApplicationBuilder(Application.class).web(true).run(args); }}@Componentpublic class StoreIntegration { @HystrixCommand(fallbackMethod = “defaultStores”) public Object getStores(Map<String, Object> parameters) { //do stuff that might fail } public Object defaultStores(Map<String, Object> parameters) { return /* something useful */; }}@HystrixCommand由名为“javanica”的Netflix contrib库提供,Spring Cloud在连接到Hystrix断路器的代理中自动包装带有该注解的Spring bean,断路器计算何时打开和关闭电路以及在发生故障时应采取的措施。要配置@HystrixCommand,可以将commandProperties属性与@HystrixProperty注解列表一起使用,有关详细信息,请参见此处,有关可用属性的详细信息,请参阅Hystrix wiki。传播安全上下文或使用Spring Scopes如果希望某些线程本地上下文传播到@HystrixCommand,则默认声明不起作用,因为它在线程池中执行该命令(在超时的情况下),你可以通过配置或直接在注解中切换Hystrix以使用与调用者相同的线程,方法是让它使用不同的“隔离策略”,以下示例演示如何在注解中设置线程:@HystrixCommand(fallbackMethod = “stubMyService”, commandProperties = { @HystrixProperty(name=“execution.isolation.strategy”, value=“SEMAPHORE”) })…如果你使用@SessionScope或@RequestScope,则同样适用,如果遇到运行时异常,表示无法找到作用域上下文,则需要使用相同的线程。你还可以选择将hystrix.shareSecurityContext属性设置为true,这样做会自动配置Hystrix并发策略插件挂钩,将SecurityContext从主线程传输到Hystrix命令使用的线程。Hystrix不会注册多个Hystrix并发策略,因此可以通过将自己的HystrixConcurrencyStrategy声明为Spring bean来实现扩展机制,Spring Cloud在Spring上下文中查找你的实现,并将其包装在自己的插件中。健康指示器连接断路器的状态也暴露在调用应用程序的/health端点中,如以下示例所示:{ “hystrix”: { “openCircuitBreakers”: [ “StoreIntegration::getStoresByLocationLink” ], “status”: “CIRCUIT_OPEN” }, “status”: “UP”}Hystrix指标流要启用Hystrix指标流,请包含对spring-boot-starter-actuator的依赖关系并设置management.endpoints.web.exposure.include: hystrix.stream,这样做会将/actuator/hystrix.stream公开为管理端点,如以下示例所示:<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>Hystrix仪表板Hystrix的主要好处之一是它收集了关于每个HystrixCommand的指标集,Hystrix仪表板以高效的方式显示每个断路器的健康状况。 ...

April 17, 2019 · 1 min · jiezi

领域驱动设计战术模式--领域事件

使用领域事件来捕获发生在领域中的一些事情。领域驱动实践者发现他们可以通过了解更多发生在问题域中的事件,来更好的理解问题域。这些事件,就是领域事件,主要是与领域专家一起进行知识提炼环节中获得。领域事件,可以用于一个限界上下文内的领域模型,也可以使用消息队列在限界上下文间进行异步通信。1 理解领域事件领域事件是领域专家所关心的发生在领域中的一些事件。将领域中所发生的活动建模成一系列离散事件。每个事件都用领域对象表示。领域事件是领域模型的组成部分,表示领域中所发生的事情。领域事件的主要用途:保证聚合间的数据一致性替换批量处理实现事件源模式进行限界上下文集成2 实现领域事件领域事件表示已经发生的某种事实,该事实在发生后便不会改变。因此,领域事件通常建模成值对象。但,这也有特殊的情况,为了迎合序列化和反序列化框架需求,在建模时,经常会进行一定的妥协。2.1 创建领域事件2.1.1 事件命名在建模领域事件时,我们应该根据限界上下文中的通用语言来命名事件。如果事件由聚合上的命令操作产生,通常根据该操作方法的名字来命名事件。事件名字表明聚合上的命令方法在执行成功后的事实。即事件命名需要反映过去发生过的事情。public class AccountEnabledEvent extends AbstractAggregateEvent<Long, Account> { public AccountEnabledEvent(Account source) { super(source); }}2.1.2 事件属性事件的属性主要用于驱动后续业务流程。当然,也会拥有一些通用属性。事件具有一些通用属性,如:唯一标识occurredOn 发生时间type 事件类型source 事件发生源(只针对由聚合产生的事件)通用属性可以使用事件接口来规范。接口或类含义DomainEvent通用领域事件接口AggregateEvent由聚合发布的通用领域事件接口AbstractDomainEventDomainEvent 实现类,维护 id 和 创建时间AbstractAggregateEventAggregateEvent 实现类,继承子 AbstractDomainEvent,并添加 source 属性但,事件最主要的还是业务属性。我们需要考虑,是谁导致事件的发生,这可能涉及产生事件的聚合或其他参与该操作的聚合,也可能是其他任何类型的操作数据。2.1.3 事件方法事件是事实的描述,本身不会有太多的业务操作。领域事件通常被设计为不变对象,事件所携带的数据已经反映出该事件的来源。事件构造函数完成状态初始化,同时提供属性的 getter 方法。2.1.4 事件唯一标识这里需要注意的是事件唯一标识,通常情况下,事件是不可变的,那为什么会涉及唯一标识的概念呢?对于从聚合中发布出来的领域事件,使用事件的名称、产生事件的标识、事件发生的时间等足以对不同的事件进行区分。但,这样会增加事件比较的复杂性。对于由调用方发布的事件,我们将领域事件建模成聚合,可以直接使用聚合的唯一标识作为事件的标识。事件唯一标识的引入,会大大减少事件比较的复杂性。但,其最大的意义在于限界上下文的集成。当我们需要将领域事件发布到外部的限界上下文时,唯一标识就是一种必然。为了保证事件投递的幂等性,在发送端,我们可能会进行多次发送尝试,直至明确发送成功为止;而在接收端,当接收到事件后,需要对事件进行重复性检测,以保障事件处理的幂等性。此时,事件的唯一标识便可以作为事件去重的依据。事件唯一标识,本身对领域建模影响不大,但对技术处理好处巨大。因此,将它作为通用属性进行管理。2.2 发布领域事件我们如何避免领域事件与处理者间的耦合呢?一种简单高效的方式便是使用观察者模式,这种模式可以在领域事件和外部组件间进行解耦。2.2.1 发布订阅模型为了统一,我们需要定义了一套接口和实现类,以基于观察者模式,完成事件的发布。涉及接口和实现类如下:接口或类含义DomainEventPublisher用于发布领域事件DomainEventHandlerRegistry用于注册 DomainEventHandlerDomainEventBus扩展自 DomainEventPublisher 和 DomainEventHandlerRegistry 用于发布和管理领域事件处理器DefaultDomainEventBusDomainEventBus 默认实现DomainEventHandler用于处理领域事件DomainEventSubscriber用于判断是否接受领域事件DomainEventExecutor用于执行领域事件处理器使用实例如 DomainEventBusTest 所示:public class DomainEventBusTest { private DomainEventBus domainEventBus; @Before public void setUp() throws Exception { this.domainEventBus = new DefaultDomainEventBus(); } @After public void tearDown() throws Exception { this.domainEventBus = null; } @Test public void publishTest(){ // 创建事件处理器 TestEventHandler eventHandler = new TestEventHandler(); // 注册事件处理器 this.domainEventBus.register(TestEvent.class, eventHandler); // 发布事件 this.domainEventBus.publish(new TestEvent(“123”)); // 检测事件处理器是够运行 Assert.assertEquals(“123”, eventHandler.data); } @Value class TestEvent extends AbstractDomainEvent{ private String data; } class TestEventHandler implements DomainEventHandler<TestEvent>{ private String data; @Override public void handle(TestEvent event) { this.data = event.getData(); } }}在构建完发布订阅结构后,需要将其与领域模型进行关联。领域模型如何获取 Publisher,事件处理器如何进行订阅。2.2.2 基于 ThreadLocal 的事件发布比较常用的方案便是将 DomainEventBus 绑定到线程上下文。这样,只要是同一调用线程都可以方便的获取 DomainEventBus 对象。具体的交互如下:DomainEventBusHolder 用于管理 DomainEventBus。public class DomainEventBusHolder { private static final ThreadLocal<DomainEventBus> THREAD_LOCAL = new ThreadLocal<DomainEventBus>(){ @Override protected DomainEventBus initialValue() { return new DefaultDomainEventBus(); } }; public static DomainEventPublisher getPubliser(){ return THREAD_LOCAL.get(); } public static DomainEventHandlerRegistry getHandlerRegistry(){ return THREAD_LOCAL.get(); } public static void clean(){ THREAD_LOCAL.remove(); }}Account 的 enable 直接使用 DomainEventBusHolder 进行发布。public class Account extends JpaAggregate { public void enable(){ AccountEnabledEvent event = new AccountEnabledEvent(this); DomainEventBusHolder.getPubliser().publish(event); }}public class AccountEnabledEvent extends AbstractAggregateEvent<Long, Account> { public AccountEnabledEvent(Account source) { super(source); }}AccountApplication 完成订阅器注册以及业务方法调用。public class AccountApplication extends AbstractApplication { private static final Logger LOGGER = LoggerFactory.getLogger(AccountApplication.class); @Autowired private AccountRepository repository; public void enable(Long id){ // 清理之前绑定的 Handler DomainEventBusHolder.clean(); // 注册 EventHandler AccountEnableEventHandler enableEventHandler = new AccountEnableEventHandler(); DomainEventBusHolder.getHandlerRegistry().register(AccountEnabledEvent.class, enableEventHandler); Optional<Account> accountOptional = repository.getById(id); if (accountOptional.isPresent()) { Account account = accountOptional.get(); // enable 使用 DomainEventBusHolder 直接发布事件 account.enable(); repository.save(account); } } class AccountEnableEventHandler implements DomainEventHandler<AccountEnabledEvent>{ @Override public void handle(AccountEnabledEvent event) { LOGGER.info(“handle enable event”); } }}2.2.3 基于实体缓存的事件发布先将事件缓存在实体中,在实体状态成功持久化到存储后,再进行事件发布。具体交互如下:实例代码如下:public class Account extends JpaAggregate { public void enable(){ AccountEnabledEvent event = new AccountEnabledEvent(this); registerEvent(event); }}Account 的 enable 方法,调用 registerEvent 对事件进行注册。@MappedSuperclasspublic abstract class AbstractAggregate<ID> extends AbstractEntity<ID> implements Aggregate<ID> { private static final Logger LOGGER = LoggerFactory.getLogger(AbstractAggregate.class); @JsonIgnore @QueryTransient @Transient @org.springframework.data.annotation.Transient private final transient List<DomainEventItem> events = Lists.newArrayList(); protected void registerEvent(DomainEvent event) { events.add(new DomainEventItem(event)); } protected void registerEvent(Supplier<DomainEvent> eventSupplier) { this.events.add(new DomainEventItem(eventSupplier)); } @Override @JsonIgnore public List<DomainEvent> getEvents() { return Collections.unmodifiableList(events.stream() .map(eventSupplier -> eventSupplier.getEvent()) .collect(Collectors.toList())); } @Override public void cleanEvents() { events.clear(); } private class DomainEventItem { DomainEventItem(DomainEvent event) { Preconditions.checkArgument(event != null); this.domainEvent = event; } DomainEventItem(Supplier<DomainEvent> supplier) { Preconditions.checkArgument(supplier != null); this.domainEventSupplier = supplier; } private DomainEvent domainEvent; private Supplier<DomainEvent> domainEventSupplier; public DomainEvent getEvent() { if (domainEvent != null) { return domainEvent; } DomainEvent event = this.domainEventSupplier != null ? this.domainEventSupplier.get() : null; domainEvent = event; return domainEvent; } }}registerEvent 方法在 AbstractAggregate 中,registerEvent 方法将事件保存到 events 集合,getEvents 方法获取所有事件,cleanEvents 方法清理缓存的事件。Application 实例如下:@Servicepublic class AccountApplication extends AbstractApplication { private static final Logger LOGGER = LoggerFactory.getLogger(AccountApplication.class); @Autowired private AccountRepository repository; @Autowired private DomainEventBus domainEventBus; @PostConstruct public void init(){ // 使用 Spring 生命周期注册事件处理器 this.domainEventBus.register(AccountEnabledEvent.class, new AccountEnableEventHandler()); } public void enable(Long id){ Optional<Account> accountOptional = repository.getById(id); if (accountOptional.isPresent()) { Account account = accountOptional.get(); // enable 将事件缓存在 account 中 account.enable(); repository.save(account); List<DomainEvent> events = account.getEvents(); if (!CollectionUtils.isEmpty(events)){ // 成功持久化后,对事件进行发布 this.domainEventBus.publishAll(events); } } } class AccountEnableEventHandler implements DomainEventHandler<AccountEnabledEvent>{ @Override public void handle(AccountEnabledEvent event) { LOGGER.info(“handle enable event”); } }}AccountApplication 的 init 方法完成事件监听器的注册,enable 方法在实体成功持久化后,将缓存的事件通过 DomainEventBus 实例 publish 出去。2.2.4 由调用方发布事件通常情况下,领域事件是由聚合的命令方法产生,并在命令方法执行成功后,进行事件的发布。有时,领域事件并不是聚合中的命令方法产生的,而是由用户所发生的请求产生。此时,我们需要将领域事件建模成一个聚合,并且拥有自己的资源库。但,由于领域事件表示的是过去发生的事情,因此资源库只做追加操作,不能对事件进行修改和删除功能。例如,对用户点击事件进行发布。@Entity@Datapublic class ClickAction extends JpaAggregate implements DomainEvent { @Setter(AccessLevel.PRIVATE) private Long userId; @Setter(AccessLevel.PRIVATE) private String menuId; public ClickAction(Long userId, String menuId){ Preconditions.checkArgument(userId != null); Preconditions.checkArgument(StringUtils.isNotEmpty(menuId)); setUserId(userId); setMenuId(menuId); } @Override public String id() { return String.valueOf(getId()); } @Override public Date occurredOn() { return getCreateTime(); }}ClickAction 继承自 JpaAggregate 实现 DomainEvent 接口,并重写 id 和 occurredOn 方法。@Servicepublic class ClickActionApplication extends AbstractApplication { @Autowired private ClickActionRepository repository; @Autowired private DomainEventBus domainEventBus; public void clickMenu(Long id, String menuId){ ClickAction clickAction = new ClickAction(id, menuId); clickAction.prePersist(); this.repository.save(clickAction); domainEventBus.publish(clickAction); }}ClickActionApplication 在成功保存 ClickAction 后,使用 DomainEventBus 对事件进行发布。2.3 订阅领域事件由什么组件向领域事件注册订阅器呢?大多数请求,由应用服务完成,有时也可以由领域服务进行注册。由于应用服务是领域模型的直接客户,它是注册领域事件订阅器的理想场所,即在应用服务调用领域方法之前,就完成了对事件的订阅。基于 ThreadLocal 进行订阅:public void enable(Long id){ // 清理之前绑定的 Handler DomainEventBusHolder.clean(); // 注册 EventHandler AccountEnableEventHandler enableEventHandler = new AccountEnableEventHandler(); DomainEventBusHolder.getHandlerRegistry().register(AccountEnabledEvent.class, enableEventHandler); Optional<Account> accountOptional = repository.getById(id); if (accountOptional.isPresent()) { Account account = accountOptional.get(); // enable 使用 DomainEventBusHolder 直接发布事件 account.enable(); repository.save(account); }}基于实体缓存进行订阅:@PostConstructpublic void init(){ // 使用 Spring 生命周期注册事件处理器 this.domainEventBus.register(AccountEnabledEvent.class, new AccountEnableEventHandler());}public void enable(Long id){ Optional<Account> accountOptional = repository.getById(id); if (accountOptional.isPresent()) { Account account = accountOptional.get(); // enable 将事件缓存在 account 中 account.enable(); repository.save(account); List<DomainEvent> events = account.getEvents(); if (!CollectionUtils.isEmpty(events)){ // 成功持久化后,对事件进行发布 this.domainEventBus.publishAll(events); } }}2.4 处理领域事件完成事件发布后,让我们一起看下事件处理。2.4.1 保证聚合间的数据一致性我们通常将领域事件用于维护模型的一致性。在聚合建模中有一个原则,就是在一个事务中,只能对一个聚合进行修改,由此产生的变化必须在独立的事务中运行。在这种情况下,需要谨慎处理的事务的传播性。应用服务控制着事务。不要在事件通知过程中修改另一个聚合实例,因为这样会破坏聚合的一大原则:在一个事务中,只能对一个聚合进行修改。对于简单场景,我们可以使用特殊的事务隔离策略对聚合的修改进行隔离。具体流程如下:但,最佳方案是使用异步处理。及每一个定义方都在各自独立的事务中修改额外的聚合实例。事件订阅方不应该在另一个聚合上执行命令方法,因为这样将破坏“在单个事务中只修改单个聚合实例”的原则。所有聚合实例间的最终一致性必须通过异步方式处理。详见,异步处理领域事件。2.4.2 替换批量处理批处理过程通常需要复杂的查询,并且需要庞大的事务支持。如果在接收到领域事件时,系统就立即处理,业务需求不仅得到了更快的满足,而且杜绝了批处理操作。在系统的非高峰时期,通常使用批处理进行一些系统的维护,比如删除过期数据、创建新的对象、通知用户、更新统计信息等。这些批处理往往需要复杂的查询,并需要庞大的事务支持。如果我们监听系统中的领域事件,在接收领域事件时,系统立即处理。这样,原本批量集中处理的过程就被分散成许多小的处理单元,业务需要也能更快的满足,用户可以可以及时的进行下一步操作。2.4.3 实现事件源模式对于单个限界上下文中的所有领域事件,为它们维护一个事件存储具有很多的好处。对事件进行存储可以:将事件存储作为消息队列来使用,然后将领域事件通过消息设施发布出去。将事件存储用于基于 Rest 的事件通知。检查模型命名方法产生结果的历史记录。使用事件存储来进行业务预测和分析。使用事件来重建聚合实例。执行聚合的撤销操作。事件存储是个比较大的课题,将有专门章节进行讲解。2.4.4 进行限界上下文集成基于领域事件的限界上下文集成,主要由消息队列和 REST 事件两种模式。在此,重心讲解基于消息队列的上下文集成。在不同的上下文中采用消息系统时,我们必须保证最终一致性。在这种情况下,我们至少需要在两种存储之间保存最终一致性:领域模型所使用的存储和消息队列所使用的持久化存储。我们必须保证在持久化领域模型时,对于的事件也已经成功发布。如果两种不同步,模型可能会处于不正确的状态。一般情况下,有三种方式:领域模型和消息共享持久化存储。在这种情况下,模型和事件的提交在一个事务中完成,从而保证两种的一致性。领域模型和消息由全局事务控制。这种情况下,模型和消息所用的持久化存储可以分离,但会降低系统性能。在领域持久化存储中,创建一个特殊的存储区域用于存储事件(也就是事件存储),从而在本地事务中完成领域和事件的存储。然后,通过后台服务将事件异步发送到消息队列中。一般情况下,第三种,是比较优雅的解决方案。在一致性要求不高时,可以通过领域事件订阅器直接向消息队列发送事件。具体流程如下:对一致性要求高时,需要先将事件存储,然后通过后台线程加载并分发到消息队列。具体流程如下:2.5 异步处理领域事件领域事件可以与异步工作流程协同,包括限界上下文间使用消息队列进行异步通信。当然,在同一个限界上下文中,也可以启动异步处理流程。作为事件的发布者,不应关心是否执行异步处理。异常处理是由事件执行者决定。DomainEventExecutor 提供对异步处理的支持。DomainEventExecutor eventExecutor = new ExecutorBasedDomainEventExecutor(“EventHandler”, 1, 100);this.domainEventBus.register(AccountEnabledEvent.class, eventExecutor, new AccountEnableEventHandler());异步处理,就意味着放弃数据库事务的 ACID 特性,而选择使用最终一致性。2.6 内部事件与外部事件使用领域事件时需要对事件进行区分,以避免技术实现的问题。认识内部事件和外部事件之间的区别至关重要。内部事件,是一个领域模型内部的事件,不在有界上下文间进行共享。外部事件,是对外发布的事件,在多个有界上下文中进行共享。一般情况下,在典型的业务用例中,可能会有很多的内部事件,而只有一两个外部事件。2.6.1 内部事件内部事件存在于限界上下文内部,受限界上下文边界保护。内部事件被限制在单个有界上下文边界内部,所以可以直接引用领域对象。public interface AggregateEvent<ID, A extends Aggregate<ID>> extends DomainEvent{ A source(); default A getSource(){ return source(); }}比如 AggregateEvent 中的 source 指向发布该事件的聚合。public class LikeSubmittedEvent extends AbstractAggregateEvent<Long, Like> { public LikeSubmittedEvent(Like source) { super(source); } public LikeSubmittedEvent(String id, Like source) { super(id, source); }}LikeSubmittedEvent 类直接引用 Like 聚合。2.6.2 外部事件外部事件存在于限界上下文间,被多个上下文共享。一般情况下,外部事件,只作为数据载体存在。常常采用平面结构,并公开所有属性。@Datapublic class SubmittedEvent { private Owner owner; private Target target;}SubmittedEvent 为扁平化结构,主要是对数据的封装。由于外部事件被多个上下文共享,版本管理就显得非常重要,以避免重大更改对其服务造成影响。3 实现领域事件模式领域事件是一种通用模式,它的本质是将领域概念添加到发布-订阅模式。3.1 封装领域事件的“发布-订阅”模式发布-订阅是比较成熟的设计模式,具有很高的通用性。因此,建议针对领域需求进行封装。比如直接使用 geekhalo-ddd 相关模块。定义领域事件:@Valuepublic class LikeCancelledEvent extends AbstractAggregateEvent<Long, Like> { public LikeCancelledEvent(Like source) { super(source); }}订阅领域事件:this.domainEventBus.register(LikeCancelledEvent.class, likeCancelledEvent->{ CanceledEvent canceledEvent = new CanceledEvent(); canceledEvent.setOwner(likeCancelledEvent.getSource().getOwner()); canceledEvent.setTarget(likeCancelledEvent.getSource().getTarget()); this.redisBasedQueue.pushLikeEvent(canceledEvent); });异步执行领域事件:DomainEventExecutor eventExecutor = new ExecutorBasedDomainEventExecutor(“LikeEventHandler”, 1, 100);this.domainEventBus.register(LikeCancelledEvent.class, eventExecutor, likeCancelledEvent->{ CanceledEvent canceledEvent = new CanceledEvent(); canceledEvent.setOwner(likeCancelledEvent.getSource().getOwner()); canceledEvent.setTarget(likeCancelledEvent.getSource().getTarget()); this.redisBasedQueue.pushLikeEvent(canceledEvent); });3.2 内存总线处理内部事件,消息队列处理外部事件内存总线简单高效,同时支持同步、异步两个处理方案,比较适合处理繁杂的内部事件;消息队列虽然复杂,但擅长解决服务间通信问题,适合处理外部事件。3.3 使用实体缓存领域事件理论上,只有在业务成功完成后,才应该对外发布事件。因此,将领域事件缓存在实体中,并在完成业务操作后将其进行发布,是一种较好的解决方案。相比,使用 ThreadLocal 管理订阅器,并在事件 publish 时进行订阅回调,事件缓存方案有明显的优势。3.4 使用 IOC 容器的事件发布功能IOC 容器为我们提供了很多使用功能,其中也包括发布-订阅功能,如 Spring。通常情况下,领域模型不应该直接依赖于 Spring 容器。因此,在领域中我们仍然使用内存总线,为其添加一个订阅者,将内存总线中的事件转发到 Spring 容器中。class SpringEventDispatcher implements ApplicationEventPublisherAware { @Autowired private DomainEventBus domainEventBus; private ApplicationEventPublisher eventPublisher; @Override public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { this.eventPublisher = applicationEventPublisher; } @PostConstruct public void addListener(){ this.domainEventBus.register(event->true, event -> {this.eventPublisher.publishEvent(event);}); }}此时,我们就可以直接使用 Spring 的 EventListener 机制对领域事件进行处理。@Componentpublic class RedisBasedQueueExporter { @Autowired private RedisBasedQueue redisBasedQueue; @EventListener public void handle(LikeSubmittedEvent likeSubmittedEvent){ SubmittedEvent submittedEvent = new SubmittedEvent(); submittedEvent.setOwner(likeSubmittedEvent.getSource().getOwner()); submittedEvent.setTarget(likeSubmittedEvent.getSource().getTarget()); this.redisBasedQueue.pushLikeEvent(submittedEvent); } @EventListener public void handle(LikeCancelledEvent likeCancelledEvent){ CanceledEvent canceledEvent = new CanceledEvent(); canceledEvent.setOwner(likeCancelledEvent.getSource().getOwner()); canceledEvent.setTarget(likeCancelledEvent.getSource().getTarget()); this.redisBasedQueue.pushLikeEvent(canceledEvent); }}4 小结领域事件是发生在问题域中的事实,它是通用语言的一部分。领域事件优先使用发布-订阅模式,会发布事件并且触发相应的事件处理器。限界上下文内,优先使用内部事件和内存总线;限界上下文间,优先使用外部事件和消息队列。领域事件使异步操作变得简单。领域事件为聚合间提供了最终一致性。领域事件可以将大的批量操作简化为许多小的业务操作。领域事件可以完成强大的事件存储。领域事件可以完成限界上下文间的集成。领域事件是更复杂架构(CQRS)的一种支持。 ...

April 16, 2019 · 5 min · jiezi

Spring Cloud 参考文档(服务发现:Eureka Server)

服务发现:Eureka Server本节介绍如何设置Eureka服务器。如何包含Eureka服务器要在项目中包含Eureka Server,请使用组ID为org.springframework.cloud和工件ID为spring-cloud-starter-netflix-eureka-server的启动器。如果你的项目已使用Thymeleaf作为其模板引擎,则可能无法正确加载Eureka服务器的Freemarker模板,在这种情况下,有必要手动配置模板加载器:application.ymlspring: freemarker: template-loader-path: classpath:/templates/ prefer-file-system-access: false如何运行Eureka服务器以下示例显示了最小的Eureka服务器:@SpringBootApplication@EnableEurekaServerpublic class Application { public static void main(String[] args) { new SpringApplicationBuilder(Application.class).web(true).run(args); }}服务器有一个UI主页和在/eureka/*下用于正常Eureka功能的HTTP API端点。以下链接有一些Eureka背景阅读:flux capacitor和谷歌小组讨论。由于Gradle的依赖关系解析规则和缺少父bom特性,依赖spring-cloud-starter-netflix-eureka-server可能导致应用程序启动失败,要解决此问题,请添加Spring Boot Gradle插件并导入Spring cloud starter parent bom,如下所示:build.gradlebuildscript { dependencies { classpath(“org.springframework.boot:spring-boot-gradle-plugin:{spring-boot-docs-version}”) }}apply plugin: “spring-boot"dependencyManagement { imports { mavenBom “org.springframework.cloud:spring-cloud-dependencies:{spring-cloud-version}” }}高可用性、Zones和RegionsEureka服务器没有后端存储,但注册表中的服务实例都必须发送心跳以使其注册保持最新(因此可以在内存中完成),客户端还有一个Eureka注册的内存缓存(因此,它们不必对服务的每个请求都去注册表)。默认情况下,每个Eureka服务器也是Eureka客户端,并且需要(至少一个)服务URL来定位对等体,如果你不提供该服务,该服务将运行并工作,但它会在你的日志中填充很多关于无法向对等方注册的噪音。独立模式两个缓存(客户端和服务器)的组合和心跳使独立的Eureka服务器能够很好地应对故障,只要有某种监视器或弹性运行时(例如Cloud Foundry)保持活着。在独立模式下,你可能更愿意关闭客户端行为,以便它不会继续尝试并且无法访问其对等方,以下示例显示如何关闭客户端行为:application.yml(独立Eureka服务器)。server: port: 8761eureka: instance: hostname: localhost client: registerWithEureka: false fetchRegistry: false serviceUrl: defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/请注意,serviceUrl指向与本地实例相同的主机。对等感知通过运行多个实例并要求它们相互注册,可以使Eureka更具弹性和可用性,实际上,这是默认行为,因此你需要做的就是将有效的serviceUrl添加到对等体,如以下示例所示:application.yml(两个Peer Aware Eureka服务器)。—spring: profiles: peer1eureka: instance: hostname: peer1 client: serviceUrl: defaultZone: http://peer2/eureka/—spring: profiles: peer2eureka: instance: hostname: peer2 client: serviceUrl: defaultZone: http://peer1/eureka/在前面的示例中,我们有一个YAML文件,可以通过在不同的Spring配置文件中运行它来在两个主机(peer1和peer2)上运行相同的服务器,你可以使用此配置通过操作/etc/hosts来解析主机名来测试单个主机上的对等感知(在生产中执行此操作没有太大价值)。实际上,如果你在知道自己的主机名的计算机上运行,则不需要eureka.instance.hostname(默认情况下,使用java.net.InetAddress查找它)。你可以将多个对等体添加到一个系统中,并且只要它们通过至少一个边缘彼此连接,它们就会在它们之间同步注册,如果对等体在物理上是分开的(在数据中心内或在多个数据中心之间),那么系统原则上可以在“裂脑”类型故障中存活,你可以向一个系统添加多个对等体,只要它们彼此直接连接,它们就会在它们之间同步注册。application.yml(三个Peer Aware Eureka服务器)。eureka: client: serviceUrl: defaultZone: http://peer1/eureka/,http://peer2/eureka/,http://peer3/eureka/—spring: profiles: peer1eureka: instance: hostname: peer1—spring: profiles: peer2eureka: instance: hostname: peer2—spring: profiles: peer3eureka: instance: hostname: peer3何时首选IP地址在某些情况下,Eureka最好公布服务的IP地址而不是主机名,将eureka.instance.preferIpAddress设置为true,当应用程序向eureka注册时,它使用其IP地址而不是其主机名。如果Java无法确定主机名,则将IP地址发送给Eureka,设置主机名的明确方法是只有设置eureka.instance.hostname属性,你可以使用环境变量在运行时设置主机名 — 例如,eureka.instance.hostname=${HOST_NAME}。保护Eureka服务器只需将spring-boot-starter-security添加到服务器的类路径中,即可通过Spring Security保护你的Eureka服务器,默认情况下,当Spring Security位于类路径上时,它将要求应用程序的每次请求都要发送有效的CSRF令牌,Eureka客户端通常不会拥有有效的跨站点请求伪造(CSRF)令牌,你需要为/eureka/端点禁用此要求,例如:@EnableWebSecurityclass WebSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().ignoringAntMatchers("/eureka/”); super.configure(http); }}有关CSRF的更多信息,请参阅Spring Security文档。可以在Spring Cloud Samples存储库中找到Eureka Server demo。JDK 11支持在JDK 11中删除了Eureka服务器所依赖的JAXB模块,如果你打算在运行Eureka服务器时使用JDK 11,则必须在POM或Gradle文件中包含这些依赖项。<dependency> <groupId>javax.xml.bind</groupId> <artifactId>jaxb-api</artifactId> <version>2.3.0</version></dependency><dependency> <groupId>com.sun.xml.bind</groupId> <artifactId>jaxb-core</artifactId> <version>2.3.0</version></dependency><dependency> <groupId>com.sun.xml.bind</groupId> <artifactId>jaxb-impl</artifactId> <version>2.3.0</version></dependency> ...

April 16, 2019 · 1 min · jiezi

【SpringSecurity系列02】SpringSecurity 表单认证逻辑源码解读

概要前面一节,通过简单配置即可实现SpringSecurity表单认证功能,而今天这一节将通过阅读源码的形式来学习SpringSecurity是如何实现这些功能, 前方高能预警,本篇分析源码篇幅较长。<!– more –>过滤器链前面我说过SpringSecurity是基于过滤器链的形式,那么我解析将会介绍一下具体有哪些过滤器。Filter Class介绍SecurityContextPersistenceFilter判断当前用户是否登录CrsfFilter用于防止csrf攻击LogoutFilter处理注销请求UsernamePasswordAuthenticationFilter处理表单登录的请求(也是我们今天的主角)BasicAuthenticationFilter处理http basic认证的请求由于过滤器链中的过滤器实在太多,我没有一一列举,调了几个比较重要的介绍一下。通过上面我们知道SpringSecurity对于表单登录的认证请求是交给了UsernamePasswordAuthenticationFilter处理的,那么具体的认证流程如下:从上图可知,UsernamePasswordAuthenticationFilter继承于抽象类AbstractAuthenticationProcessingFilter。具体认证是:进入doFilter方法,判断是否要认证,如果需要认证则进入attemptAuthentication方法,如果不需要直接结束attemptAuthentication方法中根据username跟password构造一个UsernamePasswordAuthenticationToken对象(此时的token是未认证的),并且将它交给ProviderManger来完成认证。ProviderManger中维护这一个AuthenticationProvider对象列表,通过遍历判断并且最后选择DaoAuthenticationProvider对象来完成最后的认证。DaoAuthenticationProvider根据ProviderManger传来的token取出username,并且调用我们写的UserDetailsService的loadUserByUsername方法从数据库中读取用户信息,然后对比用户密码,如果认证通过,则返回用户信息也是就是UserDetails对象,在重新构造UsernamePasswordAuthenticationToken(此时的token是 已经认证通过了的)。接下来我们将通过源码来分析具体的整个认证流程。AbstractAuthenticationProcessingFilterAbstractAuthenticationProcessingFilter 是一个抽象类。所有的认证认证请求的过滤器都会继承于它,它主要将一些公共的功能实现,而具体的验证逻辑交给子类实现,有点类似于父类设置好认证流程,子类负责具体的认证逻辑,这样跟设计模式的模板方法模式有点相似。现在我们分析一下 它里面比较重要的方法1、doFilterpublic void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { // 省略不相干代码。。。 // 1、判断当前请求是否要认证 if (!requiresAuthentication(request, response)) { // 不需要直接走下一个过滤器 chain.doFilter(request, response); return; } try { // 2、开始请求认证,attemptAuthentication具体实现给子类,如果认证成功返回一个认证通过的Authenticaion对象 authResult = attemptAuthentication(request, response); if (authResult == null) { return; } // 3、登录成功 将认证成功的用户信息放入session SessionAuthenticationStrategy接口,用于扩展 sessionStrategy.onAuthentication(authResult, request, response); } catch (InternalAuthenticationServiceException failed) { //2.1、发生异常,登录失败,进入登录失败handler回调 unsuccessfulAuthentication(request, response, failed); return; } catch (AuthenticationException failed) { //2.1、发生异常,登录失败,进入登录失败处理器 unsuccessfulAuthentication(request, response, failed); return; } // 3.1、登录成功,进入登录成功处理器。 successfulAuthentication(request, response, chain, authResult); }2、successfulAuthentication登录成功处理器protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException { //1、登录成功 将认证成功的Authentication对象存入SecurityContextHolder中 // SecurityContextHolder本质是一个ThreadLocal SecurityContextHolder.getContext().setAuthentication(authResult); //2、如果开启了记住我功能,将调用rememberMeServices的loginSuccess 将生成一个token // 将token放入cookie中这样 下次就不用登录就可以认证。具体关于记住我rememberMeServices的相关分析我 们下面几篇文章会深入分析的。 rememberMeServices.loginSuccess(request, response, authResult); // Fire event //3、发布一个登录事件。 if (this.eventPublisher != null) { eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent( authResult, this.getClass())); } //4、调用我们自己定义的登录成功处理器,这样也是我们扩展得知登录成功的一个扩展点。 successHandler.onAuthenticationSuccess(request, response, authResult); }3、unsuccessfulAuthentication登录失败处理器protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException { //1、登录失败,将SecurityContextHolder中的信息清空 SecurityContextHolder.clearContext(); //2、关于记住我功能的登录失败处理 rememberMeServices.loginFail(request, response); //3、调用我们自己定义的登录失败处理器,这里可以扩展记录登录失败的日志。 failureHandler.onAuthenticationFailure(request, response, failed); }关于AbstractAuthenticationProcessingFilter主要分析就到这。我们可以从源码中知道,当请求进入该过滤器中具体的流程是判断该请求是否要被认证调用attemptAuthentication方法开始认证,由于是抽象方法具体认证逻辑给子类如果登录成功,则将认证结果Authentication对象根据session策略写入session中,将认证结果写入到SecurityContextHolder,如果开启了记住我功能,则根据记住我功能,生成token并且写入cookie中,最后调用一个successHandler对象的方法,这个对象可以是我们配置注入的,用于处理我们的自定义登录成功的一些逻辑(比如记录登录成功日志等等)。如果登录失败,则清空SecurityContextHolder中的信息,并且调用我们自己注入的failureHandler对象,处理我们自己的登录失败逻辑。UsernamePasswordAuthenticationFilter从上面分析我们可以知道,UsernamePasswordAuthenticationFilter是继承于AbstractAuthenticationProcessingFilter,并且实现它的attemptAuthentication方法,来实现认证具体的逻辑实现。接下来,我们通过阅读UsernamePasswordAuthenticationFilter的源码来解读,它是如何完成认证的。 由于这里会涉及UsernamePasswordAuthenticationToken对象构造,所以我们先看看UsernamePasswordAuthenticationToken的源码1、UsernamePasswordAuthenticationToken// 继承至AbstractAuthenticationToken // AbstractAuthenticationToken主要定义一下在SpringSecurity中toke需要存在一些必须信息// 例如权限集合 Collection<GrantedAuthority> authorities; 是否认证通过boolean authenticated = false;认证通过的用户信息Object details;public class UsernamePasswordAuthenticationToken extends AbstractAuthenticationToken { // 未登录情况下 存的是用户名 登录成功情况下存的是UserDetails对象 private final Object principal; // 密码 private Object credentials; /** * 构造函数,用户没有登录的情况下,此时的authenticated是false,代表尚未认证 / public UsernamePasswordAuthenticationToken(Object principal, Object credentials) { super(null); this.principal = principal; this.credentials = credentials; setAuthenticated(false); } /* * 构造函数,用户登录成功的情况下,多了一个参数 是用户的权限集合,此时的authenticated是true,代表认证成功 / public UsernamePasswordAuthenticationToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities) { super(authorities); this.principal = principal; this.credentials = credentials; super.setAuthenticated(true); // must use super, as we override }}接下来我们就可以分析attemptAuthentication方法了。2、attemptAuthenticationpublic Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { // 1、判断是不是post请求,如果不是则抛出AuthenticationServiceException异常,注意这里抛出的异常都在AbstractAuthenticationProcessingFilter#doFilter方法中捕获,捕获之后会进入登录失败的逻辑。 if (postOnly && !request.getMethod().equals(“POST”)) { throw new AuthenticationServiceException( “Authentication method not supported: " + request.getMethod()); } // 2、从request中拿用户名跟密码 String username = obtainUsername(request); String password = obtainPassword(request); // 3、非空处理,防止NPE异常 if (username == null) { username = “”; } if (password == null) { password = “”; } // 4、除去空格 username = username.trim(); // 5、根据username跟password构造出一个UsernamePasswordAuthenticationToken对象 从上文分析可知道,此时的token是未认证的。 UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken( username, password); // 6、配置一下其他信息 ip 等等 setDetails(request, authRequest); // 7、调用ProviderManger的authenticate的方法进行具体认证逻辑 return this.getAuthenticationManager().authenticate(authRequest); }ProviderManager维护一个AuthenticationProvider列表,进行认证逻辑验证1、authenticatepublic Authentication authenticate(Authentication authentication) throws AuthenticationException { // 1、拿到token的类型。 Class<? extends Authentication> toTest = authentication.getClass(); AuthenticationException lastException = null; Authentication result = null; // 2、遍历AuthenticationProvider列表 for (AuthenticationProvider provider : getProviders()) { // 3、AuthenticationProvider不支持当前token类型,则直接跳过 if (!provider.supports(toTest)) { continue; } try { // 4、如果Provider支持当前token,则交给Provider完成认证。 result = provider.authenticate(authentication); } catch (AccountStatusException e) { throw e; } catch (InternalAuthenticationServiceException e) { throw e; } catch (AuthenticationException e) { lastException = e; } } // 5、登录成功 返回登录成功的token if (result != null) { eventPublisher.publishAuthenticationSuccess(result); return result; } }AbstractUserDetailsAuthenticationProvider1、authenticateAbstractUserDetailsAuthenticationProvider实现了AuthenticationProvider接口,并且实现了部分方法,DaoAuthenticationProvider继承于AbstractUserDetailsAuthenticationProvider类,所以我们先来看看AbstractUserDetailsAuthenticationProvider的实现。public abstract class AbstractUserDetailsAuthenticationProvider implements AuthenticationProvider, InitializingBean, MessageSourceAware { // 国际化处理 protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor(); /* * 对token一些检查,具体检查逻辑交给子类实现,抽象方法 / protected abstract void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException; /* * 认证逻辑的实现,调用抽象方法retrieveUser根据username获取UserDetails对象 */ public Authentication authenticate(Authentication authentication) throws AuthenticationException { // 1、获取usernmae String username = (authentication.getPrincipal() == null) ? “NONE_PROVIDED” : authentication.getName(); // 2、尝试去缓存中获取UserDetails对象 UserDetails user = this.userCache.getUserFromCache(username); // 3、如果为空,则代表当前对象没有缓存。 if (user == null) { cacheWasUsed = false; try { //4、调用retrieveUser去获取UserDetail对象,为什么这个方法是抽象方法大家很容易知道,如果UserDetail信息存在关系数据库 则可以重写该方法并且去关系数据库获取用户信息,如果UserDetail信息存在其他地方,可以重写该方法用其他的方法去获取用户信息,这样丝毫不影响整个认证流程,方便扩展。 user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication); } catch (UsernameNotFoundException notFound) { // 捕获异常 日志处理 并且往上抛出,登录失败。 if (hideUserNotFoundExceptions) { throw new BadCredentialsException(messages.getMessage( “AbstractUserDetailsAuthenticationProvider.badCredentials”, “Bad credentials”)); } else { throw notFound; } } } try { // 5、前置检查 判断当前用户是否锁定,禁用等等 preAuthenticationChecks.check(user); // 6、其他的检查,在DaoAuthenticationProvider是检查密码是否一致 additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication); } catch (AuthenticationException exception) { } // 7、后置检查,判断密码是否过期 postAuthenticationChecks.check(user); // 8、登录成功通过UserDetail对象重新构造一个认证通过的Token对象 return createSuccessAuthentication(principalToReturn, authentication, user); } protected Authentication createSuccessAuthentication(Object principal, Authentication authentication, UserDetails user) { // 调用第二个构造方法,构造一个认证通过的Token对象 UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken( principal, authentication.getCredentials(), authoritiesMapper.mapAuthorities(user.getAuthorities())); result.setDetails(authentication.getDetails()); return result; }}接下来我们具体看看retrieveUser的实现,没看源码大家应该也可以知道,retrieveUser方法应该是调用UserDetailsService去数据库查询是否有该用户,以及用户的密码是否一致。DaoAuthenticationProviderDaoAuthenticationProvider 主要是通过UserDetailService来获取UserDetail对象。1、retrieveUserprotected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException { try { // 1、调用UserDetailsService接口的loadUserByUsername方法获取UserDeail对象 UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username); // 2、如果loadedUser为null 代表当前用户不存在,抛出异常 登录失败。 if (loadedUser == null) { throw new InternalAuthenticationServiceException( “UserDetailsService returned null, which is an interface contract violation”); } // 3、返回查询的结果 return loadedUser; } }2、additionalAuthenticationChecksprotected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException { // 1、如果密码为空,则抛出异常、 if (authentication.getCredentials() == null) { throw new BadCredentialsException(messages.getMessage( “AbstractUserDetailsAuthenticationProvider.badCredentials”, “Bad credentials”)); } // 2、获取用户输入的密码 String presentedPassword = authentication.getCredentials().toString(); // 3、调用passwordEncoder的matche方法 判断密码是否一致 if (!passwordEncoder.matches(presentedPassword, userDetails.getPassword())) { logger.debug(“Authentication failed: password does not match stored value”); // 4、如果不一致 则抛出异常。 throw new BadCredentialsException(messages.getMessage( “AbstractUserDetailsAuthenticationProvider.badCredentials”, “Bad credentials”)); } }总结至此,整认证流程已经分析完毕,大家如果有什么不懂可以关注我的公众号一起讨论。学习是一个漫长的过程,学习源码可能会很困难但是只要努力一定就会有获取,大家一致共勉。 ...

April 16, 2019 · 4 min · jiezi

Spring Cloud 参考文档(Spring Cloud Config Client)

Spring Cloud Config ClientSpring Boot应用程序可以立即利用Spring Config Server(或应用程序开发人员提供的其他外部属性源),它还提取了一些与Environment变化事件相关的额外有用特性。配置优先Bootstrap在类路径上具有Spring Cloud Config Client的任何应用程序的默认行为如下:当配置客户端启动时,它会绑定到Config Server(通过spring.cloud.config.uri bootstrap配置属性)并使用远程属性源初始化Spring Environment。这种行为的最终结果是,所有想要使用Config Server的客户端应用程序都需要一个bootstrap.yml(或环境变量),其服务器地址在spring.cloud.config.uri中设置(默认为“http://localhost:8888" )。发现优先Bootstrap如果你使用DiscoveryClient实现,例如Spring Cloud Netflix和Eureka Service Discovery或Spring Cloud Consul,你可以将Config Server注册到Discovery Service,但是,在默认的“配置优先Bootstrap”模式下,客户端无法利用注册。如果你更喜欢使用DiscoveryClient来定位Config Server,可以通过设置spring.cloud.config.discovery.enabled=true(默认值为false)来实现,这样做的最终结果是客户端应用程序都需要具有适当发现配置的bootstrap.yml(或环境变量)。例如,使用Spring Cloud Netflix,你需要定义Eureka服务器地址(例如,在eureka.client.serviceUrl.defaultZone中),使用此选项的代价是启动时额外的网络往返,以查找服务注册,好处是,只要Discovery Service是固定点,Config Server就可以更改其坐标。默认服务ID是configserver,但你可以通过设置spring.cloud.config.discovery.serviceId在客户端上更改它(并在服务器上,以通常的方式提供服务,例如通过设置spring.application.name) 。发现客户端实现都支持某种元数据映射(例如,我们为Eureka提供了eureka.instance.metadataMap),可能需要在其服务注册元数据中配置Config Server的一些额外属性,以便客户端可以正确连接。如果使用HTTP Basic保护Config Server,则可以将凭据配置为user和password,此外,如果Config Server具有上下文路径,则可以设置configPath,例如,以下YAML文件用于作为Eureka客户端的Config Server:bootstrap.ymleureka: instance: … metadataMap: user: osufhalskjrtl password: lviuhlszvaorhvlo5847 configPath: /configConfig Client快速失败在某些情况下,如果服务无法连接到Config Server,你可能希望服务启动失败,如果这是所需的行为,请将bootstrap配置属性spring.cloud.config.fail-fast=true设置为使客户端停止并显示异常。Config Client重试如果你预期配置服务器在应用程序启动时偶尔可能不可用,你可以在失败后继续尝试。首先,你需要设置spring.cloud.config.fail-fast=true,然后,你需要在类路径中添加spring-retry和spring-boot-starter-aop,默认行为是重试六次,初始回退间隔为1000毫秒,后续回退的指数乘数为1.1,你可以通过设置spring.cloud.config.retry.*配置属性来配置这些属性(和其他属性)。要完全控制重试行为,请添加一个类型为RetryOperationsInterceptor的@Bean,其ID为configServerRetryInterceptor,Spring Retry有一个RetryInterceptorBuilder支持创建它。查找远程配置资源Config Service从/{name}/{profile}/{label}提供属性源,其中客户端应用程序中的默认绑定如下:“name” = ${spring.application.name}“profile” = ${spring.profiles.active}(实际上是Environment.getActiveProfiles())“label” = “master”设置属性${spring.application.name}时,不要在应用程序名称前加上保留字application-,以防止解析正确的属性源问题。你可以通过设置spring.cloud.config.来覆盖所有这些(其中是name、profile或label),该label可用于回滚到以前版本的配置,使用默认的Config Server实现,它可以是git标签,分支名称或提交ID。标签也可以以逗号分隔列表的形式提供,在这种情况下,列表中的项目将逐个尝试,直到成功为止,在处理特性分支时,此行为非常有用。例如,你可能希望将配置标签与你的分支对齐,但使其成为可选(在这种情况下,请使用spring.cloud.config.label=myfeature,develop)。为Config Server指定多个URL当你部署了多个Config Server实例并预期一个或多个实例不时不可用时,为确保高可用性,你可以指定多个URL(作为spring.cloud.config.uri属性下的逗号分隔列表),也可以让所有实例在Eureka等Service Registry中注册(如果使用发现优先Bootstrap模式)。请注意,只有在Config Server未运行时(即应用程序已退出时)或发生连接超时时,才能确保高可用性,例如,如果Config Server返回500(内部服务器错误)响应或Config Client从Config Server收到401(由于凭据错误或其他原因),则Config Client不会尝试从其他URL获取属性,这种错误表示用户问题而不是可用性问题。如果在Config Server上使用HTTP基本安全性,则仅当你在spring.cloud.config.uri属性下指定的每个URL中嵌入凭据时,才能支持每个Config Server身份验证凭据,如果使用任何其他类型的安全机制,则无法(目前)支持每个Config Server身份验证和授权。配置读取超时如果要配置读取超,可以使用属性spring.cloud.config.request-read-timeout来完成此操作。安全性如果你在服务器上使用HTTP Basic安全性,客户端需要知道密码(如果不是默认值,则需要用户名),你可以通过配置服务器URI或单独的用户名和密码属性指定用户名和密码,如以下示例所示:bootstrap.ymlspring: cloud: config: uri: https://user:secret@myconfig.mycompany.com以下示例显示了传递相同信息的另一种方法:bootstrap.ymlspring: cloud: config: uri: https://myconfig.mycompany.com username: user password: secretspring.cloud.config.password和spring.cloud.config.username值覆盖URI中提供的任何内容。如果你在Cloud Foundry上部署应用程序,提供密码的最佳方式是通过服务凭据(例如在URI中,因为它不需要在配置文件中),以下示例在本地运行,并在名为configserver的Cloud Foundry上为用户提供服务:bootstrap.ymlspring: cloud: config: uri: ${vcap.services.configserver.credentials.uri:http://user:password@localhost:8888}如果你使用其他形式的安全性,则可能需要向ConfigServicePropertySourceLocator提供一个RestTemplate(例如,通过在引导程序上下文中获取它并注入它)。健康指示器Config Client提供尝试从Config Server加载配置的Spring Boot Health Indicator,可以通过设置health.config.enabled=false来禁用健康指示器,出于性能原因,还会缓存响应,生存的默认缓存时间为5分钟,要更改该值,请设置health.config.time-to-live属性(以毫秒为单位)。提供自定义RestTemplate在某些情况下,你可能需要自定义从客户端向配置服务器发出的请求,通常,这样做涉及传递特殊的Authorization headers来验证对服务器的请求,要提供自定义RestTemplate:使用PropertySourceLocator的实现创建一个新的配置bean,如以下示例所示:CustomConfigServiceBootstrapConfiguration.java@Configurationpublic class CustomConfigServiceBootstrapConfiguration { @Bean public ConfigServicePropertySourceLocator configServicePropertySourceLocator() { ConfigClientProperties clientProperties = configClientProperties(); ConfigServicePropertySourceLocator configServicePropertySourceLocator = new ConfigServicePropertySourceLocator(clientProperties); configServicePropertySourceLocator.setRestTemplate(customRestTemplate(clientProperties)); return configServicePropertySourceLocator; }}在resources/META-INF中,创建一个名为spring.factories的文件并指定自定义配置,如以下示例所示:spring.factoriesorg.springframework.cloud.bootstrap.BootstrapConfiguration = com.my.config.client.CustomConfigServiceBootstrapConfigurationVault使用Vault作为配置服务器的后端时,客户端需要为服务器提供令牌以从Vault检索值,可以通过在bootstrap.yml中设置spring.cloud.config.token在客户端内提供此令牌,如以下示例所示:bootstrap.ymlspring: cloud: config: token: YourVaultTokenVault中的嵌套密钥Vault支持将密钥嵌套在Vault中存储的值中,如以下示例所示:echo -n ‘{“appA”: {“secret”: “appAsecret”}, “bar”: “baz”}’ | vault write secret/myapp -此命令将JSON对象写入Vault,要在Spring中访问这些值,可以使用传统的点(.)注解,如以下示例所示:@Value("${appA.secret}")String name = “World”;上面的代码会将name变量的值设置为appAsecret。上一篇:推送通知和Spring Cloud Bus ...

April 16, 2019 · 1 min · jiezi

聊聊spring tx的EnableTransactionManagement

序本文主要研究一下spring tx的EnableTransactionManagementEnableTransactionManagementspring-tx-5.1.6.RELEASE-sources.jar!/org/springframework/transaction/annotation/EnableTransactionManagement.java@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Documented@Import(TransactionManagementConfigurationSelector.class)public @interface EnableTransactionManagement { /** * Indicate whether subclass-based (CGLIB) proxies are to be created ({@code true}) as * opposed to standard Java interface-based proxies ({@code false}). The default is * {@code false}. <strong>Applicable only if {@link #mode()} is set to * {@link AdviceMode#PROXY}</strong>. * <p>Note that setting this attribute to {@code true} will affect <em>all</em> * Spring-managed beans requiring proxying, not just those marked with * {@code @Transactional}. For example, other beans marked with Spring’s * {@code @Async} annotation will be upgraded to subclass proxying at the same * time. This approach has no negative impact in practice unless one is explicitly * expecting one type of proxy vs another, e.g. in tests. / boolean proxyTargetClass() default false; /* * Indicate how transactional advice should be applied. * <p><b>The default is {@link AdviceMode#PROXY}.</b> * Please note that proxy mode allows for interception of calls through the proxy * only. Local calls within the same class cannot get intercepted that way; an * {@link Transactional} annotation on such a method within a local call will be * ignored since Spring’s interceptor does not even kick in for such a runtime * scenario. For a more advanced mode of interception, consider switching this to * {@link AdviceMode#ASPECTJ}. / AdviceMode mode() default AdviceMode.PROXY; /* * Indicate the ordering of the execution of the transaction advisor * when multiple advices are applied at a specific joinpoint. * <p>The default is {@link Ordered#LOWEST_PRECEDENCE}. / int order() default Ordered.LOWEST_PRECEDENCE;}EnableTransactionManagement有三个属性,分别是proxyTargetClass、AdviceMode、order;它import了TransactionManagementConfigurationSelectorproxyTargetClass设置为true表示使用基于子类实现的代理(CGLIB),设置为false表示使用基于接口实现的代理,默认为falseAdviceMode表示是使用哪种transactional advice,有PROXY及ASPECTJ两种,默认是AdviceMode.PROXYTransactionManagementConfigurationSelectorspring-tx-5.1.6.RELEASE-sources.jar!/org/springframework/transaction/annotation/TransactionManagementConfigurationSelector.javapublic class TransactionManagementConfigurationSelector extends AdviceModeImportSelector<EnableTransactionManagement> { /* * Returns {@link ProxyTransactionManagementConfiguration} or * {@code AspectJ(Jta)TransactionManagementConfiguration} for {@code PROXY} * and {@code ASPECTJ} values of {@link EnableTransactionManagement#mode()}, * respectively. / @Override protected String[] selectImports(AdviceMode adviceMode) { switch (adviceMode) { case PROXY: return new String[] {AutoProxyRegistrar.class.getName(), ProxyTransactionManagementConfiguration.class.getName()}; case ASPECTJ: return new String[] {determineTransactionAspectClass()}; default: return null; } } private String determineTransactionAspectClass() { return (ClassUtils.isPresent(“javax.transaction.Transactional”, getClass().getClassLoader()) ? TransactionManagementConfigUtils.JTA_TRANSACTION_ASPECT_CONFIGURATION_CLASS_NAME : TransactionManagementConfigUtils.TRANSACTION_ASPECT_CONFIGURATION_CLASS_NAME); }}TransactionManagementConfigurationSelector继承了抽象类AdviceModeImportSelector,它覆盖了selectImports,该方法根据adviceMode返回要创建的类名;如果是PROXY模式,则返回AutoProxyRegistrar.class.getName(),ProxyTransactionManagementConfiguration.class.getName();如果是ASPECTJ模式,则返回TransactionManagementConfigUtils.JTA_TRANSACTION_ASPECT_CONFIGURATION_CLASS_NAME或者是TransactionManagementConfigUtils.TRANSACTION_ASPECT_CONFIGURATION_CLASS_NAMEAbstractTransactionManagementConfigurationspring-tx-5.1.6.RELEASE-sources.jar!/org/springframework/transaction/annotation/AbstractTransactionManagementConfiguration.java@Configurationpublic abstract class AbstractTransactionManagementConfiguration implements ImportAware { @Nullable protected AnnotationAttributes enableTx; /* * Default transaction manager, as configured through a {@link TransactionManagementConfigurer}. / @Nullable protected PlatformTransactionManager txManager; @Override public void setImportMetadata(AnnotationMetadata importMetadata) { this.enableTx = AnnotationAttributes.fromMap( importMetadata.getAnnotationAttributes(EnableTransactionManagement.class.getName(), false)); if (this.enableTx == null) { throw new IllegalArgumentException( “@EnableTransactionManagement is not present on importing class " + importMetadata.getClassName()); } } @Autowired(required = false) void setConfigurers(Collection<TransactionManagementConfigurer> configurers) { if (CollectionUtils.isEmpty(configurers)) { return; } if (configurers.size() > 1) { throw new IllegalStateException(“Only one TransactionManagementConfigurer may exist”); } TransactionManagementConfigurer configurer = configurers.iterator().next(); this.txManager = configurer.annotationDrivenTransactionManager(); } @Bean(name = TransactionManagementConfigUtils.TRANSACTIONAL_EVENT_LISTENER_FACTORY_BEAN_NAME) @Role(BeanDefinition.ROLE_INFRASTRUCTURE) public static TransactionalEventListenerFactory transactionalEventListenerFactory() { return new TransactionalEventListenerFactory(); }}AbstractTransactionManagementConfiguration为EnableTransactionManagement注解提供了通用的结构,这里主要是初始化了txManager,以及创建了TransactionalEventListenerFactory;它有三个子类,分别是ProxyTransactionManagementConfiguration、AspectJTransactionManagementConfiguration、AspectJJtaTransactionManagementConfigurationProxyTransactionManagementConfigurationspring-tx-5.1.6.RELEASE-sources.jar!/org/springframework/transaction/annotation/ProxyTransactionManagementConfiguration.java@Configurationpublic class ProxyTransactionManagementConfiguration extends AbstractTransactionManagementConfiguration { @Bean(name = TransactionManagementConfigUtils.TRANSACTION_ADVISOR_BEAN_NAME) @Role(BeanDefinition.ROLE_INFRASTRUCTURE) public BeanFactoryTransactionAttributeSourceAdvisor transactionAdvisor() { BeanFactoryTransactionAttributeSourceAdvisor advisor = new BeanFactoryTransactionAttributeSourceAdvisor(); advisor.setTransactionAttributeSource(transactionAttributeSource()); advisor.setAdvice(transactionInterceptor()); if (this.enableTx != null) { advisor.setOrder(this.enableTx.<Integer>getNumber(“order”)); } return advisor; } @Bean @Role(BeanDefinition.ROLE_INFRASTRUCTURE) public TransactionAttributeSource transactionAttributeSource() { return new AnnotationTransactionAttributeSource(); } @Bean @Role(BeanDefinition.ROLE_INFRASTRUCTURE) public TransactionInterceptor transactionInterceptor() { TransactionInterceptor interceptor = new TransactionInterceptor(); interceptor.setTransactionAttributeSource(transactionAttributeSource()); if (this.txManager != null) { interceptor.setTransactionManager(this.txManager); } return interceptor; }}ProxyTransactionManagementConfiguration继承了AbstractTransactionManagementConfiguration,这里它注册了BeanFactoryTransactionAttributeSourceAdvisor、TransactionAttributeSource(AnnotationTransactionAttributeSource)、TransactionInterceptorAnnotationTransactionAttributeSourcespring-tx-5.1.6.RELEASE-sources.jar!/org/springframework/transaction/annotation/AnnotationTransactionAttributeSource.javapublic class AnnotationTransactionAttributeSource extends AbstractFallbackTransactionAttributeSource implements Serializable { private static final boolean jta12Present; private static final boolean ejb3Present; static { ClassLoader classLoader = AnnotationTransactionAttributeSource.class.getClassLoader(); jta12Present = ClassUtils.isPresent(“javax.transaction.Transactional”, classLoader); ejb3Present = ClassUtils.isPresent(“javax.ejb.TransactionAttribute”, classLoader); } private final boolean publicMethodsOnly; private final Set<TransactionAnnotationParser> annotationParsers; /* * Create a default AnnotationTransactionAttributeSource, supporting * public methods that carry the {@code Transactional} annotation * or the EJB3 {@link javax.ejb.TransactionAttribute} annotation. / public AnnotationTransactionAttributeSource() { this(true); } /* * Create a custom AnnotationTransactionAttributeSource, supporting * public methods that carry the {@code Transactional} annotation * or the EJB3 {@link javax.ejb.TransactionAttribute} annotation. * @param publicMethodsOnly whether to support public methods that carry * the {@code Transactional} annotation only (typically for use * with proxy-based AOP), or protected/private methods as well * (typically used with AspectJ class weaving) / public AnnotationTransactionAttributeSource(boolean publicMethodsOnly) { this.publicMethodsOnly = publicMethodsOnly; if (jta12Present || ejb3Present) { this.annotationParsers = new LinkedHashSet<>(4); this.annotationParsers.add(new SpringTransactionAnnotationParser()); if (jta12Present) { this.annotationParsers.add(new JtaTransactionAnnotationParser()); } if (ejb3Present) { this.annotationParsers.add(new Ejb3TransactionAnnotationParser()); } } else { this.annotationParsers = Collections.singleton(new SpringTransactionAnnotationParser()); } } //……}AnnotationTransactionAttributeSource的无参构造器,设置了publicMethodsOnly为true,同时创建了SpringTransactionAnnotationParserTransactionInterceptorspring-tx-5.1.6.RELEASE-sources.jar!/org/springframework/transaction/interceptor/TransactionInterceptor.javapublic class TransactionInterceptor extends TransactionAspectSupport implements MethodInterceptor, Serializable { /* * Create a new TransactionInterceptor. * <p>Transaction manager and transaction attributes still need to be set. * @see #setTransactionManager * @see #setTransactionAttributes(java.util.Properties) * @see #setTransactionAttributeSource(TransactionAttributeSource) / public TransactionInterceptor() { } /* * Create a new TransactionInterceptor. * @param ptm the default transaction manager to perform the actual transaction management * @param attributes the transaction attributes in properties format * @see #setTransactionManager * @see #setTransactionAttributes(java.util.Properties) / public TransactionInterceptor(PlatformTransactionManager ptm, Properties attributes) { setTransactionManager(ptm); setTransactionAttributes(attributes); } /* * Create a new TransactionInterceptor. * @param ptm the default transaction manager to perform the actual transaction management * @param tas the attribute source to be used to find transaction attributes * @see #setTransactionManager * @see #setTransactionAttributeSource(TransactionAttributeSource) / public TransactionInterceptor(PlatformTransactionManager ptm, TransactionAttributeSource tas) { setTransactionManager(ptm); setTransactionAttributeSource(tas); } @Override @Nullable public Object invoke(MethodInvocation invocation) throws Throwable { // Work out the target class: may be {@code null}. // The TransactionAttributeSource should be passed the target class // as well as the method, which may be from an interface. Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null); // Adapt to TransactionAspectSupport’s invokeWithinTransaction… return invokeWithinTransaction(invocation.getMethod(), targetClass, invocation::proceed); } //——————————————————————— // Serialization support //——————————————————————— private void writeObject(ObjectOutputStream oos) throws IOException { // Rely on default serialization, although this class itself doesn’t carry state anyway… oos.defaultWriteObject(); // Deserialize superclass fields. oos.writeObject(getTransactionManagerBeanName()); oos.writeObject(getTransactionManager()); oos.writeObject(getTransactionAttributeSource()); oos.writeObject(getBeanFactory()); } private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { // Rely on default serialization, although this class itself doesn’t carry state anyway… ois.defaultReadObject(); // Serialize all relevant superclass fields. // Superclass can’t implement Serializable because it also serves as base class // for AspectJ aspects (which are not allowed to implement Serializable)! setTransactionManagerBeanName((String) ois.readObject()); setTransactionManager((PlatformTransactionManager) ois.readObject()); setTransactionAttributeSource((TransactionAttributeSource) ois.readObject()); setBeanFactory((BeanFactory) ois.readObject()); }}TransactionInterceptor继承了抽象类TransactionAspectSupport,同时实现了MethodInterceptor, Serializable接口;其invoke方法首先读取targetClass,然后调用了抽象类TransactionAspectSupport的invokeWithinTransaction方法TransactionAspectSupportspring-tx-5.1.6.RELEASE-sources.jar!/org/springframework/transaction/interceptor/TransactionAspectSupport.javapublic abstract class TransactionAspectSupport implements BeanFactoryAware, InitializingBean { // NOTE: This class must not implement Serializable because it serves as base // class for AspectJ aspects (which are not allowed to implement Serializable)! private static final Object DEFAULT_TRANSACTION_MANAGER_KEY = new Object(); private static final ThreadLocal<TransactionInfo> transactionInfoHolder = new NamedThreadLocal<>(“Current aspect-driven transaction”); @Nullable protected static TransactionInfo currentTransactionInfo() throws NoTransactionException { return transactionInfoHolder.get(); } public static TransactionStatus currentTransactionStatus() throws NoTransactionException { TransactionInfo info = currentTransactionInfo(); if (info == null || info.transactionStatus == null) { throw new NoTransactionException(“No transaction aspect-managed TransactionStatus in scope”); } return info.transactionStatus; } protected final Log logger = LogFactory.getLog(getClass()); @Nullable private String transactionManagerBeanName; @Nullable private PlatformTransactionManager transactionManager; @Nullable private TransactionAttributeSource transactionAttributeSource; @Nullable private BeanFactory beanFactory; private final ConcurrentMap<Object, PlatformTransactionManager> transactionManagerCache = new ConcurrentReferenceHashMap<>(4); //…… /* * General delegate for around-advice-based subclasses, delegating to several other template * methods on this class. Able to handle {@link CallbackPreferringPlatformTransactionManager} * as well as regular {@link PlatformTransactionManager} implementations. * @param method the Method being invoked * @param targetClass the target class that we’re invoking the method on * @param invocation the callback to use for proceeding with the target invocation * @return the return value of the method, if any * @throws Throwable propagated from the target invocation / @Nullable protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass, final InvocationCallback invocation) throws Throwable { // If the transaction attribute is null, the method is non-transactional. TransactionAttributeSource tas = getTransactionAttributeSource(); final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null); final PlatformTransactionManager tm = determineTransactionManager(txAttr); final String joinpointIdentification = methodIdentification(method, targetClass, txAttr); if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) { // Standard transaction demarcation with getTransaction and commit/rollback calls. TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification); Object retVal = null; try { // This is an around advice: Invoke the next interceptor in the chain. // This will normally result in a target object being invoked. retVal = invocation.proceedWithInvocation(); } catch (Throwable ex) { // target invocation exception completeTransactionAfterThrowing(txInfo, ex); throw ex; } finally { cleanupTransactionInfo(txInfo); } commitTransactionAfterReturning(txInfo); return retVal; } else { final ThrowableHolder throwableHolder = new ThrowableHolder(); // It’s a CallbackPreferringPlatformTransactionManager: pass a TransactionCallback in. try { Object result = ((CallbackPreferringPlatformTransactionManager) tm).execute(txAttr, status -> { TransactionInfo txInfo = prepareTransactionInfo(tm, txAttr, joinpointIdentification, status); try { return invocation.proceedWithInvocation(); } catch (Throwable ex) { if (txAttr.rollbackOn(ex)) { // A RuntimeException: will lead to a rollback. if (ex instanceof RuntimeException) { throw (RuntimeException) ex; } else { throw new ThrowableHolderException(ex); } } else { // A normal return value: will lead to a commit. throwableHolder.throwable = ex; return null; } } finally { cleanupTransactionInfo(txInfo); } }); // Check result state: It might indicate a Throwable to rethrow. if (throwableHolder.throwable != null) { throw throwableHolder.throwable; } return result; } catch (ThrowableHolderException ex) { throw ex.getCause(); } catch (TransactionSystemException ex2) { if (throwableHolder.throwable != null) { logger.error(“Application exception overridden by commit exception”, throwableHolder.throwable); ex2.initApplicationException(throwableHolder.throwable); } throw ex2; } catch (Throwable ex2) { if (throwableHolder.throwable != null) { logger.error(“Application exception overridden by commit exception”, throwableHolder.throwable); } throw ex2; } } } //……}TransactionAspectSupport的invokeWithinTransaction方法对于TransactionAttribute不为null且PlatformTransactionManager是CallbackPreferringPlatformTransactionManager类型的在TransactionCallback中事务处理;其他的则使用标准的getTransaction and commit/rollback calls模式来进行事务处理,执行前调用createTransactionIfNecessary,异常时调用completeTransactionAfterThrowing,finally时调用cleanupTransactionInfo,最后调用commitTransactionAfterReturningcreateTransactionIfNecessaryspring-tx-5.1.6.RELEASE-sources.jar!/org/springframework/transaction/interceptor/TransactionAspectSupport.java /* * Create a transaction if necessary based on the given TransactionAttribute. * <p>Allows callers to perform custom TransactionAttribute lookups through * the TransactionAttributeSource. * @param txAttr the TransactionAttribute (may be {@code null}) * @param joinpointIdentification the fully qualified method name * (used for monitoring and logging purposes) * @return a TransactionInfo object, whether or not a transaction was created. * The {@code hasTransaction()} method on TransactionInfo can be used to * tell if there was a transaction created. * @see #getTransactionAttributeSource() / @SuppressWarnings(“serial”) protected TransactionInfo createTransactionIfNecessary(@Nullable PlatformTransactionManager tm, @Nullable TransactionAttribute txAttr, final String joinpointIdentification) { // If no name specified, apply method identification as transaction name. if (txAttr != null && txAttr.getName() == null) { txAttr = new DelegatingTransactionAttribute(txAttr) { @Override public String getName() { return joinpointIdentification; } }; } TransactionStatus status = null; if (txAttr != null) { if (tm != null) { status = tm.getTransaction(txAttr); } else { if (logger.isDebugEnabled()) { logger.debug(“Skipping transactional joinpoint [” + joinpointIdentification + “] because no transaction manager has been configured”); } } } return prepareTransactionInfo(tm, txAttr, joinpointIdentification, status); } /* * Prepare a TransactionInfo for the given attribute and status object. * @param txAttr the TransactionAttribute (may be {@code null}) * @param joinpointIdentification the fully qualified method name * (used for monitoring and logging purposes) * @param status the TransactionStatus for the current transaction * @return the prepared TransactionInfo object / protected TransactionInfo prepareTransactionInfo(@Nullable PlatformTransactionManager tm, @Nullable TransactionAttribute txAttr, String joinpointIdentification, @Nullable TransactionStatus status) { TransactionInfo txInfo = new TransactionInfo(tm, txAttr, joinpointIdentification); if (txAttr != null) { // We need a transaction for this method… if (logger.isTraceEnabled()) { logger.trace(“Getting transaction for [” + txInfo.getJoinpointIdentification() + “]”); } // The transaction manager will flag an error if an incompatible tx already exists. txInfo.newTransactionStatus(status); } else { // The TransactionInfo.hasTransaction() method will return false. We created it only // to preserve the integrity of the ThreadLocal stack maintained in this class. if (logger.isTraceEnabled()) { logger.trace(“Don’t need to create transaction for [” + joinpointIdentification + “]: This method isn’t transactional.”); } } // We always bind the TransactionInfo to the thread, even if we didn’t create // a new transaction here. This guarantees that the TransactionInfo stack // will be managed correctly even if no transaction was created by this aspect. txInfo.bindToThread(); return txInfo; }createTransactionIfNecessary方法基于TransactionAttribute判断是否需要新创建transaction,然后执行prepareTransactionInfo创建TransactionInfo并bindToThreadcompleteTransactionAfterThrowingspring-tx-5.1.6.RELEASE-sources.jar!/org/springframework/transaction/interceptor/TransactionAspectSupport.java /* * Handle a throwable, completing the transaction. * We may commit or roll back, depending on the configuration. * @param txInfo information about the current transaction * @param ex throwable encountered / protected void completeTransactionAfterThrowing(@Nullable TransactionInfo txInfo, Throwable ex) { if (txInfo != null && txInfo.getTransactionStatus() != null) { if (logger.isTraceEnabled()) { logger.trace(“Completing transaction for [” + txInfo.getJoinpointIdentification() + “] after exception: " + ex); } if (txInfo.transactionAttribute != null && txInfo.transactionAttribute.rollbackOn(ex)) { try { txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus()); } catch (TransactionSystemException ex2) { logger.error(“Application exception overridden by rollback exception”, ex); ex2.initApplicationException(ex); throw ex2; } catch (RuntimeException | Error ex2) { logger.error(“Application exception overridden by rollback exception”, ex); throw ex2; } } else { // We don’t roll back on this exception. // Will still roll back if TransactionStatus.isRollbackOnly() is true. try { txInfo.getTransactionManager().commit(txInfo.getTransactionStatus()); } catch (TransactionSystemException ex2) { logger.error(“Application exception overridden by commit exception”, ex); ex2.initApplicationException(ex); throw ex2; } catch (RuntimeException | Error ex2) { logger.error(“Application exception overridden by commit exception”, ex); throw ex2; } } } }completeTransactionAfterThrowing用于处理异常情况,它根据TransactionInfo及Throwable信息判断是要rollback还是commitcleanupTransactionInfospring-tx-5.1.6.RELEASE-sources.jar!/org/springframework/transaction/interceptor/TransactionAspectSupport.java /* * Reset the TransactionInfo ThreadLocal. * <p>Call this in all cases: exception or normal return! * @param txInfo information about the current transaction (may be {@code null}) / protected void cleanupTransactionInfo(@Nullable TransactionInfo txInfo) { if (txInfo != null) { txInfo.restoreThreadLocalStatus(); } }cleanupTransactionInfo用于重置ThreadLocal的TransactionInfocommitTransactionAfterReturningspring-tx-5.1.6.RELEASE-sources.jar!/org/springframework/transaction/interceptor/TransactionAspectSupport.java /* * Execute after successful completion of call, but not after an exception was handled. * Do nothing if we didn’t create a transaction. * @param txInfo information about the current transaction */ protected void commitTransactionAfterReturning(@Nullable TransactionInfo txInfo) { if (txInfo != null && txInfo.getTransactionStatus() != null) { if (logger.isTraceEnabled()) { logger.trace(“Completing transaction for [” + txInfo.getJoinpointIdentification() + “]”); } txInfo.getTransactionManager().commit(txInfo.getTransactionStatus()); } }commitTransactionAfterReturning用于在方法执行成功时来commit事务小结EnableTransactionManagement有三个属性,分别是proxyTargetClass、AdviceMode、order;它import了TransactionManagementConfigurationSelector;proxyTargetClass设置为true表示使用基于子类实现的代理(CGLIB),设置为false表示使用基于接口实现的代理,默认为false;AdviceMode表示是使用哪种transactional advice,有PROXY及ASPECTJ两种,默认是AdviceMode.PROXYTransactionManagementConfigurationSelector继承了抽象类AdviceModeImportSelector,它覆盖了selectImports,该方法根据adviceMode返回要创建的类名;如果是PROXY模式,则返回AutoProxyRegistrar.class.getName(),ProxyTransactionManagementConfiguration.class.getName();如果是ASPECTJ模式,则返回TransactionManagementConfigUtils.JTA_TRANSACTION_ASPECT_CONFIGURATION_CLASS_NAME或者是TransactionManagementConfigUtils.TRANSACTION_ASPECT_CONFIGURATION_CLASS_NAMEProxyTransactionManagementConfiguration继承了AbstractTransactionManagementConfiguration,这里它注册了BeanFactoryTransactionAttributeSourceAdvisor、TransactionAttributeSource(AnnotationTransactionAttributeSource)、TransactionInterceptorTransactionInterceptor继承了抽象类TransactionAspectSupport,同时实现了MethodInterceptor, Serializable接口;其invoke方法首先读取targetClass,然后调用了抽象类TransactionAspectSupport的invokeWithinTransaction方法TransactionAspectSupport的invokeWithinTransaction方法对于TransactionAttribute不为null且PlatformTransactionManager是CallbackPreferringPlatformTransactionManager类型的在TransactionCallback中事务处理;其他的则使用标准的getTransaction and commit/rollback calls模式来进行事务处理,执行前调用createTransactionIfNecessary,异常时调用completeTransactionAfterThrowing,finally时调用cleanupTransactionInfo,最后调用commitTransactionAfterReturningdoc5.7. TransactionalityTransactions with Spring and JPAUsing Transactions in Spring Data JPA ...

April 16, 2019 · 10 min · jiezi

超全的设计模式简介(45种)

该文建议配合 design-patterns-for-humans 中文版 一起看。推荐阅读超全的设计模式简介(45种)design-patterns-for-humans 中文版(github 仓库永久更新)MongoDB 资源、库、工具、应用程序精选列表中文版有哪些鲜为人知,但是很有意思的网站?一份攻城狮笔记每天搜集 Github 上优秀的项目一些有趣的民间故事超好用的谷歌浏览器、Sublime Text、Phpstorm、油猴插件合集设计模式(Design pattern)代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。 设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了重用代码、让代码更容易被他人理解、保证代码可靠性。 毫无疑问,设计模式于己于他人于系统都是多赢的,设计模式使代码编制真正工程化,设计模式是软件工程的基石,如同大厦的一块块砖石一样。项目中合理地运用设计模式可以完美地解决很多问题,每种模式在现实中都有相应的原理来与之对应,每种模式都描述了一个在我们周围不断重复发生的问题,以及该问题的核心解决方案,这也是设计模式能被广泛应用的原因。设计模式的类型共有 23 种设计模式。这些模式可以分为三大类:创建型模式(Creational Patterns)- 这些设计模式提供了一种在创建对象的同时隐藏创建逻辑的方式,而不是使用 new 运算符直接实例化对象。这使得程序在判断针对某个给定实例需要创建哪些对象时更加灵活。工厂模式(Factory Pattern)抽象工厂模式(Abstract Factory Pattern)单例模式(Singleton Pattern)建造者模式(Builder Pattern)原型模式(Prototype Pattern)对象池模式 (Pool)多例模式 (Multiton)静态工厂模式 (Static Factory)结构型模式(Structural Patterns)- 这些设计模式关注类和对象的组合。继承的概念被用来组合接口和定义组合对象获得新功能的方式。适配器模式(Adapter Pattern)桥接模式(Bridge Pattern)过滤器模式(Filter、Criteria Pattern)组合模式(Composite Pattern)装饰器模式(Decorator Pattern)外观模式(Facade Pattern)享元模式(Flyweight Pattern)代理模式(Proxy Pattern)数据映射模式 (Data Mapper)依赖注入模式 (Dependency Injection)门面模式 (Facade)流接口模式 (Fluent Interface)注册模式 (Registry)行为型模式(Behavioral Patterns)- 这些设计模式特别关注对象之间的通信。责任链模式(Chain of Responsibility Pattern)命令模式(Command Pattern)解释器模式(Interpreter Pattern)迭代器模式(Iterator Pattern)中介者模式(Mediator Pattern)备忘录模式(Memento Pattern)观察者模式(Observer Pattern)状态模式(State Pattern)空对象模式(Null Object Pattern)策略模式(Strategy Pattern)模板模式(Template Pattern)访问者模式(Visitor Pattern)规格模式 (Specification)访问者模式 (Visitor)J2EE 设计模式 - 这些设计模式特别关注表示层。这些模式是由 Sun Java Center 鉴定的。MVC模式(MVC Pattern)业务代表模式(Business Delegate Pattern)组合实体模式(Composite Entity Pattern)数据访问对象模式(Data Access Object Pattern)前端控制器模式(Front Controller Pattern)拦截过滤器模式(Intercepting Filter Pattern)服务定位器模式(Service Locator Pattern)传输对象模式(Transfer Object Pattern)委托模式 (Delegation)资源库模式 (Repository)下面用一个图片来整体描述一下设计模式之间的关系:设计模式的六大原则1、开闭原则(Open Close Principle)开闭原则的意思是:对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。简言之,是为了使程序的扩展性好,易于维护和升级。想要达到这样的效果,我们需要使用接口和抽象类,后面的具体设计中我们会提到这点。2、里氏代换原则(Liskov Substitution Principle)里氏代换原则是面向对象设计的基本原则之一。 里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。LSP 是继承复用的基石,只有当派生类可以替换掉基类,且软件单位的功能不受到影响时,基类才能真正被复用,而派生类也能够在基类的基础上增加新的行为。里氏代换原则是对开闭原则的补充。实现开闭原则的关键步骤就是抽象化,而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。3、依赖倒转原则(Dependence Inversion Principle)这个原则是开闭原则的基础,具体内容:针对接口编程,依赖于抽象而不依赖于具体。4、接口隔离原则(Interface Segregation Principle)这个原则的意思是:使用多个隔离的接口,比使用单个接口要好。它还有另外一个意思是:降低类之间的耦合度。由此可见,其实设计模式就是从大型软件架构出发、便于升级和维护的软件设计思想,它强调降低依赖,降低耦合。5、迪米特法则,又称最少知道原则(Demeter Principle)最少知道原则是指:一个实体应当尽量少地与其他实体之间发生相互作用,使得系统功能模块相对独立。6、合成复用原则(Composite Reuse Principle)合成复用原则是指:尽量使用合成 / 聚合的方式,而不是使用继承。工厂模式工厂模式(Factory Pattern)最常用的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。 在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。介绍意图: 定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类,工厂模式使其创建过程延迟到子类进行。主要解决: 主要解决接口选择的问题。何时使用: 我们明确地计划不同条件下创建不同实例时。如何解决: 让其子类实现工厂接口,返回的也是一个抽象的产品。关键代码: 创建过程在其子类执行。应用实例:您需要一辆汽车,可以直接从工厂里面提货,而不用去管这辆汽车是怎么做出来的,以及这个汽车里面的具体实现。Hibernate 换数据库只需换方言和驱动就可以。优点:一个调用者想创建一个对象,只要知道其名称就可以了。扩展性高,如果想增加一个产品,只要扩展一个工厂类就可以。屏蔽产品的具体实现,调用者只关心产品的接口。缺点: 每次增加一个产品时,都需要增加一个具体类和对象实现工厂,使得系统中类的个数成倍增加,在一定程度上增加了系统的复杂度,同时也增加了系统具体类的依赖。这并不是什么好事。使用场景:日志记录器:记录可能记录到本地硬盘、系统事件、远程服务器等,用户可以选择记录日志到什么地方。数据库访问,当用户不知道最后系统采用哪一类数据库,以及数据库可能有变化时。设计一个连接服务器的框架,需要三个协议,“POP3”、“IMAP”、“HTTP”,可以把这三个作为产品类,共同实现一个接口。注意事项: 作为一种创建类模式,在任何需要生成复杂对象的地方,都可以使用工厂方法模式。有一点需要注意的地方就是复杂对象适合使用工厂模式,而简单对象,特别是只需要通过 new 就可以完成创建的对象,无需使用工厂模式。如果使用工厂模式,就需要引入一个工厂类,会增加系统的复杂度。抽象工厂模式抽象工厂模式(Abstract Factory Pattern)是围绕一个超级工厂创建其他工厂。该超级工厂又称为其他工厂的工厂。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。 在抽象工厂模式中,接口是负责创建一个相关对象的工厂,不需要显式指定它们的类。每个生成的工厂都能按照工厂模式提供对象。介绍意图: 提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。主要解决: 主要解决接口选择的问题。何时使用: 系统的产品有多于一个的产品族,而系统只消费其中某一族的产品。如何解决: 在一个产品族里面,定义多个产品。关键代码: 在一个工厂里聚合多个同类产品。应用实例: 工作了,为了参加一些聚会,肯定有两套或多套衣服吧,比如说有商务装(成套,一系列具体产品)、时尚装(成套,一系列具体产品),甚至对于一个家庭来说,可能有商务女装、商务男装、时尚女装、时尚男装,这些也都是成套的,即一系列具体产品。假设一种情况(现实中是不存在的,要不然,没法进入共产主义了,但有利于说明抽象工厂模式),在您的家中,某一个衣柜(具体工厂)只能存放某一种这样的衣服(成套,一系列具体产品),每次拿这种成套的衣服时也自然要从这个衣柜中取出了。用 OO 的思想去理解,所有的衣柜(具体工厂)都是衣柜类的(抽象工厂)某一个,而每一件成套的衣服又包括具体的上衣(某一具体产品),裤子(某一具体产品),这些具体的上衣其实也都是上衣(抽象产品),具体的裤子也都是裤子(另一个抽象产品)。优点: 当一个产品族中的多个对象被设计成一起工作时,它能保证客户端始终只使用同一个产品族中的对象。缺点: 产品族扩展非常困难,要增加一个系列的某一产品,既要在抽象的 Creator 里加代码,又要在具体的里面加代码。使用场景:QQ 换皮肤,一整套一起换。生成不同操作系统的程序。注意事项: 产品族难扩展,产品等级易扩展。单例模式单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。 这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。注意:1、单例类只能有一个实例。2、单例类必须自己创建自己的唯一实例。3、单例类必须给所有其他对象提供这一实例。介绍意图: 保证一个类仅有一个实例,并提供一个访问它的全局访问点。主要解决: 一个全局使用的类频繁地创建与销毁。何时使用: 当您想控制实例数目,节省系统资源的时候。如何解决: 判断系统是否已经有这个单例,如果有则返回,如果没有则创建。关键代码: 构造函数是私有的。应用实例:一个班级只有一个班主任。Windows 是多进程多线程的,在操作一个文件的时候,就不可避免地出现多个进程或线程同时操作一个文件的现象,所以所有文件的处理必须通过唯一的实例来进行。一些设备管理器常常设计为单例模式,比如一个电脑有两台打印机,在输出的时候就要处理不能两台打印机打印同一个文件。优点:在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)。避免对资源的多重占用(比如写文件操作)。缺点: 没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。使用场景:要求生产唯一序列号。WEB 中的计数器,不用每次刷新都在数据库里加一次,用单例先缓存起来。创建的一个对象需要消耗的资源过多,比如 I/O 与数据库的连接等。注意事项:getInstance() 方法中需要使用同步锁 synchronized (Singleton.class) 防止多线程同时进入造成 instance 被多次实例化。建造者模式建造者模式(Builder Pattern)使用多个简单的对象一步一步构建成一个复杂的对象。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。 一个 Builder 类会一步一步构造最终的对象。该 Builder 类是独立于其他对象的。介绍意图: 将一个复杂的构建与其表示相分离,使得同样的构建过程可以创建不同的表示。主要解决: 主要解决在软件系统中,有时候面临着 “一个复杂对象” 的创建工作,其通常由各个部分的子对象用一定的算法构成;由于需求的变化,这个复杂对象的各个部分经常面临着剧烈的变化,但是将它们组合在一起的算法却相对稳定。何时使用: 一些基本部件不会变,而其组合经常变化的时候。如何解决: 将变与不变分离开。关键代码: 建造者:创建和提供实例,导演:管理建造出来的实例的依赖关系。应用实例:去肯德基,汉堡、可乐、薯条、炸鸡翅等是不变的,而其组合是经常变化的,生成出所谓的 “套餐”。JAVA 中的 StringBuilder。优点:建造者独立,易扩展。便于控制细节风险。缺点:产品必须有共同点,范围有限制。如内部变化复杂,会有很多的建造类。使用场景:需要生成的对象具有复杂的内部结构。需要生成的对象内部属性本身相互依赖。注意事项: 与工厂模式的区别是:建造者模式更加关注与零件装配的顺序。原型模式原型模式(Prototype Pattern)是用于创建重复的对象,同时又能保证性能。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。 这种模式是实现了一个原型接口,该接口用于创建当前对象的克隆。当直接创建对象的代价比较大时,则采用这种模式。例如,一个对象需要在一个高代价的数据库操作之后被创建。我们可以缓存该对象,在下一个请求时返回它的克隆,在需要的时候更新数据库,以此来减少数据库调用。介绍意图: 用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。主要解决: 在运行期建立和删除原型。何时使用:当一个系统应该独立于它的产品创建,构成和表示时。当要实例化的类是在运行时刻指定时,例如,通过动态装载。为了避免创建一个与产品类层次平行的工厂类层次时。当一个类的实例只能有几个不同状态组合中的一种时。建立相应数目的原型并克隆它们可能比每次用合适的状态手工实例化该类更方便一些。如何解决: 利用已有的一个原型对象,快速地生成和原型对象一样的实例。关键代码:实现克隆操作,在 JAVA 继承 Cloneable,重写 clone(),在 .NET 中可以使用 Object 类的 MemberwiseClone() 方法来实现对象的浅拷贝或通过序列化的方式来实现深拷贝。原型模式同样用于隔离类对象的使用者和具体类型(易变类)之间的耦合关系,它同样要求这些 “易变类” 拥有稳定的接口。应用实例:细胞分裂。JAVA 中的 Object clone() 方法。优点:性能提高。逃避构造函数的约束。缺点:配备克隆方法需要对类的功能进行通盘考虑,这对于全新的类不是很难,但对于已有的类不一定很容易,特别当一个类引用不支持串行化的间接对象,或者引用含有循环结构的时候。必须实现 Cloneable 接口。使用场景:资源优化场景。类初始化需要消化非常多的资源,这个资源包括数据. 硬件资源等。性能和安全要求的场景。通过 new 产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式。一个对象多个修改者的场景。一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用。在实际项目中,原型模式很少单独出现,一般是和工厂方法模式一起出现,通过 clone 的方法创建一个对象,然后由工厂方法提供给调用者。原型模式已经与 Java 融为浑然一体,大家可以随手拿来使用。注意事项: 与通过对一个类进行实例化来构造新对象不同的是,原型模式是通过拷贝一个现有对象生成新对象的。浅拷贝实现 Cloneable,重写,深拷贝是通过实现 Serializable 读取二进制流。对象池模式对象池(也称为资源池)被用来管理对象缓存。对象池是一组已经初始化过且可以直接使用的对象集合,用户在使用对象时可以从对象池中获取对象,对其进行操作处理,并在不需要时归还给对象池而非销毁它。 若对象初始化、实例化的代价高,且需要经常实例化,但每次实例化的数量较少的情况下,使用对象池可以获得显著的性能提升。常见的使用对象池模式的技术包括线程池、数据库连接池、任务队列池、图片资源对象池等。 当然,如果要实例化的对象较小,不需要多少资源开销,就没有必要使用对象池模式了,这非但不会提升性能,反而浪费内存空间,甚至降低性能。示例代码Pool.php<?phpnamespace DesignPatterns\Creational\Pool;class Pool{ private $instances = array(); private $class; public function __construct($class) { $this->class = $class; } public function get() { if (count($this->instances) > 0) { return array_pop($this->instances); } return new $this->class(); } public function dispose($instance) { $this->instances[] = $instance; }}Processor.php<?phpnamespace DesignPatterns\Creational\Pool;class Processor{ private $pool; private $processing = 0; private $maxProcesses = 3; private $waitingQueue = []; public function __construct(Pool $pool) { $this->pool = $pool; } public function process($image) { if ($this->processing++ < $this->maxProcesses) { $this->createWorker($image); } else { $this->pushToWaitingQueue($image); } } private function createWorker($image) { $worker = $this->pool->get(); $worker->run($image, array($this, ‘processDone’)); } public function processDone($worker) { $this->processing–; $this->pool->dispose($worker); if (count($this->waitingQueue) > 0) { $this->createWorker($this->popFromWaitingQueue()); } } private function pushToWaitingQueue($image) { $this->waitingQueue[] = $image; } private function popFromWaitingQueue() { return array_pop($this->waitingQueue); }}Worker.php<?phpnamespace DesignPatterns\Creational\Pool;class Worker{ public function __construct() { // let’s say that constuctor does really expensive work… // for example creates “thread” } public function run($image, array $callback) { // do something with $image… // and when it’s done, execute callback call_user_func($callback, $this); }}多例模式多例模式和单例模式类似,但可以返回多个实例。比如我们有多个数据库连接,MySQL、SQLite、Postgres,又或者我们有多个日志记录器,分别用于记录调试信息和错误信息,这些都可以使用多例模式实现。示例代码Multiton.php<?phpnamespace DesignPatterns\Creational\Multiton;/ * Multiton类 /class Multiton{ / * * 第一个实例 / const INSTANCE_1 = ‘1’; / * * 第二个实例 / const INSTANCE_2 = ‘2’; / * 实例数组 * * @var array / private static $instances = array(); / * 构造函数是私有的,不能从外部进行实例化 * / private function __construct() { } / * 通过指定名称返回实例(使用到该实例的时候才会实例化) * * @param string $instanceName * * @return Multiton / public static function getInstance($instanceName) { if (!array_key_exists($instanceName, self::$instances)) { self::$instances[$instanceName] = new self(); } return self::$instances[$instanceName]; } / * 防止实例从外部被克隆 * * @return void / private function __clone() { } / * 防止实例从外部反序列化 * * @return void / private function __wakeup() { }}静态工厂模式与简单工厂类似,该模式用于创建一组相关或依赖的对象,不同之处在于静态工厂模式使用一个静态方法来创建所有类型的对象,该静态方法通常是 factory 或 build。示例代码StaticFactory.php<?phpnamespace DesignPatterns\Creational\StaticFactory;class StaticFactory{ / * 通过传入参数创建相应对象实例 * * @param string $type * * @static * * @throws \InvalidArgumentException * @return FormatterInterface / public static function factory($type) { $className = NAMESPACE . ‘\Format’ . ucfirst($type); if (!class_exists($className)) { throw new \InvalidArgumentException(‘Missing format class.’); } return new $className(); }}FormatterInterface.php<?phpnamespace DesignPatterns\Creational\StaticFactory;/ * FormatterInterface接口 /interface FormatterInterface{}FormatString.php<?phpnamespace DesignPatterns\Creational\StaticFactory;/ * FormatNumber类 /class FormatNumber implements FormatterInterface{}适配器模式适配器模式(Adapter Pattern)是作为两个不兼容的接口之间的桥梁。这种类型的设计模式属于结构型模式,它结合了两个独立接口的功能。 这种模式涉及到一个单一的类,该类负责加入独立的或不兼容的接口功能。举个真实的例子,读卡器是作为内存卡和笔记本之间的适配器。您将内存卡插入读卡器,再将读卡器插入笔记本,这样就可以通过笔记本来读取内存卡。介绍意图: 将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。主要解决: 主要解决在软件系统中,常常要将一些 “现存的对象” 放到新的环境中,而新环境要求的接口是现对象不能满足的。何时使用:系统需要使用现有的类,而此类的接口不符合系统的需要。想要建立一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作,这些源类不一定有一致的接口。通过接口转换,将一个类插入另一个类系中。(比如老虎和飞禽,现在多了一个飞虎,在不增加实体的需求下,增加一个适配器,在里面包容一个虎对象,实现飞的接口。)如何解决: 继承或依赖(推荐)。关键代码: 适配器继承或依赖已有的对象,实现想要的目标接口。应用实例:美国电器 110V,中国 220V,就要有一个适配器将 110V 转化为 220V。JAVA JDK 1.1 提供了 Enumeration 接口,而在 1.2 中提供了 Iterator 接口,想要使用 1.2 的 JDK,则要将以前系统的 Enumeration 接口转化为 Iterator 接口,这时就需要适配器模式。在 LINUX 上运行 WINDOWS 程序。 4. JAVA 中的 jdbc。优点:可以让任何两个没有关联的类一起运行。提高了类的复用。增加了类的透明度。灵活性好。缺点:过多地使用适配器,会让系统非常零乱,不易整体进行把握。比如,明明看到调用的是 A 接口,其实内部被适配成了 B 接口的实现,一个系统如果太多出现这种情况,无异于一场灾难。因此如果不是很有必要,可以不使用适配器,而是直接对系统进行重构。由于 JAVA 至多继承一个类,所以至多只能适配一个适配者类,而且目标类必须是抽象类。使用场景: 有动机地修改一个正常运行的系统的接口,这时应该考虑使用适配器模式。注意事项: 适配器不是在详细设计时添加的,而是解决正在服役的项目的问题。桥接模式桥接(Bridge)是用于把抽象化与实现化解耦,使得二者可以独立变化。这种类型的设计模式属于结构型模式,它通过提供抽象化和实现化之间的桥接结构,来实现二者的解耦。 这种模式涉及到一个作为桥接的接口,使得实体类的功能独立于接口实现类。这两种类型的类可被结构化改变而互不影响。介绍意图: 将抽象部分与实现部分分离,使它们都可以独立的变化。主要解决: 在有多种可能会变化的情况下,用继承会造成类爆炸问题,扩展起来不灵活。何时使用: 实现系统可能有多个角度分类,每一种角度都可能变化。如何解决: 把这种多角度分类分离出来,让它们独立变化,减少它们之间耦合。关键代码: 抽象类依赖实现类。应用实例:猪八戒从天蓬元帅转世投胎到猪,转世投胎的机制将尘世划分为两个等级,即:灵魂和肉体,前者相当于抽象化,后者相当于实现化。生灵通过功能的委派,调用肉体对象的功能,使得生灵可以动态地选择。墙上的开关,可以看到的开关是抽象的,不用管里面具体怎么实现的。优点:抽象和实现的分离。优秀的扩展能力。实现细节对客户透明。缺点: 桥接模式的引入会增加系统的理解与设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计与编程。使用场景:如果一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性,避免在两个层次之间建立静态的继承联系,通过桥接模式可以使它们在抽象层建立一个关联关系。对于那些不希望使用继承或因为多层次继承导致系统类的个数急剧增加的系统,桥接模式尤为适用。一个类存在两个独立变化的维度,且这两个维度都需要进行扩展。注意事项: 对于两个独立变化的维度,使用桥接模式再适合不过了。过滤器模式过滤器模式(Filter Pattern)或标准模式(Criteria Pattern)是一种设计模式,这种模式允许开发人员使用不同的标准来过滤一组对象,通过逻辑运算以解耦的方式把它们连接起来。这种类型的设计模式属于结构型模式,它结合多个标准来获得单一标准。组合模式组合模式(Composite Pattern),又叫部分整体模式,是用于把一组相似的对象当作一个单一的对象。组合模式依据树形结构来组合对象,用来表示部分以及整体层次。这种类型的设计模式属于结构型模式,它创建了对象组的树形结构。 这种模式创建了一个包含自己对象组的类。该类提供了修改相同对象组的方式。介绍意图: 将对象组合成树形结构以表示 “部分 - 整体” 的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。主要解决: 它在我们树型结构的问题中,模糊了简单元素和复杂元素的概念,客户程序可以向处理简单元素一样来处理复杂元素,从而使得客户程序与复杂元素的内部结构解耦。何时使用:您想表示对象的部分 - 整体层次结构(树形结构)。您希望用户忽略组合对象与单个对象的不同,用户将统一地使用组合结构中的所有对象。如何解决: 树枝和叶子实现统一接口,树枝内部组合该接口。关键代码: 树枝内部组合该接口,并且含有内部属性 List,里面放 Component。应用实例:算术表达式包括操作数. 操作符和另一个操作数,其中,另一个操作符也可以是操作数. 操作符和另一个操作数。在 JAVA AWT 和 SWING 中,对于 Button 和 Checkbox 是树叶,Container 是树枝。优点:高层模块调用简单。节点自由增加。缺点: 在使用组合模式时,其叶子和树枝的声明都是实现类,而不是接口,违反了依赖倒置原则。使用场景: 部分. 整体场景,如树形菜单,文件. 文件夹的管理。注意事项: 定义时为具体类。装饰器模式装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构。这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装。 这种模式创建了一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能。介绍意图: 动态地给一个对象添加一些额外的职责。就增加功能来说,装饰器模式相比生成子类更为灵活。主要解决: 一般的,我们为了扩展一个类经常使用继承方式实现,由于继承为类引入静态特征,并且随着扩展功能的增多,子类会很膨胀。何时使用: 在不想增加很多子类的情况下扩展类。如何解决: 将具体功能职责划分,同时继承装饰者模式。关键代码:Component 类充当抽象角色,不应该具体实现。修饰类引用和继承 Component 类,具体扩展类重写父类方法。应用实例:孙悟空有 72 变,当他变成 “庙宇” 后,他的根本还是一只猴子,但是他又有了庙宇的功能。不论一幅画有没有画框都可以挂在墙上,但是通常都是有画框的,并且实际上是画框被挂在墙上。在挂在墙上之前,画可以被蒙上玻璃,装到框子里;这时画、玻璃和画框形成了一个物体。优点: 装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。缺点: 多层装饰比较复杂。使用场景:扩展一个类的功能。动态增加功能,动态撤销。注意事项: 可代替继承。外观模式外观模式(Facade Pattern)隐藏系统的复杂性,并向客户端提供了一个客户端可以访问系统的接口。这种类型的设计模式属于结构型模式,它向现有的系统添加一个接口,来隐藏系统的复杂性。 这种模式涉及到一个单一的类,该类提供了客户端请求的简化方法和对现有系统类方法的委托调用。介绍意图: 为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。主要解决: 降低访问复杂系统的内部子系统时的复杂度,简化客户端与之的接口。何时使用:客户端不需要知道系统内部的复杂联系,整个系统只需提供一个 “接待员” 即可。定义系统的入口。如何解决: 客户端不与系统耦合,外观类与系统耦合。关键代码: 在客户端和复杂系统之间再加一层,这一层将调用顺序. 依赖关系等处理好。应用实例:去医院看病,可能要去挂号、门诊、划价、取药,让患者或患者家属觉得很复杂,如果有提供接待人员,只让接待人员来处理,就很方便。JAVA 的三层开发模式。优点:减少系统相互依赖。提高灵活性。提高了安全性。缺点: 不符合开闭原则,如果要改东西很麻烦,继承重写都不合适。使用场景:为复杂的模块或子系统提供外界访问的模块。子系统相对独立。预防低水平人员带来的风险。注意事项: 在层次化结构中,可以使用外观模式定义系统中每一层的入口。享元模式享元模式(Flyweight Pattern)主要用于减少创建对象的数量,以减少内存占用和提高性能。这种类型的设计模式属于结构型模式,它提供了减少对象数量从而改善应用所需的对象结构的方式。 享元模式尝试重用现有的同类对象,如果未找到匹配的对象,则创建新对象。我们将通过创建 5 个对象来画出 20 个分布于不同位置的圆来演示这种模式。由于只有 5 种可用的颜色,所以 color 属性被用来检查现有的 Circle 对象。介绍意图: 运用共享技术有效地支持大量细粒度的对象。主要解决: 在有大量对象时,有可能会造成内存溢出,我们把其中共同的部分抽象出来,如果有相同的业务请求,直接返回在内存中已有的对象,避免重新创建。何时使用:系统中有大量对象。这些对象消耗大量内存。这些对象的状态大部分可以外部化。这些对象可以按照内蕴状态分为很多组,当把外蕴对象从对象中剔除出来时,每一组对象都可以用一个对象来代替。系统不依赖于这些对象身份,这些对象是不可分辨的。如何解决: 用唯一标识码判断,如果在内存中有,则返回这个唯一标识码所标识的对象。关键代码: 用 HashMap 存储这些对象。应用实例:JAVA 中的 String,如果有则返回,如果没有则创建一个字符串保存在字符串缓存池里面。2. 数据库的数据池。优点: 大大减少对象的创建,降低系统的内存,使效率提高。缺点: 提高了系统的复杂度,需要分离出外部状态和内部状态,而且外部状态具有固有化的性质,不应该随着内部状态的变化而变化,否则会造成系统的混乱。使用场景:系统有大量相似对象。需要缓冲池的场景。注意事项:注意划分外部状态和内部状态,否则可能会引起线程安全问题。这些类必须有一个工厂对象加以控制。代理模式在代理模式(Proxy Pattern)中,一个类代表另一个类的功能。这种类型的设计模式属于结构型模式。 在代理模式中,我们创建具有现有对象的对象,以便向外界提供功能接口。介绍意图: 为其他对象提供一种代理以控制对这个对象的访问。主要解决: 在直接访问对象时带来的问题,比如说:要访问的对象在远程的机器上。在面向对象系统中,有些对象由于某些原因(比如对象创建开销很大,或者某些操作需要安全控制,或者需要进程外的访问),直接访问会给使用者或者系统结构带来很多麻烦,我们可以在访问此对象时加上一个对此对象的访问层。何时使用: 想在访问一个类时做一些控制。如何解决: 增加中间层。关键代码: 实现与被代理类组合。应用实例:Windows 里面的快捷方式。猪八戒去找高翠兰结果是孙悟空变的,可以这样理解:把高翠兰的外貌抽象出来,高翠兰本人和孙悟空都实现了这个接口,猪八戒访问高翠兰的时候看不出来这个是孙悟空,所以说孙悟空是高翠兰代理类。买火车票不一定在火车站买,也可以去代售点。一张支票或银行存单是账户中资金的代理。支票在市场交易中用来代替现金,并提供对签发人账号上资金的控制。spring aop。优点:职责清晰。高扩展性。智能化。缺点:由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢。实现代理模式需要额外的工作,有些代理模式的实现非常复杂。使用场景: 按职责来划分,通常有以下使用场景:远程代理。虚拟代理。Copy-on-Write 代理。保护(Protect or Access)代理。Cache 代理。防火墙(Firewall)代理。同步化(Synchronization)代理。智能引用(Smart Reference)代理。注意事项:和适配器模式的区别:适配器模式主要改变所考虑对象的接口,而代理模式不能改变所代理类的接口。和装饰器模式的区别:装饰器模式为了增强功能,而代理模式是为了加以控制。数据映射模式在了解数据映射模式之前,先了解下数据映射,它是在持久化数据存储层(通常是关系型数据库)和驻于内存的数据表现层之间进行双向数据传输的数据访问层。 数据映射模式的目的是让持久化数据存储层、驻于内存的数据表现层、以及数据映射本身三者相互独立、互不依赖。这个数据访问层由一个或多个映射器(或者数据访问对象)组成,用于实现数据传输。通用的数据访问层可以处理不同的实体类型,而专用的则处理一个或几个。 数据映射模式的核心在于它的数据模型遵循单一职责原则(Single Responsibility Principle), 这也是和 Active Record 模式的不同之处。最典型的数据映射模式例子就是数据库 ORM 模型 (Object Relational Mapper)。 准确来说该模式是个架构模式。依赖注入模式依赖注入(Dependency Injection)是控制反转(Inversion of Control)的一种实现方式。 我们先来看看什么是控制反转。 当调用者需要被调用者的协助时,在传统的程序设计过程中,通常由调用者来创建被调用者的实例,但在这里,创建被调用者的工作不再由调用者来完成,而是将被调用者的创建移到调用者的外部,从而反转被调用者的创建,消除了调用者对被调用者创建的控制,因此称为控制反转。 要实现控制反转,通常的解决方案是将创建被调用者实例的工作交由 IoC 容器来完成,然后在调用者中注入被调用者(通过构造器/方法注入实现),这样我们就实现了调用者与被调用者的解耦,该过程被称为依赖注入。 依赖注入不是目的,它是一系列工具和手段,最终的目的是帮助我们开发出松散耦合(loose coupled)、可维护、可测试的代码和程序。这条原则的做法是大家熟知的面向接口,或者说是面向抽象编程。门面模式门面模式(Facade)又称外观模式,用于为子系统中的一组接口提供一个一致的界面。门面模式定义了一个高层接口,这个接口使得子系统更加容易使用:引入门面角色之后,用户只需要直接与门面角色交互,用户与子系统之间的复杂关系由门面角色来实现,从而降低了系统的耦合度。示例代码Facade.php<?phpnamespace DesignPatterns\Structural\Facade;/* * 门面类 /class Facade{ /* * @var OsInterface / protected $os; /* * @var BiosInterface / protected $bios; /* * This is the perfect time to use a dependency injection container * to create an instance of this class * * @param BiosInterface $bios * @param OsInterface $os / public function __construct(BiosInterface $bios, OsInterface $os) { $this->bios = $bios; $this->os = $os; } /* * turn on the system / public function turnOn() { $this->bios->execute(); $this->bios->waitForKeyPress(); $this->bios->launch($this->os); } /* * turn off the system / public function turnOff() { $this->os->halt(); $this->bios->powerDown(); }}OsInterface.php<?phpnamespace DesignPatterns\Structural\Facade;/* * OsInterface接口 /interface OsInterface{ /* * halt the OS / public function halt();}BiosInterface.php<?phpnamespace DesignPatterns\Structural\Facade;/* * BiosInterface接口 /interface BiosInterface{ /* * execute the BIOS / public function execute(); /* * wait for halt / public function waitForKeyPress(); /* * launches the OS * * @param OsInterface $os / public function launch(OsInterface $os); /* * power down BIOS / public function powerDown();}流接口模式在软件工程中,流接口(Fluent Interface)是指实现一种面向对象的、能提高代码可读性的 API 的方法,其目的就是可以编写具有自然语言一样可读性的代码,我们对这种代码编写方式还有一个通俗的称呼 —— 方法链。 Laravel 中流接口模式有着广泛使用,比如查询构建器,邮件等等。示例代码Sql.php<?phpnamespace DesignPatterns\Structural\FluentInterface;/* * SQL 类 /class Sql{ /* * @var array / protected $fields = array(); /* * @var array / protected $from = array(); /* * @var array / protected $where = array(); /* * 添加 select 字段 * * @param array $fields * * @return SQL / public function select(array $fields = array()) { $this->fields = $fields; return $this; } /* * 添加 FROM 子句 * * @param string $table * @param string $alias * * @return SQL / public function from($table, $alias) { $this->from[] = $table . ’ AS ’ . $alias; return $this; } /* * 添加 WHERE 条件 * * @param string $condition * * @return SQL / public function where($condition) { $this->where[] = $condition; return $this; } /* * 生成查询语句 * * @return string / public function getQuery() { return ‘SELECT ’ . implode(’,’, $this->fields) . ’ FROM ’ . implode(’,’, $this->from) . ’ WHERE ’ . implode(’ AND ‘, $this->where); }}注册模式注册模式(Registry)也叫做注册树模式,注册器模式。注册模式为应用中经常使用的对象创建一个中央存储器来存放这些对象 —— 通常通过一个只包含静态方法的抽象类来实现(或者通过单例模式)。示例代码Registry.php<?phpnamespace DesignPatterns\Structural\Registry;/* * class Registry /abstract class Registry{ const LOGGER = ’logger’; /* * @var array / protected static $storedValues = array(); /* * sets a value * * @param string $key * @param mixed $value * * @static * @return void / public static function set($key, $value) { self::$storedValues[$key] = $value; } /* * gets a value from the registry * * @param string $key * * @static * @return mixed / public static function get($key) { return self::$storedValues[$key]; } // typically there would be methods to check if a key has already been registered and so on …}责任链模式顾名思义,责任链模式(Chain of Responsibility Pattern)为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。这种类型的设计模式属于行为型模式。 在这种模式中,通常每个接收者都包含对另一个接收者的引用。如果一个对象不能处理该请求,那么它会把相同的请求传给下一个接收者,依此类推。介绍意图: 避免请求发送者与接收者耦合在一起,让多个对象都有可能接收请求,将这些对象连接成一条链,并且沿着这条链传递请求,直到有对象处理它为止。主要解决: 职责链上的处理者负责处理请求,客户只需要将请求发送到职责链上即可,无须关心请求的处理细节和请求的传递,所以职责链将请求的发送者和请求的处理者解耦了。何时使用: 在处理消息的时候以过滤很多道。如何解决: 拦截的类都实现统一接口。关键代码: Handler 里面聚合它自己,在 HandlerRequest 里判断是否合适,如果没达到条件则向下传递,向谁传递之前 set 进去。应用实例:红楼梦中的 “击鼓传花”。JS 中的事件冒泡。JAVA WEB 中 Apache Tomcat 对 Encoding 的处理,Struts2 的拦截器,jsp servlet 的 Filter。优点:降低耦合度。它将请求的发送者和接收者解耦。简化了对象。使得对象不需要知道链的结构。增强给对象指派职责的灵活性。通过改变链内的成员或者调动它们的次序,允许动态地新增或者删除责任。增加新的请求处理类很方便。缺点:不能保证请求一定被接收。系统性能将受到一定影响,而且在进行代码调试时不太方便,可能会造成循环调用。可能不容易观察运行时的特征,有碍于除错。使用场景:有多个对象可以处理同一个请求,具体哪个对象处理该请求由运行时刻自动确定。在不明确指定接收者的情况下,向多个对象中的一个提交一个请求。可动态指定一组对象处理请求。注意事项: 在 JAVA WEB 中遇到很多应用。命令模式命令模式(Command Pattern)是一种数据驱动的设计模式,它属于行为型模式。请求以命令的形式包裹在对象中,并传给调用对象。调用对象寻找可以处理该命令的合适的对象,并把该命令传给相应的对象,该对象执行命令。介绍意图: 将一个请求封装成一个对象,从而使您可以用不同的请求对客户进行参数化。主要解决: 在软件系统中,行为请求者与行为实现者通常是一种紧耦合的关系,但某些场合,比如需要对行为进行记录、撤销或重做、事务等处理时,这种无法抵御变化的紧耦合的设计就不太合适。何时使用: 在某些场合,比如要对行为进行 “记录、撤销 / 重做、事务” 等处理,这种无法抵御变化的紧耦合是不合适的。在这种情况下,如何将 “行为请求者” 与 “行为实现者” 解耦?将一组行为抽象为对象,可以实现二者之间的松耦合。如何解决: 通过调用者调用接受者执行命令,顺序:调用者→接受者→命令。关键代码: 定义三个角色:received 真正的命令执行对象Commandinvoker 使用命令对象的入口应用实例: struts 1 中的 action 核心控制器 ActionServlet 只有一个,相当于 Invoker,而模型层的类会随着不同的应用有不同的模型类,相当于具体的 Command。优点:降低了系统耦合度。新的命令可以很容易添加到系统中去。缺点: 使用命令模式可能会导致某些系统有过多的具体命令类。使用场景: 认为是命令的地方都可以使用命令模式,比如:GUI 中每一个按钮都是一条命令。模拟 CMD。注意事项: 系统需要支持命令的撤销 (Undo) 操作和恢复 (Redo) 操作,也可以考虑使用命令模式,见命令模式的扩展。解释器模式解释器模式(Interpreter Pattern)提供了评估语言的语法或表达式的方式,它属于行为型模式。这种模式实现了一个表达式接口,该接口解释一个特定的上下文。这种模式被用在 SQL 解析、符号处理引擎等。介绍意图: 给定一个语言,定义它的文法表示,并定义一个解释器,这个解释器使用该标识来解释语言中的句子。主要解决: 对于一些固定文法构建一个解释句子的解释器。何时使用: 如果一种特定类型的问题发生的频率足够高,那么可能就值得将该问题的各个实例表述为一个简单语言中的句子。这样就可以构建一个解释器,该解释器通过解释这些句子来解决该问题。如何解决: 构建语法树,定义终结符与非终结符。关键代码: 构建环境类,包含解释器之外的一些全局信息,一般是 HashMap。应用实例: 编译器、运算表达式计算。优点:可扩展性比较好,灵活。增加了新的解释表达式的方式。易于实现简单文法。缺点:可利用场景比较少。对于复杂的文法比较难维护。解释器模式会引起类膨胀。解释器模式采用递归调用方法。使用场景:可以将一个需要解释执行的语言中的句子表示为一个抽象语法树。一些重复出现的问题可以用一种简单的语言来进行表达。一个简单语法需要解释的场景。注意事项: 可利用场景比较少,JAVA 中如果碰到可以用 expression4J 代替。迭代器模式迭代器模式(Iterator Pattern)是 Java 和 .Net 编程环境中非常常用的设计模式。这种模式用于顺序访问集合对象的元素,不需要知道集合对象的底层表示。 迭代器模式属于行为型模式。介绍意图: 提供一种方法顺序访问一个聚合对象中各个元素, 而又无须暴露该对象的内部表示。主要解决: 不同的方式来遍历整个整合对象。何时使用: 遍历一个聚合对象。如何解决: 把在元素之间游走的责任交给迭代器,而不是聚合对象。关键代码: 定义接口:hasNext, next。应用实例: JAVA 中的 iterator。优点:它支持以不同的方式遍历一个聚合对象。迭代器简化了聚合类。在同一个聚合上可以有多个遍历。在迭代器模式中,增加新的聚合类和迭代器类都很方便,无须修改原有代码。缺点: 由于迭代器模式将存储数据和遍历数据的职责分离,增加新的聚合类需要对应增加新的迭代器类,类的个数成对增加,这在一定程度上增加了系统的复杂性。使用场景:访问一个聚合对象的内容而无须暴露它的内部表示。需要为聚合对象提供多种遍历方式。为遍历不同的聚合结构提供一个统一的接口。注意事项: 迭代器模式就是分离了集合对象的遍历行为,抽象出一个迭代器类来负责,这样既可以做到不暴露集合的内部结构,又可让外部代码透明地访问集合内部的数据。中介者模式中介者模式(Mediator Pattern)是用来降低多个对象和类之间的通信复杂性。这种模式提供了一个中介类,该类通常处理不同类之间的通信,并支持松耦合,使代码易于维护。中介者模式属于行为型模式。介绍意图: 用一个中介对象来封装一系列的对象交互,中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。主要解决: 对象与对象之间存在大量的关联关系,这样势必会导致系统的结构变得很复杂,同时若一个对象发生改变,我们也需要跟踪与之相关联的对象,同时做出相应的处理。何时使用: 多个类相互耦合,形成了网状结构。如何解决: 将上述网状结构分离为星型结构。关键代码: 对象 Colleague 之间的通信封装到一个类中单独处理。应用实例:中国加入 WTO 之前是各个国家相互贸易,结构复杂,现在是各个国家通过 WTO 来互相贸易。机场调度系统。MVC 框架,其中 C(控制器)就是 M(模型)和 V(视图)的中介者。优点:降低了类的复杂度,将一对多转化成了一对一。各个类之间的解耦。符合迪米特原则。缺点: 中介者会庞大,变得复杂难以维护。使用场景:系统中对象之间存在比较复杂的引用关系,导致它们之间的依赖关系结构混乱而且难以复用该对象。想通过一个中间类来封装多个类中的行为,而又不想生成太多的子类。注意事项: 不应当在职责混乱的时候使用。备忘录模式备忘录模式(Memento Pattern)保存一个对象的某个状态,以便在适当的时候恢复对象。备忘录模式属于行为型模式。介绍意图: 在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。主要解决: 所谓备忘录模式就是在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样可以在以后将对象恢复到原先保存的状态。何时使用: 很多时候我们总是需要记录一个对象的内部状态,这样做的目的就是为了允许用户取消不确定或者错误的操作,能够恢复到他原先的状态,使得他有 “后悔药” 可吃。如何解决: 通过一个备忘录类专门存储对象状态。关键代码: 客户不与备忘录类耦合,与备忘录管理类耦合。应用实例:后悔药。打游戏时的存档。Windows 里的 ctri + z。IE 中的后退。数据库的事务管理。优点:给用户提供了一种可以恢复状态的机制,可以使用户能够比较方便地回到某个历史的状态。实现了信息的封装,使得用户不需要关心状态的保存细节。缺点: 消耗资源。如果类的成员变量过多,势必会占用比较大的资源,而且每一次保存都会消耗一定的内存。使用场景:需要保存 / 恢复数据的相关状态场景。提供一个可回滚的操作。注意事项:为了符合迪米特原则,还要增加一个管理备忘录的类。为了节约内存,可使用原型模式 + 备忘录模式。观察者模式当对象间存在一对多关系时,则使用观察者模式(Observer Pattern)。比如,当一个对象被修改时,则会自动通知它的依赖对象。观察者模式属于行为型模式。介绍意图: 定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。主要解决: 一个对象状态改变给其他对象通知的问题,而且要考虑到易用和低耦合,保证高度的协作。何时使用: 一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知,进行广播通知。如何解决: 使用面向对象技术,可以将这种依赖关系弱化。关键代码: 在抽象类里有一个 ArrayList 存放观察者们。应用实例:拍卖的时候,拍卖师观察最高标价,然后通知给其他竞价者竞价。西游记里面悟空请求菩萨降服红孩儿,菩萨洒了一地水招来一个老乌龟,这个乌龟就是观察者,他观察菩萨洒水这个动作。优点:观察者和被观察者是抽象耦合的。建立一套触发机制。缺点:如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。使用场景:一个抽象模型有两个方面,其中一个方面依赖于另一个方面。将这些方面封装在独立的对象中使它们可以各自独立地改变和复用。一个对象的改变将导致其他一个或多个对象也发生改变,而不知道具体有多少对象将发生改变,可以降低对象之间的耦合度。一个对象必须通知其他对象,而并不知道这些对象是谁。需要在系统中创建一个触发链,A 对象的行为将影响 B 对象,B 对象的行为将影响 C 对象……,可以使用观察者模式创建一种链式触发机制。注意事项:JAVA 中已经有了对观察者模式的支持类。避免循环引用。如果顺序执行,某一观察者错误会导致系统卡壳,一般采用异步方式。状态模式在状态模式(State Pattern)中,类的行为是基于它的状态改变的。这种类型的设计模式属于行为型模式。 在状态模式中,我们创建表示各种状态的对象和一个行为随着状态对象改变而改变的 context 对象。介绍意图: 允许对象在内部状态发生改变时改变它的行为,对象看起来好像修改了它的类。主要解决: 对象的行为依赖于它的状态(属性),并且可以根据它的状态改变而改变它的相关行为。何时使用: 代码中包含大量与对象状态有关的条件语句。如何解决: 将各种具体的状态类抽象出来。关键代码: 通常命令模式的接口中只有一个方法。而状态模式的接口中有一个或者多个方法。而且,状态模式的实现类的方法,一般返回值,或者是改变实例变量的值。也就是说,状态模式一般和对象的状态有关。实现类的方法有不同的功能,覆盖接口中的方法。状态模式和命令模式一样,也可以用于消除 if…else 等条件选择语句。应用实例:打篮球的时候运动员可以有正常状态. 不正常状态和超常状态。曾侯乙编钟中,‘钟是抽象接口’,‘钟 A’等是具体状态,‘曾侯乙编钟’是具体环境(Context)。优点:封装了转换规则。枚举可能的状态,在枚举状态之前需要确定状态种类。将所有与某个状态有关的行为放到一个类中,并且可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为。允许状态转换逻辑与状态对象合成一体,而不是某一个巨大的条件语句块。可以让多个环境对象共享一个状态对象,从而减少系统中对象的个数。缺点:状态模式的使用必然会增加系统类和对象的个数。状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱。状态模式对 “开闭原则” 的支持并不太好,对于可以切换状态的状态模式,增加新的状态类需要修改那些负责状态转换的源代码,否则无法切换到新增状态,而且修改某个状态类的行为也需修改对应类的源代码。使用场景:行为随状态改变而改变的场景。条件、分支语句的代替者。注意事项: 在行为受状态约束的时候使用状态模式,而且状态不超过 5 个。空对象模式在空对象模式(Null Object Pattern)中,一个空对象取代 NULL 对象实例的检查。Null 对象不是检查空值,而是反应一个不做任何动作的关系。这样的 Null 对象也可以在数据不可用的时候提供默认的行为。 在空对象模式中,我们创建一个指定各种要执行的操作的抽象类和扩展该类的实体类,还创建一个未对该类做任何实现的空对象类,该空对象类将无缝地使用在需要检查空值的地方。策略模式在策略模式(Strategy Pattern)中,一个类的行为或其算法可以在运行时更改。这种类型的设计模式属于行为型模式。 在策略模式中,我们创建表示各种策略的对象和一个行为随着策略对象改变而改变的 context 对象。策略对象改变 context 对象的执行算法。介绍意图: 定义一系列的算法, 把它们一个个封装起来, 并且使它们可相互替换。主要解决: 在有多种算法相似的情况下,使用 if…else 所带来的复杂和难以维护。何时使用: 一个系统有许多许多类,而区分它们的只是他们直接的行为。如何解决: 将这些算法封装成一个一个的类,任意地替换。关键代码: 实现同一个接口。应用实例:诸葛亮的锦囊妙计,每一个锦囊就是一个策略。旅行的出游方式,选择骑自行车. 坐汽车,每一种旅行方式都是一个策略。JAVA AWT 中的 LayoutManager。优点:算法可以自由切换。避免使用多重条件判断。扩展性良好。缺点:策略类会增多。所有策略类都需要对外暴露。使用场景:如果在一个系统里面有许多类,它们之间的区别仅在于它们的行为,那么使用策略模式可以动态地让一个对象在许多行为中选择一种行为。一个系统需要动态地在几种算法中选择一种。如果一个对象有很多的行为,如果不用恰当的模式,这些行为就只好使用多重的条件选择语句来实现。注意事项: 如果一个系统的策略多于四个,就需要考虑使用混合模式,解决策略类膨胀的问题。模板模式在模板模式(Template Pattern)中,一个抽象类公开定义了执行它的方法的方式 / 模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行。这种类型的设计模式属于行为型模式。介绍意图: 定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。主要解决: 一些方法通用,却在每一个子类都重新写了这一方法。何时使用: 有一些通用的方法。如何解决: 将这些通用算法抽象出来。关键代码: 在抽象类实现,其他步骤在子类实现。应用实例:在造房子的时候,地基、走线、水管都一样,只有在建筑的后期才有加壁橱加栅栏等差异。 2. 西游记里面菩萨定好的 81 难,这就是一个顶层的逻辑骨架。spring 中对 Hibernate 的支持,将一些已经定好的方法封装起来,比如开启事务. 获取 Session. 关闭 Session 等,程序员不重复写那些已经规范好的代码,直接丢一个实体就可以保存。优点:封装不变部分,扩展可变部分。提取公共代码,便于维护。行为由父类控制,子类实现。缺点: 每一个不同的实现都需要一个子类来实现,导致类的个数增加,使得系统更加庞大。使用场景:有多个子类共有的方法,且逻辑相同。重要的、复杂的方法,可以考虑作为模板方法。注意事项: 为防止恶意操作,一般模板方法都加上 final 关键词。访问者模式在访问者模式(Visitor Pattern)中,我们使用了一个访问者类,它改变了元素类的执行算法。通过这种方式,元素的执行算法可以随着访问者改变而改变。这种类型的设计模式属于行为型模式。根据模式,元素对象已接受访问者对象,这样访问者对象就可以处理元素对象上的操作。介绍意图: 主要将数据结构与数据操作分离。主要解决: 稳定的数据结构和易变的操作耦合问题。何时使用: 需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而需要避免让这些操作 “污染” 这些对象的类,使用访问者模式将这些封装到类中。如何解决: 在被访问的类里面加一个对外提供接待访问者的接口。关键代码: 在数据基础类里面有一个方法接受访问者,将自身引用传入访问者。应用实例: 您在朋友家做客,您是访问者,朋友接受您的访问,您通过朋友的描述,然后对朋友的描述做出一个判断,这就是访问者模式。优点:符合单一职责原则。优秀的扩展性。灵活性。缺点:具体元素对访问者公布细节,违反了迪米特原则。具体元素变更比较困难。违反了依赖倒置原则,依赖了具体类,没有依赖抽象。使用场景:对象结构中对象对应的类很少改变,但经常需要在此对象结构上定义新的操作。需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而需要避免让这些操作 “污染” 这些对象的类,也不希望在增加新操作时修改这些类。注意事项: 访问者可以对功能进行统一,可以做报表、UI、拦截器与过滤器。规格模式规格模式(Specification)可以认为是组合模式的一种扩展。有时项目中某些条件决定了业务逻辑,这些条件就可以抽离出来以某种关系(与、或、非)进行组合,从而灵活地对业务逻辑进行定制。另外,在查询、过滤等应用场合中,通过预定义多个条件,然后使用这些条件的组合来处理查询或过滤,而不是使用逻辑判断语句来处理,可以简化整个实现逻辑。 这里的每个条件就是一个规格,多个规格/条件通过串联的方式以某种逻辑关系形成一个组合式的规格。访问者模式我们去银行柜台办业务,一般情况下会开几个个人业务柜台的,你去其中任何一个柜台办理都是可以的。我们的访问者模式可以很好付诸在这个场景中:对于银行柜台来说,他们是不用变化的,就是说今天和明天提供个人业务的柜台是不需要有变化的。而我们作为访问者,今天来银行可能是取消费流水,明天来银行可能是去办理手机银行业务,这些是我们访问者的操作,一直是在变化的。 访问者模式就是表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。MVC模式MVC 模式代表 Model-View-Controller(模型 - 视图 - 控制器) 模式。这种模式用于应用程序的分层开发。Model(模型) - 模型代表一个存取数据的对象或 JAVA POJO。它也可以带有逻辑,在数据变化时更新控制器。View(视图) - 视图代表模型包含的数据的可视化。Controller(控制器) - 控制器作用于模型和视图上。它控制数据流向模型对象,并在数据变化时更新视图。它使视图与模型分离开。业务代表模式业务代表模式(Business Delegate Pattern)用于对表示层和业务层解耦。它基本上是用来减少通信或对表示层代码中的业务层代码的远程查询功能。在业务层中我们有以下实体。客户端(Client) - 表示层代码可以是 JSP、servlet 或 UI java 代码。业务代表(Business Delegate) - 一个为客户端实体提供的入口类,它提供了对业务服务方法的访问。查询服务(LookUp Service) - 查找服务对象负责获取相关的业务实现,并提供业务对象对业务代表对象的访问。业务服务(Business Service) - 业务服务接口。实现了该业务服务的实体类,提供了实际的业务实现逻辑。组合实体模式组合实体模式(Composite Entity Pattern)用在 EJB 持久化机制中。一个组合实体是一个 EJB 实体 bean,代表了对象的图解。当更新一个组合实体时,内部依赖对象 beans 会自动更新,因为它们是由 EJB 实体 bean 管理的。以下是组合实体 bean 的参与者。组合实体(Composite Entity) - 它是主要的实体 bean。它可以是粗粒的,或者可以包含一个粗粒度对象,用于持续生命周期。粗粒度对象(Coarse-Grained Object) - 该对象包含依赖对象。它有自己的生命周期,也能管理依赖对象的生命周期。依赖对象(Dependent Object) - 依赖对象是一个持续生命周期依赖于粗粒度对象的对象。策略(Strategies) - 策略表示如何实现组合实体。数据访问对象模式数据访问对象模式(Data Access Object Pattern)或 DAO 模式用于把低级的数据访问 API 或操作从高级的业务服务中分离出来。以下是数据访问对象模式的参与者。数据访问对象接口(Data Access Object Interface) - 该接口定义了在一个模型对象上要执行的标准操作。数据访问对象实体类(Data Access Object concrete class) - 该类实现了上述的接口。该类负责从数据源获取数据,数据源可以是数据库,也可以是 xml,或者是其他的存储机制。模型对象 / 数值对象(Model Object/Value Object) - 该对象是简单的 POJO,包含了 get/set 方法来存储通过使用 DAO 类检索到的数据。前端控制器模式前端控制器模式(Front Controller Pattern)是用来提供一个集中的请求处理机制,所有的请求都将由一个单一的处理程序处理。该处理程序可以做认证 / 授权 / 记录日志,或者跟踪请求,然后把请求传给相应的处理程序。以下是这种设计模式的实体。前端控制器(Front Controller) - 处理应用程序所有类型请求的单个处理程序,应用程序可以是基于 web 的应用程序,也可以是基于桌面的应用程序。调度器(Dispatcher) - 前端控制器可能使用一个调度器对象来调度请求到相应的具体处理程序。视图(View) - 视图是为请求而创建的对象。拦截过滤器模式拦截过滤器模式(Intercepting Filter Pattern)用于对应用程序的请求或响应做一些预处理 / 后处理。定义过滤器,并在把请求传给实际目标应用程序之前应用在请求上。过滤器可以做认证 / 授权 / 记录日志,或者跟踪请求,然后把请求传给相应的处理程序。以下是这种设计模式的实体。过滤器(Filter) - 过滤器在请求处理程序执行请求之前或之后,执行某些任务。过滤器链(Filter Chain) - 过滤器链带有多个过滤器,并在 Target 上按照定义的顺序执行这些过滤器。Target - Target 对象是请求处理程序。过滤管理器(Filter Manager) - 过滤管理器管理过滤器和过滤器链。客户端(Client) - Client 是向 Target 对象发送请求的对象。服务定位器模式服务定位器模式(Service Locator Pattern)用在我们想使用 JNDI 查询定位各种服务的时候。考虑到为某个服务查找 JNDI 的代价很高,服务定位器模式充分利用了缓存技术。在首次请求某个服务时,服务定位器在 JNDI 中查找服务,并缓存该服务对象。当再次请求相同的服务时,服务定位器会在它的缓存中查找,这样可以在很大程度上提高应用程序的性能。以下是这种设计模式的实体。当系统中的组件需要调用某一服务来完成特定的任务时,通常最简单的做法是使用 new 关键字来创建该服务的实例,或者通过工厂模式来解耦该组件与服务的具体实现部分,以便通过配置信息等更为灵活的方式获得该服务的实例。然而,这些做法都有着各自的弊端:在组件中直接维护对服务实例的引用,会造成组件与服务之间的关联依赖,当需要替换服务的具体实现时,不得不修改组件中调用服务的部分并重新编译解决方案;即使采用工厂模式来根据配置信息动态地获得服务的实例,也无法针对不同的服务类型向组件提供一个管理服务实例的中心位置;由于组件与服务之间的这种关联依赖,使得项目的开发过程受到约束。在实际项目中,开发过程往往是并行的,但又不是完全同步的,比如组件的开发跟其所需要的服务的开发同时进行,但很有可能当组件需要调用服务时,服务却还没完成开发和单体测试。遇到这种问题时,通常会将组件调用服务的部分暂时空缺,待到服务完成开发和单体测试之后,将其集成到组件的代码中。但这种做法不仅费时,而且增大了出错的风险;针对组件的单体测试变得复杂。每当对组件进行单体测试时,不得不为其配置并运行所需要的服务,而无法使用Service Stub来解决组件与服务之间的依赖;在组件中可能存在多个地方需要引用服务的实例,在这种情况下,直接创建服务实例的代码会散布到整个程序中,造成一段程序存在多个副本,大大增加维护和排错成本;当组件需要调用多个服务时,不同服务初始化各自实例的方式又可能存在差异。开发人员不得不了解所有服务初始化的API,以便在程序中能够正确地使用这些服务;某些服务的初始化过程需要耗费大量资源,因此多次重复地初始化服务会大大增加系统的资源占用和性能损耗。程序中需要有一个管理服务初始化过程的机制,在统一初始化接口的同时,还需要为程序提供部分缓存功能。要解决以上问题,我们可以在应用程序中引入服务定位器(Service Locator)模式。服务定位器(Service Locator)模式是一种企业级应用程序体系结构模式,它能够为应用程序中服务的创建和初始化提供一个中心位置,并解决了上文中所提到的各种设计和开发问题。服务定位器模式和依赖注入模式都是控制反转(IoC)模式的实现。我们在服务定位器中注册给定接口的服务实例,然后通过接口获取服务并在应用代码中使用而不需要关心其具体实现。我们可以在启动时配置并注入服务提供者。如果你了解 Laravel 框架,你对这一流程会很熟悉,没错,这就是 Laravel 框架的核心机制,我们在服务提供者中绑定接口及其实现,将服务实例注册到服务容器中,然后在使用时可以通过依赖注入或者通过服务接口/别名获取服务实例的方式调用服务。服务(Service) - 实际处理请求的服务。对这种服务的引用可以在 JNDI 服务器中查找到。Context / 初始的 Context - JNDI Context 带有对要查找的服务的引用。服务定位器(Service Locator) - 服务定位器是通过 JNDI 查找和缓存服务来获取服务的单点接触。缓存(Cache) - 缓存存储服务的引用,以便复用它们。客户端(Client) - Client 是通过 ServiceLocator 调用服务的对象。传输对象模式传输对象模式(Transfer Object Pattern)用于从客户端向服务器一次性传递带有多个属性的数据。传输对象也被称为数值对象。传输对象是一个具有 getter/setter 方法的简单的 POJO 类,它是可序列化的,所以它可以通过网络传输。它没有任何的行为。服务器端的业务类通常从数据库读取数据,然后填充 POJO,并把它发送到客户端或按值传递它。对于客户端,传输对象是只读的。客户端可以创建自己的传输对象,并把它传递给服务器,以便一次性更新数据库中的数值。以下是这种设计模式的实体。业务对象(Business Object) - 为传输对象填充数据的业务服务。传输对象(Transfer Object) - 简单的 POJO,只有设置 / 获取属性的方法。客户端(Client) - 客户端可以发送请求或者发送传输对象到业务对象。委托模式委托是对一个类的功能进行扩展和复用的方法。它的做法是:写一个附加的类提供附加的功能,并使用原来的类的实例提供原有的功能。假设我们有一个 TeamLead 类,将其既定任务委托给一个关联辅助对象 JuniorDeveloper 来完成:本来 TeamLead 处理 writeCode 方法,Usage 调用 TeamLead 的该方法,但现在 TeamLead 将 writeCode 的实现委托给 JuniorDeveloper 的 writeBadCode 来实现,但 Usage 并没有感知在执行 writeBadCode 方法。示例代码Usage.php<?phpnamespace DesignPatterns\More\Delegation;// 初始化 TeamLead 并委托辅助者 JuniorDeveloper$teamLead = new TeamLead(new JuniorDeveloper());// TeamLead 将编写代码的任务委托给 JuniorDeveloperecho $teamLead->writeCode();TeamLead.php<?phpnamespace DesignPatterns\More\Delegation;/* * TeamLead类 * @package DesignPatterns\Delegation * TeamLead 类将工作委托给 JuniorDeveloper /class TeamLead{ /* @var JuniorDeveloper / protected $slave; /* * 在构造函数中注入初级开发者JuniorDeveloper * @param JuniorDeveloper $junior / public function __construct(JuniorDeveloper $junior) { $this->slave = $junior; } /* * TeamLead 喝咖啡, JuniorDeveloper 工作 * @return mixed / public function writeCode() { return $this->slave->writeBadCode(); }}JuniorDeveloper.php<?phpnamespace DesignPatterns\More\Delegation;/* * JuniorDeveloper 类 * @package DesignPatterns\Delegation /class JuniorDeveloper{ public function writeBadCode() { return “Some junior developer generated code…”; }}资源库模式Repository 是一个独立的层,介于领域层与数据映射层(数据访问层)之间。它的存在让领域层感觉不到数据访问层的存在,它提供一个类似集合的接口提供给领域层进行领域对象的访问。Repository 是仓库管理员,领域层需要什么东西只需告诉仓库管理员,由仓库管理员把东西拿给它,并不需要知道东西实际放在哪。 Repository 模式是架构模式,在设计架构时,才有参考价值。应用 Repository 模式所带来的好处,远高于实现这个模式所增加的代码。只要项目分层,都应当使用这个模式。示例代码Post.php<?phpnamespace DesignPatterns\More\Repository;/* * Post 类 * @package DesignPatterns\Repository /class Post{ /* * @var int / private $id; /* * @var string / private $title; /* * @var string / private $text; /* * @var string / private $author; /* * @var \DateTime / private $created; /* * @param int $id / public function setId($id) { $this->id = $id; } /* * @return int / public function getId() { return $this->id; } /* * @param string $author / public function setAuthor($author) { $this->author = $author; } /* * @return string / public function getAuthor() { return $this->author; } /* * @param \DateTime $created / public function setCreated($created) { $this->created = $created; } /* * @return \DateTime / public function getCreated() { return $this->created; } /* * @param string $text / public function setText($text) { $this->text = $text; } /* * @return string / public function getText() { return $this->text; } /* * @param string $title / public function setTitle($title) { $this->title = $title; } /* * @return string / public function getTitle() { return $this->title; }}PostRepository.php<?phpnamespace DesignPatterns\More\Repository;use DesignPatterns\More\Repository\Storage;/* * Post 对应的 Repository * 该类介于数据实体层(Post) 和访问对象层(Storage)之间 * * Repository 封装了持久化对象到数据存储器以及在展示层显示面向对象的视图操作 * * Repository 还实现了领域层和数据映射层的分离和单向依赖 * * PostRepository 类 * @package DesignPatterns\Repository /class PostRepository{ private $persistence; public function __construct(Storage $persistence) { $this->persistence = $persistence; } /* * 通过指定id返回Post对象 * * @param int $id * @return Post|null / public function getById($id) { $arrayData = $this->persistence->retrieve($id); if (is_null($arrayData)) { return null; } $post = new Post(); $post->setId($arrayData[‘id’]); $post->setAuthor($arrayData[‘author’]); $post->setCreated($arrayData[‘created’]); $post->setText($arrayData[’text’]); $post->setTitle($arrayData[’title’]); return $post; } /* * 保存指定对象并返回 * * @param Post $post * @return Post / public function save(Post $post) { $id = $this->persistence->persist(array( ‘author’ => $post->getAuthor(), ‘created’ => $post->getCreated(), ’text’ => $post->getText(), ’title’ => $post->getTitle() )); $post->setId($id); return $post; } /* * 删除指定的 Post 对象 * * @param Post $post * @return bool / public function delete(Post $post) { return $this->persistence->delete($post->getId()); }}Storage.php<?phpnamespace DesignPatterns\More\Repository;/* * Storage接口 * * 该接口定义了访问数据存储器的方法 * 具体的实现可以是多样化的,比如内存、关系型数据库、NoSQL数据库等等 * * @package DesignPatterns\Repository /interface Storage{ /* * 持久化数据方法 * 返回新创建的对象ID * * @param array() $data * @return int / public function persist($data); /* * 通过指定id返回数据 * 如果为空返回null * * @param int $id * @return array|null / public function retrieve($id); /* * 通过指定id删除数据 * 如果数据不存在返回false,否则如果删除成功返回true * * @param int $id * @return bool / public function delete($id);}MemoryStorage.php<?phpnamespace DesignPatterns\More\Repository;use DesignPatterns\More\Repository\Storage;/* * MemoryStorage类 * @package DesignPatterns\Repository /class MemoryStorage implements Storage{ private $data; private $lastId; public function __construct() { $this->data = array(); $this->lastId = 0; } /* * {@inheritdoc} / public function persist($data) { $this->data[++$this->lastId] = $data; return $this->lastId; } /* * {@inheritdoc} / public function retrieve($id) { return isset($this->data[$id]) ? $this->data[$id] : null; } /* * {@inheritdoc} */ public function delete($id) { if (!isset($this->data[$id])) { return false; } $this->data[$id] = null; unset($this->data[$id]); return true; }}参考链接https://github.com/domnikl/DesignPatternsPHPhttps://laravelacademy.org/category/design-patternshttp://www.runoob.com/design-pattern/design-pattern-tutorial.html ...

April 16, 2019 · 10 min · jiezi

面试问你java中的序列化怎么答?

记得很久以前写代码的时候,每次新建一个实体都会下意识的继承Serializable接口,大部分人都知道这是对对象的序列化,可是你们真的知道序列化吗?这篇文章就简单的说下java中的序列化,让你更多的理解java这门语言。关于上篇文章说的,在应用登录前使用第三方的人机验证,如果第三方的产品突然出现故障,无法使用,这种状况我们应该怎么应对,在团队中我们也讨论过这种情况,我们的方案就是客户端不直接的请求第三方,而是由后端服务器充当一个中介的角色,起转发作用,这样在第三方出现问题,我们服务器端会做处理,这也是不把鸡蛋放在同一个篮子里的思想。接下来,简单的说下序列化,将数据对象转换为二进制流的过程就称为对象的序列化(Serialization),反过来,将二进制流转换为对象就是反序列化(Deserializable)。序列化的用处是什么呢?共两点:1、数据持久化:在很多应用中,需要对好多对象进行序列化,存到物理硬盘,较长时间的保存,比如,Session对象,当有数万用户并发访问的时候,就会有数万的Session对象,内存会承受很大的压力,这个时候,就会把一些对象先序列化到硬盘中,需要使用的时候再还原到内存中。序列化对象要保留充分的信息,用来恢复数据对象,但是为了节约存储空间和网络带宽,序列化出的二进制流要尽可能小。2、网络传输:当两个进程在互相通信的时候,就会进行数据传输,不管是何种类型的数据,都必须要转成二进制流来传输,接受方收到后再转为数据对象。重点来了,序列化在代码中是怎么实现的呢?以下介绍三种:1、java原生序列化:java类通过实现Serializable接口来实现。这个接口没有任何方法,只是标识,java序列化保留了对象的元数据,以及对象数据,兼容性最好,但是不支持跨语言,性能也一般。public class BaseEntity implements Serializable {private static final long serialVersionUID = -7333816285916354999L;private Long id;public BaseEntity() {}public Long getId() { return this.id;}public void setId(Long id) { this.id = id;}}实现这个接口,idea会给你一个警告,它会建议你设置一个serialVersionUID,如果你不设置,编译器会根据类的内部实现,包括类名、接口名、方法和属性来自动生成serialVersionUID 如果类的源码有修改,重新编译后这个值也会变化。在修改类的时候,我们要根据兼容性来决定是否修改serialVersionUID,如果是兼容性质的升级,不建议修改,因为可能会反序列化失败。如果是不兼容的,就需要修改,避免反序列化变得混乱。java原生序列化,在反序列化的时候不会调用类的无参构造方法,而是调用native方法将属性赋值为对应类型的初始值。最后,基于性能及兼容性,不推荐使用。2、Hessian序列化:Hessian序列化是一种支持动态类型、跨语言、基于对象传输的网络协议,java对象序列化后的二进制流,可以被其他语言反序列化。它的特性:自描述序列化类型,不依赖外部描述文件或接口定义,用一个字节表示常用的基础类型,极大缩短二进制流;语言无关,支持脚本语言。协议简单,比java原生的要高效。需要注意一点:Hessian会把复杂对象所有属性存储在一个Map中进行序列化,所以在父类和子类含有相同字段的情况下,先序列化子类,后序列化父类,这样的结果是子类的同名属性会被父类覆盖掉。以下是代码实现/** * Hessian实现序列化 * * @param TestClass * @return * @throws IOException */private static byte[] serialize(TestClass TestClass) { ByteArrayOutputStream byteArrayOutputStream = null; HessianOutput hessianOutput = null; try { byteArrayOutputStream = new ByteArrayOutputStream(); // Hessian的序列化 hessianOutput = new HessianOutput(byteArrayOutputStream); hessianOutput.writeObject(TestClass); return byteArrayOutputStream.toByteArray(); } catch (IOException e) { e.printStackTrace(); } finally { try { byteArrayOutputStream.close(); } catch (IOException e) { e.printStackTrace(); } try { hessianOutput.close(); } catch (IOException e) { e.printStackTrace(); } } return null;}3、JSON序列化:JSON是一种轻量级的数据交换格式,这种序列化方式就是讲数据对象转换为JSON字符串。在序列化的过程中舍弃了类型信息。反序列化是只有在提供了类型信息的情况下才能完成。public static <T> T fromJson(String json, Class<T> type) { if (json != null && !json.equals("")) { try { return getObjectMapper().readValue(json, type); } catch (Exception var3) { var3.printStackTrace(); return null; } } else { return null; }}相信大部分读者的公司和前端和客户端数据的交互格式都是JSON吧,因为JSON的这种格式可读性较好,而且也方便调试。序列化通常会用于网络传输数据对象,而对象中常常会含有敏感数据,所以黑客常常会攻击这点,攻击手段通常是利用反序列化过程构造恶意代码,怎么应对这种情况呢?可以使用transient关键字来修饰这个属性,这样在反序列化之后该属性就会为空,如果一定要传递的话,可以使用对称加密或非对称加密独立传输,在数据传输的问题上,我们一定要具备安全意识。希望看完这篇文章的你能有所收获。分享以下自己的思考:”对于用户来说,系统太灵活是他们的负担,意味着他们要做更多的选择,对于技术人员来说,架构灵活可以应对更多的运营策略。怎样折中,是个问题。“这样的分享我会一直持续,你的关注、转发和点赞是对我最大的支持,感谢。题图:作者实拍 ...

April 15, 2019 · 1 min · jiezi

Spring Cloud 参考文档(推送通知和Spring Cloud Bus)

推送通知和Spring Cloud Bus许多源代码存储库提供程序(例如Github、Gitlab、Gitea、Gitee、Gogs或Bitbucket)通过webhook通知你存储库中的更改,你可以通过提供程序的用户界面将webhook配置为URL以及你感兴趣的一组事件。例如,Github使用POST到webhook,其中包含一个JSON体,其中包含一个提交列表和一个header(X-Github-Event)设置为push,如果添加了对spring-cloud-config-monitor库的依赖并在Config Server中激活Spring Cloud Bus,则会启用/monitor端点。激活webhook后,Config Server会针对它认为可能已更改的应用程序发送一个RefreshRemoteApplicationEvent,变更检测可以制定策略。但是,默认情况下,它会查找与应用程序名称匹配的文件中的更改(例如,foo.properties的目标是foo应用程序,而application.properties则针对所有应用程序)。要覆盖该行为时使用的策略是PropertyPathNotificationExtractor,它接受请求headers和body作为参数,并返回已更改的文件路径列表。默认配置的开箱即用的使用Github、Gitlab、Gitea、Gitee、Gogs或Bitbucket,除了来自Github,Gitlab,Gitee或Bitbucket的JSON通知,你可以通过使用path={name}模式中的form-encoded的body参数POST到/monitor来触发更改通知,这样做会广播到匹配{name}模式(可以包含通配符)的应用程序。仅当在Config Server和客户端应用程序中激活spring-cloud-bus时,才会传输RefreshRemoteApplicationEvent。默认配置还检测本地git存储库中的文件系统更改,在这种情况下,不使用webhook,但是,只要编辑配置文件,就会广播刷新。上一篇:嵌入Config Server

April 15, 2019 · 1 min · jiezi

Spring Cloud 参考文档(嵌入Config Server)

嵌入Config ServerConfig Server作为独立应用程序运行最佳,但是,如果需要,你可以将其嵌入另一个应用程序中,为此,请使用@EnableConfigServer注解。在这种情况下,名为spring.cloud.config.server.bootstrap的可选属性非常有用,它是一个标志,指示服务器是否应从其自己的远程存储库配置自身,默认情况下,该标志处于关闭状态,因为它可能会延迟启动。但是,当嵌入到另一个应用程序中时,以与任何其他应用程序相同的方式初始化是有意义的,将spring.cloud.config.server.bootstrap设置为true时,还必须使用组合环境存储库配置,例如:spring: application: name: configserver profiles: active: composite cloud: config: server: composite: - type: native search-locations: ${HOME}/Desktop/config bootstrap: true如果使用bootstrap标志,则配置服务器需要在bootstrap.yml中配置其名称和存储库URI。要更改服务器端点的位置,你可以(可选)设置spring.cloud.config.server.prefix(例如,/config),以便在前缀下提供资源,前缀应该开始但不以/结束,它应用于Config Server中的@RequestMappings(即Spring Boot server.servletPath和server.contextPath前缀下面)。如果要直接从后端存储库(而不是从配置服务器)读取应用程序的配置,你基本上需要一个没有端点的嵌入式配置服务器,你可以通过不使用@EnableConfigServer注解完全关闭端点(设置spring.cloud.config.server.bootstrap=true)。上一篇:提供纯文本配置访问下一篇:推送通知和Spring Cloud Bus

April 15, 2019 · 1 min · jiezi

Spring Cloud 参考文档(Spring Cloud Config Server)

Spring Cloud Config ServerSpring Cloud Config Server为外部配置提供基于HTTP资源的API(名称—值对或等效的YAML内容),通过使用@EnableConfigServer注解,服务器可嵌入Spring Boot应用程序中,因此,以下应用程序是配置服务器:ConfigServer.java@SpringBootApplication@EnableConfigServerpublic class ConfigServer { public static void main(String[] args) { SpringApplication.run(ConfigServer.class, args); }}与所有Spring Boot应用程序一样,它默认在端口8080上运行,但你可以通过各种方式将其切换到更传统的端口8888。最简单的,也是设置默认配置存储库,是通过spring.config.name=configserver启动它(Config Server jar中有一个configserver.yml),另一种方法是使用你自己的application.properties,如以下示例所示:application.propertiesserver.port: 8888spring.cloud.config.server.git.uri: file://${user.home}/config-repo其中${user.home}/config-repo是一个包含YAML和属性文件的git存储库。在Windows上,如果文件URL是绝对的驱动器前缀,则需要额外的“/”(例如,file:///${user.home}/config-repo)。以下清单显示了在前面的示例中创建git存储库的步骤:$ cd $HOME$ mkdir config-repo$ cd config-repo$ git init .$ echo info.foo: bar > application.properties$ git add -A .$ git commit -m “Add application.properties"使用git存储库的本地文件系统仅用于测试,生产中你应该使用服务器托管配置存储库。如果只保留文本文件,则配置存储库的初始克隆可以快速有效,如果存储二进制文件(尤其是大型文件),则第一次请求配置可能会出现延迟或服务器中遇到内存不足错误。环境存储库应该在哪里存储配置服务器的配置数据?管理此行为的策略是EnvironmentRepository,为Environment对象提供服务,此Environment是Spring Environment中域的浅拷贝(包括propertySources作为主要功能),Environment资源由三个变量参数化:{application},它映射到客户端的spring.application.name。{profile},它映射到客户端(逗号分隔列表)的spring.profiles.active。{label},这是标记配置文件集“版本化”的服务器端特性。存储库实现通常表现得像Spring Boot应用程序,从spring.config.name等于{application}参数,spring.profiles.active等于{profiles}参数加载配置文件。配置文件的优先规则也与常规Spring Boot应用程序中的规则相同:活动配置文件优先于默认配置文件,如果有多个配置文件,则最后一个配置文件获胜(类似于向Map添加条目)。以下示例客户端应用程序具有此bootstrap配置:bootstrap.ymlspring: application: name: foo profiles: active: dev,mysql像通常一样,Spring Boot应用程序也可以通过环境变量或命令行参数来设置这些属性。如果存储库是基于文件的,则服务器从application.yml(在所有客户端之间共享)和foo.yml(以foo.yml优先)创建Environment。如果YAML文件中包含指向Spring配置文件的文档,那么这些文档将以更高的优先级应用(按列出的配置文件的顺序)。如果存在特定配置文件的YAML(或属性)文件,则这些文件的优先级也高于默认值,较高的优先级转换为Environment中先前列出的PropertySource(这些相同的规则适用于独立的Spring Boot应用程序)。你可以将spring.cloud.config.server.accept-empty设置为false,以便如果应用程序找不到,则Server返回HTTP 404状态,默认情况下,此标志设置为true。健康指示器Config Server附带一个健康指示器,用于检查配置的EnvironmentRepository是否正常工作,默认情况下,它会向EnvironmentRepository请求名为app的应用程序、default配置文件以及EnvironmentRepository实现提供的默认标签。你可以配置健康指示器以检查更多应用程序以及自定义配置文件和自定义标签,如以下示例所示:spring: cloud: config: server: health: repositories: myservice: label: mylabel myservice-dev: name: myservice profiles: development你可以通过设置spring.cloud.config.server.health.enabled=false来禁用监控指示器。安全性你可以以对你有意义的任何方式保护你的Config Server(从物理网络安全到OAuth2承载令牌),因为Spring Security和Spring Boot为许多安全安排提供支持。要使用默认的Spring Boot配置的HTTP Basic安全性,请在类路径中包含Spring Security(例如,通过spring-boot-starter-security),默认值为user的用户名和随机生成的密码,随机密码在实践中没有用,因此建议你配置密码(通过设置spring.security.user.password)并对其进行加密(有关如何执行此操作的说明,请参阅下文)。加密和解密要使用加密和解密特性,你需要在JVM中安装完整的JCE(默认情况下不包括),你可以从Oracle下载“Java Cryptography Extension(JCE)Unlimited Strength Jurisdiction Policy Files”并按照安装说明进行操作(实际上,你需要将JRE lib/security目录中的两个策略文件替换为你下载的策略文件)。如果远程属性源包含加密内容(以{cipher}开头的值),则在通过HTTP发送到客户端之前对它们进行解密,此设置的主要优点是,属性值在“静止”时不必是纯文本格式(例如,在git存储库中)。如果某个值无法解密,则会从属性源中删除该值,并添加一个附加属性,该属性具有相同的键但前缀为invalid,且值为“不适用”(通常为<n/a>),这主要是为了防止密文被用作密码并意外泄露。如果为配置客户端应用程序设置远程配置存储库,则它可能包含类似于以下内容的application.yml:spring: datasource: username: dbuser password: ‘{cipher}FKSAJDFGYOS8F7GLHAKERGFHLSAJ’.properties文件中的加密值不能用引号括起来,否则,该值不会被解密,以下示例显示了有效的值:application.propertiesspring.datasource.username: dbuserspring.datasource.password: {cipher}FKSAJDFGYOS8F7GLHAKERGFHLSAJ你可以安全地将此纯文本推送到共享的git存储库,并且密码仍然受到保护。服务器还公开/encrypt和/decrypt端点(假设这些端点是安全的并且只能由授权代理访问),如果编辑远程配置文件,则可以使用Config Server通过POST到/encrypt端点来加密值,如以下示例所示:$ curl localhost:8888/encrypt -d mysecret682bc583f4641835fa2db009355293665d2647dade3375c0ee201de2a49f7bda如果你加密的值中包含需要进行URL编码的字符,则应使用–data-urlencode选项进行curl以确保它们已正确编码。请确保不要在加密值中包含任何curl命令统计信息,将值输出到文件可以帮助避免此问题。通过/decrypt也可以使用反向操作(前提是服务器配置了对称密钥或完整密钥对),如以下示例所示:$ curl localhost:8888/decrypt -d 682bc583f4641835fa2db009355293665d2647dade3375c0ee201de2a49f7bdamysecret如果你用curl测试,那么使用–data-urlencode(替代-d)或设置一个显式的Content-Type: text/plain来确保curl在有特殊字符时正确编码数据(’+‘特别棘手)。获取加密值并添加{cipher}前缀,然后再将其放入YAML或属性文件中,然后再提交并将其推送到远程(可能不安全)存储。/encrypt和/decrypt端点也接受//{name}/{profiles}形式的路径,当客户端调用主环境资源时,可用于在每个应用程序(名称)和每个配置文件的基础上控制加密。要以这种精细的方式控制加密,你还必须提供类型为TextEncryptorLocator的@Bean,它为每个名称和配置文件创建不同的加密器,默认情况下提供的那个不会这样做(所有加密都使用相同的密钥)。spring命令行客户端(安装了Spring Cloud CLI扩展)也可用于加密和解密,如以下示例所示:$ spring encrypt mysecret –key foo682bc583f4641835fa2db009355293665d2647dade3375c0ee201de2a49f7bda$ spring decrypt –key foo 682bc583f4641835fa2db009355293665d2647dade3375c0ee201de2a49f7bdamysecret要使用文件中的密钥(例如用于加密的RSA公钥),请在密钥值前加上“@”并提供文件路径,如以下示例所示:$ spring encrypt mysecret –key @${HOME}/.ssh/id_rsa.pubAQAjPgt3eFZQXwt8tsHAVv/QHiY5sI2dRcR+…–key参数是必需的(尽管有–前缀)。密钥管理Config Server可以使用对称(共享)密钥或非对称密钥(RSA密钥对),非对称选择在安全性方面更优越,但使用对称密钥通常更方便,因为它是在bootstrap.properties中配置的单个属性值。要配置对称密钥,需要将encrypt.key设置为秘密字符串(或使用ENCRYPT_KEY环境变量将其排除在纯文本配置文件之外)。无法使用encrypt.key配置非对称密钥。要配置非对称密钥,请使用密钥库(例如,由JDK附带的keytool实用工具创建),密钥库属性是encrypt.keyStore.,*等于:属性描述encrypt.keyStore.location包含Resource的位置encrypt.keyStore.password保存解锁密钥库的密码encrypt.keyStore.alias标识要使用存储中的哪个密钥加密是使用公钥完成的,并且需要私钥进行解密,因此,原则上,如果只想加密(并准备使用私钥本地解密值),则只配置服务器中的公钥。实际上,你可能不希望在本地进行解密,因为它会围绕所有客户端传播密钥管理过程,而不是将其集中在服务器中,另一方面,如果你的配置服务器相对不安全且只有少数客户端需要加密属性,那么它可能是一个有用的选项。创建用于测试的密钥库要创建用于测试的密钥库,可以使用类似于以下内容的命令:$ keytool -genkeypair -alias mytestkey -keyalg RSA \ -dname “CN=Web Server,OU=Unit,O=Organization,L=City,S=State,C=US” \ -keypass changeme -keystore server.jks -storepass letmein将server.jks文件放在类路径中(例如),然后在bootstrap.yml中为Config Server创建以下设置:encrypt: keyStore: location: classpath:/server.jks password: letmein alias: mytestkey secret: changeme使用多个密钥和密钥轮换除了加密属性值中的{cipher}前缀之外,Config Server还会在(Base64编码)密文开头之前查找零个或多个{name:value}前缀,密钥传递给TextEncryptorLocator,它可以执行为密文定位TextEncryptor所需的任何逻辑。如果已配置密钥库(encrypt.keystore.location),则默认定位器将查找具有key前缀提供的别名的密钥,密文类似于以下内容:foo: bar: {cipher}{key:testkey}...定位器查找名为“testkey”的密钥,也可以通过在前缀中使用{secret:…}值来提供秘密,但是,如果未提供,则默认使用密钥库密码(这是你在构建密钥库时未指定秘密),如果你提供秘密,你还应该使用自定义SecretLocator加密秘密。当密钥仅用于加密几个字节的配置数据时(也就是说,它们没有在其他地方使用),在加密方面几乎不需要密钥轮换。但是,你可能偶尔需要更改密钥(例如,在发生安全漏洞时),在这种情况下,所有客户端都需要更改其源配置文件(例如,在git中)并在所有密文中使用新的{key:…}前缀,请注意,客户端需要首先检查Config Server密钥库中的密钥别名是否可用。如果你想让Config Server处理所有加密和解密,{name:value}前缀也可以作为纯文本添加发布到/encrypt端点。提供加密属性有时你希望客户端在本地解密配置,而不是在服务器中执行此操作。在这种情况下,如果你提供encrypt.*配置来定位密钥,你仍然可以拥有/encrypt和/decrypt端点,但是你需要通过在bootstrap.[yml|properties]中放置spring.cloud.config.server.encrypt.enabled=false来明确地关闭输出属性的解密,如果你不关心端点,那么如果你不配置密钥或启用标志,它应该可以工作。提供选择性的格式环境端点的默认JSON格式非常适合Spring应用程序使用,因为它直接映射到Environment抽象,如果你愿意,可以通过向资源路径添加后缀(“.yml”,“.yaml”或“.properties”)来使用与YAML或Java属性相同的数据,对于不关心JSON端点结构或它们提供的额外元数据的应用程序来说,这可能很有用(例如,不使用Spring的应用程序可能会受益于此方法的简单性)。YAML和属性表示有一个额外的标志(作为名为resolvePlaceholders的布尔查询参数提供),表示源文档中的占位符(在标准的Spring ${…}形式)应该在渲染之前在输出中解析(在可能的情况),对于不了解Spring占位符约定的消费者而言,这是一个有用的特性。使用YAML或属性格式存在限制,主要与元数据丢失有关。例如,JSON为属性源的有序列表结构,其名称与源相关,YAML和属性形式合并为单个映射,即使值的来源有多个源,并且原始源文件的名称丢失。此外,YAML表示不一定是支持存储库中YAML源的可靠表示,它由一个平面属性源列表构成,必须对键的形式进行假设。上一篇:Spring Cloud Config快速入门下一篇:提供纯文本 ...

April 15, 2019 · 1 min · jiezi

Spring Cloud 参考文档(提供纯文本配置访问)

提供纯文本你的应用程序可能需要根据其环境定制的通用纯文本配置文件,而不是使用Environment抽象(或YAML或属性格式中的其中一种替代表示)。Config Server通过/{name}/{profile}/{label}/{path}中的附加端点提供这些,其中name、profile和label与常规环境端点具有相同的含义,但path是文件名(例如log.xml)。此端点的源文件的定位方式与环境端点相同,相同的搜索路径用于属性和YAML文件,但是,不是聚合所有匹配的资源,而是仅返回要匹配的第一个。找到资源后,通过使用提供的应用程序名称、配置文件和标签的有效Environment来解析正常格式(${…})的占位符,通过这种方式,资源端点与环境端点紧密集成,请考虑以下GIT或SVN存储库示例:application.ymlnginx.conf其中nginx.conf看起来像这样:server { listen 80; server_name ${nginx.server.name};}application.yml像这样:nginx: server: name: example.com—spring: profiles: developmentnginx: server: name: develop.com/foo/default/master/nginx.conf资源可能如下:server { listen 80; server_name example.com;}/foo/development/master/nginx.conf是这样的:server { listen 80; server_name develop.com;}与环境配置的源文件一样,profile用于解析文件名,因此,如果你需要特定配置文件,//development//logback.xml可以被解析为名为logback-development.xml的文件(优先于logback.xml)。如果你不想提供label并让服务器使用默认标签,则可以提供useDefaultLabel请求参数,因此,default配置文件的前面示例可能是/foo/default/nginx.conf?useDefaultLabel。上一篇:Spring Cloud Config Server

April 15, 2019 · 1 min · jiezi

分布式并发场景下SpringSession(Redis) 的数据脏读问题

问题现象问题来源于一个临时订单重复提交管控场景,通过在Session中写入本次提交的临时订单ID防止同个表单的重复提交。但在用户使用某些浏览器(如QQ浏览器、微信内置浏览器)时,仍有偶发性的重复提交现象。相关核心代码如下:原因分析该问题主要原因是因为当有A、B两个一样的请求时,如果在A还没响应完毕的时候SpringMvc又接收了B请求,B请求在获取Session中的值时,会获取到A请求改写之前的数据。其根本原因在于SpringSession在写入或删除Session属性时,会根据配置中的FlushMode决定在什么时候序列化到Redis,而默认的FlushMode为ON_SAVE,API原文是这样的:也就是说,在默认情况下只有在Response被提交时Session内容才会序列化到Redis。所以导致了并发场景下的Session数据脏读问题解决方案目前我们采取将RedisFlushMode改为IMMEDIATE,修改方法为在@EnableRedisHttpSession注解中指定flushMode:如此修改后,在每次调用removeAttribure后,都能正确的观察到Redis中相应的属性被置为空,问题也就基本得到了解决。

April 15, 2019 · 1 min · jiezi

springboot实战(一)

最近在看《JavaEE开发的颠覆者 Spring Boot实战》,顺便写了一个小框架,在这里作为记录,供以后回顾github:源码地址当前进度描述core 核心模块aop定义(aop包)日志切面异常切面抽象(base包)controller抽象,封装返回结果对象|controler异常通知器service抽象,为业务模块提供通用的业务逻辑,如增、删、改、查等mapper抽象,为业务模块提供通用的持久化逻辑,如增、删、改、查等。通过反射技术结合Mybatis的注解,提供通用的SQLentity抽象,定义通用的字段,如创建人、创建时间、修改人、修改时间、删除标识等通用工具Spring上下文工具Spring属性文件工具配置Http请求过滤器条件注入,根据配置文件中定义的spring.http.encoding的配置,动态创建CharacterEncodingFilter通用配置druid数据源、监视器配置动态数据源注册器 引入bean扫描目录的定义,扫描范围是com.wt 下属的所有包多数据源(datasource包)核心代码是DynamicDataSource,通过继承Springboot提供的DynamicDataSource,来实现多数据源定义了aop切面DynamicDattaSourceInterceptor,拦截方法调用,发现有指定的@TargetDataSource注解,就会将当前线程的数据源指定为注解指定的数据源多数据源相关的配置类,利用了Springboot的动态配置特性,定义spring.factories文件指定DynamicDataSourceConfiguration配置类,根据配置文件中的slave.enable的值决定是否加载动态数据源的相关配置反射工具j2ee 依赖管理,添加必要的web项目依赖root maven构建方式定义version 管理依赖的版本

April 15, 2019 · 1 min · jiezi

工具集核心教程 | 第三篇: Thymeleaf模板引擎入门到进阶

thymeleaf介绍简单说, Thymeleaf 是一个跟 Velocity、FreeMarker 类似的模板引擎,它可以完全替代 JSP。相较与其他的模板引擎,它有如下三个极吸引人的特点:1.Thymeleaf 在有网络和无网络的环境下皆可运行,即它可以让美工在浏览器查看页面的静态效果,也可以让程序员在服务器查看带数据的动态页面效果。这是由于它支持 html 原型,然后在 html 标签里增加额外的属性来达到模板+数据的展示方式。浏览器解释 html 时会忽略未定义的标签属性,所以 thymeleaf 的模板可以静态地运行;当有数据返回到页面时,Thymeleaf 标签会动态地替换掉静态内容,使页面动态显示。2.Thymeleaf 开箱即用的特性。它提供标准和spring标准两种方言,可以直接套用模板实现JSTL、 OGNL表达式效果,避免每天套模板、该jstl、改标签的困扰。同时开发人员也可以扩展和创建自定义的方言。3.Thymeleaf 提供spring标准方言和一个与 SpringMVC 完美集成的可选模块,可以快速的实现表单绑定、属性编辑器、国际化等功能。标准表达式语法它们分为四类:1.变量表达式2.选择或星号表达式3.文字国际化表达式4.URL表达式变量表达式变量表达式即OGNL表达式或Spring EL表达式(在Spring术语中也叫model attributes)。如下所示:${session.user.name}它们将以HTML标签的一个属性来表示:<span th:text="${book.author.name}"> <li th:each=“book : ${books}"> 选择(星号)表达式选择表达式很像变量表达式,不过它们用一个预先选择的对象来代替上下文变量容器(map)来执行,如下:{customer.name}被指定的object由th:object属性定义: <div th:object="${book}"> … <span th:text=”{title}">…</span> … </div> 文字国际化表达式文字国际化表达式允许我们从一个外部文件获取区域文字信息(.properties),用Key索引Value,还可以提供一组参数(可选). #{main.title} #{message.entrycreated(${entryId})} 可以在模板文件中找到这样的表达式代码: <table> … <th th:text="#{header.address.city}">…</th> <th th:text="#{header.address.country}">…</th> … </table> URL表达式URL表达式指的是把一个有用的上下文或回话信息添加到URL,这个过程经常被叫做URL重写。@{/order/list}URL还可以设置参数:@{/order/details(id=${orderId})}相对路径:@{../documents/report}让我们看这些表达式: <form th:action="@{/createOrder}"> <a href=“main.html” th:href="@{/main}">变量表达式和星号表达有什么区别吗?如果不考虑上下文的情况下,两者没有区别;星号语法评估在选定对象上表达,而不是整个上下文什么是选定对象?就是父标签的值,如下: <div th:object="${session.user}"> <p>Name: <span th:text="{firstName}">Sebastian</span>.</p> <p>Surname: <span th:text="{lastName}">Pepper</span>.</p> <p>Nationality: <span th:text="{nationality}">Saturn</span>.</p> </div>这是完全等价于: <div th:object="${session.user}"> <p>Name: <span th:text="${session.user.firstName}">Sebastian</span>.</p> <p>Surname: <span th:text="${session.user.lastName}">Pepper</span>.</p> <p>Nationality: <span th:text="${session.user.nationality}">Saturn</span>.</p> </div>当然,美元符号和星号语法可以混合使用: <div th:object="${session.user}"> <p>Name: <span th:text="{firstName}">Sebastian</span>.</p> <p>Surname: <span th:text="${session.user.lastName}">Pepper</span>.</p> <p>Nationality: <span th:text="{nationality}">Saturn</span>.</p> </div>表达式支持的语法字面(Literals)文本文字(Text literals): ‘one text’, ‘Another one!’,…数字文本(Number literals): 0, 34, 3.0, 12.3,…布尔文本(Boolean literals): true, false空(Null literal): null文字标记(Literal tokens): one, sometext, main,…文本操作(Text operations)字符串连接(String concatenation): +文本替换(Literal substitutions): |The name is ${name}|算术运算(Arithmetic operations)二元运算符(Binary operators): +, -, , /, %减号(单目运算符)Minus sign (unary operator): -布尔操作(Boolean operations)二元运算符(Binary operators):and, or布尔否定(一元运算符)Boolean negation (unary operator):!, not比较和等价(Comparisons and equality)比较(Comparators): >, <, >=, <= (gt, lt, ge, le)等值运算符(Equality operators):==, != (eq, ne)条件运算符(Conditional operators)If-then: (if) ? (then)If-then-else: (if) ? (then) : (else)Default: (value) ?: (defaultvalue)所有这些特征可以被组合并嵌套:‘User is of type ’ + (${user.isAdmin()} ? ‘Administrator’ : (${user.type} ?: ‘Unknown’))常用th标签都有那些?关键字功能介绍案例th:id 替换id<input th:id="‘xxx’ + ${collect.id}"/> th:text文本替换<p th:text="${collect.description}">description</p>th:utext支持html的文本替换<p th:utext="${htmlcontent}">conten</p>th:object替换对象<div th:object="${session.user}">th:value属性赋值<input th:value="${user.name}" />th:with变量赋值运算<div th:with=“isEven=${prodStat.count}%2==0”></div>th:style设置样式th:style="‘display:’ + @{(${sitrue} ? ’none’ : ‘inline-block’)} + ‘’“th:onclick点击事件th:onclick="‘getCollect()’“th:each属性赋值tr th:each=“user,userStat:${users}">th:if判断条件<a th:if="${userId == collect.userId}” >th:unless和th:if判断相反<a th:href=”@{/login}” th:unless=${session.user != null}>Login</a>th:href链接地址<a th:href="@{/login}" th:unless=${session.user != null}>Login</a> />th:switch多路选择 配合th:case 使用<div th:switch="${user.role}">th:caseth:switch的一个分支<p th:case="‘admin’">User is an administrator</p>th:fragment布局标签,定义一个代码片段,方便其它地方引用<div th:fragment=“alert”>th:include布局标签,替换内容到引入的文件<head th:include=“layout :: htmlhead” th:with=“title=‘xx’"></head> />th:replace布局标签,替换整个标签到引入的文件<div th:replace=“fragments/header :: title”></div>th:selected selected选择框 选中th:selected=”(${xxx.id} == ${configObj.dd})“th:src图片类地址引入<img class=“img-responsive” alt=“App Logo” th:src=”@{/img/logo.png}" />th:inline定义js脚本可以使用变量<script type=“text/javascript” th:inline=“javascript”>th:action表单提交的地址<form action=“subscribe.html” th:action="@{/subscribe}">th:remove删除某个属性<tr th:remove=“all”> 1.all:删除包含标签和所有的孩子。2.body:不包含标记删除,但删除其所有的孩子。3.tag:包含标记的删除,但不删除它的孩子。4.all-but-first:删除所有包含标签的孩子,除了第一个。5.none:什么也不做。这个值是有用的动态评估。th:attr设置标签属性,多个属性可以用逗号分隔比如 th:attr=“src=@{/image/aa.jpg},title=#{logo}",此标签不太优雅,一般用的比较少。还有非常多的标签,这里只列出最常用的几个,由于一个标签内可以包含多个th:x属性,其生效的优先级顺序为:include,each,if/unless/switch/case,with,attr/attrprepend/attrappend,value/href,src ,etc,text/utext,fragment,remove。几种常用的使用方法1、赋值、字符串拼接 <p th:text="${collect.description}">description</p> <span th:text="‘Welcome to our application, ’ + ${user.name} + ‘!’">字符串拼接还有另外一种简洁的写法<span th:text="|Welcome to our application, ${user.name}!|">2、条件判断 If/UnlessThymeleaf中使用th:if和th:unless属性进行条件判断,下面的例子中,<a>标签只有在th:if中条件成立时才显示:<a th:if="${myself==‘yes’}” > </i> </a><a th:unless=${session.user != null} th:href="@{/login}" >Login</a>th:unless于th:if恰好相反,只有表达式中的条件不成立,才会显示其内容。也可以使用 (if) ? (then) : (else) 这种语法来判断显示的内容3、for 循环 <tr th:each=“collect,iterStat : ${collects}"> <th scope=“row” th:text="${collect.id}">1</th> <td > <img th:src="${collect.webLogo}”/> </td> <td th:text="${collect.url}">Mark</td> <td th:text="${collect.title}">Otto</td> <td th:text="${collect.description}">@mdo</td> <td th:text="${terStat.index}">index</td> </tr>iterStat称作状态变量,属性有:index:当前迭代对象的index(从0开始计算)count: 当前迭代对象的index(从1开始计算)size:被迭代对象的大小current:当前迭代变量even/odd:布尔值,当前循环是否是偶数/奇数(从0开始计算) first:布尔值,当前循环是否是第一个last:布尔值,当前循环是否是最后一个4、URLURL在Web应用模板中占据着十分重要的地位,需要特别注意的是Thymeleaf对于URL的处理是通过语法@{…}来处理的。如果需要Thymeleaf对URL进行渲染,那么务必使用th:href,th:src等属性,下面是一个例子<!– Will produce ‘http://localhost:8080/standard/unread’ (plus rewriting) –> <a th:href="@{/standard/{type}(type=${type})}">view</a><!– Will produce ‘/gtvg/order/3/details’ (plus rewriting) –><a href=“details.html” th:href="@{/order/{orderId}/details(orderId=${o.id})}">view</a>设置背景<div th:style="‘background:url(’ + @{/<path-to-image>} + ‘);’"></div>根据属性值改变背景 <div class=“media-object resource-card-image” th:style="‘background:url(’ + @{(${collect.webLogo}==’’ ? ‘img/favicon.png’ : ${collect.webLogo})} + ‘)’" ></div>几点说明:上例中URL最后的(orderId=${o.id}) 表示将括号内的内容作为URL参数处理,该语法避免使用字符串拼接,大大提高了可读性@{...}表达式中可以通过{orderId}访问Context中的orderId变量@{/order}是Context相关的相对路径,在渲染时会自动添加上当前Web应用的Context名字,假设Context名字为app,那么结果应该是/app/order5、内联js内联文本:[[…]]内联文本的表示方式,使用时,必须先用th:inline=“text/javascript/none"激活,th:inline可以在父级标签内使用,甚至作为body的标签。内联文本尽管比th:text的代码少,不利于原型显示。<script th:inline=“javascript”>/<![CDATA[/…var username = /[[${sesion.user.name}]]/ ‘Sebastian’;var size = /[[${size}]]/ 0;…/]]>/</script>js附加代码:/[+var msg = ‘This is a working application’;+]/js移除代码:/[- /var msg = ‘This is a non-working template’;/ -]/6、内嵌变量为了模板更加易用,Thymeleaf还提供了一系列Utility对象(内置于Context中),可以通过#直接访问: dates : java.util.Date的功能方法类。 calendars : 类似#dates,面向java.util.Calendar numbers : 格式化数字的功能方法类 strings : 字符串对象的功能类,contains,startWiths,prepending/appending等等。 objects: 对objects的功能类操作。 bools: 对布尔值求值的功能方法。 arrays:对数组的功能类方法。 lists: 对lists功能类方法 sets maps ...下面用一段代码来举例一些常用的方法:dates/* * Format date with the specified pattern * Also works with arrays, lists or sets /${#dates.format(date, ‘dd/MMM/yyyy HH:mm’)}${#dates.arrayFormat(datesArray, ‘dd/MMM/yyyy HH:mm’)}${#dates.listFormat(datesList, ‘dd/MMM/yyyy HH:mm’)}${#dates.setFormat(datesSet, ‘dd/MMM/yyyy HH:mm’)}/ * Create a date (java.util.Date) object for the current date and time /${#dates.createNow()}/ * Create a date (java.util.Date) object for the current date (time set to 00:00) /${#dates.createToday()}strings/ * Check whether a String is empty (or null). Performs a trim() operation before check * Also works with arrays, lists or sets /${#strings.isEmpty(name)}${#strings.arrayIsEmpty(nameArr)}${#strings.listIsEmpty(nameList)}${#strings.setIsEmpty(nameSet)}/ * Check whether a String starts or ends with a fragment * Also works with arrays, lists or sets /${#strings.startsWith(name,‘Don’)} // also array, list* and set*${#strings.endsWith(name,endingFragment)} // also array*, list* and set*/* * Compute length * Also works with arrays, lists or sets /${#strings.length(str)}/ * Null-safe comparison and concatenation /${#strings.equals(str)}${#strings.equalsIgnoreCase(str)}${#strings.concat(str)}${#strings.concatReplaceNulls(str)}/ * Random */${#strings.randomAlphanumeric(count)}使用thymeleaf布局使用thymeleaf布局非常的方便定义代码片段:<footer th:fragment=“copy”> &copy; 2016</footer>在页面任何地方引入:<body> <div th:include=“footer :: copy”></div> <div th:replace=“footer :: copy”></div> </body>th:include 和 th:replace区别,include只是加载,replace是替换返回的HTML如下:<body> <div> &copy; 2016 </div> <footer>&copy; 2016 </footer> </body>下面是一个常用的后台页面布局,将整个页面分为头部,尾部、菜单栏、隐藏栏,点击菜单只改变content区域的页面<body class=“layout-fixed”> <div th:fragment=“navbar” class=“wrapper” role=“navigation”> <div th:replace=“fragments/header :: header”>Header</div> <div th:replace=“fragments/left :: left”>left</div> <div th:replace=“fragments/sidebar :: sidebar”>sidebar</div> <div layout:fragment=“content” id=“content” ></div> <div th:replace=“fragments/footer :: footer”>footer</div> </div></body>任何页面想使用这样的布局值只需要替换中见的 content模块即可 <html xmlns:th=“http://www.thymeleaf.org” layout:decorator=“layout”> <body> <section layout:fragment=“content”> …也可以在引用模版的时候传参<head th:include=“layout :: htmlhead” th:with=“title=‘Hello’"></head>layout 是文件地址,如果有文件夹可以这样写 fileName/layout:htmlheadhtmlhead 是指定义的代码片段 如th:fragment=“copy"参考新一代Java模板引擎ThymeleafThymeleaf基本知识thymeleaf总结文章Thymeleaf 模板的使用thymeleaf 学习笔记写在最后欢迎关注、喜欢、和点赞后续将推出更多的工具集教程,敬请期待。欢迎关注我的微信公众号获取更多更全的学习资源,视频资料,技术干货!公众号回复“学习”,拉你进程序员技术讨论群,干货资源第一时间分享。公众号回复“视频”,领取800GJava视频学习资源。公众号回复“全栈”,领取1T前端,Java,产品经理,微信小程序,Python等资源合集大放送。公众号回复“慕课”,领取1T慕课实战学习资源。公众号回复“实战”,领取750G项目实战学习资源。公众号回复“面试”,领取8G面试实战学习资源。 ...

April 14, 2019 · 3 min · jiezi

工具集核心教程 | 第四篇: Velocity模板引擎入门到进阶

Velocity是一个基于java的模板引擎(template engine)。它允许任何人仅仅简单的使用模板语言(template language)来引用由java代码定义的对象。 当Velocity应用于web开发时,界面设计人员可以和java程序开发人员同步开发一个遵循MVC架构的web站点,也就是说,页面设计人员可以只 关注页面的显示效果,而由java程序开发人员关注业务逻辑编码。Velocity将java代码从web页面中分离出来,这样为web站点的长期维护提 供了便利,同时也为我们在JSP和PHP之外又提供了一种可选的方案。Velocity脚本语法摘要1. 变量(1)变量的定义:#set($name = “hello”) 说明:velocity中变量是弱类型的。当使用#set 指令时,括在双引号中的字面字符串将解析和重新解释,如下所示:#set($directoryRoot = “www” )#set($templateName = “index.vm” )#set($template = “$directoryRoot/$templateName” )$template输出将会是:www/index.vm注:在velocity中使用$2.5这样的货币标识是没有问题得的,因为velocity中的变量总是以一个大写或者小写的字母开始的。(2)变量规范的写法${name} ,也可以写成:$name。提倡用前面的写法。例如:你希望通过一个变量$vice来动态的组织一个字符串。 Jack is a $vicemaniac.本来变量是$vice现在却变成了$vicemaniac,这样Veloctiy就不知道您到底要什么了。所以,应该使用规范的格式书写 : Jack is a ${vice}maniac现在Velocity知道变量是$vice而不是$vicemaniac。注意:当引用属性的时候不能加{}(3)变量的赋值: $name=“hello"赋值的左边必须是一个变量或者是属性引用。右边可以是下面六种类型之一: 变量引用,字面字符串,属性引用,方法引用,字面数字,数组列表。下面的例子演示了上述的每种类型:#set( $monkey = $bill ) ## variable reference#set( $monkey.Friend = “monica” ) ## string#set( $monkey.Blame = $whitehouse.Leak ) ## property reference#set( $monkey.Plan = $spindoctor.weave($web) ) ## method reference#set( $monkey.Number = 123 ) ##number#set( $monkey.Say = [“Not”, $my, “fault”] ) ## ArrayList注意:①如果上述例子中的右值是null, 则左值不会被赋值,也就是说会保留以前的值。②velocity模板中未被定义的变量将被认为是一个字符串。例如:#set($foo = “gibbous”)$moon = $foo输出结果为:$moon = gibbous③velocity模板中不会将reference解释为对象的实例变量。例如:$foo.Name将被解释为Foo对象的getName()方法,而不是Foo对象的Name实例变量。例如:$foo.getBar() 等同于$foo.Bar ;$data.getUser(“jon”) 等同于$data.User(“jon”) ;data.getRequest().getServerName() 等同于$data.Request.ServerName等同于${data.Request.ServerName}2. 循环#foreach ($element in $list) This is $element. $velocityCount#end例子:#set( $list = [“pine”, “oak”, “maple”])#foreach ($element in $list)$velocityCountThis is $element.#end输出的结果为:1 This is pine.2 This is oak.3 This is maple.每次循环$list中的一个值都会赋给$element变量。$list可以是一个Vector、Hashtable或者Array。分配给$element的值是一个java对象,并且可以通过变量被引用。例如:如果$element t是一个java的Product类,并且这个产品的名字可以通过调用他的getName()方法得到。#foreach ( $key in $list.keySet())Key: $key -> Value: $list.get($key) <br>#end提示:velocity中大小写敏感。Velocity还特别提供了得到循环次数的方法,$velocityCount变量的名字是Velocity默认的名字。 例子:First example:#foreach ( $foo in [1..5] ) $foo #endSecond example:#foreach ( $bar in [2..-2] ) $bar #endThird example:#set ( $arr = [0..1] ) #foreach ( $i in $arr ) $i #end上面三个例子的输出结果为: First example: 1 2 3 4 5Second example: 2 1 0 -1 -2Third example: 0 13. 条件语句#if (condition)#elseif (condition)#else #end4. 语句的嵌套#foreach ($element in $list) ## inner foreach 内循环#foreach ($element in $list)This is $element. $velocityCount <br>inner<br>#end ## inner foreach 内循环结束 ## outer foreachThis is $element.$velocityCount <br>outer<br>#end语句中也可以嵌套其他的语句,如#if…#else…#end等。5. 注释(1)单行注释: ## This is a single line comment.(2)多行注释:# Thus begins a multi-line comment. Online visitors won’t see this text because the Velocity Templating Engine will ignore it. #(3)文档格式:#** This is a VTL comment block and may be used to store such information as the document author and versioninginformation:@version 1.1@author xiao*#6. 关系和逻辑操作符Velocity 也具有逻辑AND, OR 和 NOT 操作符。如## example for AND#if($foo && $bar)<strong>This and that</strong>#end例子中#if() 指令仅在$foo 和$bar 都为真的时候才为真。如果$foo 为假,则表达式也为假;并且 $bar 将不被求值。如果 $foo 为真,Velocity 模板引擎将继续检查$bar的值,如果 $bar 为真,则整个表达式为真。并且输出This AND that 。如果 $bar 为假,将没有输出因为整个表达式为假。7.Velocity 中的宏Velocity中的宏我们可以理解为函数。①宏的定义#macro(宏的名称 $参数1 $参数2 …) 语句体(即函数体)#end②宏的调用#宏的名称($参数1 $参数2 …) 说明:参数之间用空格隔开。8.#stop 停止执行模板引擎并返回,把它应用于debug是很有帮助的。9.#include与#parse#include和#parse的作用都是引入本地文件, 为了安全的原因,被引入的本地文件只能在TEMPLATE_ROOT目录下。区别:(1) 与#include不同的是,#parse只能指定单个对象。而#include可以有多个如果您需要引入多个文件,可以用逗号分隔就行:#include (“one.gif”, “two.txt”, “three.htm” )在括号内可以是文件名,但是更多的时候是使用变量的:#include ( “greetings.txt”, $seasonalstock )(2) #include被引入文件的内容将不会通过模板引擎解析; 而#parse引入的文件内容Velocity将解析其中的velocity语法并移交给模板,意思就是说相当与把引入的文件copy到文件中。#parse是可以递归调用的,例如:如果dofoo.vm包含如下行:Count down.<br>#set ($count = 8)#parse (“parsefoo.vm”)<br>All done with dofoo.vm!那么在parsefoo.vm模板中,你可以包含如下VTL:$count#set($count = $count - 1)#if ( $count > 0 )<br>#parse( “parsefoo.vm” )#else<br>All done with parsefoo.vm!#end的显示结果为:Count down.876543210All done with parsefoo.vm!All done with dofoo.vm!注意:在vm中使用#parse来嵌套另外一个vm时的变量共享问题。如:->a.vm 里嵌套 b.vm;->a.vm 里定义了变量 $param;->b.vm 里可以直接使用$param,无任何限制。但需要特别注意的是,如果b.vm里同时定义有变量$param,则b.vm里将使用b.vm里定义的值。10.转义字符’‘的使用如果reference被定义,两个’\’意味着输出一个’\’,如果未被定义,刚按原样输出。如:#set($email = “foo” )$email$email\$email\$email输出:foo$email\foo$email如果$email 未定义$email$email\$email\$email输出:$email$email\$email\$email11.内置对象Velocity内置了一些对象,在vm模版里可以直接调用,列举如下:$request、$response、$session,另外,模板内还可以使用 $msg内的消息工具访问 Struts 的国际化资源,达到简便实现国际化的方法。12. 数组访问对数组的访问在Velocity中存在问题,因为Velocity只能访问对象的方法,而数组又是一个特殊的Array,所以虽然数组可以进行循环列举,但却不能定位访问特定位置的元素,如 strs[2],数组对固定位置元素的访问调用了Array的反射方法get(Object array, int index),而Velocity没能提供这样的访问,所以数组要么改成List等其他类容器的方式来包装,要么就通过公用Util类的方式来提供,传入数组对象和要访问的位置参数,从而达到返回所需值的目的。结束语Velocity 可以被应用在各种各样的情景下,本文介绍的只是它的一种用途而已,它还可以被用来做 MVC 结构中的view 层,或者动态内容静态化等。另外,Velocity 并不是唯一的模板框架,同样很优秀的 Freemarker、Thymeleaf 也获得了非常广泛的应用,有兴趣的读者可以去深入研究更多的功能和用途。附录及参考文献使用 Velocity 模板引擎快速生成代码写在最后欢迎关注、喜欢、和点赞后续将推出更多的工具集教程,敬请期待。欢迎关注我的微信公众号获取更多更全的学习资源,视频资料,技术干货!公众号回复“学习”,拉你进程序员技术讨论群,干货资源第一时间分享。公众号回复“视频”,领取800GJava视频学习资源。公众号回复“全栈”,领取1T前端,Java,产品经理,微信小程序,Python等资源合集大放送。公众号回复“慕课”,领取1T慕课实战学习资源。公众号回复“实战”,领取750G项目实战学习资源。公众号回复“面试”,领取8G面试实战学习资源。 ...

April 14, 2019 · 2 min · jiezi

工具集核心教程 | 第五篇: 利用Velocity模板引擎生成模板代码

前言不知道大家有没有这样的感觉,在平时开发中,经常有很多dao、service类中存着很多重复的代码,Velocity提供了模板生成工具,今天我教大家怎么和这些大量的重复代码说再见。参考项目:https://github.com/bigbeef/cppba-codeTemplate个人博客:http://www.zhangbox.cn注意大家可以写适合自己的模板,这里为了演示,就直接拿cppba-web的模板来示范,至于velocity的语法大家可以查看这篇文章:工具集核心教程 | 第四篇: Velocity模板引擎入门到大神maven配置 <!– velocity –><dependency> <groupId>org.apache.velocity</groupId> <artifactId>velocity</artifactId> <version>1.7</version></dependency>创建模板文件首先看下目录结构:这里演示我就只贴出ServiceImplTemplate.java,需要其他模板代码可以到我github里面下载#set ($domain = $!domainName.substring(0,1).toLowerCase()+$!domainName.substring(1))package $!{packageName}.service.impl;import $!{packageName}.core.bean.PageEntity;import $!{packageName}.dao.$!{domainName}Dao;import $!{packageName}.dto.$!{domainName}Dto;import $!{packageName}.dto.BaseDto;import $!{packageName}.entity.$!{domainName};import $!{packageName}.service.$!{domainName}Service;import org.springframework.stereotype.Service;import org.springframework.transaction.annotation.Transactional;import javax.annotation.Resource;import java.util.HashMap;import java.util.List;import java.util.Map;import java.util.Objects;/** * 开发者 * nickName:星缘 * email:1342541819@qq.com * github:https://github.com/bigbeef * velocity模板生成 cppba-codeTemplate /@Service@Transactionalpublic class $!{domainName}ServiceImpl implements $!{domainName}Service{ @Resource private $!{domainName}Dao $!{domain}Dao; @Override public void save($!{domainName} $!{domain}) { $!{domain}Dao.save($!{domain}); } @Override public void delete($!{domainName} $!{domain}) { $!{domain}Dao.delete($!{domain}); } @Override public void update($!{domainName} $!{domain}) { $!{domain}Dao.update($!{domain}); } @Override public $!{domainName} findById(int id) { return ($!{domainName}) $!{domain}Dao.get($!{domainName}.class, id); } @Override public PageEntity<$!{domainName}> query(BaseDto baseDto) { String hql = " select distinct $!{domain} from $!{domainName} $!{domain} where 1=1 “; Map params = new HashMap<String,Object>(); $!{domainName}Dto $!{domain}Dto = ($!{domainName}Dto)baseDto; $!{domainName} $!{domain} = $!{domain}Dto.get$!{domainName}(); int page = $!{domain}Dto.getPage(); int pageSize = $!{domain}Dto.getPageSize(); List list = $!{domain}Dao.query(hql,params,page,pageSize); long count = $!{domain}Dao.count(hql,params); PageEntity<$!{domainName}> pe = new PageEntity<$!{domainName}>(); pe.setCount(count); pe.setList(list); return pe; }}模板生成接下来是生成模板的主函数:package com.cppba.core;import org.apache.velocity.Template;import org.apache.velocity.VelocityContext;import org.apache.velocity.app.Velocity;import org.apache.velocity.app.VelocityEngine;import java.io.BufferedWriter;import java.io.File;import java.io.FileOutputStream;import java.io.OutputStreamWriter;import java.util.HashMap;import java.util.Map;import java.util.Properties;/* * 开发者 * nickName:星缘 * email:1342541819@qq.com * github:https://github.com/bigbeef /public class Main { static String domainName = “Articles”; //类名 static String packageName = “com.cppba”;//类包 static String templateDir = “\src\main\webapp\template\”; static String sourcePath = System.getProperty(“user.dir”)+templateDir; static String resultDir = “\out”; static String targetPath = System.getProperty(“user.dir”) + resultDir + “\” + packageName.replace(”.", “\”); public static void main(String []args) throws Exception{ Map<String,Object> map = new HashMap(); map.put(“DaoTemplate.java”,“dao/” + domainName + “Dao.java”); map.put(“ServiceTemplate.java”,“service/” + domainName + “Service.java”); map.put(“ServiceImplTemplate.java”,“service/impl/” + domainName + “ServiceImpl.java”); map.put(“DtoTemplate.java”,“dto/” + domainName + “Dto.java”); for(String templateFile:map.keySet()){ String targetFile = (String) map.get(templateFile); Properties pro = new Properties(); pro.setProperty(Velocity.OUTPUT_ENCODING, “UTF-8”); pro.setProperty(Velocity.INPUT_ENCODING, “UTF-8”); pro.setProperty(Velocity.FILE_RESOURCE_LOADER_PATH, sourcePath); VelocityEngine ve = new VelocityEngine(pro); VelocityContext context = new VelocityContext(); context.put(“domainName”,domainName); context.put(“packageName”,packageName); Template t = ve.getTemplate(templateFile, “UTF-8”); File file = new File(targetPath, targetFile); if (!file.getParentFile().exists()) file.getParentFile().mkdirs(); if (!file.exists()) file.createNewFile(); FileOutputStream outStream = new FileOutputStream(file); OutputStreamWriter writer = new OutputStreamWriter(outStream, “UTF-8”); BufferedWriter sw = new BufferedWriter(writer); t.merge(context, sw); sw.flush(); sw.close(); outStream.close(); System.out.println(“成功生成Java文件:” + (targetPath + targetFile).replaceAll("/", “\\”)); } }}生成java文件我们可以修改domainName和packageName来修改我们的包名和类名,我们运行下看:我们看到生成成功,我们打开ArticlesServiceImpl.java看下:package com.cppba.service.impl;import com.cppba.core.bean.PageEntity;import com.cppba.dao.ArticlesDao;import com.cppba.dto.ArticlesDto;import com.cppba.dto.BaseDto;import com.cppba.entity.Articles;import com.cppba.service.ArticlesService;import org.springframework.stereotype.Service;import org.springframework.transaction.annotation.Transactional;import javax.annotation.Resource;import java.util.HashMap;import java.util.List;import java.util.Map;import java.util.Objects;/* * 开发者 * nickName:星缘 * email:1342541819@qq.com * github:https://github.com/bigbeef * velocity模板生成 cppba-codeTemplate */@Service@Transactionalpublic class ArticlesServiceImpl implements ArticlesService{ @Resource private ArticlesDao articlesDao; @Override public void save(Articles articles) { articlesDao.save(articles); } @Override public void delete(Articles articles) { articlesDao.delete(articles); } @Override public void update(Articles articles) { articlesDao.update(articles); } @Override public Articles findById(int id) { return (Articles) articlesDao.get(Articles.class, id); } @Override public PageEntity<Articles> query(BaseDto baseDto) { String hql = " select distinct articles from Articles articles where 1=1 “; Map params = new HashMap<String,Object>(); ArticlesDto articlesDto = (ArticlesDto)baseDto; Articles articles = articlesDto.getArticles(); int page = articlesDto.getPage(); int pageSize = articlesDto.getPageSize(); List list = articlesDao.query(hql,params,page,pageSize); long count = articlesDao.count(hql,params); PageEntity<Articles> pe = new PageEntity<Articles>(); pe.setCount(count); pe.setList(list); return pe; }}生成成功,我们拷贝到cppba-web中可完美运行!写在最后欢迎关注、喜欢、和点赞后续将推出更多的工具集教程,敬请期待。欢迎关注我的微信公众号获取更多更全的学习资源,视频资料,技术干货!公众号回复“学习”,拉你进程序员技术讨论群,干货资源第一时间分享。公众号回复“视频”,领取800GJava视频学习资源。公众号回复“全栈”,领取1T前端,Java,产品经理,微信小程序,Python等资源合集大放送。公众号回复“慕课”,领取1T慕课实战学习资源。公众号回复“实战”,领取750G项目实战学习资源。公众号回复“面试”,领取8G面试实战学习资源。 ...

April 14, 2019 · 3 min · jiezi

工具集核心教程 | 第六篇: Freemarker模板引擎入门到进阶

Freemarker的介绍 Freemarker 是一款模板引擎,是一种基于模版生成静态文件的通用 工具,它是为程序员提供的一个开发包,或者说是一个类库,它不是面向最终用户的,而是为程序员提供了一款可以嵌入他们开发产品的应用程序。 Freemarker 是使用纯java编写的,为了提高页面的访问速度,需要把页面静态化, 那么Freemarker就是被用来生成html页面。 到目前为止,Freemarker使用越来越广泛,不光光只是它强大的生成技术,而且它能够与进行很好的集成。 现在开始一层层揭开它的神秘面纱。。。特点1. 轻量级模版引擎,不需要Servlet环境就可以很轻松的嵌入到应用程序中2. 能生成各种文本,如html,xml,java,等3. 入门简单,它是用java编写的,很多语法和java相似工作原理:(借用网上的图片)目录1.FTL指令规则2.插值规则3.表达式4.FreeMarker的常用指令5.高级方法前言FreeMarker的模板文件并不比HTML页面复杂多少,FreeMarker模板文件主要由如下4个部分组成:1.文本:直接输出的部分2.注释:<#– … –>格式部分,不会输出3.插值:即${…}或#{…}格式的部分,将使用数据模型中的部分替代输出4.FTL指令:FreeMarker指定,和HTML标记类似,名字前加#予以区分,不会输出下面是一个FreeMarker模板的例子,包含了以上所说的4个部分:<html> <head> <title>Welcome!</title> </head> <body> <#– 注释部分 –> <#– 下面使用插值 –> <h1>Welcome ${user} !</h1> <p>We have these animals:</p> <u1> <#– 使用FTL指令 –> <#list animals as being> <li>${being.name} for ${being.price} Euros</li> <#list> <u1> </body></html>1. FTL指令规则在FreeMarker中,使用FTL标签来使用指令,FreeMarker有3种FTL标签,这和HTML标签是完全类似的.开始标签:<#directivename parameter>结束标签:</#directivename>空标签:<#directivename parameter/>实际上,使用标签时前面的符号#也可能变成@,如果该指令是一个用户指令而不是系统内建指令时,应将#符号改成@符号.使用FTL标签时,应该有正确的嵌套,而不是交叉使用,这和XML标签的用法完全一样.如果全用不存在的指令,FreeMarker不会使用模板输出,而是产生一个错误消息.FreeMarker会忽略FTL标签中的空白字符.值得注意的是< , /> 和指令之间不允许有空白字符.2. 插值规则FreeMarker的插值有如下两种类型:通用插值${expr};数字格式化插值:#{expr}或#{expr;format}2.1 通用插值对于通用插值,又可以分为以下4种情况:1.插值结果为字符串值:直接输出表达式结果2.插值结果为数字值:根据默认格式(由#setting指令设置)将表达式结果转换成文本输出.可以使用内建的字符串 函数格式化单个插值,如下面的例子:<#settion number_format=“currency”/><#assign answer=42/>${answer}${answer?string} <#– the same as ${answer} –>${answer?string.number}${answer?string.currency}${answer?string.percent}${answer}输出结果是:$42.00$42.0042$42.004,200%3.插值结果为日期值:根据默认格式(由#setting指令设置)将表达式结果转换成文本输出.可以使用内建的字符串函数格式化单个插值,如下面的例子:${lastUpdated?string(“yyyy-MM-dd HH:mm:ss zzzz”)}${lastUpdated?string(“EEE, MMM d, ‘‘yy”)}${lastUpdated?string(“EEEE, MMMM dd, yyyy, hh:mm:ss a ‘(‘zzz’)’”)}输出结果是:2008-04-08 08:08:08 Pacific Daylight TimeTue, Apr 8, ‘03Tuesday, April 08, 2003, 08:08:08 PM (PDT)4.插值结果为布尔值:根据默认格式(由#setting指令设置)将表达式结果转换成文本输出.可以使用内建的字符串函数格式化单个插值,如下面的例子:<#assign foo=true/>${foo?string(“yes”, “no”)}输出结果是:yes2.2 数字格式化插值数字格式化插值可采用#{expr;format}形式来格式化数字,其中format可以是:mX:小数部分最小X位MX:小数部分最大X位如下面的例子:<#assign x=2.582/><#assign y=4/>#{x; M2} <#– 输出2.58 –>#{y; M2} <#– 输出4 –>#{x; m2} <#– 输出2.6 –>#{y; m2} <#– 输出4.0 –>#{x; m1M2} <#– 输出2.58 –>#{x; m1M2} <#– 输出4.0 –>3. 表达式表达式是FreeMarker模板的核心功能,表达式放置在插值语法${}之中时,表明需要输出表达式的值;表达式语法也可与FreeMarker标签结合,用于控制输出.实际上FreeMarker的表达式功能非常强大,它不仅支持直接指定值,输出变量值,也支持字符串格式化输出和集合访问等功能.3.1 直接指定值使用直接指定值语法让FreeMarker直接输出插值中的值,而不是输出变量值.直接指定值可以是字符串,数值,布尔值,集合和MAP对象.字符串直接指定字符串值使用单引号或双引号限定,如果字符串值中包含特殊字符需要转义,看下面的例子:${“我的文件保存在C:\盘”}${‘我名字是"annlee"’}输出结果是:我的文件保存在C:\盘我名字是"annlee"FreeMarker支持如下转义字符:"; 双引号(u0022)'; 单引号(u0027)\; 反斜杠(u005C)\n; 换行(u000A)\r; 回车(u000D)\t; Tab(u0009)\b; 退格键(u0008)\f; Form feed(u000C)\l; <\g; >\a; &{; {\xCode; 直接通过4位的16进制数来指定Unicode码,输出该unicode码对应的字符.如果某段文本中包含大量的特殊符号,FreeMarker提供了另一种特殊格式:可以在指定字符串内容的引号前增加r标记,在r标记后的文件将会直接输出。看如下代码:${r"${foo}"}${r"C:\foo\bar"}输出结果是:${foo}C:\foo\bar数值表达式中的数值直接输出,不需要引号.小数点使用".“分隔,不能使用分组”,“符号.FreeMarker目前还不支持科学计数法,所以"1E3"是错误的.在FreeMarker表达式中使用数值需要注意以下几点:数值不能省略小数点前面的0,所以”.5"是错误的写法数值8 , +8 , 8.00都是相同的布尔值,直接使用true和false,不使用引号.集合, 集合以方括号包括,各集合元素之间以英文逗号",“分隔如下为集合元素遍历的例子:<#list [“星期一”, “星期二”, “星期三”, “星期四”, “星期五”, “星期六”, “星期天”] as x> ${x}</#list>输出结果是:星期一星期二星期三星期四星期五星期六星期天除此之外,集合元素也可以是表达式,例子如下:[2 + 2, [1, 2, 3, 4], “whatnot”]还可以使用数字范围定义数字集合,如2..5等同于[2, 3, 4, 5], 但是更有效率。 注意,使用数字范围来定义集合时无需使用方括号,数字范围也支持反递增的数字范围,如5..2Map对象,Map对象使用花括号包括,Map中的key-value对之间以英文冒号”:“分隔,多组key-value对之间以英文逗号”,“分隔。下面是一个例子:{“语文”:78, “数学”:80}Map对象的key和value都是表达式,但是key必须是字符串。3.2 输出变量值FreeMarker的表达式输出变量时,这些变量可以是顶层变量,也可以是Map对象中的变量,还可以是集合中的变量,并可以使用点(.)语法来访问Java对象的属性。下面分别讨论这些情况:1. 顶层变量所谓顶层变量就是直接放在数据模型中的值,例如有如下数据模型:Map root = new HashMap(); //创建数据模型root.put(“name”,“annlee”); //name是一个顶层变量对于顶层变量,直接使用${variableName}来输出变量值,变量名只能是字母,数字,下划线,$,@和#的组合,且不能以数字开头号.为了输出上面的name的值,可以使用如下语法:${name}2. 输出集合元素如果需要输出集合元素,则可以根据集合元素的索引来输出集合元素,集合元素的索引以方括号指定。假设有索引:[“星期一”,“星期二”,“星期三”,“星期四”,“星期五”,“星期六”,“星期天”],该索引名为week,如果需要输出星期三,则可以使用如下语法:${week[2]} //输出第三个集合元素此外,FreeMarker还支持返回集合的子集合,如果需要返回集合的子集合,则可以使用如下语法:week[3..5] //返回week集合的子集合,子集合中的元素是week集合中的第4-6个元素3. 输出Map元素这里的Map对象可以是直接HashMap的实例,甚至包括JavaBean实例,对于JavaBean实例而言,我们一样可以把其当成属性为key,属性值为value的Map实例.为了输出Map元素的值,可以使用点语法或方括号语法.假如有下面的数据模型:Map root = new HashMap();Book book = new Book();Author author = new Author();author.setName(“annlee”);author.setAddress(“gz”);book.setName(“struts2”);book.setAuthor(author);root.put(“info”,“struts”);root.put(“book”, book);为了访问数据模型中名为struts2的书的作者的名字,可以使用如下语法:book.author.name //全部使用点语法book[“author”].namebook.author[“name”] //混合使用点语法和方括号语法book[“author”][“name”] //全部使用方括号语法使用点语法时,变量名字有顶层变量一样的限制,但方括号语法没有该限制,因为名字可以是任意表达式的结果.3.3 字符串操作FreeMarker的表达式对字符串操作非常灵活,可以将字符串常量和变量连接起来,也可以返回字符串的子串等。字符串连接有两种语法:使用${..}或#{..}在字符串常量部分插入表达式的值,从而完成字符串连接.直接使用连接运算符+来连接字符串例如有如下数据模型:Map root = new HashMap(); root.put(“user”,“annlee”);下面将user变量和常量连接起来:${“hello, ${user}!”} //使用第一种语法来连接${“hello, " + user + “!”} //使用+号来连接上面的输出字符串都是hello,annlee!,可以看出这两种语法的效果完全一样.值得注意的是,${..}只能用于文本部分,不能用于表达式,下面的代码是错误的:<#if ${isBig}>Wow!</#if><#if “${isBig}">Wow!</#if>应该写成:<#if isBig>Wow!</#if>截取子串可以根据字符串的索引来进行,截取子串时如果只指定了一个索引值,则用于取得字符串中指定索引所对应的字符;如果指定两个索引值,则返回两个索引中间的字符串子串。假如有如下数据模型:Map root = new HashMap(); root.put(“book”,“struts2,freemarker”);可以通过如下语法来截取子串:${book[0]}${book[4]} //结果是su${book[1..4]} //结果是tru3.4 集合连接运算符这里所说的集合运算符是将两个集合连接成一个新的集合,连接集合的运算符是+,看如下的例子:<#list [“星期一”,“星期二”,“星期三”] + [“星期四”,“星期五”,“星期六”,“星期天”] as x> ${x}</#list>输出结果是:星期一 星期二 星期三 星期四 星期五 星期六 星期天3.5 Map连接运算符Map对象的连接运算符也是将两个Map对象连接成一个新的Map对象,Map对象的连接运算符是+,如果两个Map对象具有相同的key,则右边的值替代左边的值.看如下的例子:<#assign scores = {“语文”:86,“数学”:78} + {“数学”:87,“Java”:93}>语文成绩是${scores.语文}数学成绩是${scores.数学}Java成绩是${scores.Java}输出结果是:语文成绩是86数学成绩是87Java成绩是933.6 算术运算符FreeMarker表达式中完全支持算术运算,FreeMarker支持的算术运算符包括:+, - , * , / , % 看如下的代码:<#assign x=5>${ x * x - 100 }${ x /2 }${ 12 %10 }输出结果是:-75 2.5 2在表达式中使用算术运算符时要注意以下几点:运算符两边的运算数字必须是数字使用+运算符时,如果一边是数字,一边是字符串,就会自动将数字转换为字符串再连接,如: ${3 + “5”},结果是:35使用内建的int函数可对数值取整,如:<#assign x=5>${ (x/2)?int }${ 1.1?int }${ 1.999?int }${ -1.1?int }${ -1.999?int }结果是:2 1 1 -1 -13.7 比较运算符表达式中支持的比较运算符有如下几个:=或者==:判断两个值是否相等.!=:判断两个值是否不等.>或者gt:判断左边值是否大于右边值>=或者gte:判断左边值是否大于等于右边值<或者lt:判断左边值是否小于右边值<=或者lte:判断左边值是否小于等于右边值注意:=和!=可以用于字符串,数值和日期来比较是否相等,但=和!=两边必须是相同类型的值,否则会产生错误,而且FreeMarker是精确比较,“x”,“x “,“X"是不等的.其它的运行符可以作用于数字和日期,但不能作用于字符串,大部分的时候,使用gt等字母运算符代替>会有更好的效果,因为FreeMarker会把>解释成FTL标签的结束字符,当然,也可以使用括号来避免这种情况,如:<#if (x>y)>。3.8 逻辑运算符逻辑运算符有如下几个:逻辑与: &&逻辑或: ||逻辑非: !逻辑运算符只能作用于布尔值,否则将产生错误。3.9 内建函数FreeMarker还提供了一些内建函数来转换输出,可以在任何变量后紧跟“?”,“?”后紧跟内建函数,就可以通过内建函数来轮换输出变量.下面是常用的内建的字符串函数:html:对字符串进行HTML编码cap_first:使字符串第一个字母大写lower_case:将字符串转换成小写upper_case:将字符串转换成大写trim:去掉字符串前后的空白字符下面是集合的常用内建函数size:获取序列中元素的个数下面是数字值的常用内建函数int:取得数字的整数部分,结果带符号例如:<#assign test=“Tom & Jerry”>${test?html}${test?upper_case?html}结果是:Tom &amp; Jerry TOM &amp; JERRY3.10 空值处理运算符FreeMarker对空值的处理非常严格,FreeMarker的变量必须有值,没有被赋值的变量就会抛出异常,因为FreeMarker未赋值的变量强制出错可以杜绝很多潜在的错误,如缺失潜在的变量命名,或者其他变量错误.这里所说的空值,实际上也包括那些并不存在的变量,对于一个Java的null值而言,我们认为这个变量是存在的,只是它的值为null,但对于FreeMarker模板而言,它无法理解null值,null值和不存在的变量完全相同.为了处理缺失变量,FreeMarker提供了两个运算符:! :指定缺失变量的默认值??:判断某个变量是否存在其中,!运算符的用法有如下两种:variable!或variable!defaultValue,第一种用法不给缺失的变量指定默认值,表明默认值是空字符串,长度为0的集合,或者长度为0的Map对象。使用!指定默认值时,并不要求默认值的类型和变量类型相同.使用??运算符非常简单,它总是返回一个布尔值,用法为:variable??,如果该变量存在,返回true,否则返回false。3.11 运算符的优先级FreeMarker中的运算符优先级如下(由高到低排列):一元运算符: !内建函数: ?乘除法: , / , %加减法: - , +比较: > , < , >= , <= (lt , lte , gt , gte)相等: == , = , !=逻辑与: &&逻辑或: ||数字范围: ..实际上,我们在开发过程中应该使用括号来严格区分,这样的可读性好,出错少。4. FreeMarker的常用指令FreeMarker的FTL指令也是模板的重要组成部分,这些指令可实现对数据模型所包含数据的抚今迭代,分支控制.除此之外,还有一些重要的功能,也是通过FTL指令来实现的.4.1 if指令这是一个典型的分支控制指令,该指令的作用完全类似于Java语言中的if,if指令的语法格式如下:<#if condition>…<#elseif condition>…<#elseif condition>…<#else> …</#if>例子如下:<#assign age=23><#if (age>60)> 老年人<#elseif (age>40)> 中年人<#elseif (age>20)> 青年人<#else> 少年人</#if>输出结果是:青年人上面的代码中的逻辑表达式用括号括起来主要是因为里面有>符号,由于FreeMarker会将>符号当成标签的结束字符,可能导致程序出错,为了避免这种情况,我们应该在凡是出现这些符号的地方都使用括号。4.2 switch , case , default , break指令这些指令显然是分支指令,作用类似于Java的switch语句,switch指令的语法结构如下:<#switch value> <#case refValue>…<#break> <#case refValue>…<#break> <#default>…</#switch>4.3 list, break指令list指令是一个迭代输出指令,用于迭代输出数据模型中的集合,list指令的语法格式如下:<#list sequence as item> …</#list>上面的语法格式中,sequence就是一个集合对象,也可以是一个表达式,但该表达式将返回一个集合对象,而item是一个任意的名字,就是被迭代输出的集合元素.此外,迭代集合对象时,还包含两个特殊的循环变量:item_index:当前变量的索引值item_has_next:是否存在下一个对象除此之外,也可以使用<#break>指令跳出迭代。例子如下:<#list [“星期一”, “星期二”, “星期三”, “星期四”, “星期五”, “星期六”, “星期天”] as x> ${x_index + 1}.${x}<#if x_has_next>,</if> <#if x=“星期四”><#break></#if></#list>4.4 include指令include指令的作用类似于JSP的包含指令,用于包含指定页.include指令的语法格式如下:<#include filename [options]>在上面的语法格式中,两个参数的解释如下:filename:该参数指定被包含的模板文件options:该参数可以省略,指定包含时的选项,包含encoding和parse两个选项,其中encoding指定包含页面时所用的解码集,而parse指定被包含文件是否作为FTL文件来解析,如果省略了parse选项值,则该选项默认是true.4.5 import指令import指令用于导入FreeMarker模板中的所有变量,并将该变量放置在指定的Map对象中,import指令的语法格式如下:<#import “/lib/common.ftl” as com>上面的代码将导入/lib/common.ftl模板文件中的所有变量,交将这些变量放置在一个名为com的Map对象中.4.6 noparse指令noparse指令指定FreeMarker不处理该指定里包含的内容,该指令的语法格式如下:<#noparse>…</#noparse>看如下的例子:<#noparse><#list books as book> <tr><td>${book.name}<td>作者:${book.author}</#list></#noparse>输出如下:<#list books as book> <tr><td>${book.name}<td>作者:${book.author}</#list>4.7 escape , noescape指令escape指令导致body区的插值都会被自动加上escape表达式,但不会影响字符串内的插值,只会影响到body内出现的插值,使用escape指令的语法格式如下:<#escape identifier as expression>… <#noescape>…</#noescape></#escape>看如下的代码:<#escape x as x?html> First name:${firstName} Last name:${lastName} Maiden name:${maidenName}</#escape>上面的代码等同于:First name:${firstName?html}Last name:${lastName?html}Maiden name:${maidenName?html}escape指令在解析模板时起作用而不是在运行时起作用,除此之外,escape指令也嵌套使用,子escape继承父escape的规则,如下例子:<#escape x as x?html> Customer Name:${customerName} Items to ship; <#escape x as itemCodeToNameMap[x]> ${itemCode1} ${itemCode2} ${itemCode3} ${itemCode4} </#escape></#escape>上面的代码类似于:Customer Name:${customerName?html}Items to ship;${itemCodeToNameMap[itemCode1]?html}${itemCodeToNameMap[itemCode2]?html}${itemCodeToNameMap[itemCode3]?html}${itemCodeToNameMap[itemCode4]?html}对于放在escape指令中所有的插值而言,这此插值将被自动加上escape表达式,如果需要指定escape指令中某些插值无需添加escape表达式,则应该使用noescape指令,放在noescape指令中的插值将不会添加escape表达式。4.8 assign指令assign指令在前面已经使用了多次,它用于为该模板页面创建或替换一个顶层变量,assign指令的用法有多种,包含创建或替换一个顶层变量,或者创建或替换多个变量等,它的最简单的语法如下:<#assign name=value [in namespacehash]>这个用法用于指定一个名为name的变量,该变量的值为value,此外,FreeMarker允许在使用assign指令里增加in子句,in子句用于将创建的name变量放入namespacehash命名空间中。assign指令还有如下用法:<#assign name1=value1 name2=value2 … nameN=valueN [in namespacehash]>这个语法可以同时创建或替换多个顶层变量,此外,还有一种复杂的用法,如果需要创建或替换的变量值是一个复杂的表达式,则可以使用如下语法格式:<#assign name [in namespacehash]>capture this</#assign>在这个语法中,是指将assign指令的内容赋值给name变量。如下例子:<#assign x> <#list [“星期一”, “星期二”, “星期三”, “星期四”, “星期五”, “星期六”, “星期天”] as n> ${n} </#list></#assign>${x}上面的代码将产生如下输出:星期一 星期二 星期三 星期四 星期五 星期六 星期天虽然assign指定了这种复杂变量值的用法,但是我们也不要滥用这种用法,如下例子:<#assign x>Hello ${user}!</#assign>以上代码改为如下写法更合适:<#assign x=“Hello ${user}!">4.9 setting指令该指令用于设置FreeMarker的运行环境,该指令的语法格式如下:<#setting name=value>,在这个格式中,name的取值范围包含如下几个:locale:该选项指定该模板所用的国家/语言选项number_format:指定格式化输出数字的格式boolean_format:指定两个布尔值的语法格式,默认值是true,falsedate_format,time_format,datetime_format:指定格式化输出日期的格式time_zone:设置格式化输出日期时所使用的时区4.10 macro , nested , return指令macro可以用于实现自定义指令,通过使用自定义指令,可以将一段模板片段定义成一个用户指令,使用macro指令的语法格式如下:<#macro name param1 param2 … paramN> … <#nested loopvar1, loopvar2, …, loopvarN> … <#return> …</#macro>在上面的格式片段中,包含了如下几个部分:name:name属性指定的是该自定义指令的名字,使用自定义指令时可以传入多个参数paramX:该属性就是指定使用自定义指令时报参数,使用该自定义指令时,必须为这些参数传入值nested指令:nested标签输出使用自定义指令时的中间部分nested指令中的循环变量:这此循环变量将由macro定义部分指定,传给使用标签的模板return指令:该指令可用于随时结束该自定义指令.看如下的例子:<#macro book> //定义一个自定义指令j2ee</#macro><@book /> //使用刚才定义的指令上面的代码输出结果为:j2ee在上面的代码中,可能很难看出自定义标签的用处,因为我们定义的book指令所包含的内容非常简单,实际上,自定义标签可包含非常多的内容,从而可以实现更好的代码复用.此外,还可以在定义自定义指令时,为自定义指令指定参数,看如下代码:<#macro book booklist> //定义一个自定义指令booklist是参数 <#list booklist as book> ${book} </#list></#macro><@book booklist=[“spring”,“j2ee”] /> //使用刚刚定义的指令上面的代码为book指令传入了一个参数值,上面的代码的输出结果为:spring j2ee不仅如此,还可以在自定义指令时使用nested指令来输出自定义指令的中间部分,看如下例子:<#macro page title><html><head> <title>FreeMarker示例页面 - ${title?html}</title></head><body> <h1>${title?html}</h1> <#nested> //用于引入用户自定义指令的标签体</body></html></#macro>上面的代码将一个HTML页面模板定义成一个page指令,则可以在其他页面中如此page指令:<#import “/common.ftl” as com> //假设上面的模板页面名为common.ftl,导入页面<@com.page title=“book list”><u1><li>spring</li><li>j2ee</li></ul></@com.page >从上面的例子可以看出,使用macro和nested指令可以非常容易地实现页面装饰效果,此外,还可以在使用nested指令时,指定一个或多个循环变量,看如下代码:<#macro book><#nested 1> //使用book指令时指定了一个循环变量值<#nested 2></#macro><@book ;x> ${x} .图书</@book >当使用nested指令传入变量值时,在使用该自定义指令时,就需要使用一个占位符(如book指令后的;x).上面的代码输出文本如下:1 .图书 2 .图书在nested指令中使用循环变量时,可以使用多个循环变量,看如下代码:<#macro repeat count> <#list 1..count as x> //使用nested指令时指定了三个循环变量 <#nested x, x/2, x==count> </#list></#macro><@repeat count=4 ; c halfc last> ${c}. ${halfc}<#if last> Last! </#if></@repeat >上面的输出结果为:1. 0.5 2. 1 3. 1.5 4. 2 Last;return指令用于结束macro指令,一旦在macro指令中执行了return指令,则FreeMarker不会继续处理macro指令里的内容,看如下代码:<#macro book>spring<#return>j2ee</#macro><@book />上面的代码输出:spring而j2ee位于return指令之后,不会输出。4.11 t, lt, rt 指令语法:<#t> 去掉左右空白和回车换行<#lt> 去掉左边空白和回车换行<#rt> 去掉右边空白和回车换行<#nt> 取消上面的效果4.12 指令总结if, else, elseifswitch, case, default, breaklist, breakincludeImportcompressescape, noescapeassignglobalsettingmacro, nested, returnt, lt, rt, nt5. 高级方法5.1 自定义方法自定义方法, 如:${timer(“yyyy-MM-dd H:mm:ss”, x)}${timer(“yyyy-MM-dd “, x)}在模板中除了可以通过对象来调用方法外(${object.methed(args)})也可以直接调用java实现的方法,java类必须实现接口TemplateMethodModel的方法exec(List args). 下面以把毫秒的时间转换成按格式输出的时间为例子:public class LongToDate implements TemplateMethodModel { public TemplateModel exec(List args) throws TemplateModelException { SimpleDateFormat mydate = new SimpleDateFormat((String) args.get(0))); return mydate.format(new Date(Long.parseLong((String)args.get(1))); }} 将LongToDate对象放入到数据模型中: root.put(“timer”, new LongToDate());ftl模板里使用:<#assign x = “123112455445”>${timer(“yyyy-MM-dd H:mm:ss”, x)}${timer(“yyyy-MM-dd “, x)}输出结果:2001-10-12 5:21:122001-10-125.2 自定义 Transforms实现自定义的<@transform>文本或表达式</@transform >的功能,允许对中间的最终文本进行解析转换例子:实现<@upcase>str</@upcase > 将str转换成STR的功能.代码如下:import java.io.;import java.util.*;import freemarker.template.TemplateTransformModel;public class UpperCaseTransform implements TemplateTransformModel { public Writer getWriter(Writer out, Map args) { return new UpperCaseWriter(out); } private class UpperCaseWriter extends Writer { private Writer out; UpperCaseWriter (Writer out) { this.out = out; } public void write(char[] cbuf, int off, int len) throws IOException { out.write(new String(cbuf, off, len).toUpperCase()); } public void flush() throws IOException { out.flush(); } public void close() { } }}然后将此对象put到数据模型中, 如下所示:root.put(“upcase”, new UpperCaseTransform());在view(ftl)页面中可以如下方式使用:<@upcase>hello world</@upcase >打印输出:HELLO WORLD写在最后欢迎关注、喜欢、和点赞后续将推出更多的工具集教程,敬请期待。欢迎关注我的微信公众号获取更多更全的学习资源,视频资料,技术干货!公众号回复“学习”,拉你进程序员技术讨论群,干货资源第一时间分享。公众号回复“视频”,领取800GJava视频学习资源。公众号回复“全栈”,领取1T前端,Java,产品经理,微信小程序,Python等资源合集大放送。公众号回复“慕课”,领取1T慕课实战学习资源。公众号回复“实战”,领取750G项目实战学习资源。公众号回复“面试”,领取8G面试实战学习资源。 ...

April 14, 2019 · 4 min · jiezi

Spring 5 core 中的 @NonNull 是个什么鬼?!

说明在Spring 5的 spring-core jar包中添加了 jsr-305 相关注解。在 Spring 源码中已经被大量使用。如下图:JSR-305介绍诸如 FindBugs、IntelliJ、Checkstyle 和 PMD 这样的静态分析工具在 Java 开发中得到了广泛应用。这些工具都很强大,但是有一些共同的问题它们都很难解决。在 API 的设计中,有一些决策是不言而喻的,比如何时值可以为 null,或者何时数字值不能为负。完备的 API 会将这些设计细节记录在 JavaDoc 之中,但是分析工具却无法发现类似细节,从而有可能将其忽略或是导致错误的检测结果。为了解决这些问题,有些静态分析工具开发人员试图使用注解来定义相关细节。比如 FindBugs 和 IntelliJ 都定义了自己的注解,以表示方法何时返回 null。不过,这两个工具使用的注解有细微不同,也因此有了标准化的需求。由 FindBugs 创建人 Bill Pugh 带领制定的 JSR-305 标准,试图创建一套标准注解供分析工具使用,同时希望允许开发人员根据自己的需要添加额外的注解。当前提案中包括供判断是否为空、正负号、开发语言和线程等方面的众多注解。更多jsr-305介绍请查看,JSR-305:供检查软件缺陷用的注解使用场景由上文的介绍,我们知晓了 jsr-305 的目标:供检查软件缺陷用。方便静态代码检查工具及时查找出潜在的 bug。所以这些注解特别适合基础组件和工具包,增强 IDE 提示,减少潜在 bug。lutool 1.x 中复制了spring 5中的 jsr-305 相关注解到源码中,到 mica - Spring boot 微服务开发核心包 由于依赖的 Spring boot 2.1.x,则直接使用 spring core 中的注解。使用添加包级规则@NonNullFields表示Field不为null。@NonNullApi表示方法参数和返回值不为null。对于不想使用包级别不为null,可直接使用@NonNull,使用方式同下文@Nullable。在包下添加package-info.java,内容如下:@NonNullApi@NonNullFieldspackage net.dreamlu.mica.core.utils;import org.springframework.lang.NonNullApi;import org.springframework.lang.NonNullFields;添加完该注解后编写代码时编辑器会给出提示,如下图:@Nullable对于部分可为空的Field、方法参数和返回值需要使用@Nullable进行标示。参数可为nullpublic static boolean isBlank(@Nullable final CharSequence cs) { return !StringUtils.hasText(cs);}返回值可为null@Nullablepublic static String getCookieVal(HttpServletRequest request, String name) { Cookie cookie = getCookie(request, name); return cookie != null ? cookie.getValue() : null;}属性可为null@Nullableprivate String msg;开源推荐mica Spring boot 微服务核心组件集:gitee.com/596392912/m…Avue 一款基于vue可配置化的神奇框架:gitee.com/smallweigit…pig 宇宙最强微服务(架构师必备):gitee.com/log4j/pigSpringBlade 完整的线上解决方案(企业开发必备):gitee.com/smallc/Spri…IJPay 支付SDK让支付触手可及:gitee.com/javen205/IJ…关注我们扫描上面二维码,更多精彩内容每天推荐! ...

April 14, 2019 · 1 min · jiezi

工具集核心教程 | 第一篇: .md即markdown文件的基本常用编写语法(图文并茂)

序言:感觉只要是不写博客,人就很变得很懒,学的知识点感觉还是记不住,渐渐地让我明白,看的越多,懂的越少(你这话不是有毛病吗?应该是看的越多,懂的越多才对),此话怎讲,当你在茫茫的知识库里面东看看,西看看的时候,很快就被海量的知识给淹没了,根本就不知道哪些是对的,哪些是错的,感觉好像这个也懂了,那个也懂了,但是真正写起来,脑子又一片空白,又好像什么都不懂,这种状态时有发生,这就叫不懂装懂,最根本的原因就是看的太多,写的太少,所以为了改掉这样毛病,把被动学习变成主动学习,接下来的日子,多写写,即使是写一些学习工作中遇到的坑也是好的,没事翻出来看看,还可以加深印象,好了,废话到处!起因:因为现在前后端、测试交互很频繁,一个完整的项目或者教程,说明性文件必不可少!那就难免要写一些readme等等的说明性文件,但是这样的文件一般都是.md的文件,编写的语法自然跟其他格式的文件有所区别,置于为什么要用这种格式的文件,不要问我,我也不知道,大家都这么用,跟着用就对了,如果有大神知道的,不妨告知小弟,本文也是我学习写markdown文件的一个笔记吧,仅供参考!正文:1.标题的几种写法:第一种:前面带#号,后面带文字,分别表示h1-h6,上图可以看出,只到h6,而且h1下面会有一条横线,注意,#号后面有空格第二种:这种方式好像只能表示一级和二级标题,而且=和-的数量没有限制,只要大于一个就行第三种:这里的标题支持h1-h6,为了减少篇幅,我就偷个懒,只写前面二个,这个比较好理解,相当于标签闭合,注意,标题与#号要有空格那既然3种都可以使用,可不可以混合使用呢?我试了一下,是可以的,但是为了让页面标签的统一性,不建议混合使用,推荐使用第一种,比较简洁,全面为了搞清楚原理,我特意在网上搜一下在线编写markdown的工具,发现实际上是把这些标签最后转化为html标签,如图:在线地址请看这里: markdown在线编辑 (只是想看看背后的转换原理,没有广告之嫌)2.列表我们都知道,列表分为有序列表和无序列表,下面直接展示2种列表的写法:可以看到,无序列表可以用* , + , — 来创建,用在线编辑器看,实际上是转换成了ul>li ,所以使用哪个都可以,推荐使用吧有序列表就相对简单一点,只有这一种方式,注意,数字后面的点只能是英文的点,特别注意,有序列表的序号是根据第一行列表的数字顺序来的,比如说:第一组本来是3 2 1 倒序,但是现实3 4 5 ,后面一组 序号是乱的, 但是还是显示 3 4 5 ,这点必须注意了3.区块引用比如说,你想对某个部分做的内容做一些说明或者引用某某的话等,可以用这个语句无序列表下方的便是引用,可以有多种用途,看你的需求了,用法就是在语句前面加一个 > ,注意是英文的那个右尖括号,注意空格引用因为是一个区块,理论上是应该什么内容都可以放,比如说:标题,列表,引用等等,看看下图:将上面的代码稍微改一下,全部加上引用标签,就变成了一个大的引用,还有引用里面还有引用,那引用嵌套引用还没有别的写法呢?上图可以看出,想要在上一次引用中嵌套一层引用,只需多加一个>,理论上可以无限嵌套,我就不整那么多了,注意:多层嵌套的>是不需要连续在一起的,只要在一行就可以了,中间允许有空格,但是为了好看,还是把排版搞好吧4.华丽的分割线分割线可以由 - _(星号,减号,底线)这3个符号的至少3个符号表示,注意至少要3个,且不需要连续,有空格也可以应该看得懂吧,但是为了代码的排版好看,你们自己定规则吧,前面有用到星号,建议用减号5.链接支持2种链接方式:行内式和参数式,不管是哪一种,链接文字都是用 [方括号] 来标记。上图可知,行内式的链接格式是:链接的文字放在[]中,链接地址放在随后的()中,举一反三,经常出现的列表链接就应该这样写:链接还可以带title属性,好像也只能带title,带不了其他属性,注意,是链接地址后面空一格,然后用引号引起来这是行内式的写法,参数式的怎么写:这就好理解了,就是把链接当成参数,适合多出使用相同链接的场景,注意参数的对应关系,参数定义时,这3种写法都可以:还支持这种写法,如果你不想混淆的话:其实还有一种隐式链接的写法,但是我觉得那种写法不直观,所以就不写了,经常用的一般就上面2种,如果你想了解隐式链接,可以看我文章最后放出的参考地址6.图片图片也有2种方式:行内式和参数式,用法跟链接的基本一样,唯一的不同就是,图片前面要写一个!(这是必须的),没什么好说的7.代码框这个就比较重要了,很多时候都需要展示出一些代码如果代码量比较少,只有单行的话,可以用单反引号包起来,如下:要是多行这个就不行了,多行可以用这个,也就是一对`:多行用三个反引号,如果要写注释,可以在反引号后面写8.表格这个写的有点麻烦,注意看从这3种不同写法看,表格的格式不一定要对的非常起,但是为了好看,对齐肯定是最好的,第一种的分割线后面的冒号表示对齐方式,写在左边表示左对齐,右边为右对齐,两边都写表示居中,还是有点意思的,不过现实出来的结果是,表格外面并没有线框包起来,不知道别人的怎么弄的9.强调一个星号或者是一个下划线包起来,会转换为<em>倾斜,如果是2个,会转换为<strong>加粗10.转义就不一一列举了,基本上跟js转义是一样的11.删除线常用的基本上就这些了,如果还有一些常用的,可以跟我留言,我补充上去,我觉得图文并茂才是高效学习的正确姿势,但愿为你的学习带来帮助!最后推荐一款Window下markdown编辑器typora非常好用的一款本地MD文件编辑器 ,中文版下载地址:typora下载界面如下:参考文献:http://www.appinn.com/markdown/http://sspai.com/25137写在最后欢迎关注、喜欢、和点赞后续将推出更多的工具集教程,敬请期待。欢迎关注我的微信公众号获取更多更全的学习资源,视频资料,技术干货!公众号回复“学习”,拉你进程序员技术讨论群,干货资源第一时间分享。公众号回复“视频”,领取800GJava视频学习资源。公众号回复“全栈”,领取1T前端,Java,产品经理,微信小程序,Python等资源合集大放送。公众号回复“慕课”,领取1T慕课实战学习资源。公众号回复“实战”,领取750G项目实战学习资源。公众号回复“面试”,领取8G面试实战学习资源。

April 14, 2019 · 1 min · jiezi

工具集核心教程 | 第二篇: IDEA入门到进阶(图文并茂)

前言:IntelliJ IDEA如果说IntelliJ IDEA是一款现代化智能开发工具的话,Eclipse则称得上是石器时代的东西了。其实笔者也是一枚从Eclipse转IDEA的探索者,随着近期的不断开发实践和调试,逐步体会到这款智能IDE带来的巨大开发便利,在强大的插件功能支持下,诸如对Git和Maven的支持简直让人停不下来,各种代码提示,包括JS更是手到擒来,最终不得不被这款神奇的IDE所折服。为了让身边更多的小伙伴参与进来,决定写下这篇文章,与君共享。(^_^)高级传送门:IntelliJ IDEA 官网下载 - Ultimate 终极版激活方法: 安装完成后 选择 License 输入 http://intellij.mandroid.cn正文:IntelliJ IDEA 使用教程1. IDEA VS Eclipse 核心术语比较由下图可见:两者最大的转变就在于工作空间概念的转变,并且在IDEA当中,Project和 Module是作为两个不同的概念,对项目结构是重要意义的,这也恰恰是许多IDEA初学者觉得困扰的地方。 1.1 为什么要取消工作空间?答:简单来说,IDEA不需要设置工作空间,因为每一个Project都具备一个工作空间!!对于每一个IDEA的项目工程(Project)而言,它的每一个子模块(Module)都可以使用独立的JDK和MAVEN。这对于传统项目迈向新项目的重构添加了极大的便利性,这种多元化的灵活性正是Eclipse所缺失的,因为开始Eclipse在初次使用时已经绑死了工作空间。 1.2 此外,很多新手都会问,为什么IDEA里面的子工程要称为Module ?答:其实就是模块化的概念,作为聚合工程亦或普通的根目录,它称之为Project,而下面的子工程称为模块,每一个子模块之间可以相关联,也可以没有任何关联。2. 当前项目配置VS 默认配置 2.1 为什么有了当前项目配置,还需要默认配置呢?答:因为IDEA没有工作空间的概念,所以每个新项目(Project)都需要设置自己的JDK和MAVEN等相关配置,这样虽然提高了灵活性,但是却要为每个新项目都要重新配置,这显然不符合我们的预期。在这个背景下,默认配置给予当前项目配置提供了Default选项,问题自然就迎刃而解了。 2.2 初始化步骤 打开默认配置:顶部导航栏 -> File -> Other Settings -> Default Settings /ProjectStructs 打开当前配置:顶部导航栏 -> File -> Settings / ProjectStructs示例图:=============================================接下来,来看看IDEA如何快速搭建Java开发环境!!=============================================3. 全局JDK(默认配置) 具体步骤:顶部工具栏 File ->Other Settins -> Default Project Structure -> SDKs -> JDK 示例: 根据下图步骤设置JDK目录,最后点击OK保存。4. 全局Maven(默认配置) 具体步骤:顶部工具栏 File ->Other Settings -> Default Settings -> Build & Tools -> Maven 示例: 理论上只要配置了Maven主目录即可,实际开发推荐采用User Settins file .5. 版本控制Git/Svn (默认配置) 具体步骤:顶部工具栏 File ->Other Settings -> Default Settings -> Version Control -> Git 示例: IDEA默认集成了对Git/Svn的支持 直接设置执行程序,右边Test提示成功即可。 部分小伙伴反馈说无法找到svn.exe,解决方法:重装SVN,配置项重新选择command line client tools 即可。6. 自动导包和智能移除 (默认配置) 具体步骤:顶部工具栏 File ->Other Settings -> Default Settings -> Auto Import 说明: 在网上看到很多人在提问IDEA为什么不能优化导包而Eclipse可以, 所以特意抽出来跟大家分享IDEA如何优化导包。 7. Tomcat Server(当前项目配置) 很多小伙伴刚开始都找不到Tomcat的配置,其实很简单,Tomcat或者Jetty这些都是部署的容器,自然会联想到Deployment ,打开部署配置,可以看到应用服务器的配置。 配置Tomcat方法: File -> Settings -> Deployment -> Application Servers -> Tomcat Server 具体配置方法,如下图:IDEA 必备小技能 为了提升开发效率,撸主贴心为大家准备以下实用指数五颗星的小技巧:8. 自动编译 具体步骤:顶部工具栏 File ->Other Settings -> Default Settings -> Auto Import 说明:开启自动编译之后,结合Ctrl+Shift+F9 会有热更新效果。自动编译(Runtime) 具体步骤: 敲击 Ctrl + Shift + Alt + / 然后双击Shift搜索进入Registry 找到compiler.automake.allow.when.app.running ,然后勾选上。9. 取消大小写敏感 具体步骤:File | Settings | Editor | General | Code Completion Case | Sensitive Completion = None 取消大小敏感,在编写代码的时候,代码的自动提示将更加全面和丰富。11. 调整字体类型和字体大小默认的白色背景和细小的字体会影响大家的编码体验,这里特意提供了调整代码窗的快捷配置。打开配置,搜索Font,然后再Font可以调整字体类型,Size可以调整字体大小,如图:12. 将快捷键设置为跟Eclipse一样很多人可能并不习惯IDEA的快捷键,为了方便,这里我们将快捷键设置为跟 Eclipse一样。 具体步骤: File -> Settings -> Keymap - > 选择Eclipse .13. 打开常用工具栏 具体步骤:顶部导航栏 - View -> 勾选 Toolbar & Tool Buttons如下图所示:14. 打开Maven神器(强烈推荐!) 具体步骤:右侧直接点击 Maven Project 管理插件 ,记得先打开常用工具栏,详见8.3。 如下图所示: 还在Eclipse使用Update命令苦苦挣扎的童鞋,请火速尝试此款插件,能给你带来前所未有的愉快感!!15. 懒人必备快捷键1. 按【鼠标中键】快速打开智能提示,取代alt+enter 。 File->Settings-> Keymap-> 搜索 Show Intention Actions -> 添加快捷键为鼠标中键。2. 按【F2】快速修改文件名,告别双手操作。 File->Settings-> Keymap-> 搜索 Rename -> 将快捷键设置为F2 。3. 按【F3】直接打开文件所在目录,浏览一步到位。 File->Settings-> Keymap-> 搜索 Show In Explorer -> 将快捷键设置为F3 。4. 按【Ctrl+右键】直接打开实现类,方便开发查询。 File->Settings-> Keymap-> 搜索 implementation-> Add Mouse Shortcut 将快捷键设置为Ctrl+ 鼠标右键。16. 重度强迫症患者1.取消大小写敏感,让自动完成更齐全! File | Settings | Editor | General | Code Completion Case | Sensitive Completion = None。2.自动隐藏注释,让源码阅读更为清爽! File -> Settings -> Editor -> General -> Code Folding -> Documentation comments 勾选。如何想快速一键打开全部注释,则单击鼠标右键,选择Folding -> Expand Doc comments 。3. Maven自动下载源码包,告别反编译,直接上源码注释!! File | Settings | Build, Execution, Deployment | Build Tools | Maven | Importing 将Automatically Download 的 Source 勾上。17. IDEA十问十答 (1)如何打开本地工程/已存在的工程?答:点击File -> Open 打开 工程文件夹即可,注意先配置好JDK、Maven等基础配置。(2)IDEA如何删除项目工程?答:问这个问题的Coder真的好可爱啊哈哈,很肯定的回答你,不需要删,点击File-> Close Project 即可快速关闭当前项目; 示例:什么?你还是想要干掉整个目录?那也阔以,右键Show In Explorer ,删掉文件夹即可。不过笔者建议还是直接Close关掉就好啦,万一以后用得上呢,你说呢?(3)如何在单个窗口打开多个Maven工程啊?答:随便新建一个文件夹,然后将工程都扔进去,使用IDEA打开这个文件夹。(4)如何为当前项目工程添加多个模块啊?答:对着工程右键 -> 选择New -> Module -> 通常选择Spring Initializr ,如图:写在最后欢迎关注、喜欢、和点赞后续将推出更多的工具集教程,敬请期待。欢迎关注我的微信公众号获取更多更全的学习资源,视频资料,技术干货!公众号回复“学习”,拉你进程序员技术讨论群,干货资源第一时间分享。公众号回复“视频”,领取800GJava视频学习资源。公众号回复“全栈”,领取1T前端,Java,产品经理,微信小程序,Python等资源合集大放送。公众号回复“慕课”,领取1T慕课实战学习资源。公众号回复“实战”,领取750G项目实战学习资源。公众号回复“面试”,领取8G面试实战学习资源。 ...

April 14, 2019 · 2 min · jiezi

Spring Cloud 参考文档(Spring Cloud Config快速入门)

Spring Cloud Config快速入门这个快速入门使用Spring Cloud Config Server的服务器和客户端。首先,启动服务器,如下所示:$ cd spring-cloud-config-server$ ../mvnw spring-boot:run服务器是一个Spring Boot应用程序,因此如果你愿意,可以从IDE运行它(主类是ConfigServerApplication)。接下来尝试一个客户端,如下所示:$ curl localhost:8888/foo/development{“name”:“foo”,“label”:“master”,“propertySources”:[ {“name”:“https://github.com/scratches/config-repo/foo-development.properties”,“source”:{“bar”:“spam”}}, {“name”:“https://github.com/scratches/config-repo/foo.properties”,“source”:{“foo”:“bar”}}]}定位属性源的默认策略是克隆git存储库(在spring.cloud.config.server.git.uri)并使用它来初始化一个微型SpringApplication,微型应用程序的Environment用于枚举属性源并在JSON端点发布它们。HTTP服务具有以下形式的资源:/{application}/{profile}[/{label}]/{application}-{profile}.yml/{label}/{application}-{profile}.yml/{application}-{profile}.properties/{label}/{application}-{profile}.propertiesapplication是SpringApplication中的spring.config.name(常规Spring Boot应用程序中的正常application),profile是一个活动的配置文件(或以逗号分隔的属性列表),label是一个可选的git标签(默认为master)。Spring Cloud Config Server从git存储库(必须提供)中提取远程客户端的配置,如以下示例所示:spring: cloud: config: server: git: uri: https://github.com/spring-cloud-samples/config-repo客户端使用要在应用程序中使用这些功能,你可以将其构建为依赖于spring-cloud-config-client的Spring Boot应用程序(例如,请参阅config-client或示例应用程序的测试用例)。添加依赖项最方便的方法是使用Spring Boot启动器org.springframework.cloud:spring-cloud-starter-config,还有一个用于Maven用户的父pom和BOM(spring-cloud-starter-parent)以及一个用于Gradle和Spring CLI用户的Spring IO版本管理属性文件,以下示例显示了典型的Maven配置:<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>{spring-boot-docs-version}</version> <relativePath /> <!– lookup parent from repository –> </parent><dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>{spring-cloud-version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies></dependencyManagement><dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-config</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency></dependencies><build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins></build> <!– repositories also needed for snapshots and milestones –>现在你可以创建一个标准的Spring Boot应用程序,例如以下HTTP服务器:@SpringBootApplication@RestControllerpublic class Application { @RequestMapping("/") public String home() { return “Hello World!”; } public static void main(String[] args) { SpringApplication.run(Application.class, args); }}当此HTTP服务器运行时,它从端口8888上的默认本地配置服务器(如果它正在运行)中获取外部配置,要修改启动行为,可以使用bootstrap.properties更改配置服务器的位置(类似于application.properties但适用于应用程序上下文的bootstrap阶段),如以下示例所示:spring.cloud.config.uri: http://myconfigserver.com默认情况下,如果未设置应用程序名称,则将使用application,要修改名称,可以将以下属性添加到bootstrap.properties文件中:spring.application.name: myapp设置属性${spring.application.name}时,不要在应用程序名称前加上保留字application-,以防止解析正确属性源的问题。bootstrap属性在/env端点中显示为高优先级属性源,如以下示例所示。$ curl localhost:8080/env{ “profiles”:[], “configService:https://github.com/spring-cloud-samples/config-repo/bar.properties”:{“foo”:“bar”}, “servletContextInitParams”:{}, “systemProperties”:{…}, …}名为configService:<URL of remote repository>/<file name>的属性源包含值为bar且具有最高优先级的foo属性。属性源名称中的URL是git存储库,而不是配置服务器URL。上一篇:Spring Cloud Commons:通用的抽象 ...

April 13, 2019 · 1 min · jiezi