今天我要和大家分享的是 AOP(Aspect-Oriented Programming)这个东西,名字与 OOP 仅差一个字母,其实它是对 OOP 编程方式的一种补充,并非是取而代之。翻译过来就是“面向方面编程”,可我更倾向于翻译为“面向切面编程”。它听起有些的神秘,为什么呢?当你看完这篇文章的时候,就就知道,我们做的很重要的工作就是去写这个“切面” 。那么什么是“切面”呢?
没错!就是用一把刀来切一坨面。注意,相对于面而言,我们一定是横着来切它,这简称为“横切”。可以把一段代码想象成一坨面,同样也可以用一把刀来横切它,下面要做的就是如何去实现这把刀!
需要澄清的是,这个概念不是由 Rod Johnson(老罗)提出的。其实很早以前就有了,目前最知名最强大的 Java 开源项目就是 AspectJ 了,然而它的前身是 AspectWerkz(该项目已经在 2005 年停止更新),这才是 AOP 的老祖宗。老罗(一个头发秃得和我老爸有一拼的天才)写了一个叫做 Spring 框架,从此一炮走红,成为了 Spring 之父。他在自己的 IOC 的基础之上,又实现了一套 AOP 的框架,后来仿佛发现自己越来越走进深渊里,在不能自拔的时候,有人建议他还是集成 AspectJ 吧,他在万般无奈之下才接受了该建议。于是,我们现在用得最多的想必就是 Spring + AspectJ 这种 AOP 框架了。
那么 AOP 到底是什么?如何去使用它?本文将逐步带您进入 AOP 的世界,让您感受到前所未有的畅快!
不过在开始讲解 AOP 之前,我想有必要回忆一下这段代码:
1. 写死代码
先来一个接口:
1 public interface Greeting {
2
3 void sayHello(String name);
4 }
还有一个实现类:
01 public class GreetingImpl implements Greeting {
02
03 @Override
04 public void sayHello(String name) {
05 before();
06 System.out.println("Hello! " + name);
07 after();
08 }
09
10 private void before() {
11 System.out.println("Before");
12 }
13
14 private void after() {
15 System.out.println("After");
16 }
17 }
before() 与 after() 方法写死在 sayHello() 方法体中了,这样的代码的味道非常不好。如果哪位仁兄大量写了这样的代码,肯定要被你的架构师骂个够呛。
比如:我们要统计每个方法的执行时间,以对性能作出评估,那是不是要在每个方法的一头一尾都做点手脚呢?
再比如:我们要写一个 JDBC 程序,那是不是也要在方法的开头去连接数据库,方法的末尾去关闭数据库连接呢?
这样的代码只会把程序员累死,把架构师气死!
一定要想办法对上面的代码进行重构,首先给出三个解决方案:
2. 静态代理
最简单的解决方案就是使用静态代理模式了,我们单独为 GreetingImpl 这个类写一个代理类:
01 |
public class GreetingProxy implements Greeting { |
03 |
private GreetingImpl greetingImpl; |
05 |
public GreetingProxy(GreetingImpl greetingImpl) { |
06 |
this .greetingImpl = greetingImpl; |
10 |
public void sayHello(String name) { |
12 |
greetingImpl.sayHello(name); |
16 |
private void before() { |
17 |
System.out.println( "Before" ); |
20 |
private void after() { |
21 |
System.out.println( "After" ); |
就用这个 GreetingProxy 去代理 GreetingImpl,下面看看客户端如何来调用:
1 public class Client {
2
3 public static void main(String[] args) {
4 Greeting greetingProxy = new GreetingProxy(new GreetingImpl());
5 greetingProxy.sayHello("Jack");
6 }
7 }
这样写没错,但是有个问题,XxxProxy 这样的类会越来越多,如何才能将这些代理类尽可能减少呢?最好只有一个代理类。
这时我们就需要使用 JDK 提供的动态代理了。
3. JDK 动态代理
01 |
public class JDKDynamicProxy implements InvocationHandler { |
03 |
private Object target; |
05 |
public JDKDynamicProxy(Object target) { |
09 |
@SuppressWarnings ( "unchecked" ) |
10 |
public <T> T getProxy() { |
11 |
return (T) Proxy.newProxyInstance( |
12 |
target.getClass().getClassLoader(), |
13 |
target.getClass().getInterfaces(), |
19 |
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { |
21 |
Object result = method.invoke(target, args); |
26 |
private void before() { |
27 |
System.out.println( "Before" ); |
30 |
private void after() { |
31 |
System.out.println( "After" ); |
客户端是这样调用的:
3 |
public static void main(String[] args) { |
4 |
Greeting greeting = new JDKDynamicProxy( new GreetingImpl()).getProxy(); |
5 |
greeting.sayHello( "Jack" ); |
这样所有的代理类都合并到动态代理类中了,但这样做仍然存在一个问题:JDK 给我们提供的动态代理只能代理接口,而不能代理没有接口的类。有什么方法可以解决呢?
4. CGLib 动态代理
我们使用开源的 CGLib 类库可以代理没有接口的类,这样就弥补了 JDK 的不足。CGLib 动态代理类是这样玩的:
01 |
public class CGLibDynamicProxy implements MethodInterceptor { |
03 |
private static CGLibDynamicProxy instance = new CGLibDynamicProxy(); |
05 |
private CGLibDynamicProxy() { |
08 |
public static CGLibDynamicProxy getInstance() { |
12 |
@SuppressWarnings ( "unchecked" ) |
13 |
public <T> T getProxy(Class<T> cls) { |
14 |
return (T) Enhancer.create(cls, this ); |
18 |
public Object intercept(Object target, Method method, Object[] args, MethodProxy proxy) throws Throwable { |
20 |
Object result = proxy.invokeSuper(target, args); |
25 |
private void before() { |
26 |
System.out.println( "Before" ); |
29 |
private void after() { |
30 |
System.out.println( "After" ); |
以上代码中了 Singleton 模式,那么客户端调用也更加轻松了:
3 |
public static void main(String[] args) { |
4 |
Greeting greeting = CGLibDynamicProxy.getInstance().getProxy(GreetingImpl. class ); |
5 |
greeting.sayHello( "Jack" ); |
到此为止,我们能做的都做了,问题似乎全部都解决了。但事情总不会那么完美,而我们一定要追求完美!
老罗搞出了一个 AOP 框架,能否做到完美而优雅呢?请大家继续往下看吧!
5. Spring AOP:前置增强、后置增强、环绕增强(编程式)
在 Spring AOP 的世界里,与 AOP 相关的术语实在太多,往往也是我们的“拦路虎”,不管是看那本书或是技术文档,在开头都要将这些术语逐个灌输给读者。我想这完全是在吓唬人了,其实没那么复杂的,大家放轻松一点。
我们上面例子中提到的 before() 方法,在 Spring AOP 里就叫 Before Advice(前置增强)。有些人将 Advice 直译为“通知”,我想这是不太合适的,因为它根本就没有“通知”的含义,而是对原有代码功能的一种“增强”。再说,CGLib 中也有一个 Enhancer 类,它就是一个增强类。
此外,像 after() 这样的方法就叫 After Advice(后置增强),因为它放在后面来增强代码的功能。
如果能把 before() 与 after() 合并在一起,那就叫 Around Advice(环绕增强),就像汉堡一样,中间夹一根火腿。
这三个概念是不是轻松地理解了呢?如果是,那就继续吧!
我们下面要做的就是去实现这些所谓的“增强类”,让他们横切到代码中,而不是将这些写死在代码中。
先来一个前置增强类吧:
1 |
public class GreetingBeforeAdvice implements MethodBeforeAdvice { |
4 |
public void before(Method method, Object[] args, Object target) throws Throwable { |
5 |
System.out.println( "Before" ); |
注意:这个类实现了 org.springframework.aop.MethodBeforeAdvice 接口,我们将需要增强的代码放入其中。
再来一个后置增强类吧:
1 |
public class GreetingAfterAdvice implements AfterReturningAdvice { |
4 |
public void afterReturning(Object result, Method method, Object[] args, Object target) throws Throwable { |
5 |
System.out.println( "After" ); |
类似地,这个类实现了 org.springframework.aop.AfterReturningAdvice 接口。
最后用一个客户端来把它们集成起来,看看如何调用吧:
03 |
public static void main(String[] args) { |
04 |
ProxyFactory proxyFactory = new ProxyFactory(); |
05 |
proxyFactory.setTarget( new GreetingImpl()); |
06 |
proxyFactory.addAdvice( new GreetingBeforeAdvice()); |
07 |
proxyFactory.addAdvice( new GreetingAfterAdvice()); |
09 |
Greeting greeting = (Greeting) proxyFactory.getProxy(); |
10 |
greeting.sayHello( "Jack" ); |
请仔细阅读以上代码及其注释,您会发现,其实 Spring AOP 还是挺简单的,对吗?
当然,我们完全可以只定义一个增强类,让它同时实现 MethodBeforeAdvice 与 AfterReturningAdvice 这两个接口,如下:
01 |
public class GreetingBeforeAndAfterAdvice implements MethodBeforeAdvice, AfterReturningAdvice { |
04 |
public void before(Method method, Object[] args, Object target) throws Throwable { |
05 |
System.out.println( "Before" ); |
09 |
public void afterReturning(Object result, Method method, Object[] args, Object target) throws Throwable { |
10 |
System.out.println( "After" ); |
这样我们只需要使用一行代码,同时就可以添加前置与后置增强:
1 |
proxyFactory.addAdvice( new GreetingBeforeAndAfterAdvice());
|
刚才有提到“环绕增强”,其实这个东西可以把“前置增强”与“后置增强”的功能给合并起来,无需让我们同时实现以上两个接口。
01 |
public class GreetingAroundAdvice implements MethodInterceptor { |
04 |
public Object invoke(MethodInvocation invocation) throws Throwable { |
06 |
Object result = invocation.proceed(); |
11 |
private void before() { |
12 |
System.out.println( "Before" ); |
15 |
private void after() { |
16 |
System.out.println( "After" ); |
环绕增强类需要实现 org.aopalliance.intercept.MethodInterceptor 接口。注意,这个接口不是 Spring 提供的,它是 AOP 联盟(一个很牛逼的联盟)写的,Spring 只是借用了它。
在客户端中同样也需要将该增强类的对象添加到代理工厂中:
1 |
proxyFactory.addAdvice( new GreetingAroundAdvice());
|
好了,这就是 Spring AOP 的基本用法,但这只是“编程式”而已。Spring AOP 如果只是这样,那就太傻逼了,它曾经也是一度宣传用 Spring 配置文件的方式来定义 Bean 对象,把代码中的 new 操作全部解脱出来。
6. Spring AOP:前置增强、后置增强、环绕增强(声明式)
先看 Spring 配置文件是如何写的吧:
01 |
<? xml version = "1.0" encoding = "UTF-8" ?> |
02 |
< beans xmlns = "http://www.springframework.org/schema/beans" |
03 |
xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance" |
04 |
xmlns:context = "http://www.springframework.org/schema/context" |
05 |
xsi:schemaLocation="http://www.springframework.org/schema/beans |
06 |
http://www.springframework.org/schema/beans/spring-beans.xsd |
07 |
http://www.springframework.org/schema/context |
08 |
http://www.springframework.org/schema/context/spring-context.xsd"> |
11 |
< context:component-scan base-package = "aop.demo" /> |
14 |
< bean id = "greetingProxy" class = "org.springframework.aop.framework.ProxyFactoryBean" > |
15 |
< property name = "interfaces" value = "aop.Greeting" /> |
16 |
< property name = "target" ref = "greetingImpl" /> |
17 |
< property name = "interceptorNames" > |
19 |
< value >greetingAroundAdvice</ value > |
一定要阅读以上代码的注释,其实使用 ProxyFactoryBean 就可以取代前面的 ProxyFactory,其实它们俩就一回事儿。我认为 interceptorNames 应该改名为 adviceNames 或许会更容易让人理解,不就是往这个属性里面添加增强类吗?
此外,如果只有一个增强类,可以使用以下方法来简化:
3 |
< bean id = "greetingProxy" class = "org.springframework.aop.framework.ProxyFactoryBean" >
|
4 |
< property name = "interfaces" value = "aop.Greeting" />
|
5 |
< property name = "target" ref = "greetingImpl" />
|
6 |
< property name = "interceptorNames" value = "greetingAroundAdvice" />
|
还需要注意的是,这里使用了 Spring 2.5+ 的特性“Bean 扫描”,这样我们就无需在 Spring 配置文件里不断地定义 <bean id="xxx" class="xxx"/> 了,从而解脱了我们的双手。
看看这是有多么的简单:
2 |
public class GreetingImpl implements Greeting {
|
2 |
public class GreetingAroundAdvice implements MethodInterceptor {
|
最后看看客户端吧:
3 |
public static void main(String[] args) {
|
4 |
ApplicationContext context = new ClassPathXmlApplicationContext( "aop/demo/spring.xml" );
|
5 |
Greeting greeting = (Greeting) context.getBean( "greetingProxy" ); |
6 |
greeting.sayHello( "Jack" ); |
代码量确实少了,我们将配置性的代码放入配置文件,这样也有助于后期维护。更重要的是,代码只关注于业务逻辑,而将配置放入文件中。这是一条最佳实践!
除了上面提到的那三类增强以外,其实还有两类增强也需要了解一下,关键的时候您要能想得到它们才行。
7. Spring AOP:抛出增强
程序报错,抛出异常了,一般的做法是打印到控制台或日志文件中,这样很多地方都得去处理,有没有一个一劳永逸的方法呢?那就是 Throws Advice(抛出增强),它确实很强,不信你就继续往下看:
02 |
public class GreetingImpl implements Greeting {
|
05 |
public void sayHello(String name) {
|
06 |
System.out.println( "Hello! " + name);
|
08 |
throw new RuntimeException( "Error" );
|
下面是抛出增强类的代码:
02 |
public class GreetingThrowAdvice implements ThrowsAdvice {
|
04 |
public void afterThrowing(Method method, Object[] args, Object target, Exception e) {
|
05 |
System.out.println( "---------- Throw Exception ----------" ); |
06 |
System.out.println( "Target Class: " + target.getClass().getName());
|
07 |
System.out.println( "Method Name: " + method.getName());
|
08 |
System.out.println( "Exception Message: " + e.getMessage());
|
09 |
System.out.println( "-------------------------------------" ); |
抛出增强类需要实现 org.springframework.aop.ThrowsAdvice 接口,在接口方法中可获取方法、参数、目标对象、异常对象等信息。我们可以把这些信息统一写入到日志中,当然也可以持久化到数据库中。
这个功能确实太棒了!但还有一个更厉害的增强。如果某个类实现了 A 接口,但没有实现 B 接口,那么该类可以调用 B 接口的方法吗?如果您没有看到下面的内容,一定不敢相信原来这是可行的!
8. Spring AOP:引入增强
以上提到的都是对方法的增强,那能否对类进行增强呢?用 AOP 的行话来讲,对方法的增强叫做 Weaving(织入),而对类的增强叫做 Introduction(引入)。而 Introduction Advice(引入增强)就是对类的功能增强,它也是 Spring AOP 提供的最后一种增强。建议您一开始千万不要去看《Spring Reference》,否则您一定会后悔的。因为当您看了以下的代码示例后,一定会彻底明白什么才是引入增强。
定义了一个新接口 Apology(道歉):
1 |
public interface Apology {
|
3 |
void saySorry(String name);
|
但我不想在代码中让 GreetingImpl 直接去实现这个接口,我想在程序运行的时候动态地实现它。因为假如我实现了这个接口,那么我就一定要改写 GreetingImpl 这个类,关键是我不想改它,或许在真实场景中,这个类有1万行代码,我实在是不敢动了。于是,我需要借助 Spring 的引入增强。这个有点意思了!
02 |
public class GreetingIntroAdvice extends DelegatingIntroductionInterceptor implements Apology {
|
05 |
public Object invoke(MethodInvocation invocation) throws Throwable {
|
06 |
return super .invoke(invocation);
|
10 |
public void saySorry(String name) {
|
11 |
System.out.println( "Sorry! " + name);
|
以上定义了一个引入增强类,扩展了 org.springframework.aop.support.DelegatingIntroductionInterceptor 类,同时也实现了新定义的 Apology 接口。在类中首先覆盖了父类的 invoke() 方法,然后实现了 Apology 接口的方法。我就是想用这个增强类去丰富 GreetingImpl 类的功能,那么这个 GreetingImpl 类无需直接实现 Apology 接口,就可以在程序运行的时候调用 Apology 接口的方法了。这简直是太神奇的!
看看是如何配置的吧:
01 |
<? xml version = "1.0" encoding = "UTF-8" ?>
|
02 |
< beans xmlns = "http://www.springframework.org/schema/beans"
|
03 |
xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance" |
04 |
xmlns:context = "http://www.springframework.org/schema/context" |
05 |
xsi:schemaLocation="http://www.springframework.org/schema/beans |
06 |
http://www.springframework.org/schema/beans/spring-beans.xsd |
07 |
http://www.springframework.org/schema/context |
08 |
http://www.springframework.org/schema/context/spring-context.xsd"> |
10 |
< context:component-scan base-package = "aop.demo" />
|
12 |
< bean id = "greetingProxy" class = "org.springframework.aop.framework.ProxyFactoryBean" >
|
13 |
< property name = "interfaces" value = "aop.demo.Apology" />
|
14 |
< property name = "target" ref = "greetingImpl" />
|
15 |
< property name = "interceptorNames" value = "greetingIntroAdvice" />
|
16 |
< property name = "proxyTargetClass" value = "true" />
|
需要注意 proxyTargetClass 属性,它表明是否代理目标类,默认为 false,也就是代理接口了,此时 Spring 就用 JDK 动态代理。如果为 true,那么 Spring 就用 CGLib 动态代理。这简直就是太方便了!Spring 封装了这一切,让程序员不在关心那么多的细节。我们要向老罗同志致敬,您是我们心中永远的 idol!
当您看完下面的客户端代码,一定会完全明白以上的这一切:
03 |
public static void main(String[] args) {
|
04 |
ApplicationContext context = new ClassPathXmlApplicationContext( "aop/demo/spring.xml" );
|
05 |
GreetingImpl greetingImpl = (GreetingImpl) context.getBean( "greetingProxy" ); |
06 |
greetingImpl.sayHello( "Jack" ); |
08 |
Apology apology = (Apology) greetingImpl; |
09 |
apology.saySorry( "Jack" ); |
没想到 saySorry() 方法原来是可以被 greetingImpl 对象来直接调用的,只需将其强制转换为该接口即可。
推荐看http://my.oschina.net/huangyong/blog/158380
相关推荐
spring AOP 理论知识点总结.wpsspring AOP 理论知识点总结.wpsspring AOP 理论知识点总结.wps
Spring AOP,全称Aspect-Oriented Programming(面向切面编程),是Spring框架的重要组成部分,用于实现横切关注点的模块化。它允许开发者定义“切面”,将那些与业务逻辑无关,却为多个对象共有的行为(如日志、...
AOP(Aspect-Oriented Programming,面向切面编程)是一种编程范式,旨在减少代码的重复性和增强可维护性,特别是在处理系统中的横切关注点时。这些关注点,如日志、事务管理、安全检查等,往往分散在系统的各个部分...
面向切面编程(AOP,Aspect Oriented Programming)是一种编程范式,旨在通过将关注点分离,提高软件的模块化程度。AOP的核心是切面,它封装了横切关注点,如日志、事务管理、性能监控等,使得这些功能可以独立于主...
spring-aop-1.1.1.jar spring-aop-1.2.6.jar spring-aop-1.2.9.jar spring-aop-2.0.2.jar spring-aop-2.0.6.jar spring-aop-2.0.7.jar spring-aop-2.0.8.jar spring-aop-2.0.jar spring-aop-2.5.1.jar spring-aop-...
AOP的核心是切面(Aspect),它封装了跨越多个对象的行为或关注点,如日志记录。切点(Pointcut)定义了在何处应用切面,通常通过方法签名或注解来指定。通知(Advice)是切面实际执行的动作,例如在方法调用前后...
4. **丰富的切入点表达式语言**:Spring AOP支持使用SpEL(Spring Expression Language)来定义复杂的切入点表达式,这让开发者能够更加灵活地控制通知的触发条件。 #### 四、Spring AOP的实现示例 接下来,我们...
Spring AOP(Aspect Oriented Programming,面向切面编程)是Spring框架的重要组成部分,它提供了一种在不修改源代码的情况下,对程序进行功能增强的技术。这个"spring aop jar 包"包含了实现这一功能所需的类和接口,...
### AOP Alliance 白皮书核心知识点解析 #### 一、引言 AOP(Aspect Oriented Programming,面向切面编程)是一种编程范式,它旨在通过将横切关注点(cross-cutting concerns)从业务逻辑中分离出来,提高系统的...
AOP(Aspect Oriented Programming,面向切面编程)是一种编程范式,旨在通过分离关注点来简化软件开发。aopalliance是一个Java库,它提供了一个统一的接口,使得不同的AOP框架(如AspectJ、Spring AOP等)能够相互...
在IT行业中,AOP(Aspect-Oriented Programming,面向切面编程)是一种编程范式,它旨在提高软件的模块化程度,将关注点分离。在Java世界里,AOP常用于处理日志、事务管理、权限检查等横切关注点。当我们谈到“AOP...
开发工具 aopalliance-1.0开发工具 aopalliance-1.0开发工具 aopalliance-1.0开发工具 aopalliance-1.0开发工具 aopalliance-1.0开发工具 aopalliance-1.0开发工具 aopalliance-1.0开发工具 aopalliance-1.0开发工具...
**AOP Alliance简介** AOP Alliance是一个开源项目,它的全称是Aspect Oriented Programming(面向切面编程)Alliance,是Java平台上的一个接口集合,为面向切面编程的实现提供了一个统一的API。这个库的主要目的是...
Spring AOP模块提供了实现AOP规范的功能,它允许开发者定义“切面”来封装系统中的横切关注点,如日志、事务管理等。该jar文件包含了Spring AOP的核心类和接口,如`org.springframework.aop.*`包下的`...
在软件开发领域,AOP(Aspect-Oriented Programming,面向切面编程)是一种重要的编程范式,它旨在提高代码的可重用性和模块化程度,使得关注点分离得以更好地实现。AOP Alliance 1.0 是一个关键的开源项目,它提供...
Spring AOP(面向切面编程)是Spring框架的重要组成部分,它提供了一种模块化和声明式的方式来处理系统中的交叉关注点问题,如日志、事务管理、安全性等。本示例将简要介绍如何在Spring应用中实现AOP,通过实际的...
在OOP中,这些关注点可能会分散在各个对象中,而在AOP中,它们被集中处理,称为横切关注点,横切关注点是那些影响整个应用的共同关注点,如安全、日志等。 Spring AOP提供了在不修改源代码的情况下,动态插入这些横...
面向切面编程(AOP)是一种编程范式,旨在将横切关注点(如日志、安全等)与业务逻辑分离,从而提高模块化。AOP通过预定义的“切面”对横切关注点进行模块化,从而可以在不修改业务逻辑代码的情况下增加新功能。动态...
面向切面编程(Aspect-Oriented Programming,AOP)是一种编程范式,旨在将横切关注点(crosscutting concerns)从业务逻辑代码中分离出来,以提高模块化。C++中的AOP理论探讨了如何在C++平台上实现面向切面编程,...
本项目实现了Spring AOP的基本概念和操作,以下是对相关知识点的详细说明: 一、AOP核心概念 1. 切面(Aspect):切面是关注点的模块化,它封装了横切关注点,如日志、事务管理等。 2. 连接点(Join Point):程序...