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 {
3
4 void before(MethodInvocation invocation);
5 }
6
1 public class BeforeServiceImpl implements BeforeService {
2
3 @Override
4 public void before(MethodInvocation invocation) {
5 System.out.println("before method "+invocation.getMethod().getName());
6 }
7 }
8
然后有一个切面,这个切面依赖前置服务,然后输出一条方法调用结束语句。
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<?>>):返回值为指定类型的方法
通常只需要使用上面的方法或者组合方法就能满足我们的需求。
相关推荐
Spring AOP (面向切面编程框架): Spring框架内建了支持动态代理的功能,使用@AspectJ注解可以轻松地创建AOP代理。Spring AOP既支持JDK动态代理也支持CGLIB,会根据目标类是否实现了接口选择合适的底层技术。 Guice ...
- Guice支持AOP(面向切面编程),可以通过编写拦截器(Interceptors)来添加横切关注点。 - 支持自定义绑定策略,如绑定到特定类实例、单例、多例等。 7. **实战应用**: - 在Guice-3.0中,开发者可以通过创建...
而Spring是Java领域广泛使用的全面的企业级应用框架,它同样提供了DI功能,还有AOP(面向切面编程)、事务管理、数据访问等多种服务。 描述中的"uptodate-maven-plugin.zip,强制使用最新的依赖项/父项"指的是一个...
Guice还支持AOP(面向切面编程)特性,通过`@Interceptor`注解和`@Aspect`注解,可以方便地添加日志、事务管理等横切关注点。此外,Guice与其它Google库如Guava的集成也非常紧密,能够更好地支持复杂的应用场景。 ...
4. **事务管理**:Guice支持AOP(面向切面编程),可以方便地进行事务边界定义,确保数据一致性。 5. **查询语句执行**:使用PreparedStatement或Statement执行SQL查询,处理结果集,如ResultSet。 6. **异常处理**...
AOP(Aspect Oriented Programming,面向切面编程)是一种编程范式,主要目的是解决软件系统中的横切关注点,如日志、事务管理、权限控制等。这些关注点通常会散布在应用程序的多个类和方法中,使得代码复用困难且...
相比之下,Spring 框架是一个更为全面的 Java 应用开发框架,除了 DI,还包括 AOP(面向切面编程)、数据访问、Web 框架等功能。Spring 的 DI 主要是通过 XML 配置文件或注解来实现,虽然也可以使用注解,但其核心...
4. `aopalliance.jar`:AOP Alliance是一个接口集,提供了面向切面编程(Aspect-Oriented Programming,AOP)的通用API,Guice可以与之配合,实现更灵活的拦截器和切面逻辑。 5. `guice-spring-1.0.jar`:这个扩展...
- **全面的解决方案**:Spring不仅包含DI,还提供了AOP(面向切面编程)、事务管理、数据访问等功能,形成了一套完整的应用框架。 - **XML或注解配置**:Spring支持XML配置和注解配置,为不同喜好和需求的开发者...
Spring 3.1版本引入了AOP(面向切面编程)功能,它允许开发者通过定义切面来分离关注点,提高代码的可维护性和模块化。AOP在Spring中主要用来实现如日志记录、事务管理、性能监控等横切关注点。 首先,我们要了解...
- **面向切面编程**(Aspect-Oriented Programming, AOP):通过简单的配置即可实现切面逻辑的插入,简化代码结构。 #### 五、Guice与其他框架的比较 - **与Spring的比较**: - **易用性**:Guice的配置更为简洁...
6. **AOP(面向切面编程)支持**:虽然Guice不是专门的AOP框架,但可以通过注解和拦截器(Interceptors)实现类似的功能,例如日志记录、事务管理等。 7. **子Injector(Sub-Injector)**:Guice 3.0引入了子...
7. **AOP(面向切面编程)支持:** 虽然Guice本身不直接支持AOP,但可以与AspectJ等库结合使用,实现面向切面的编程,比如日志记录、事务管理等。 8. **绑定到具体类型:** Guice允许你将接口绑定到具体的类,也...
在不与 AOP(面向切面编程)框架整合的情况下,这些注解的功能可能无法充分发挥,因为它们需要在运行时被拦截并执行相应的权限检查。 Guice 作为一个轻量级的 AOP 框架,提供了模块化和动态绑定的能力,能够无缝地...
7. **AOP(面向切面编程)**:虽然Guice本身不直接支持AOP,但可以通过与其他库(如AspectJ)结合,实现拦截器(Interceptors)和切面。 8. **模块继承与组合**:Guice模块可以继承其他模块,也可以通过`install()`...
同时,Guice提供的AOP(面向切面编程)特性,如预后拦截器,也能帮助我们更好地控制和监控代码执行。 在给出的压缩包文件"struts2_guice"中,可能包含了示例项目的源代码,包括Struts2配置文件、Guice模块定义、...
"licenceCheck:Guice AOP示例项目" 这个标题表明这是一个关于许可证检查的项目,而且它使用了Google的Guice框架的面向切面编程(AOP)特性。Guice是Java中一个轻量级的依赖注入框架,而AOP则是一种编程范式,用于...
7. **AOP支持**:Guice与AspectJ结合,可以实现面向切面编程,如方法拦截和事务管理。 在实际应用中,Guice常用于构建复杂的应用框架或服务。例如,`AromaRI`可能是一个使用Guice实现的后台服务,通过Guice管理其...
Guice还提供了AOP(面向切面编程)特性,如`@Singleton`注解可以确保一个类只有一个实例。 Guice的优势在于它的简洁性和灵活性,可以适应各种规模的项目。通过模块化的配置,我们可以轻松地组织和管理代码中的依赖...
AOP(Aspect Oriented Programming,面向切面编程)是一种编程范式,旨在提供一种方法来组织和模块化系统中的交叉关注点,如日志、事务管理、安全性等。这些关注点通常分散在应用程序的各个部分,导致代码重复和难以...