0%

上一篇文章中已经详细介绍了AOP是什么和AOP中重要的概念。因此,本篇文章将进入我们的正题,来对Spring AOP进行概述和介绍器实现的原理。

spring AOP 概述

Spring AOP采用Java作为AOP的实现语言(AOL),SPring AOP可以更快捷地融入开发过程,学习曲线相对要平滑得多。而且,Spring AOP的设计哲学也是简单而强大,他不打算将所有的AOP需求全部囊括在内,而是要以有限的20%的AOP支持,来满足80%的AOP需求。如果Spring AOP无法满足需求,可以求助于AspectJ,Spring AOP对AspectJ也提供了很好的集成。

Spring AOP的AOL语言为Java。在此基础上,Spring AOP对AOP概念进行了适当的抽象和实现,使得每个AOP概念都可以落到实处,这些概念的抽象和实现,都以我们所熟悉的JAva语言的结构呈现在我们眼前。

Spring AOP实现机制

Spring AOP属于第二代AOP,代用动态代理机制和字节码生成技术实现。动态代理机制和字节码生成都是在运行期间为目标对象生成一个代理对象,而将横切逻辑织入到这个代理对象中,系统最终使用的是织入了横切逻辑的代理对象,而不是真正的目标对象。

下面为了理解这种差别以及最中国可以达到的想过,我们这有必要先从动态代理机制的根源–代理模式开始说起

设计模式之代理模式

说到代理,我们大家应该都不会陌生。类如房产中介就是一种代理,我们偶尔也会使用到网络代理,等等。代理处于访问者与被访问者之间,可以隔离这俩者之间的直接交互,访问者与代理打交道就好像在跟被访问者在打交道一样,因为代理通常几乎会全权拥有被代理者的职能。
在软件系统中,代理机制的实现有现成的设计模式支持,就叫代理模式。在代理模式中,通常涉及4中角色,如下图所示:
Xnip2019-07-14_16-51-26

  • ISubject:该接口是对被访问者或被访问资源的抽象。在严格的设计模式中,这样的抽象接口是必须的,但往宽了说,某些场景下不使用类似的统一抽象接口也是可以的。
  • SubjectImpl:被访问者或被访问资源的具体实现类。
  • SubjectProxy:被访问者或被访问资源的代理实现类,该类持有一个ISubject接口的具体事例。
  • Client:代表访问者的抽象角色,Client将会访问ISubject类型的对象或者资源。在这个场景中,Client将无法请求具体的SubjectImpl,而是必须通过ISubject资源的访问代理类SubjectProxy进行。

在将请求转发给被代理对象SubjectImpl之前或者之后,都可以根据情况插入其他处理逻辑,比如在转发之前记录方法执行开始时间,在转发之后记录结束时间,这样就能够对SubjectImpl的request执行的时间进行检测。当然也可以做其他的事情。甚至也可以不做请求转发。具体的调用关系如下图所示
Xnip2019-07-14_16-57-55
代理对象SubjectProxy就像是SubjectImpl的影子,只不过这个影子通常拥有更多功能。如果SubjectImpl是系统中的Joinpoint所在的对象,即目标对象,那么就可以为这个目标对象创建一个代理对象,然后将横切逻辑添加到这个代理对象中。当系统使用这个代理对象运行的时候,原有逻辑实现和横切逻辑完全融合到一个系统中。
Spring AOP本质上就是采用这种代理机制实现的。但是,具体实现细节有所不同。
假设对系统中所有的request方法进行拦截,在每天午夜0点到次日6点之间,request调用不被接受,那么我们应该WieSubjectImpl提供一个ServiceControlSubjectProxy,已添加这样横切逻辑。这样就有了下面这份代码清单:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class ServiceControlSubjectProxy implements ISubject{
private ISubject subject;

public ServiceControlSubjectProxy(ISubject s){
this.subject = s;
}
public String request(){
TimeOfDay startTime = new TimeOfDay(0,0,0);
TimeOfDay endTime = new TimeOfDay(5,59,59);
TImeOfDay currentTime = new TimeOfDay();
if(currentTime.isAfter(startTime) &&
currentTime.isBefore(endTime)){
return null;
}
String originResult = subject.request;
return originResut;
}
}

有了这个代理类之后,就可以像使用SUbjectImpl一样使用这个类。
但是系统中可不一定就ISubject的实现类有request方法,IRequestable接口以及相应实现类可能也有request方法,他们也是我们需要横切的关注点。IRequestable及其实现类的代理如同上面ServiceControlSubjectProxy的代码差不多,只不过将接口换成了Irequest。
这里就会出现一个问题,虽然JoinPoint相同(request方法的执行),但是对应的目标对象类型是不一样的。针对不一样的目标对象类型,我们要为器单独实现一个代理对象。而实际上,这些代理对象所要添加的横切逻辑是一样的。当系统中存在成百上千的复合Pointcut匹配条件的目标对象时,我们就要为这成百上千的目标单独创建成百上千的代理对象,。
这种为对应的目标对象创建静态代理的方法,原理上是可行的,但具体应用上存在问题,所以要找到一种办法,来解决这种困境。

动态代理

JDK1.3之后引入了一种称之为动态代理的机制。使用该机制,我们可以为指定的接口在系统运行期间动态地生成代理对象,从而帮助我们走出最初使用静态代理实现AOP的窘境。
动态代理机制的实现主要是有一个类和一个接口组成,即java.lang.reflect.Proxy和java..lang.reflect.InvocationHandler接口。下面,我们看一下如何使用动态代理来实现之前的request访问时间控制功能。虽然要为ISubject和IRequestable俩种类型提供代理对象,但因为代理对象要添加的横切逻辑是一样的,所以,我们只需要实现一个InvocationHAndler就可以了,实现代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class ReuqestCtrlInvocationHandler implements InvocationHandler{
private Object target;
public ReuqestCtrlInvocationHandler(Object target){
this.target = target;
}

public Object invoke(Object proxy,Method method,Object[] args)
throws Throwable{
if(method.getName().equals("request")){
TimeOfDay startTime = new TimeOfDay(0,0,0);
TimeOfDay endTime = new TimeOfDay(5,59,59);
TImeOfDay currentTime = new TimeOfDay();
if(currentTime.isAfter(startTime) &&
currentTime.isBefore(endTime)){
return null;
}
String originResult = subject.request;
return originResut;
}
}
}

然后,我们就可以使用Proxy类,根据RequestCtrlinvocationHandler的逻辑,为ISubject和IRequestable俩种类型生成相应的代理对象实例。代码如下:

1
2
3
4
ISubject subject = (ISubject)Proxy.newProxyInstance(ProxyRunner.class.getClassLoader(),new Class[]{ISubject.class},
new ReuqestCtrlInvocationHandler());

IRequstable requestable .....大体上与上面类似

即使还有更多的目标对象类型,只要他们依然织入的横切逻辑相同,用ReuqestCtrlInvocationHandler一个类并通过Proxy为他们生成响应的动态代理实例就可以满足要求。当Proxy动态生成的代理对象上相应的接口方法被调用时,对应的InvocationHandler就会拦截响应的方法调用,并进行逻辑处理。

InvocationHandler就是我们实现横切逻辑的地方,它是横切逻辑的载体,作用跟Advice是一样的。所以,在使用动态代理机制实现AOP的过程中,我们可以在InnvocationHandler的基础上细化程序结构,并根据Advice的类型,分化出对应不同Advice类型的程序结构。
动态代理虽然好,但不能满足所有的需求。因为动态代理机制只能对实现了相应Interface的类使用。因此对于没有实现任何Interface的目标对象,我们需要寻找其他方式为其动态的生成代理对象。
默认情况下,如果Spring AOP发现目标对象实现了相应Interface,则采用动态代理机制为其生成代理对象实例。而如果目标对象没有实现任何Interface,Spring AOP会尝试使用一个称谓CGLIB的开源的动态字节码生成类库,为目标对象生成动态的代理对象实例。

动态字节码生成

使用动态字节码生成技术扩展对象行为的原理是,我们可以对目标对象进行继承扩展,为其生成相应的子类,而子类可以通过覆写来扩展父类的行为,只要将横切逻辑的实现放到子类中,然后让系统使用扩展后的目标对象的子类,就可以达到与代理模式相同的效果。
但是,使用继承的方式来扩展对象定义,也不能像静态代理模式那样,为每个不同类型目标对象都单独创建相应的扩展子类。所以,我们要借助于CGLIB这样的同台字节码生成库,在系统运行期间动态地为目标对象生成相应的扩展子类。
为了演示CGLIB的使用以及最终可以达到的效果,我们定义的目标类如下所示:

1
2
3
4
5
public class Requestable{
public void request(){
System.out.println("rg in Requestable without inplement any interface")
}
}

CGLIB可以对实现了某种接口的类,或者没有实现任何接口的类进行扩展。但我们已经说过,可以使用动态dialing机制来扩展实现了某种接口的饿目标类,所以,这里主要演示没有实现任何接口的目标类是如何使用CGLIB来进行扩展。

要对Requestable类进行扩展,首先要实现一个net.sf.cglib.proxy.Callback。不过更多的时候,我们会直接使用net.sf.cglin.proxy.MethodInterceptor接口(MethodInterceptor扩展了Callback接口)。代码清单8-6给出了针对我们的Requestable所提供的Callback实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class RequestCtrlCallback implements MethodInterceptor{
public object intercept(Object object,Method method,Object[] args,MethodProxy proxy) throws Throwable{
if(method.getName().equals("request")){
TimeOfDay startTime = new TimeOfDay(0,0,0);
TimeOfDay endTime = new TimeOfDay(5,59,59);
TImeOfDay currentTime = new TimeOfDay();
if(currentTime.isAfter(startTime) &&
currentTime.isBefore(endTime)){
return null;
}
String originResult = proxy.invokeSuper(object,args);
return originResut;
}
}
}

这样,RequestCtrlCallback就实现了对Request方法请求进行访问扩展的逻辑。想在我们要通过CGLIB的Enchaner为目标对象动态地生成一个子类,并将RequestCtrlCallback中的横切逻辑附加到该子类中,代码如下所示:

1
2
3
4
5
Enchacner enchacner = new Enchacner();
enchancer.setSuperClass(Requestable.class);
enchancer.setCallback(new RequestCtrlCallback() );
Requestable proxy = (Requestable)enchancer.create();
proxy.request();

通过为enchancer指定需要生成的子类对应的父类,以及Callback实现,enhancer最终为我们生成了需要的代理对象示例。使用CGLIB对类进行扩展的唯一限制就是无法对final方法进行覆写。

本文翻译自Zuul 2 : The Netflix Journey to Asynchronous, Non-Blocking Systems

前面在学习Spring Cloud中,了解到zuul这个应用网关,但是由于当时看到的是zuul1,使用的Servlet方式实现,所以整体性能很低。不过Netflix实际上升级了zuul,使用了netty来改造网关,也就是现在网关底层使用的是异步非阻塞的架构。下面我们就一起来拜读下他们的改造路程。
翻译如下:

我们最近对我们的云网关Zuul进行了重大架构改造。Zuul2的功能与其前任相同 - 作为Netflix服务器基础设施的前门,处理来自全球所有Netflix用户的流量。它还可以路由请求,支持开发人员的测试和调试,深入了解我们的整体服务运行状况,保护Netflix免受攻击,并在AWS区域出现问题时将流量引导至其他云区域。 Zuul 2和原始版本之间的主要架构差异是Zuul2使用Netty在异步和非阻塞框架上运行。在过去几个月投入生产之后,主要优势(我们在开始这项工作时所期望的一个优势)是它为设备和Web浏览器提供了在Netflix规模上持久连接回Netflix的能力。拥有超过8300万会员,每个会员都有多个连接设备,这是一个巨大的挑战。通过与我们的云基础架构保持持久连接,我们可以实现许多有趣的产品功能和创新,减少整体设备请求,提高设备性能,更好地理解和调试客户体验。

我们还希望Zuul2在延迟,吞吐量和成本方面提供弹性优势和性能改进。但正如您将在本文中了解到的那样,我们的愿望与结果不同。

阅读全文 »

应用开发完成后,会产生大量的Class文件,这些文件都是静态的,最终都需要加载到虚拟机中之后才能被运行和使用。而虚拟机如何加载这些Class文件,Class文件中的信息进入到虚拟机后会发生什么变化,这些都是本文将要了解的内容。 Java虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这个过程被称作虚拟机的类加载机制。

具体的实现就是ClassLoader,翻译过来就是类加载器,普通的java开发者其实用到的不多,但对于某些框架开发者来说却非常常见。理解ClassLoader的加载机制,也有利于我们编写出更高效的代码。ClassLoader的具体作用就是将class文件加载到jvm虚拟机中去,程序就可以正确运行了。但是,jvm启动的时候,并不会一次性加载所有的class文件,而是根据需要去动态加载。想想也是的,一次性加载那么多jar包那么多class,那内存不崩溃。本文的目的也是学习ClassLoader这种加载机制。

因此本篇文章将会详细分析类加载整个过程,以及java代码中已存在的各种类加载器。这样我们便可以做到不仅仅在概念上,也可以能够简单的运用。

阅读全文 »

简介

前面提过,如果Future集合用于存放执行结果,执行任务,最后遍历Future集合获取结果;因为Future.get()方法是阻塞的,因此不能及时获取已完成任务的执行结果。所以JUC提供了一个ExecutorService来获取结果,使得任务的提交和结果的获取都能做到异步,从而实现真正的异步。

阅读全文 »

前言

学习了一段时间JUC,也就是java对并发做的一些工具类,但是虽然学习完了,但是还是觉得没有在脑子中形成一个系统,所以这里对我学习的并发包做一个整理和总结,一方面是对自己学习的总结,另一方面也是帮助自己以后能够快速的进行查阅。

JSR 166及J.U.C

什么是JSR

JSR,全称 Java Specification Requests, 即Java规范提案, 主要是用于向JCP(Java Community Process)提出新增标准化技术规范的正式请求。每次JAVA版本更新都会有对应的JSR更新,比如在Java 8版本中,其新特性Lambda表达式对应的是JSR 335,新的日期和时间API对应的是JSR 310。

什么是JSR 166

当然,本文的关注点仅仅是JSR 166,它是一个关于Java并发编程的规范提案,在JDK中,该规范由java.util.concurrent包实现,是在JDK 5.0的时候被引入的;

另外JDK6引入Deques、Navigable collections,对应的是JSR 166x,JDK7引入fork-join框架,用于并行执行任务,对应的是JSR 166y。

什么是J.U.C

即java.util.concurrent的缩写,该包参考自EDU.oswego.cs.dl.util.concurrent,是JSR 166标准规范的一个实现;

膜拜

那么,JSR 166以及J.U.C包的作者是谁呢,没错,就是Doug Lea大神,挺牛逼的,大神级别任务,贴张照片膜拜下。
879896-20160624160226063-830249727

阅读全文 »

前面一直都是在介绍概念,正所谓实践才是检验真理的唯一标准,这里我们就来动手实现一个简单的类加载器,这里实现的类加载器很简单,但足够理解前面的所说的内容。

阅读全文 »

本篇文章主要是通过介绍Class.forNameClassLoader.loadClass这俩个方法,来引入一个重要的概念,系统字典(SystemDictionary),这里会有一些名词,我在前面的一篇文章系统,当前,线程上下文类加载概念介绍?中已经详细介绍过,具体的可以看前面的文章。

阅读全文 »

概念区分

在Java进行类加载时,一般会有多个ClassLoader可以使用,包括当前类加载器,指定类加载器,上下文类加载器三个,可以用多种方式进行类型加载,以下面这段代码来对这个三个概念进行说明:

阅读全文 »

java.util.concurrency.atomic.LongAdder是Java8新增的一个类,提供了原子累计值的方法。根据文档的描述其性能要优于AtomicLong,下面是一个简单的测试对比demo(平台:MBP):

阅读全文 »

本文翻译自:Zero Copy I: User-Mode Perspective
本文解释了Linux的零拷贝作用是什么,为什么它非常有用和在哪些地方可以使用该功能。

到目前为止,几乎每个人都听说过在Linux下的所谓的零拷贝功能,但我经常遇到对这个主题没有完全理解的人。 正因为如此,我决定写一些文章,深入探讨这件事,希望揭开这个有用的功能。 在本文中,我们从用户应用程序的角度看待零拷贝,因此故意省略了内核级别的详细信息。

阅读全文 »