读书人

Google Guice 五 aop

发布时间: 2012-09-17 12:06:51 作者: rapoo

Google Guice 5 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;
?2?
?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的条件是:

读书人网 >编程

热点推荐