简单请求:同时满足以下两大条件
- 请求方法是以下三种方法之一
HEAD
GET
POST
- HTTP的头信息不超过以下几种字段
Accept
Accept-Language
Content-Language
Last-Event-ID
Content-Type
:只限于三个值application/x-www-form-urlencoded
、multipart/form-data
、text/plain
贫,气不改;达,志不改
ConfigurationClassPostProcessor.processConfigBeanDefinitions解析配置类
processImports解析import的类:AspectJAutoProxyRegistrar
this.reader.loadBeanDefinition(configClasses)
AspectJAutoProxyRegistrar.registerBeanDefinitions(类为AnnotationAwareAspectJAutoProxyCreator)
registerBeanPostProcessors
AutowiredAnnotationBeanPostProcessor
string
、list
、set
、hash
、sorted set
、pub/sub
、transactions
memcache是一个内存缓存,key的长度小于250字符,单个item存储要小于1M,不适合虚拟机使用
redis只使用单核,而memcache可以使用多核,所以平均每一个核上redis在存储小数据时比memcache性能更高。而在100k以上的数据中,memcache性能要高于redis。
redis使用的是单线程模型,保证了数据按顺序提交。
memcache需要使用cas保证数据一致性。
redis单线程模型只能使用一个cpu,可以开启多个redis进程
mybatis提供了一级缓存的方案来优化数据库会话间重复查询的问题。实现的方式是每个sqlSession中都持有了自己的缓存,一种是session级别,即在一个Mybatis会话中执行的所有语句,都会共享这一个缓存。一种是statement级别,可以理解为缓存只对当前执行的这一个statement有效。
Mybatis的一级缓存最大返回是sqlsession内部,有多个sqlsession或者分布式的环境下,有操作数据库写的话,会引起脏数据,建议把一级缓存的默认级别设定为statement,即不使用一级缓存
当开启二级缓存后,会使用CachingExecutor装饰Executor,在进入后续执行前,先在CachingExecurot进行二级缓存的查询。在二级缓存的使用中,一个namespace下的所有操作语句,都影响着同一个Cache,即二级缓存是被多个SqlSession共享着的,是一个全局的变量。
Mybatis在多表查询时,极大可能会出现脏数据,有设计上的缺陷,安全使用的条件比较苛刻。
在分布式环境下,由于默认的Mybatis Cache实现都是基于本地的,分布式环境下必然会出现读取到脏数据,需要使用集中式缓存将Mybatis的Cache接口实现,有一定的开发成本,不如直接用Redis, Memcache实现业务上的缓存就好了。
事务 | 代码 |
---|---|
如果有事务加入,没有则新建 | REQUIRED |
不管有没有事务,都新建 | REQUIRED_NEW |
不开启事务 | NOT_SUPPORTED |
必须在事务中执行,否则抛出异常 | MANDATORY |
必须不在事务中执行,否则抛出异常 | NEVER |
如果有了事务就加入,否则就不用事务 | SUPPORTS |
Throwable
Error
程序无法处理的错误。大多数错误与代码编写者执行的操作无关,而表示代码运行时JVM出现的问题。
Exception
程序本身可以处理的异常
RuntimeException
运行时异常。编译器不要求你必须进行异常捕获处理或者抛出声明,由程序员自行决定
CheckedException
非运行时异常。编译器强制必须进行捕获处理。
HttpServletRequest
注入的是一个动态代理对象ObjectFactoryDelegatingInvocationHandler
,当我们调用request的方法的时候其实是调用了method.invoke(this.objectFactory.getObject(), args);
objectFactory
对象是RequestObjectFactory
,getObject
方法调用的是currentRequestAttributes().getRequest()
currentRequestAttributes()
方法调用的是RequestContextHolder.currentRequestAttributes()
,它从requestAttributesHolder
中取得RequestAttributes
,RequestAttributes
是一个ThreadLocal<RequestAttributes>
请求刚进入springmvc的dispatcherServlet
的时候会把request相关的对象设置到RequestContextHolder
的threadlocal
中去dispatcherServlet.processRequest -> initContextHolders -> setRequestAttributes
monitorenter
monitorexit
List
单一职责原则(SRP)
一个接口或类只有一个原因引起变化,也就是一个接口或类只有一个职责,它就负责一件事情。单一职责适用于接口、类、同时也适用于方法。一个方法尽可能做一件事情,比如一个方法修改用户密码,不要把这个方法放到”修改用户信息”方法中,这个方法粒度很粗。
里氏替换(LSP)
只要父类能出现的地方子类就可以出现,而且替换为子类也不会产生任何错误或异常,使用者可能根本就不需要知道是父类还是子类。但是反过来就不行,有子类出现的地方,父类未必就能适应。
依赖倒置
模块间的依赖通过抽象发生,实现类之间不发生直接的依赖关系,其依赖关系时通过接口或抽象类产生的
接口或抽象类不依赖于实现类
实现类依赖接口或抽象类
实践
每个类尽量都有接口或抽象类,或者抽象类和接口两者都具备
变量的表面类型尽量是接口或者是抽象类
任何类都不应该从具体的类派生
尽量不要覆写基类的方法
结合里氏替换原则使用
接口负责定义public属性和方法,并且声明与其他对象的依赖关系,抽象类负责公共构造部分的实现,实现类准确地实现业务逻辑,同时在适当的时候对父类进行细化。
接口隔离原则
接口尽量细化,同时接口中的方法尽量少
单一职责要求的是类和接口职责单一,注重的是职责,这是业务逻辑上的划分,而接口隔离原则要求接口的方法尽量少
迪米特法则
最少知识原则。一个对象应该对其他对象有最少的了解。通俗地讲,一个类应该对自己需要耦合或调用的类知道得最少。
只和朋友交流。朋友类的定义是这样的:出现在成员变量、方法的输入输出参数中的类称为成员朋友类,而出现在方法体内部的类不属于朋友类。
朋友间也是有距离的。一个类公开的public属性或方法越多,修改时涉及的面也就越大,变更引起的风险扩散也就越大。在设计时需要反复衡量:是否可以再减少public方法和属性,是否可以修改为private、package-private、protected等访问权限,是否可以加上final关键字等。
是自己的就是自己的。如果一个方法放在本类中,既不增加类间关系,也对本类不产生负面影响,就放置在本类中。
开闭原则
对扩展开发,对修改关闭。
抽象约束
元数据控制模块行为
制定项目章程
封装变化
单一职责:类或接口只有一个职责
里式替换:父类能出现的地方子类也能出现,反之不行
依赖倒置:依赖抽象,不依赖实现
接口隔离:接口尽量细化,方法尽量少
迪米特:最少知识
开闭:对修改关闭,对扩展开放
SOLLID
单一职责 single responsibility principle
开闭 open closed priciple
里式替换 liskov substitution principle
迪米特 law of demeter
接口隔离 interface segregation principle
依赖倒置 dependence inversion principle
方法调用阶段的唯一任务就是确定被调用方法的版本。Class文件在编译过程中不包含传统编译中的连接步骤,一切方法调用在Class文件里面存储的都只是符号引用,而不是方法在实际运行时内存布局中的入口地址(相当于之前说的直接引用)。
解析
所有方法调用中的目标方法在Class文件里面都是一个常量池中的符号引用,在类加载的解析阶段,会将其中的一部分引用转化为直接引用。调用目标在程序代码写好、编译器进行编译时就必须确定下来。这类方法的调用称为解析。
invokestatic:调用静态方法
invokespecial:调用实例构造器
invokevirtual:调用所有的虚方法
invokeinterface:调用接口方法,会在运行时再确定一个实现此接口的对象
invokedynamic:先在运行时动态解析出调用点限定符所引用的方法,然后再执行该方法。在此之前的4条调用指令,分派逻辑是固化在Java虚拟机内部的,而invokedynamic指令的分派逻辑是由用户所设定的引导方法决定的。
只要能被invokestatic和invokespecial指令调用的方法,都可以在解析阶段中确定唯一的调用版本,符合这个条件的静态方法、私有方法、实例构造器、父类方法4类,它们在类加载的时候就会把符号引用解析为该方法的直接引用。这些方法可以称为非虚方法,于智相反,其他方法称为虚方法(除去final方法,后文会提到)。
Human man = new Man()
Human称为变量的静态类型(Static Type),或者叫做外观类型(Apparent Type),后面的”Man”称为变量的实际类型(Actual Type),静态类型和实际类型在程序中都可以发生一些变化,区别是静态类型的变化仅仅在使用时发生,变量本身的静态类型不会被改变,并且最终的静态类型是在编译期可知的;而实际类型变化的结果在运行期才可确定,编译器在编译程序的时候并不知道一个对象的实际类型是什么。
编译器在重载时通过参数的静态类型而不是实际类型作为判定依据的。并且静态类型是编译期可知的,因此,在编译阶段,javac编译器会根据参数的静态类型决定使用哪个重载版本。
所有依赖静态类型来定位方法执行版本的分派动作称为静态分派。静态分派的典型应用是方法重载。
invokevirtual指令运行时解析过程
方法区
线程共享,用于存储已被虚拟机加载的类信息、常量、静态变量、即使编译器编译后的代码
运行时常量池
被重载的方法必须改变参数列表(参数个数或类型或顺序不一样)
被重载的方法可以改变返回类型
被重载的方法可以改变访问修饰符
被重载的方法可以声明新的或更广的检查异常
方法能够在同一个类中或者在一个子类中被重载
无法以返回值类型作为重载函数的区分标准
区别点 | 重载 | 重写 |
---|---|---|
参数列表 | 不一样 | 一样 |
返回类型 | 可以不一样 | 一样 |
异常 | 可以不一样 | 可以缩小 |
访问权限 | 可以不一样 | 可以放大 |
在新生代经历了N次垃圾回收后仍然存活的对象,就会被放到老年代,该区域中对象存活率高。老年代的垃圾回收(又称Major GC)通常使用“标记-清理”或“标记-整理”算法。整堆包括新生代和老年代的垃圾回收称为Full GC(HotSpot VM里,除了CMS之外,其他能收集老年代的GC都会同时收集整个GC堆,包括新生代)。
当老年代的空间不足时,会触发Major GC/Full GC,速度一般比Minor GC慢10倍以上
在JDK8之前的HotSpot实现中,类的元数据如方法数据、方法信息(字节码,栈和变量大小)、运行时常量池、已确定的符号引用和虚方法表等被保存在永久代中。
在JDK8的HotSpot中,把永久代从Java堆中移除了,并把类的元数据直接保存在本地内存区域(堆外空间),称为元空间。
元空间并不在虚拟机中,而是使用本地内存。类的元数据放入native memory,字符串池和类的静态变量放入java堆中,这样可以加载多少类的元数据就不再由MaxPermSize控制,而由系统的实际可用空间来控制。
-Xms 堆初始大小
-Xmx 堆最大值
-Xss 每个线程的栈大小
-XX:SurvivorRatio 年轻代中Eden区与两个Survivor区的比值
-XX:+PrintGCDetails 输出GC日志详情信息
-XX:+PrintGCApplicationStoppedTime 输出垃圾回收期间程序暂停的时间
-Xloggc: 把相关日志信息记录到文件以便分析
-XX:+HeapDumpOnOutOfMemoryError 发生内存溢出时生成heapdump文件
-XX:HeapDumpPath= heapdump文件地址
jps
jstat
jinfo
jmap
jhat
jstack
String 字符串常量
StringBuffer 字符串变量(线程安全)
StringBuilder 字符串变量(非线程安全)
statement,基于SQL语句的复制
不需要记录每一行数据的变化,减少binlog日志量,节约IO,提高性能。因为它只需要记录在master上所执行的语句细节,以及执行语句时候的上下文信息。
row,基于行的复制
它不记录sql语句的上下文相关信息,仅需要记录那条记录被修改成什么了。
所有的执行语句当记录到日志中的时候,都将以每行记录的修改来记录,这样可能会产生大量的日志内容。
新版本的MySQL中对row level模式也被做了优化,并不是所有的修改都会以row level来记录,像遇到表结构变更的时候就会以statement模式来记录,如果sql语句确实就是update或者delete等修改数据的语句,那么还是会记录所有行的变更
mixed,混合模式复制
实际上就是statement与row的结合
一般的语句修改使用statement格式保存binlog,如一些函数,statement无法完成主从复制的操作,则采用row格式保存binlog,MySQL会根据执行的每一条具体的sql语句来区分对待记录的日志形式,也就是在statement和row之间选择一种。
设置使用查询缓存的方式,query_cache_type
设置查询缓存的大小
query_cache_size:查询缓存的总体可用空间
查询缓存相关参数
1 | show global variables like 'query_cache%' |
query_cache_min_res_unit:查询缓存中分配内存的最小单位(注意:此值通常需要调整,此值被调整为接近所有查询结果的平均值是最好的)
计算单个查询的平均缓存大小:(query_cache_size - Qcache_free_memory) / Qcache_queries_in_cache
query_cache_size:查询缓存的总体可用空间,单位为字节;其必须为1024的倍数
衡量缓存是否有效
1 | show global status where variable_name = 'Qcache_hits' or variable_name = 'Com_select' |
Com_select:非缓存查询次数
缓存命中率:Qcache_hits / (Qcache_hits + Com_select)
1 | show global status where variable_name = 'Qcache_hits' or variable_name = 'Qcache_inserts' |
- Qcache_hits:缓存命中次数
- Qcache_inserts:向查询缓存中添加缓存记录的条数
“命中和写入”的比率:`Qcache_hits / Qcache_inserts`
如果此比值大于3:1,说明缓存也是有效的;如果高于10:1,相当理想
1 | jedis.set(final String key, final String value, final String nxxx, final String expx, final long time) |
1 | String script = “if redis.call(‘get’, KEYS[1]) == ARGV[1] then return redis.call(‘del’, KEYS[1]) else return 0 end”; |
我们将Lua代码传到jedis.eval()方法里,并使参数KEYS[1]赋值为lockKey,ARGV[1]赋值为requestId。在eval命令执行Lua代码的时候,Lua代码将被当成一个命令去执行,并且直到eval命令执行完成,redis才会执行其他命令。
1 | public class Singleton { |
有可能会在其他的代理里被调用然后被初始化
1 | Public class Singleton { |
一定会在getInstance()方法中被初始化
1 | Public class Singleton { |
由jvm来控制同步
如果BlockingQueue是无界的,那么永远不会触发maximumPoolSize,自然keepAliveTime也就没有了意义。
如果worker数量小于corePoolSize:调用addWorker增加新的Worker。
如果可以插入到workQueue中:如果不在运行状态则拒绝;否则,如果worker的数量等于0则添加一个worker
如果workQueue已经满了:调用addWorker尝试是否可以在corePoolSize之外另外增加一个worker,如果worker数量已经达到max_pool_size,则拒绝
判断worker数量,如果超过了corePoolSize或者maximumPoolSize,则直接返回失败。否则增加一个worker数量,跳出循环
新建一个Worker,将其添加到workers中。启动Worker。
循环调用getTask方法获取任务,getTask方法从阻塞队列中获取任务,调用task.run执行任务
如果task为null则跳出循环,执行processWorkerExit方法
根据是否需要超时控制(allowCoreThreadTimeOut || wc > corePoolSize),调用workQueue.poll或者workQueue.take从工作队列中获取任务并返回
操作 | 抛出异常 | 特殊值 | 阻塞 | 超时 |
---|---|---|---|---|
插入 | add(E e) | offer(E e) | put(E e) | offer(E e, long timeout, TimeUnit unit) |
移除头部元素 | remove() | poll() | take() | poll(long timeout, TimeUnit unit) |
获取头部元素(不删除) | element() | peek() | 无 | 无 |
https://sunshinevvv.coding.me/blog/2017/02/09/HttpGETv.s.POST/
RFC7231里定义了HTTP方法的几个特性:
Safe - 安全
如果一个方法的语义在本质上是“只读”的,那么这个方法就是安全的。客户端向服务端的资源发起的请求如果使用了是安全的方法,就不应该引起服务端任何的状态变化,因此也是无害的。此RFC定义,GET,HEAD,OPTIONS,TRACE这几个方法是安全的。
Idempotent - 幂等
幂等的概念是指同一个请求方法执行多次和仅执行一次的效果完全相同。按照RFC规范,PUT,DELETE和安全方法都是幂等的。
Cacheable - 可缓存性
顾名思义就是一个方法是否可以被缓存,此RFC里GET,HEAD和某些情况下的POST都是可缓存的,当时绝大多数的浏览器的实现里仅仅支持GET和HEAD
Cache-Control
Header的约束),GET的报文主体没有任何语义https://www.jianshu.com/p/bbb6261cb13e
http://blog.csdn.net/qzcsu/article/details/72861891
通过第一次握手,B知道A能够发送数据。通过第二次握手,A知道B能发送数据。结合第一次握手和第二次握手,A知道B能接受数据。结合第三次握手,B知道A能够接收数据。
B收到确认信号,断开连接,而A在一段时间内没收到B的信号,表面B已经断开了,于是A也断开了连接。至此完成挥手过程。
挥手次数比握手多一次,是因为握手过程,通信只需要处理连接。而挥手过程,通信需要处理数据+连接
。
应用程序 | ftp | tftp | telnet | smtp | dns | http | ssh | mysql |
---|---|---|---|---|---|---|---|---|
熟知端口 | 21, 20 | 69 | 23 | 25 | 53 | 80 | 22 | 3306 |
传输层协议 | TCP | UDP | TCP | TCP | UDP | TCP |
给对象添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;任何时刻计数器为0的对象就是不可能再被使用的。
很难解决对象之间相互循环引用的问题
通过一系列的称为”GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连(用图论的话来说,就是从GC Roots到这个对象不可达)时,则证明此对象是不可用的。
可作为GC Roots的对象包括下面几种:
3种系统提供的类加载器
启动类加载器(Bootstrap ClassLoader)
这个类加载器负责将存放在<JAVA_HOME>/lib
目录中的,或者被-Xbootclasspath
参数所指定的路径中的,并且是虚拟机识别的(仅按照文件名识别,如rt.jar,名字不符合的类库即使放在lib目录中也不会被加载)类库加载到虚拟机内存中。
扩展类加载器(Extension ClassLoader)
这个加载器由sum.misc.Launcher$ExtClassLoader
实现,它负载加载<JAVA_HOME>/lib/ext
目录中的,或者被java.ext.dirs
系统变量所指定的路径中的所有类库,开发者可以直接使用扩展类加载器
应用程序类加载器(Application ClassLoader)
这个类加载器由sun.misc.Launcher$AppClassLoader
实现。由于这个类加载器是ClassLoader中的getSystemClassLoader()
方法的返回值,所以一般也称它为系统加载器。它负载加载用户类路径(ClassPath)上所指定的类库,开发者可以直接使用这个类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序默认的类加载器。
双亲委派模型要求除了顶层的启动类加载器外,其余的类加载器都应当有自己的父类加载器。这里类加载器之间的父子关系一般不会以继承(Inheritance)的关系来实现,而是都使用组合(Composition)关系来复用父加载器的代码。
双亲委派模型的工作过程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。
如果编写一个域rt.jar类库中已有类重名的Java类,将会发现可以正常编译,但永远无法被加载运行。
虚拟机规范严格规定了有且只有5中情况必须立即对类进行”初始化”(而加载、验证、准备自然需要在此之前开始):
遇到new, getstatic, putstatic或invokestatic这4条字节码指令时,如果类没有进行过初始化,则需要先触发其初始化。生成这4条指令的最常见的Java代码场景是:
使用java.lang.reflect
包的方法对类进行反射调用的时候,如果类没有进行初始化,则需要先触发其初始化。
java.lang.invoke.MethodHandle
实例最后的解析结果REF_getStatic
、REF_putStatic
、REF_invokeStatic
的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需要先触发其初始化。通过子类来引用父类中定义的静态字段,只会触发父类的初始化而不会触发子类的初始化。至于是否要触发子类的加载和验证,在虚拟机规范中并未明确规定,这点取决于虚拟机的具体实现。对于Sun HotSpot虚拟机来说,可通过-XX:+TraceClassLoading
参数观察到此操作会导致子类的加载。(结论是,子类会加载)
加载
在内存中生成一个代表这个类的java.lang.Class
对象,作为方法区这个类的各种数据的访问入口。(并没有明确规定是在Java堆中,对于HotSpot虚拟机而言,Class对象比较特殊,它虽然是对象,但是存放在方法区里面)。
数组类本身不通过类加载器创建,它是由Java虚拟机直接创建的。但数组类与类加载器仍然有很密切的关系,因为数组类的元素类型(Element Type,指的是数组去掉所有维度的类型)最终要靠类加载器去创建,一个数组类(下面简称为C)创建过程遵循以下规则:
如果数组的组件类型(Component Type,指的是数组去掉一个维度的类型)是引用类型,那就递归采用本节中定义的加载过程去加载这个组件类型,数组C将在加载该组件类型的类加载器的类名称空间上被标识
验证
确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。
准备
准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些变量所使用的内存都将在方法区中进行分配。这个阶段中有两个容易产生混淆的概念需要强调一下,首先,这时候进行内存分配的仅包括类变量(被static修饰的变量),而不包括实例变量,实例变量将会在对象实例化时随着对象一起分配在Java堆中。其次,这里所说的初始值“通常情况”下是数据类型的零值,假设一个类变量的定义为:
1 | public static int value = 123; |
编译时Javac将会为value生成ConstantValue属性,在准备阶段虚拟机就会根据ConstantValue的设置将value赋值为123。
解析
解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。
初始化
初始化阶段是执行类构造器<clinit>()
方法的过程。
<clinit>()
方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块(static{}块)中的语句合并产生的,编译器收集的顺序是由语句在源文件中出现的顺序所决定的,静态语句块中只能访问到定义在静态语句块之前的变量,定义在它之后的变量,在前面的静态语句可以赋值,但是不能访问。<clinit>()
方法与类的构造函数(或者说实例构造器<init>()
)方法不同,它不需要显式地调用父类构造器,虚拟机会保证在子类的<clinit>()
方法执行之前,父类的<clinit>()
方法已经执行完毕。因此在虚拟机中第一个被执行的<clinit>()
方法的类肯定是java.lang.Object
<clinit>()
方法先执行,也就意味着父类中定义的静态语句块要优先于子类的变量赋值操作<clinit>()
方法对于类或接口来说并不是必需的,如果一个类中没有静态语句块,也没有对变量的赋值操作,那么编译器可以不为这个类生产<clinit>()
方法。<clinit>()
方法。但接口与类不同的是,执行接口的<clinit>()
方法不需要先执行父接口的<clinit>()
方法。只有当父接口中定义的变量使用时,父接口才会初始化。另外,接口的实现类在初始化时也一样不会执行接口的<clinit>()
方法<clinit>()
方法在多线程环境中被正确地加锁、同步,如果多个线程同时去初始化一个类,那么只有一个线程去执行这个类的<clinit>()
方法,其他线程都需要阻塞等待,直到活动线程执行<clinit>()
方法完毕。如果在一个类的<clinit>()
方法中有耗时很长的操作,就可能造成多个进程阻塞。需要注意的是,其他线程虽然会被阻塞,但如果执行<clinit>()
方法的那条线程退出<clinit>()
方法后,其他线程唤醒之后不会再次进入<clinit>()
方法。同一个类加载器下,一个类型只会初始化一次。范围 | 创建型 | 结构型 | 行为型 |
---|---|---|---|
对象创建 | Singleton(单例) Prototype(原型) Factory Method(工厂方法) AbstractFactory(抽象工厂) Builder(建造者) |
||
接口适配 | Adapter(适配器) Bridge(桥接) Facade(外观) |
||
对象去耦 | Mediator(中介者) Observer(观察者) |
||
抽象集合 | Composite(组合) | Iterator(迭代器) | |
行为扩展 | Decorator(装饰) | Visitor(访问者) Chain of Responsibility(职责链) |
|
算法封装 | Template Method(模板方法) Strategy(策略) Command(命令模式) |
||
性能与对象访问 | Flyweight(享元) Proxy(代理) |
||
对象状态 | Memento(备忘录) State(状态) |
||
其他 | Interpreter(解释器) |
equals要满足一下规则:
http://www.cnblogs.com/duanxz/p/4494420.html
稳定:
ThreadLocal的作用是提供线程内的局部变量,这种变量在线程的生命周期内起作用,减少同一个线程内多个函数或者组件之间一些公共变量的传递的复杂度。
每个Thread的内部保存了一个类型为ThreadLocalMap的变量threadLocals。ThreadLocalMap是一个在ThreadLocal中实现的hashmap,Key是ThreadLocal,Value是保存的对象。
AOP是Spring的两大特性之一,本文再来回顾一下AOP的原理
首先定义一个切点:
1 | public interface Performance { |
然后定义一个切面:
1 | @Aspect |
最后定义一个配置类和一个启动类:
1 | @Configuration |
最后的输出为
1 | Silencing cell phones |
执行到invokeBeanFactoryPostProcessors
方法,获取实现了BeanDefinitionRegistryPostProcessor
接口的类:ConfigurationClassPostProcessor
。
在ConfigurationClassPostProcessor
的processConfigBeanDefinitions
方法中处理配置类。
首先调动ConfigurationClassParser.parse
方法解析配置类。核心方法是processImports(configClass, sourceClass, getImports(sourceClass), true)
。
首先调用getImports(sourceClass)
方法获取配置类中的Import
注释。这里只有一个:org.springframework.context.annotation.AspectJAutoProxyRegistrar
。然后在getImports
方法中加入到ConfigurationClass
的importBeanDefinitionRegistrars
中:
1 | Class<?> candidateClass = candidate.loadClass(); |
调用this.reader.loadBeanDefinitions(configClass)
根据配置类加载额外的beanDefinition。
在之前的配置类解析中,因为@EnableAspectJAutoProxy
注释的存在,ConfigurationClass
的importBeanDefinitionRegistrars
中有一个key为AspectJAutoProxyRegistrar
,value为StandardAnnotationMetadata
的map。
因此在loadBeanDefinitionsForConfigurationClass
方法中调用loadBeanDefinitionsFromRegistrars
,进入AspectJAutoProxyRegistrar.registerBeanDefinitions
方法,该方法会注册一个名为”org.springframework.aop.config.internalAutoProxyCreator”的beanDefintion,类为org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator
。
经过前面加载了beanDefinition,在registerBeanPostProcessors
方法中将AnnotationAwareAspectJAutoProxyCreator
注册为BeanPostProcessor
。
bean创建工作在DefaultListableBeanFactory.doCreateBean
中完成:
AbstractAutowireCapableBeanFactory.createBeanInstance
创建bean实例。AbstractAutowireCapableBeanFactory.populateBean
填充bean实例。AbstractAutowireCapableBeanFactory.initializeBean
初始化bean实例在调用initializeBean来初始化bean实例的代码中,applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName)
方法遍历所有的BeanPostProcessor
,调用其postProcessAfterInitialization
方法。
重点关注AnnotationAwareAspectJAutoProxyCreator.postProcessAfterInitialization
,它调用AbstractAutoProxyCreator.wrapIfNecessary
如果需要代理则返回经过包装的代理类:
AbstractAdvisorAutoProxyCreator.getAdvicesAndAdvisorsForBean
寻找指定bean的Advisor。createProxy
方法对给定的bean创建一个AOP代理,其调用ProxyFactory.getProxy
方法,该方法只有一行代码:1 | public Object getProxy(ClassLoader classLoader) { |
先调用createAopProxy
创建AopProxy
,再调用getProxy
获取代理对象。
回到AbstractAutowireCapableBeanFactory.initializeBean
方法,wrappedBean再经过applyBeanPostProcessorsAfterInitialization
处理,调用AbstractAutoProxyCreator.postProcessAfterInitialization
,wrappedBean变成了一个代理类。
当调用aop代理类的方法时,调用的实际上是JdkDynamicAopProxy.invoke
:
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass)
获取这个方法的拦截链MethodInvocation invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain)
创建MethodInvocation,将我们之前的代理、目标对象、拦截的method名称、拦截方法的参数、拦截器链全部整合到了ReflectiveMethodInvocation这个类中ReflectiveMethodInvocation.proceed
执行一串拦截链这篇文章我们来分析一下经过@Value注释的变量时如何注入属性值的。
@Value属性值的注入发生在调用populateBean
给实例化完成的bean填充属性之时。
遍历BeanPostProcessor
列表,当调用AutowiredAnnotationBeanPostProcesor
的postProcessPropertyValues
方法时:
1 | public PropertyValues postProcessPropertyValues( |
可以看到,首先调用findAutowiringMetadata
函数来找到需要注入的元数据,然后调用inject
方法来注入相应的属性值。
在inject
方法中遍历InjectedElement
元素,调用AutowiredFieldElement.inject
方法:
value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter)
解析表达式field.set(bean, value)
将解析的值设置到相应的字段中该方法针对bean定义解析特定的依赖,它最终调用的是DefaultListableBeanFactory.doResolveDependency
方法:
AutowireCandidateResolver.getSuggestedValue(descriptor)
获得@Value注释中的描述信息。resolveEmbeddedValue
来解析描述信息的值。默认情况下调用AbstractPropertyResolver.resolvePlaceholders
来解析这个表达式Bean的示例化在Spring的启动过程中基本上算是最后的步骤。经过了前面BeanDefinitionRegistryPostProcesser
和BeanFactoryPostProcessor
注册BeanDefintion,registerBeanPostProcessors
方法注册BeanPostProcessor
。到了最后实例化的过程。
使用如下代码:
1 | ClassPathResource resource = new ClassPathResource("application.yml"); |
未打包时可以获取到文件,打包成jar后报错。
这是因为打包后Spring试图访问文件系统路径,但无法访问jar包中的路径。因此必须使用resource.getInputStream()
1 | ClassPathResource resource = new ClassPathResource("application.yml"); |
http://blog.csdn.net/qq_18748427/article/details/78606432
https://smarterco.de/java-load-file-from-classpath-in-spring-boot/
本文介绍三种将InputStream转成Byte数组的方法:
1 | InputStream initialStream = new ByteArrayInputStream(new byte[] {0, 1, 2}); |
1 | InputStream is = new ByteArrayInputStream(new byte[] {0, 1, 2}); |
1 | InputStream initialStream = ByteSource.wrap(new byte[] {0, 1, 2}).openStream(); |
1 | ByteArrayInputStream initialStream = new ByteArrayInputStream(new byte[] {0, 1, 2}); |
http://www.baeldung.com/convert-input-stream-to-array-of-bytes
家里有个JBL Pebbles音箱,原本是给家里的台式机用的,不过随着台式机的使用频率越来越低,音箱也处于常年闲置的状态。
这几天重新折腾树莓派,想着将音箱用到树莓派中,打造一个支持airplay的无线音箱。