动态代理(CGLIB 与 JDK)
JDK 动态代理类和委托类需要都实现同一个接口。也就是说只有实现了某个接口的类可以使用 Java 动态代理机制。但是,事实上使用中并不是遇到的所有类都会给你实现一个接口。因此,对于没有实现接口的类,就不能使用该机制。而 CGLIB 则可以实现对类的动态代理。 这段话点出了两种代理的根本差异:JDK 代理基于接口,CGLIB 代理基于继承。但要在 Spring 里用好它们,还需要理解代理为什么存在、Spring 如何二选一、以及各自的边界。 为什么需要代理Spring AOP 要在不修改业务代码的前提下织入事务、日志、权限等横切逻辑。做法是:容器不直接暴露目标对象,而是暴露一个”长得一样、但方法前后能插逻辑”的代理对象。调用方无感知,增强逻辑在代理层完成——这就是代理模式在框架里的落地。 JDK 动态代理三要素:Proxy.newProxyInstance(classLoader, interfaces, handler) + 实现 InvocationHandler: Object proxy = Proxy.newProxyInstance( target.getClas...
说说 Spring AOP
面向切面编程,在我们的应用中,经常需要做一些事情,但是这些事情与核心业务无关,比如,要记录所有 update 方法的执行时间时间,操作人等等信息,记录到日志, 通过 Spring 的 AOP 技术,就可以在不修改 update 的代码的情况下完成该需求。 日志这个例子点出了 AOP 的动机(横切关注点模块化),但要用好它还需要:术语体系、通知类型与执行顺序、切入点表达式写法、以及最著名的自调用失效问题。本文把”说说 AOP”说到能直接写切面的程度。 术语速建 切面(Aspect):横切逻辑的模块化单元(@Aspect 类) 连接点(Joinpoint):可被增强的执行点(Spring AOP 中只有方法执行) 切入点(Pointcut):连接点的筛选表达式(哪些方法被增强) 通知(Advice):增强逻辑本身及其执行时机 织入(Weaving):把切面应用到目标对象创建代理的过程(Spring 在运行期织入) 五种通知与执行顺序@Aspect @Componentpublic class LogAspect { @Pointcut("execution...
Spring AOP 实现原理
Spring AOP 中的动态代理主要有两种方式,JDK 动态代理 和 CGLIB 动态代理。JDK 动态代理通过反射来接收被代理的类,并且要求被代理的类必须实现一个接口。JDK 动态代理的核心是 InvocationHandler 接口和 Proxy 类。 如果目标类没有实现接口,那么 Spring AOP 会选择使用 CGLIB 来动态代理目标类。CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态的生成某个类的子类,注意,CGLIB 是通过继承的方式做的动态代理,因此如果某个类被标记为 final,那么它是无法使用 CGLIB 做动态代理的。 两种代理方式是 AOP 的物理基础,但”原理”还应包含 Spring 层的三块机制:代理决策与创建时机(AnnotationAwareAspectJAutoProxyCreator)、通知的链式执行(拦截器链)、以及 Advisor 的匹配模型。本文把从 @Aspect 到代理对象调用的完整链路讲通。 创建时机:后处理器决策容器在 Bean 生命周期的 postProcessAfterI...
Spring IOC 如何实现
Spring 中的 org.springframework.beans 包和 org.springframework.context 包构成了 Spring 框架 IoC 容器的基础。 BeanFactory 接口对 BeanFactory(是一个子接口)进行了扩展,在 BeanFactory 的基础上添加了其他功能,比如与 Spring 的 AOP 更容易集成,也提供了处理 message resource 的机制(用于国际化)、事件传播以及应用层的特别配置,比如针对 Web 应用的 WebApplicationContext。 org.springframework.beans.factory.BeanFactory 是 Spring IoC 容器的具体实现,用来包装和管理前面提到的各种 bean。BeanFactory 接口是 Spring IoC 容器的核心接口。 包结构与接口关系是 IoC 的”静态面”,实现面要回答三个问题:定义从哪来(BeanDefinition)、实例怎么造(反射+缓存)、依赖怎么解(注入与循环依赖)。本文按这三问把 IoC 从概念落到机...
Spring Bean 的生命周期
Spring Bean 的生命周期简单易懂。在一个 bean 实例被初始化时,需要执行一系列的初始化操作以达到可用的状态。同样的,当一个 bean 不在被调用时需要进行相关的析构操作,并从 bean 容器中移除。 Spring bean factory 负责管理在 spring 容器中被创建的 bean 的生命周期。Bean 的生命周期由两组回调(call back)方法组成。 初始化之后调用的回调方法。 销毁之前调用的回调方法。 Spring 框架提供了以下四种方式来管理 bean 的生命周期事件: InitializingBean 和 DisposableBean 回调接口 针对特殊行为的其他 Aware 接口 Bean 配置文件中的 Custom init() 方法和 destroy() 方法 @PostConstruct 和 @PreDestroy 注解方式 四种回调方式列齐了,但生命周期真正的考点是完整顺序:从实例化到销毁的十几步中,Aware、后处理器、初始化回调各自的插入点。本文给出一条可背可推的主线顺序,并讲清每类扩展点的用途与优先级。 主线顺序...
BeanFactory 和 ApplicationContext 有什么区别
BeanFactory 可以理解为含有 bean 集合的工厂类。BeanFactory 包含了种 bean 的定义,以便在接收到客户端请求时将对应的 bean 实例化。 BeanFactory 还能在实例化对象的时生成协作类之间的关系。此举将 bean 自身与 bean 客户端的配置中解放出来。BeanFactory 还包含了 bean 生命周期的控制,调用客户端的初始化方法(initialization methods)和销毁方法(destruction methods)。 从表面上看,ApplicationContext 如同 BeanFactory 一样具有 bean 定义、bean 关联关系的设置,根据请求分发 bean 的功能。但 ApplicationContext 在此基础上还提供了其他的功能: 提供了支持国际化的文本消息 统一的资源文件读取方式 已在监听器中注册的 bean 的事件 功能清单之外,两者最本质的差异只有一条:bean 的实例化时机——懒加载 vs 启动时预实例化。这条差异派生了启动性能、错误暴露时机、内存占用等所有工程区别。本文以此为主...
乐观锁的业务场景及实现方式
乐观锁(Optimistic Lock) 每次获取数据的时候,都不会担心数据被修改,所以每次获取数据的时候都不会进行加锁,但是在更新数据的时候需要判断该数据是否被别人修改过。如果数据被其他线程修改,则不进行数据更新,如果数据没有被其他线程修改,则进行数据更新。由于数据没有进行加锁,期间该数据可以被其他线程进行读写操作。 比较适合读取操作比较频繁的场景,如果出现大量的写入操作,数据发生冲突的可能性就会增大,为了保证数据的一致性,应用层需要不断的重新获取数据,这样会增加大量的查询操作,降低了系统的吞吐量。 定义与适用面清楚了,落地还差三块:两种主流实现(version 与 CAS 字段)的 SQL 形态、重试策略的工程细节、以及乐观/悲观的量化选型依据。本文补齐成可直接落库的方案。 实现一:版本号(version)表加 version 列,读时带出,写时校验并自增: -- 读SELECT balance, version FROM account WHERE id = 7; -- 得 version=3-- 写(校验+更新原子完成)UPDATE account...
ABA 问题
CAS 会导致“ABA问题”。 CAS 算法实现一个重要前提需要取出内存中某时刻的数据,而在下时刻比较并替换,那么在这个时间差类会导致数据的变化。 比如说一个线程 one 从内存位置 V 中取出 A,这时候另一个线程 two 也从内存中取出 A,并且 two 进行了一些操作变成了 B,然后 two 又将 V 位置的数据变成 A,这时候线程 one 进行 CAS 操作发现内存中仍然是 A,然后 one 操作成功。尽管线程 one 的 CAS 操作成功,但是不代表这个过程就是没有问题的。 部分乐观锁的实现是通过版本号(version)的方式来解决 ABA 问题,乐观锁每次在执行数据的修改操作时,都会带上一个版本号,一旦版本号和数据的版本号一致就可以执行修改操作并对版本号执行 +1 操作,否则就执行失败。因为每次操作的版本号都会随之增加,所以不会出现 ABA 问题,因为版本号只会增加不会减少。 四段描述完整给出了问题与版本号解法,但还差两块实战内容:ABA 在什么数据结构下才真正致命(普通计数场景其实无害),以及 Java 提供的两种 stamped 工具的正确用法与代价。...
CAS 乐观锁
CAS 是项乐观锁技术,当多个线程尝试使用 CAS 同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试。 CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值。否则,处理器不做任何操作。无论哪种情况,它都会在 CAS 指令之前返回该位置的值。(在 CAS 的一些特殊情况下将仅返回 CAS 是否成功,而不提取当前值。)CAS 有效地说明了“我认为位置 V 应该包含值 A;如果包含该值,则将 B 放到这个位置;否则,不要更改该位置,只告诉我这个位置现在的值即可。”这其实和乐观锁的冲突检查 + 数据更新的原理是一样的。 三操作数模型讲清了 CAS 是什么,但工程上还需要三块:硬件如何保证 CAS 本身原子、Java 侧的入口(Unsafe/atomic/VarHandle)、以及 CAS 的三大代价(ABA、自旋开销、单变量局限)。本文补全。 硬件原子性:lock 前缀CAS 在...
synchronized 与 lock 的区别
synchronized 和 lock 的用法区别 synchronized(隐式锁):在需要同步的对象中加入此控制,synchronized 可以加在方法上,也可以加在特定代码块中,括号中表示需要锁的对象。 lock(显示锁):需要显示指定起始位置和终止位置。一般使用 ReentrantLock 类做为锁,多个线程中必须要使用一个 ReentrantLock 类做为对象才能保证锁的生效。且在加锁和解锁处需要通过 lock() 和 unlock() 显示指出。所以一般会在 finally 块中写 unlock() 以防死锁。 synchronized 和 lock 性能区别 synchronized 是托管给 JVM 执行的,而 lock 是 Java 写的控制锁的代码。在 JDK 1.5 中,synchronize 是性能低效的。因为这是一个重量级操作,需要调用操作接口,导致有可能加锁消耗的系统时间比加锁以外的操作还多。相比之下使用 Java 提供的 Lock 对象,性能更高一些。但是到了 JDK 1.6,发生了变化。synchronize 在语义上很清晰,可以进行很多优...















