Guice框架-AOP(@面向切面编程)

2.AOP 面向切面编程

2.1 AOP入门

在前面的章节主要讲Guice的依赖注入,有了依赖注入的基础后我们再来看Guice的AOP。我们先从一个例子入手,深入浅出的去理解Guice的AOP的原理和实现。

首先我们定义服务Service,这个服务有一个简单的方法sayHello,当然了我们有一个服务的默认实现ServiceImpl,然后使用@ImplementedBy将服务和默认实现关联起来,同时将服务的实现标注为单例模式。

1 @ImplementedBy(ServiceImpl.class)

2 public interface Service {

3     void sayHello();

4 }

 

在服务的实现ServiceImpl中,我们sayHello方法就是输出一行信息,这行信息包含服务的类名,hashCode以及方法名称和执行的时间。

 

 1 @Singleton

 2 public class ServiceImpl implements Service {

 3 

 4     @Override

 5     @Named("log")

 6     public void sayHello() {

 7         System.out.println(String.format("[%s#%d] execute %s at %d", this.getClass().getSimpleName(),hashCode(),"sayHello",System.nanoTime()));

 8     }

 9 

10 }

11 

接下来定义一个AOP的实现。在Aopalliance中(大家都认可的AOP联盟)实现我们的方法拦截器。这个拦截器 LoggerMethodInterceptor 也没有做什么特别的事情,只是记录些执行的时间,当然了由于执行时间比较短我们用纳秒来描述(尽管不是那么精确)。

在MethodInvocation中我们一定要调用proceed()方法,这样我们的服务才能被执行。当然了如果为了做某些控制我们就能决定是否调用服务代码了。

 1 import static java.lang.System.out;

 3 import org.aopalliance.intercept.MethodInterceptor;

 4 import org.aopalliance.intercept.MethodInvocation;

 5 

 6 public class LoggerMethodInterceptor implements MethodInterceptor {

 7 

 8     @Override

 9     public Object invoke(MethodInvocation invocation) throws Throwable {

10         String methodName = invocation.getMethod().getName();

11         long startTime=System.nanoTime();

12         out.println(String.format("before method[%s] at %s", methodName, startTime));

13         Object ret = null;

14         try {

15             ret = invocation.proceed();

16         } finally {

17             long endTime=System.nanoTime();

18             out.println(String.format(" after method[%s] at %s, cost(ns):%d", methodName, endTime,(endTime-startTime)));

19         }

20         return ret;

21     }

22 }

23 

最后才是我们的客户端程序,注意在这里我们需要绑定一个拦截器,这个拦截器匹配任何类的带有log注解的方法。所以这就是为什么我们服务的实现方法需要用log标注的原因了。

 

 

 1 public class AopDemo {

 2     @Inject

 3     private Service service;

 4 

 5     public static void main(String[] args) {

 6         Injector inj = Guice.createInjector(new Module() {

 7             @Override

 8             public void configure(Binder binder) {

 9                 binder.bindInterceptor(Matchers.any(),//

10                         Matchers.annotatedWith(Names.named("log")),//

11                         new LoggerMethodInterceptor());

12             }

13         });

14         inj.getInstance(AopDemo.class).service.sayHello();

15         inj.getInstance(AopDemo.class).service.sayHello();

16         inj.getInstance(AopDemo.class).service.sayHello();

17     }

18 }

19 

我们的程序输出了我们期望的结果。

 

before method[sayHello] at 7811306067456

[ServiceImpl$$EnhancerByGuice$$96717882#33353934] execute sayHello at 7811321912287

after method[sayHello] at 7811322140825, cost(ns):16073369

before method[sayHello] at 7811322315064

[ServiceImpl$$EnhancerByGuice$$96717882#33353934] execute sayHello at 7811322425280

after method[sayHello] at 7811322561835, cost(ns):246771

before method[sayHello] at 7811322710141

[ServiceImpl$$EnhancerByGuice$$96717882#33353934] execute sayHello at 7811322817521

after method[sayHello] at 7811322952455, cost(ns):242314

 

 

关于此结果有几点说明。

 

(1)由于使用了AOP我们的服务得到的不再是我们写的服务实现类了,而是一个继承的子类,这个子类应该是在内存中完成的。

 

(2)除了第一次调用比较耗时外(可能guice内部做了比较多的处理),其它调用事件为0毫秒(我们的服务本身也没做什么事)。

 

(3)确实完成了我们期待的AOP功能。

 

我们的例子暂且说到这里,来看看AOP的相关概念。

 

2.2 AOP相关概念

 

老实说AOP有一套完整的体系,光是概念就有一大堆,而且都不容易理解。这里我们结合例子和一些场景来大致了解下这些概念。

 

通知(Advice)

 

所谓通知就是我们切面需要完成的功能。比如2.1例子中通知就是记录方式执行的耗时,这个功能我们就称之为一个通知。

 

比 如说在很多系统中我们都会将操作者的操作过程记录下来,但是这个记录过程又不想对服务侵入太多,这样就可以使用AOP来完成,而我们记录日志的这个功能就 是一个通知。通知除了描述切面要完成的工作外还需要描述何时执行这个工作,比如是在方法的之前、之后、之前和之后还是只在有异常抛出时。

 

连接点(Joinpoint)

 

连 接点描述的是我们的通知在程序执行中的时机,这个时机可以用一个“点”来描述,也就是瞬态。通常我们这个瞬态有以下几种:方法运行前,方法运行后,抛出异 常时或者读取修改一个属性等等。总是我们的通知(功能)就是插入这些点来完成我们额外的功能或者控制我们的执行流程。比如说2.1中的例子,我们的通知 (时间消耗)不仅在方法执行前记录执行时间,在方法的执行后也输出了时间的消耗,那么我们的连接点就有两个,一个是在方法运行前,还有一个是在方法运行 后。

 

切入点(Pointcut)

 

切 入点描述的是通知的执行范围。如果通知描述的是“什么时候”做“什么事”,连接点描述有哪些“时候”,那么切入点可以理解为“什么地方”。比如在2.1例 子中我们切入点是所有Guice容器管理的服务的带有@Named(“log”)注解的方法。这样我们的通知就限制在这些地方,这些地方就是所谓的切入 点。

 

切面(Aspect)

 

切面就是通知和切入点的结合。就是说切面包括通知和切入点两部分,由此可见我们所说的切面就是通知和切入点。通俗的讲就是在什么时候在什么地方做什么事。

 

引入(Introduction)

 

引入是指允许我们向现有的类添加新的方法和属性。个人觉得这个特性尽管很强大,但是大部分情况下没有多大作用,因为如果一个类需要切面来增加新的方法或者属性的话那么我们可以有很多更优美的方式绕过此问题,而是在绕不过的时候可能就不是很在乎这个功能了。

 

目标(Target)

 

目标是被通知的对象,比如我们2.1例子中的ServiceImpl 对象。

 

代理(Proxy)

 

代理是目标对象被通知引用后创建出来新的对象。比如在2.1例子中我们拿到的Service对象都不是ServiceImpl本身,而是其包装的子类ServiceImpl$$EnhancerByGuice$$96717882。

 

织入(Weaving)

 

所谓织入就是把切面应用到目标对象来创建新的代理对象的过程。通常情况下我们有几种实际来完成织入过程:

 

编译时:就是在Java源文件编程成class时完成织入过程。AspectJ就存在一个编译器,运行在编译时将切面的字节码编译到目标字节码中。

 

类加载时:切面在目标类加载到JVM虚拟机中时织入。由于是在类装载过程发生的,因此就需要一个特殊的类装载器(ClassLoader),AspectJ就支持这种特性。

 

运行时:切面在目标类的某个运行时刻被织入。一般情况下AOP的容器会建立一个新的代理对象来完成目标对象的功能。事实上在2.1例子中Guice就是使用的此方式。

 

Guice支持AOP的条件是:

 

类必须是public或者package (default) 

类不能是final类型的 

方法必须是public,package或者protected 

方法不能使final类型的 

实例必须通过Guice的@Inject注入或者有一个无参数的构造函数 

2.3 切面注入依赖

 

如果一个切面(拦截器)也需要注入一些依赖怎么办?没关系,Guice允许在关联切面之前将切面的依赖都注入。比如看下面的例子。

 

我们有一个前置服务,就是将所有调用的方法名称输出。

 

 

1 @ImplementedBy(BeforeServiceImpl.class)

2 public interface BeforeService {

4     void before(MethodInvocation invocation);

5 }

1 public class BeforeServiceImpl implements BeforeService {

3     @Override

4     public void before(MethodInvocation invocation) {

5         System.out.println("before method "+invocation.getMethod().getName());

6     }

7 }

然后有一个切面,这个切面依赖前置服务,然后输出一条方法调用结束语句。

 

 1 public class AfterMethodInterceptor implements MethodInterceptor {

 2    @Inject

 3     private BeforeService beforeService;

 4     @Override

 5     public Object invoke(MethodInvocation invocation) throws Throwable {

 6         beforeService.before(invocation);

 7         Object ret = null;

 8         try {

 9             ret = invocation.proceed();

10         } finally {

11             System.out.println("after "+invocation.getMethod().getName());

12         }

13         return ret;

14     }

15 }

 

 

在AopDemo2中演示了如何注入切面的依赖。在第9行,AfterMethodInterceptor 请求Guice注入其依赖。

 

 1 public class AopDemo2 {

 2     @Inject

 3     private Service service;

 4     public static void main(String[] args) {

 5         Injector inj = Guice.createInjector(new Module() {

 6             @Override

 7             public void configure(Binder binder) {

 8                 AfterMethodInterceptor after= new AfterMethodInterceptor();

 9                 binder.requestInjection(after);

10                 binder.bindInterceptor(Matchers.any(),//

11                         Matchers.annotatedWith(Names.named("log")),//

12                         after);

13             }

14         });

15         AopDemo2 demo=inj.getInstance(AopDemo2.class);

16         demo.service.sayHello();

17     }

18 }

 

 

尽管切面允许注入其依赖,但是这里需要注意的是,如果切面依赖仍然走切面的话那么程序就陷入了死循环,很久就会堆溢出。

 

2.4 Matcher

 

Binder绑定一个切面的API是

 

com.google.inject.Binder.bindInterceptor(Matcher<? super Class<?>>, Matcher<? super Method>, MethodInterceptor...)

 

第一个参数是匹配类,第二个参数是匹配方法,第三个数组参数是方法拦截器。也就是说目前为止Guice只能拦截到方法,然后才做一些切面工作。

 

对于Matcher有如下API:

 

com.google.inject.matcher.Matcher.matches(T) 

com.google.inject.matcher.Matcher.and(Matcher<? super T>) 

com.google.inject.matcher.Matcher.or(Matcher<? super T>) 

其中第2、3个方法我没有发现有什么用,好像Guice不适用它们,目前没有整明白。

 

对于第一个方法,如果是匹配Class那么这里T就是一个Class<?>的类型,如果是匹配Method就是一个Method对象。不好理解吧。看一个例子。

 

 1 public class ServiceClassMatcher implements Matcher<Class<?>>{

 2     @Override

 3     public Matcher<Class<?>> and(Matcher<? super Class<?>> other) {

 4         return null;

 5     }

 6     @Override

 7     public boolean matches(Class<?> t) {

 8         return t==ServiceImpl.class;

 9     }

10     @Override

11     public Matcher<Class<?>> or(Matcher<? super Class<?>> other) {

12         return null;

13     }

14 }

 

 

在前面的例子中我们是使用的Matchers.any()对象匹配所有类而通过标注来识别方法,这里可以只匹配ServiceImpl类来控制服务运行流程。

 

事实上Guice里面有一个Matcher的抽象类com.google.inject.matcher.AbstractMatcher<T>,我们只需要覆盖其中的matches方法即可。

 

大多数情况下我们只需要使用Matchers提供的默认类即可。Matchers中有如下API:

 

com.google.inject.matcher.Matchers.any():任意类或者方法 

com.google.inject.matcher.Matchers.not(Matcher<? super T>):不满足此条件的类或者方法 

com.google.inject.matcher.Matchers.annotatedWith(Class<? extends Annotation>):带有此注解的类或者方法 

com.google.inject.matcher.Matchers.annotatedWith(Annotation):带有此注解的类或者方法 

com.google.inject.matcher.Matchers.subclassesOf(Class<?>):匹配此类的子类型(包括本身类型) 

com.google.inject.matcher.Matchers.only(Object):与指定类型相等的类或者方法(这里是指equals方法返回true) 

com.google.inject.matcher.Matchers.identicalTo(Object):与指定类型相同的类或者方法(这里是指同一个对象) 

com.google.inject.matcher.Matchers.inPackage(Package):包相同的类 

com.google.inject.matcher.Matchers.inSubpackage(String):子包中的类(包括此包) 

com.google.inject.matcher.Matchers.returns(Matcher<? super Class<?>>):返回值为指定类型的方法 

通常只需要使用上面的方法或者组合方法就能满足我们的需求。

时间: 2024-11-03 23:00:44

Guice框架-AOP(@面向切面编程)的相关文章

Javascript aop(面向切面编程)之around(环绕)分析

  这篇文章主要介绍了Javascript aop(面向切面编程)之around(环绕) ,需要的朋友可以参考下 Aop又叫面向切面编程,其中"通知"是切面的具体实现,分为before(前置通知).after(后置通知).around(环绕通知),用过spring的同学肯定对它非常熟悉,而在js中,AOP是一个被严重忽视的技术点.但是利用aop可以有效的改善js代码逻辑,比如前端框架dojo和yui3中AOP则被提升至自定义事件的一种内在机制,在源码中随处可见.得益于这种抽象使得doj

yui3的AOP(面向切面编程)和OOP(面向对象编程)

  这篇文章主要介绍了yui3的AOP(面向切面编程)和OOP(面向对象编程),需要的朋友可以参考下 首先请把手放胸前成沉思状:我上了生活,还是被生活上了自己? 没想出答案把,恩,可以读下文了.从语义角度讲,同一事物的不同表述可以反映人的主观视角的不同,从哲学角度将,世界观影响方法论,我们看事物的角度不同,有时会得出截然相悖的结论,从而会影响我们的做事方式和行为准则,现实生活如此,在丰富多彩的编程语言中更是如此,编程模式充满了对现实世界的各种模拟,包括是面向过程,面向对象,还有面向切面.我们大概

Java实现AOP面向切面编程的实例教程_java

介绍 众所周知,AOP(面向切面编程)是Spring框架的特色功能之一.通过设置横切关注点(cross cutting concerns),AOP提供了极高的扩展性.那AOP在Spring中是怎样运作的呢?当你只能使用core java,却需要AOP技术时,这个问题的解答变得极为关键.不仅如此,在高级技术岗位的面试中,此类问题也常作为考题出现.这不,我的朋友最近参加了一个面试,就被问到了这样一个棘手的问题--如何在不使用Spring及相关库,只用core Java的条件下实现AOP.因此,我将在

Javascript aop(面向切面编程)之around(环绕)

Aop又叫面向切面编程,其中"通知"是切面的具体实现,分为before(前置通知).after(后置通知).around(环绕通知),用过spring的同学肯定对它非常熟悉,而在js中,AOP是一个被严重忽视的技术点.但是利用aop可以有效的改善js代码逻辑,比如前端框架dojo和yui3中AOP则被提升至自定义事件的一种内在机制,在源码中随处可见.得益于这种抽象使得dojo的自定义事件异常强大和灵活.dojo中aop的实现在dojo/aspect模块中,主要有三个方法:before.

yui3的AOP(面向切面编程)和OOP(面向对象编程)_YUI.Ext相关

首先请把手放胸前成沉思状:我上了生活,还是被生活上了自己? 没想出答案把,恩,可以读下文了.从语义角度讲,同一事物的不同表述可以反映人的主观视角的不同,从哲学角度将,世界观影响方法论,我们看事物的角度不同,有时会得出截然相悖的结论,从而会影响我们的做事方式和行为准则,现实生活如此,在丰富多彩的编程语言中更是如此,编程模式充满了对现实世界的各种模拟,包括是面向过程,面向对象,还有面向切面.我们大概已经非常熟悉面向过程和面向对象,切面的英文是Aspects(有时译作方面,我感觉用切面更能贴切的表达A

Javascript aop(面向切面编程)之around(环绕)分析_javascript技巧

Aop又叫面向切面编程,其中"通知"是切面的具体实现,分为before(前置通知).after(后置通知).around(环绕通知),用过spring的同学肯定对它非常熟悉,而在js中,AOP是一个被严重忽视的技术点.但是利用aop可以有效的改善js代码逻辑,比如前端框架dojo和yui3中AOP则被提升至自定义事件的一种内在机制,在源码中随处可见.得益于这种抽象使得dojo的自定义事件异常强大和灵活.dojo中aop的实现在dojo/aspect模块中,主要有三个方法:before.

MVC AOP面向切面编程简单介绍及实例_java

MVC AOP面向切面编程 AOP这个词相信大家都没有接触太多过,但是实际上你们已经有所接触了,就在设计模式中.AOP所用的思想其实和设计模式是一样的,即在不修改原代码的情况下统一增加或者修改功能.还有,AOP大多用在spring里面,但是本文所写的只是在MVC中的应用,要注意. 一.简介         所谓AOP(Aspect Oriented Programming的缩写)意为面向切面的编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术.AOP是OOP的延续,是软件开发中

在.NET项目中使用PostSharp,实现AOP面向切面编程处理

PostSharp是一种Aspect Oriented Programming 面向切面(或面向方面)的组件框架,适用在.NET开发中,本篇主要介绍Postsharp在.NET开发中的相关知识,以及一些如日志.缓存.事务处理.异常处理等常用的切面处理操作. AOP(Aspect-Oriented Programming)是一种将函数的辅助性功能与业务逻辑相分离的编程泛型(programming paradigm),其目的是将横切关注点(cross-cutting concerns)分离出来,使得

spring aop面向切面编程:如何来做一个强大的日志记录功能

这个东西怎么做:spring aop 面向切面编程 如何来做一个强大的日志记录功能模板; 昨天经理把这个任务交给我,让我为公司现在的项目加上一个详细的日志记录功能模板,对所有的操作,至少是增删改运作进行一个记录,其要记录操作者,以及执行的方法,IP,以及操作的方法的参数. 我以前做过类似的功能,不过是在filter里做的,通过filter来检查action请求,记录请求中的参数及action名字.但是今天公司这个是要求用spring aop来做,这样就可以在spring里对要进行的日志记录方法进