基于APT(注解处理器)实现Lombok的@getter @setter @toString功能

1 Lombok原理 1 APT(Annotation Processing Tool )注解处理器 2 javac api处理AST(抽象语法树) 大致原理如下图所示: 上面是网上找的图,如想具体分析lombok的实现,可以从 LombokProcessor 和AnnotationProcessor 这两个类的process 方法入手 2 自己实现Lombok 2.1 创建Data注解 @Documented @Retention(RetentionPolicy.SOURCE) @Target({ElementType.TYPE}) public @interface Data { } 该Data注解只能在编译期的时候获取到,在运行期是无法获取到的 2.2 自定义注解处理器 通过实现Processor接口可以自定义注解处理器,这里我们采用更简单的方法通过继承AbstractProcessor类实现自定义注解处理器。实现抽象方法process处理我们想要的功能 2.2.1 APT简单介绍 @SupportedAnnotationTypes({"com.nicky.lombok.annotation.Data"}) @SupportedSourceVersion(SourceVersion.RELEASE_8) public class DataProcessor extends AbstractProcessor { @Override public synchronized void init(ProcessingEnvironment processingEnv) { } @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { } } @SupportedAnnotationTypes 注解表示哪些注解需要注解处理器处理,可以多个注解校验 @SupportedSourceVersion 注解 用于指定jdk使用版本

实际项目运用之State模式(状态模式)

1 模式简介 定义: 状态模式允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类。这个模式将状态封装成独立的类,并将动作委托到代表当前状态的类的对象 状态模式的优点: >封装了转换规则 枚举可能的状态,在枚举状态之前需要确定状态种类 将所有与某个状态有关的行为放到一个类中,可方便增加新的状态 允许状态转换逻辑与状态对象合成一体,而非复杂条件语句块 可以让多个环境对象共享一个状态对象,从而减少系统中对象的个数 状态模式的缺点: >增加系统类和对象的个数 结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱 对”开闭原则”的支持不太好,增加新状态类需在状态类上增加行为方法 状态模式的适用场景: 1. 对象的行为依赖于它的状态,并且可以在运行时根据状态改变行为。 2. 代码中包含大量与对象状态有关的if/else语句 3. 接口幂等要求 状态模式和策略模式: 相同点是它们都需要根据需求选择相应的状态或策略 不同点是状态模式是在一个类中通过不同的动作切换不同的状态,而策略模式是为一个类选择某个策略,即状态模式中的Context是和多个状态关联的,而策略模式中的Context只和一个策略关联 状态模式角色介绍: (1)Context类,依赖倒置原则,通过适配器模式维护状态类 (2)State:抽象状态类或状态接口,用以抽象封装行为 (3)ConcreteState类:具体状态类,实现了State中的抽象方法 2 实际运用 借贷平台的订单,有审核-发布-抢单 等等 步骤,随着操作的不同,会改变订单的状态,通常通过if/else判断订单的状态,从而实现不同的逻辑,伪代码如下: if(审核){ //审核逻辑 }elseif(发布){ //发布逻辑 }elseif(接单){ //接单逻辑 } 上述解决方案缺点非常明显:这类代码难以应对变化,在添加一种状态时,我们需要手动添加if/else,在添加一种功能时,要对所有的状态进行判断。因此代码会变得越来越臃肿,并且一旦没有处理某个状态,便会发生极其严重的BUG,难以维护 2.1 状态模式应用 状态模式本质上是一种基于状态和事件的 状态机 ,下面是订单流程的状态图 通过状态图,我们再设计一张横纵坐标关系表来比较,图如下: 通过上述表 我们可以细化为一个二维数组,来表示状态与事件直接的关系: 2.2 代码实现 我们通过状态的各种图例分析来用代码实现逻辑 状态枚举类 public enum StateEnum { //订单生成 GENERATE(1, "GENERATE"), //已审核 REVIEWED(2, "REVIEWED"), //已发布 PUBLISHED(3, "PUBLISHED"), //待付款 NOT_PAY(4, "NOT_PAY"), //已付款 PAID(5, "PAID"), //已完结 FEED_BACKED(6, "FEED_BACKED"); private int key; private String value; StateEnum(int key, String value) { this.

数据结构之BTree和B+Tree(多路平衡查找树 )

1 背景 B-Tree是为磁盘等外存储设备设计的一种平衡查找树。因此在讲B-Tree之前先了解下磁盘的相关知识。系统从磁盘读取数据到内存时是以磁盘块(block)为基本单位的,位于同一个磁盘块中的数据会被一次性读取出来,而不是需要什么取什么。 InnoDB存储引擎中有页(Page)的概念,页是其磁盘管理的最小单位。InnoDB存储引擎中默认每个页的大小为16KB,可通过参数innodb_page_size将页的大小设置为4K、8K、16K,在MySQL中可通过如下命令查看页的大小: mysql> show variables like 'innodb_page_size'; 而系统一个磁盘块的存储空间往往没有这么大,因此InnoDB每次申请磁盘空间时都会是若干地址连续磁盘块来达到页的大小16KB。InnoDB在把磁盘数据读入到磁盘时会以页为基本单位,在查询数据时如果一个页中的每条数据都能有助于定位数据记录的位置,这将会减少磁盘I/O次数,提高查询效率。 2 定义与特性 B-Tree B-Tree结构的数据可以让系统高效的找到数据所在的磁盘块。为了描述B-Tree,首先定义一条记录为一个二元组[key, data] ,key为记录的键值(关键字),对应表中的主键值,data为一行记录中除主键外的数据。对于不同的记录,key值互不相同。 一棵m阶的B-Tree有如下特性: 1. 每个节点最多有m个孩子。 2. 除了根节点和叶子节点外,其它每个节点至少有Ceil(m/2)个孩子。 3. 若根节点不是叶子节点,则至少有2个孩子 4. 所有叶子节点都在同一层,且不包含其它关键字信息 5. 每个非终端节点包含n个关键字信息(P0,P1,…Pn, k1,…kn) 6. 关键字的个数n满足:ceil(m/2)-1 <= n <= m-1 7. ki(i=1,…n)为关键字,且关键字升序排序。 8. Pi(i=1,…n)为指向子树根节点的指针。P(i-1)指向的子树的所有节点关键字均小于ki,但都大于k(i-1) B树中的每个节点根据实际情况可以包含大量的关键字信息和分支 如下图所示为一个3阶的B-Tree: 每个节点占用一个盘块的磁盘空间,一个节点上有两个升序排序的关键字和三个指向子树根节点的指针,指针存储的是子节点所在磁盘块的地址。两个关键词划分成的三个范围域对应三个指针指向的子树的数据的范围域。以根节点为例,关键字为17和35,P1指针指向的子树的数据范围小于17,P2指针指向的子树的数据范围为17~35,P3指针指向的子树的数据范围大于35 模拟查找关键字29的过程: >根据根节点找到磁盘块1,读入内存。【磁盘I/O操作第1次】 比较关键字29在区间(17,35),找到磁盘块1的指针P2。 根据P2指针找到磁盘块3,读入内存。【磁盘I/O操作第2次】 比较关键字29在区间(26,30),找到磁盘块3的指针P2。 根据P2指针找到磁盘块8,读入内存。【磁盘I/O操作第3次】 在磁盘块8中的关键字列表中找到关键字29。 分析上面过程,发现需要3次磁盘I/O操作,和3次内存查找操作。由于内存中的关键字是一个有序表结构,可以利用二分法查找提高效率。而3次磁盘I/O操作是影响整个B-Tree查找效率的决定因素。B-Tree相对于AVLTree缩减了节点个数,使每次磁盘I/O取到内存的数据都发挥了作用,从而提高了查询效率。 B+Tree B+Tree是在B-Tree基础上的一种优化,使其更适合实现外存储索引结构,InnoDB存储引擎就是用B+Tree实现其索引结构。 从B-Tree图可以看到每个节点中不仅包含数据的key值,还有data值。而每一个页的存储空间是有限的,如果data数据较大时将会导致每个节点(即一个页)能存储的key的数量很小,当存储的数据量很大时同样会导致B-Tree的深度较大,增大查询时的磁盘I/O次数,进而影响查询效率。在B+Tree中,所有数据记录节点都是按照键值大小顺序存放在同一层的叶子节点上,而非叶子节点上只存储key值信息,这样可以大大加大每个节点存储的key值数量,降低B+Tree的高度。 B+Tree相对于B-Tree有几点不同: 非叶子节点只存储键值信息。 所有叶子节点之间都有一个链指针。 数据记录都存放在叶子节点中。 将B-Tree优化,由于B+Tree的非叶子节点只存储键值信息,假设每个磁盘块能存储4个键值及指针信息,则变成B+Tree后其结构如下图所示: 通常在B+Tree上有两个头指针,一个指向根节点,另一个指向关键字最小的叶子节点,而且所有叶子节点(即数据节点)之间是一种链式环结构。因此可以对B+Tree进行两种查找运算:一种是对于主键的范围查找和分页查找,另一种是从根节点开始,进行随机查找。 可能上面例子中只有22条数据记录,看不出B+Tree的优点,下面做一个推算: InnoDB存储引擎中页的大小为16KB,一般表的主键类型为INT(占用4个字节)或BIGINT(占用8个字节),指针类型也一般为4或8个字节,也就是说一个页(B+Tree中的一个节点)中大概存储16KB/(8B+8B)=1K个键值(因为是估值,为方便计算,这里的K取值为10^3,计算可得深度为3的树可以存储10亿数量级数据 实际情况中每个节点可能不能填充满,因此在数据库中,B+Tree的高度一般都在2-4层。mysql的InnoDB存储引擎在设计时是将根节点常驻内存的,也就是说查找某一键值的行记录时最多只需要1~3次磁盘I/O操作 B+Tree索引可以分为聚集索引(clustered index)和非聚簇索引(secondary index)。上面B+Tree示例图在数据库中的实现即为聚集索引 聚集索引的B+Tree中的叶子节点存放的是整张表的行记录数据。非聚簇索引与聚集索引的区别在于非聚簇索引的叶子节点并不包含行记录的全部数据,而是存储相应行数据的聚集索引键,即主键。当通过非聚簇索引来查询数据时,InnoDB存储引擎会遍历非聚簇索引找到主键,然后再通过主键在聚集索引中找到完整的行记录数据 # 3 B树 3.

分布式全局序列ID方案之Redis优化方案

1 Redis的Flicker方案 利用redis的lua脚本功能,在每个节点上通过lua脚本生成唯一ID,生成的ID为64位,具体如下: 使用41 bit来存放时间,精确到毫秒,可以使用到2039年 使用12 bit来存放逻辑分片ID,最大分片ID是4095 使用10 bit来存放自增长ID,则每个节点,每毫秒最多可生成1024个ID 比如GTM时间 2018年6月24日11点23分 ,它的距1970年的毫秒数是 1529810591000,假定分片ID是60,自增长序列是20,则生成的ID是: 6416490681073670164 = 1529810591000 << 22 | 60 << 10 | 20 redis提供了TIME命令,取得redis服务器的秒值和微秒值 毫秒值获取命令:EVAL "local current = redis.call('TIME') ;return a[1]*1000 + a[2]/1000" 0 生成最终ID : current << (12 + 10)) | (shardingId << 10) | seq 2 ID原子性自增方案 2.1 Redis HINCRBY 命令 Redis 的 INCR 命令支持 “INCR AND GET” 原子操作。利用这个特性,我们可以在 Redis 中存序列号,让分布式环境中多个取号服务在 Redis 中通过 INCR 命令来实现取号;同时 Redis 是单进程单线程架构,不会因为多个取号方的 INCR 命令导致取号重复。因此,基于 Redis 的 INCR 命令实现序列号的生成基本能满足全局唯一与单调递增的特性,并且性能还不错。

分布式全局序列ID方案之Flicker优化方案

1 Flicker的解决方案 MySQL中id自增的特性,可以借此来生成全局的序列号,Flicker在解决全局ID生成方案里就采用了MySQL自增长ID的机制(auto_increment + replace into + MyISAM)。一个生成64位ID方案具体就是这样的: 先创建单独的数据库,然后创建一个表: CREATE TABLE borrow_order ( id bigint(20) unsigned NOT NULL auto_increment, stub char(1) NOT NULL default '', PRIMARY KEY (id), UNIQUE KEY stub (stub) ) ENGINE=MyISAM 当我们插入记录后,执行SELECT * from borrow_order ,查询结果就是这样的: +-------------------+------+ | id | stub | +-------------------+------+ | 1 | 192.168.100.102 | +-------------------+------+ 在我们的应用端需要做下面这两个操作,在一个事务会话里提交: REPLACE INTO borrow_order (stub) VALUES ('192.168.100.102'); SELECT LAST_INSERT_ID(); 上述操作,通过 replace into 操作,首先尝试插入数据到表中,如果发现表中已经有此行数据则先删除此行数据,然后插入新的数据。 如果没有此行数据的话,直接插入新数据。注意:插入的行字段需要有主键索引或者唯一索引,否则会出错

分布式全局序列ID方案之Snowflake算法

1 背景 在分布式项目中,在业务数据中需要生成一个全局唯一的序列号,比如:消息标识,订单标识,用户标识等等。同时对于id生成的要求如下: * 全局唯一 * 趋势有序 * 主键索引 方便排序 * 高可用 * 高并发 2 基础方案 2.1 数据库主键自增 利用mysql的auto_increment特性 >优点: (1)能够保证唯一性 (2)能够保证递增性 (3)步长固定 缺点: (1)无法高可用:普通的一主多从+读写分离架构,自增ID写入请求,主库挂了就GG (2)无法高并发:写入是单点,数据库主库的写性能决定ID的生成性能上限,并且难以扩展 2.2 UUID uuid算法是比较常用的算法,根据UUID的特性,可以产生一个唯一的字符串 优点: (1)本地生成ID,无需远程服务调用,低延时 (2)扩展性好,基本可以-认为没有性能上限 缺点: (1)无法保证趋势递增 (2)uuid字符串过长,作为主键建立索引查询效率低,常见优化方案为“转化为两个uint64整数存储”或者“折半存储”(折半后不能保证唯一性) (3)如使用实现版本的=不一样,在高并发情况下可能会出现UUID重复情况 2.3 时间戳 优点: (1)本地生成ID,无需远程调用,低延时 (2)ID趋势递增 (3)ID是整数,建立索引后查询效率高 缺点: (1)如果并发量超过1000,会生成重复的ID 3 Twitter Snowflake 3.1 简介 snowflake是twitter开源的分布式ID生成算法,其核心思想是:一个long型的ID,使用其中41bit作为毫秒数,10bit作为机器编号,12bit作为毫秒内序列号。这个算法单机每秒内理论上最多可以生成1000*(2^12),也就是400W的ID,完全能满足业务的需求 3.2 图示详解 (1)1位:标识部分,在java中由于long的最高位是符号位,正数是0,负数是1,一般生成的ID为正数,所以为0 (2)41位:时间戳部分,这个是毫秒级的时间,_一般实现上不会存储当前的时间戳,而是时间戳的差值(当前时间-固定的开始时间)_,这样可以使产生的ID从更小值开始;41位的时间戳可以使用69年,(1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69年

Spring之动手实现SpringMVC功能

1 简介 SpringMVC大家应该耳熟能详,只要是做Java网站开发的小伙伴,都会使用的框架。SpringMVC以 DispatcherServlet 为核心,负责协调和组织不同组件以完成请求处理并返回响应的工作,实现了MVC模式。接下来我们从 该框架的流程 来整理设计思路,最后自己实现一个mvc框架. 2 SpringMVC运行流程 springmvc的流程如下: API说明 _DispatcherServlet_: Spring提供的前端控制器,所有的请求都有经过它来统一分发。在DispatcherServlet将请求分发给Controller之前,需要借助于Spring提供的HandlerMapping定位到具体的Controller。 HandlerMapping : 完成客户请求到Controller映射,包括拦截器部分内容 Controller : Controller将处理用户请求,调用业务层接口分析,并返回ModelAndView对象给DispatcherServlet前端控制器,ModelAndView中包含了模型(Model)和视图(View) ViewResolver : Spring提供的视图解析器(ViewResolver)在Web应用中查找View对象,会根据当前的视图渲染引擎(JSP FreeMarker Thymeleaf)来渲染视图,返回给前端 流程概述 1 用户发送请求至前端控制器DispatcherServlet 2 DispatcherServlet收到请求调用HandlerMapping处理器映射器 3 处理器映射器根据请求url找到具体的处理器,生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet。 4 DispatcherServlet通过HandlerAdapter处理器适配器调用处理器 5 执行处理器(Controller,也叫后端控制器)。 6 Controller执行完成返回ModelAndView 7 HandlerAdapter将controller执行结果ModelAndView返回给DispatcherServlet 8 DispatcherServlet将ModelAndView传给ViewReslover视图解析器 9 ViewReslover解析后返回具体View 10 DispatcherServlet对View进行渲染视图(即将模型数据填充至视图中) 11 DispatcherServlet响应用户 3 MVC之九大组件 SpringMVC中的Servlet一共有三个层次,分别是HttpServletBean、FrameworkServlet和 DispatcherServlet。 HttpServletBean直接继承自java的HttpServlet,其作用是将Servlet中配置的参数设置到相应的属性; FrameworkServlet初始化了WebApplicationContext,DispatcherServlet初始化了自身的9个组件。 DispatcherServlet初始化方法 protected void onRefresh(ApplicationContext context) { initStrategies(context); } protected void initStrategies(ApplicationContext context) { initMultipartResolver(context); initLocaleResolver(context); initThemeResolver(context); initHandlerMappings(context); initHandlerAdapters(context); initHandlerExceptionResolvers(context); initRequestToViewNameTranslator(context); initViewResolvers(context); initFlashMapManager(context); } 具体9个组件介绍如下:

Java反射优化之方法句柄

1 简介 java7中为间接调用方法引入了新的api,即 方法句柄 方法句柄中包含两个重要的类,MethodHandle和MethodType MethodHandle 通过句柄我们可以直接调用该句柄所引用的底层方法。从作用上来看,方法句柄类似于反射中的Method类,是对要执行的方法的一个引用,我们也是通过它来调用底层方法,它调用时有两个方法 invoke和invokeExact,后者要求参数类型与底层方法的参数完全匹配,前者则在有出入时做修改如包装类型。 MethodType 方法签名不可变对象,是对方法的一个映射,包含返回值和参数类型。在lookup时也是通过它来寻找的。 每个方法句柄都有一个MethodType实例,用来指明方法的返回类型和参数类型。 2 简单使用 2.1 demo测试 public class MethodHandleDemo { public static void main(String[] args) throws Throwable{ //参数为返回值类型、参数类型 单个参数 MethodType methodType = MethodType.methodType(void.class, String.class); //声明定义方法句柄,通过lookup对象得到方法句柄,参数为方法所在的类、方法的名称、所匹配的方法签名 MethodHandle methodHandle = MethodHandles.lookup().findVirtual(Test.class, "testMethod", methodType); //调用底层方法 methodHandle.invoke(Test.class.newInstance(), "hello, world"); //多个参数 MethodHandle testMethod = MethodHandles.lookup().findVirtual(Test.class, "testMethod", MethodType.methodType(Object.class, String.class, int.class)); testMethod.invoke(Test.class.newInstance(), "aa", 1); MethodType f3 = MethodType.methodType(Object.class, int.class, int.class); //查找静态方法 MethodHandle aStatic = MethodHandles.lookup().findStatic(Test.class, "add", f3); //使用invokeExact调用时,参数类型和返回值类型必须与方法签名的一致 aStatic.

Spring之动手实现IOC功能

1 背景 我们经常在使用Spring生态中的组件,我们在潜移默化的DI和IOC的思想下,来创建和使用Bean对象,使用过 @Component @ComponentScan @Autowired @Bean @Configuration 等等的注解,所以了解Spring容器是如何创建和管理Bean,是我们必需掌握的技能。 下面我们通过手写DI和IOC的方式来加深对Spring的理解。 #2 依赖注入 DI,Dependency Injection,即依赖注入。具体含义表示组件之间的依赖关系由容器在应用系统运行期来决定,也就是由容器动态地将某种依赖关系的目标对象实例注入到应用系统中的各个关联的组件之中。对象只提供普通的方法让容器去决定依赖关系。 Spring中创建Bean的方式有三种: 通过XML显式配置 通过Java显式配置 (利用 @Configuration @Bean) 隐式进行bean搜索并自动装配 (利用基于@Component 元注解的方式) 通过上面叙述我们知道,创建bean其实大致可以分为两种,一种是基于XML,另一种是基于注解的方式。 2.1 XML方式 user.xml <?xml version="1.0" encoding="UTF-8"?> <beans> <bean id="studentA" class="iockids.xml.Student" scope="singleton"> <property name="age" value="20"/> <property name="name" value="nana"/> <property name="address" value="hangzhou"/> </bean> <bean id="studentB" class="iockids.xml.Student" scope="prototype"> <property name="name" value="nicky"/> <property name="age" value="22"/> <property name="address" value="ningbo"/> </bean> </beans> Student对象 @Data public class Student { private String name; private Integer age; private String address; } /** * 单例对象容器 */ private Map<String, Object> singletonBeanFactory = new ConcurrentHashMap<>(); /** * bean对象的容器 */ private Map<String, Class<?

Spring之Bean加载-解析-生命周期

1 概要 使用Spring框架,我们需要了解Bean的创建加载过程,需要熟悉Bean是如何获取和使用的。 下面我们通过分析下Spring加载XML文件的过程来分析Bean的数据流。 当前调试的Spring 版本是最新的 4.1.0 release 版本 调试代码主入口 ApplicationContext context = new ClassPathXmlApplicationContext("consumer.xml"); System.out.println("Consumer Started"); ConsumerBean bean = context.getBean(ConsumerBean.class); String secretKey = bean.getProperties().getProperty("SecretKey"); System.out.println(secretKey); 2 解析过程 创建 ClassPathXmlApplicationContext对象,会调用refresh()方法 public void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { // Prepare this context for refreshing. prepareRefresh(); // 获取xml文件的信息,存储在beanFactory对象中 ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); .................省略中间代码 // 注册bean信息 invokeBeanFactoryPostProcessors(beanFactory); // 实例化bean finishBeanFactoryInitialization(beanFactory); // Last step: publish corresponding event. finishRefresh(); } 之后会进入AbstractApplicationContext对象,处理如下方法: