心碎之事

要说知道AOP这个词倒是很久很久以前了,但是直到今天我也不敢说非常的理解它,其中的各种概念即抽象又太拗口。

在几次面试中都被问及AOP,但是真的没有答上来,或者都在面上,这给面试官的感觉就是java基础不行。可见这还是挺重要的一个概念。

在看工作中也遇到了相关的问题,在RPC的一种实现机制里应用了AOP,结果各种类一直绕来绕去看着头都大了,这也就是没有对动态代理和aop有了解导致的。

所以要好好的去掌握它,否则吃亏的还是自己。

先从概念开始

面向侧面的程序设计(aspect-oriented programming,AOP,又译作面向方面的程序设计、观点导向编程、剖面导向程序设计)是计算机科学中的一个术语,指一种程序设计范型。该范型以一种称为侧面(aspect,又译作方面)的语言构造为基础,侧面是一种新的模块化机制,用来描述分散在对象、类或函数中的横切关注点(crosscutting concern)。——维基百科

不知道看完这段话你能理解AOP是个啥吗?并不能,最多知道AOP是一种规范。所以说要理解AOP最重要还是要从具体的实现入手,才能真正明白AOP到底是干啥子的。

那么AOP的核心是什么?

其实就是一种代码增强方式,可以实现动态的代理,在运行期完成;也有一些是静态的为对象增强,在编绎期间完成。说的白话一点就是在对象进行代码上的扩展增强,就是说原先可能只能跑10行代码,增强后就可以多跑些代码,而且这种增强是通过织入的方式完成,而不是直接修改目标对象的代码。

为此还是要先理解代理这个概念,可以更好的理解AOP,那么就从学习代理开始吧。

写个静态代理例子

//创建一个接口
public interface ISay {
void say();
} //接口的实现类(可以理解为业务类)
public class SayImpl implements ISay{ @Override
public void say() {
System.out.print("我是5207.");
} } //只说一个名字太单调了,需要多多拉票,创建一个代理类来增强一下 class StaticSayImplProxy implements ISay {
private ISay target; public StaticSayImplProxy(ISay target) {
this.target = target;
} @Override
public void say() {
SayHello();
this.target.say();
ThumbUp();
} void SayHello() {
System.out.print("大家好:");
} void ThumbUp() {
System.out.print("希望大家多多点赞.");
}
} //调用代码
public class StaticProxy {
public static void main(String[] args) {
ISay aop = new SayImpl();
ISay aopProxy = new StaticSayImplProxy(aop);
aopProxy.say();
}
}

代码很简单,静态代理代码中通过增加一个StaticSayImplProxy类来对原先的实现类进行增强。注意这里的增强很重要。也就是原本只会说一句“我是5207.”,通过代理的类后就完整了许多。

但是静态代理的缺点是,代理时必须知道其类型是,比如上面代码中就必须知道ISay这个接口,最终才能在代理类里才能调用:this.target.say()这样的代码。那么也就是说如果还有另外的IOtherSay接口也需要用这个代理类就没法使用了。

所以如果能把代理集中一个类中,只要将要代理的对象给这个类就能代理是不是比较方便,代码少写好多。

整成动态代理

办法当然是有的,JDK提供了一种动态代理的技术,可以动态的为接口创建代理对象,从而实现代理模式。那么接着上面的例子我们需要做成动态代理代码如下:

class DynamicProxyImpl implements InvocationHandler {
private Object target; public DynamicProxyImpl(Object target) {
this.target = target;
} @SuppressWarnings("unchecked")
public <T> T getObject() {
return (T) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
this
);
} @Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
SayHello();
Object result = method.invoke(target, args);
ThumbUp();
return result;
} void SayHello() {
System.out.print("大家好:");
} void ThumbUp() {
System.out.println("希望大家多多点赞.");
}
}

这里可以发现DynamicProxyImpl类并没有指定具体代理的接口类型,而使用Object类型。这样就不用关心传入给这个代理类的具体对象了。所以说这个代理类的范围一下子就大了许多,只要是类似的增强功能都可以用这个代理类来完成。举个例子:

public interface IOtherSay {
void applause();
} public class OtherSayImpl implements IOtherSay { @Override
public void applause() {
System.out.print("大家鼓掌.");
} } public static void main(String[] args) {
IOtherSay oSay = new OtherSayImpl();
DynamicProxyImpl oSayProxy = new DynamicProxyImpl(oSay);
IOtherSay oSay2 = oSayProxy.getObject();
oSay2.applause();
}

可以看到使用同一个代理类也可以代理IOtherSay接口派生的对象啦。

InvocationHandler接口

另外一点就是DynamicProxyImpl实现了InvocationHandler接口,这个接口是关键,其实应当是JDK代理对象时的一个调用处理程序,这应当是暴露给开发者的代码增强接口啦。InvocationHandler接口只有一个invoke方法,我们需要做的就是在这个方法中增加需要的增强代码。

Proxy工具类

对于getObject方法才是真正的代理对象生成的过程,可以看到最终是Proxy.newProxyInstance这个方法来完成代理对象生成并返回的。那么这里可以看看Proxy的设计与原理。

因为暂时只用到了newProxyInstance方法,就从它开始吧:

@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
Objects.requireNonNull(h); final Class<?>[] intfs = interfaces.clone();
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
} /*
* Look up or generate the designated proxy class.
*/
Class<?> cl = getProxyClass0(loader, intfs); /*
* Invoke its constructor with the designated invocation handler.
*/
try {
if (sm != null) {
checkNewProxyPermission(Reflection.getCallerClass(), cl);
} final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
if (!Modifier.isPublic(cl.getModifiers())) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
cons.setAccessible(true);
return null;
}
});
}
return cons.newInstance(new Object[]{h});
} catch (IllegalAccessException|InstantiationException e) {
throw new InternalError(e.toString(), e);
} catch (InvocationTargetException e) {
Throwable t = e.getCause();
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
} else {
throw new InternalError(t.toString(), t);
}
} catch (NoSuchMethodException e) {
throw new InternalError(e.toString(), e);
}
}

方法的三个参数中要注意interfaces和h

  • interfaces

    是指的传入需要代理的接口列表,这里有一点比较重要JDK的动态代理只能代理接口哦。

  • h

    另外一个h则是指调用处理程序,发现在上面的DynamicProxyImpl类就是一个调用处理程序的实现,这里的关键就是InvocationHandler。

接着看方法体中的代码主要是几个过程:

  1. 获取/创建代理类Class:Class<?> cl = getProxyClass0(loader, intfs);
  2. 获得代理Class的构造函数:final Constructor<?> cons = cl.getConstructor(constructorParams);
  3. 创建代理类的对象实例并返回:return cons.newInstance(new Object[]{h});

这里还有些反射相关的知识就不再说明了,但是有一个点比较好奇,就是最终代理对象是如何通过invoke把目标对象的方法代理的呢?

篇幅有点多就引用一篇吧JDK动态代理实现原理

其实大体意思是最终JDK会自动的为指定的接口生成代理对象,而这个生成的代理对象就和前面手写的动态代理方法类似,只不过生成的代理类调用的是InvocationHandler的invoke。jdk帮我们做了自动生成的过程,这样就可以在运行期生成代理类。

到此,最为重要的代理差不多说完了,这也就是AOP的奥秘所在。

Spring中的AOP

了解了代理后再来看AOP的相关概念还是理解不了,什么切面、连接点、通知、切入点之类的,所以说还是抛开吧,这样要轻松许多。相信大家看aop的时候肯定是看到了Advice这个东东吧?嗯,就从它入手吧。

在Spring中Advice下面这些类型:

  • Before Advice:在目标方法被调用前调用,涉及接口org.springframework.aop.MethodBeforeAdvice
  • After Advice:在目标方法被调用后调用,涉及接口为org.springframework.aop.AfterReturningAdvice
  • Throws Advice:目标方法抛出异常时调用,涉及接口org.springframework.aop.ThrowsAdvice
  • Around Advice:拦截对目标对象方法调用,涉及接口为org.aopalliance.intercept.MethodInterceptor
  • Introduction Advice:为拦截的目标增加方法,涉及的接口为org.springframework.aop.support.DelegatingIntroductionInterceptor

嗯,都是些啥意思?Berfore Advice可以理解为SayHello方法,那么ThumbUp就是After Advice,两个加一起就是Around Advice。对于Throws Advice是针对异常抛出时的增强。最后还有一个比较牛的就是Introduction Advice则是可以对即有的对象进行增加方法,这个貌似更强大点。

从这些描述可以总结出来和前面动态代理时有异曲同工之处,在Spring中其实就是利用了动态代理的技术,结合AOP的概念对代码提供了一种更友好的扩展方式。当然也可以说换了一个角度来理解代码。举个例子说,希望在现有系统中监控所有action的执行时间,就可以拦截所有的action,加一个Before Advice记录一下进入的时间,再加一个After Advice计算一下完成的时间。这样就不会对现有action代码做什么修改,非常优雅啊,这对于系统设计时会有非常有用。

进一步理解Spring AOP

为了能够更深入的理解Spring AOP,还是需要更深入的去阅读源代码。这里再以DynamicProxyImpl为例子,如果使用Spring如何实现它的功能呢?以Spring xml配置方式试一下吧。

  • 首先实现用于增强SayImpl的代码,这里使用环绕增强,和前面DynamicProxyImpl的效果一致。
package aop.demo;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation; public class SayImplAroundAdvice implements MethodInterceptor{ public Object invoke(MethodInvocation invocation) throws Throwable {
SayHello();
Object result = invocation.proceed();
ThumbUp();
return result;
} void SayHello() {
System.out.print("大家好:");
} void ThumbUp() {
System.out.println("希望大家多多点赞.");
}
}
  • 使用Spring的配置文件声明对象和AOP代理,spring.xml如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd"> <!-- 声明被代理的目标对象 -->
<bean id="sayImpl" class="aop.demo.SayImpl"></bean>
<!-- 声明用于增强的拦截器对象 -->
<bean id="sayImplAroundAdvice" class="aop.demo.SayImplAroundAdvice"></bean>
<!-- 声明代理对象 -->
<bean id="sayProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="interfaces" value="aop.demo.ISay"/> <!-- 这个就是被代理的接口 -->
<property name="target" ref="sayImpl"/> <!-- 这个就是被代理的对象 -->
<property name="interceptorNames" value="sayImplAroundAdvice"/><!-- 这个就是代理的增强器 -->
</bean>
</beans>
  • 实现代理的访问
package aop.demo;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext; public class Client { @SuppressWarnings("resource")
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("aop/demo/spring.xml");
ISay say = (ISay)context.getBean("sayProxy"); say.say();
} }

最后执行一下程序输出的结果是:

大家好:我是5207.希望大家多多点赞.

哎呀呀和前面的动态代理一致,效果达成。_

当然也可以通过代码方式完成上面的功能

package aop.demo;

import org.springframework.aop.framework.ProxyFactory;

public class ClientCode {

	public static void main(String[] args) {
ProxyFactory proxyFactory = new ProxyFactory(); // 创建代理工厂
proxyFactory.setTarget(new SayImpl()); // 射入目标类对象
proxyFactory.addAdvice(new SayImplAroundAdvice());
ISay say = (ISay) proxyFactory.getProxy();
say.say();
} }

总结

写了一天才写到这里,只不过还是值得的。原来一直以为AOP就是动态代理,没想到自己错了,AOP是一种规范,而动态代理只是实现AOP的一种方式而已。

接下来继续研究spring aop,进一步学习ProxyFactoryBean和ProxyFactory。

引用与参考

AOP 那点事儿

Java之美[从菜鸟到高手演练]之JDK动态代理的实现及原理

JDK动态代理实现原理

Spring AOP代理详解

注:此文章为原创,欢迎转载,请在文章页面明显位置给出此文链接!

若您觉得这篇文章还不错请点击下右下角的推荐,非常感谢!

http://www.cnblogs.com/5207

学习AOP之认识一下Spring AOP的更多相关文章

  1. Spring学习总结(4)——Spring AOP教程

    一.概念 AOP(Aspect Oriented Programming):面向切面编程. 面向切面编程(也叫面向方面编程),是目前软件开发中的一个热点,也是Spring框架中的一个重要内容.利用AO ...

  2. 学习AOP之深入一点Spring Aop

    上一篇<学习AOP之认识一下SpringAOP>中大体的了解了代理.动态代理及SpringAop的知识.因为写的篇幅长了点所以还是再写一篇吧.接下来开始深入一点Spring aop的一些实 ...

  3. 框架源码系列十:Spring AOP(AOP的核心概念回顾、Spring中AOP的用法、Spring AOP 源码学习)

    一.AOP的核心概念回顾 https://docs.spring.io/spring/docs/5.1.3.RELEASE/spring-framework-reference/core.html#a ...

  4. Spring学习进阶 (三) Spring AOP

    一.是什么AOP是Aspect Oriented Programing的简称,最初被译为“面向方面编程”:AOP通过横向抽取机制为无法通过纵向继承体系进行抽象的重复性代码提供了解决方案.比如事务的控制 ...

  5. Spring学习总结(1)——Spring AOP的概念理解

    1.我所知道的aop 初看aop,上来就是一大堆术语,而且还有个拉风的名字,面向切面编程,都说是OOP的一种有益补充等等.一下子让你不知所措,心想着:怪不得 很多人都和我说aop多难多难 .当我看进去 ...

  6. Spring学习总结(15)——Spring AOP 拦截器的基本实现

    一个程序猿在梦中解决的 Bug 没有人是不做梦的,在所有梦的排行中,白日梦最令人伤感.不知道身为程序猿的大家,有没有睡了一觉,然后在梦中把睡之前代码中怎么也搞不定的 Bug 给解决的经历?反正我是有过 ...

  7. Spring学习总结(9)——Spring AOP总结

    spring IOC和AOP是Spring框架的两大核心基石,本文将对Spring AOP做一个系统的总结. 什么是AOP AOP(Aspect-Oriented Programming,面向切面编程 ...

  8. Spring学习总结(17)——Spring AOP权限管理

    每个项目都会有权限管理系统 无论你是一个简单的企业站,还是一个复杂到爆的平台级项目,都会涉及到用户登录.权限管理这些必不可少的业务逻辑.有人说,企业站需要什么权限管理阿?那行吧,你那可能叫静态页面,就 ...

  9. Spring学习总结(16)——Spring AOP实现执行数据库操作前根据业务来动态切换数据源

    深刻讨论为什么要读写分离? 为了服务器承载更多的用户?提升了网站的响应速度?分摊数据库服务器的压力?就是为了双机热备又不想浪费备份服务器?上面这些回答,我认为都不是错误的,但也都不是完全正确的.「读写 ...

随机推荐

  1. PHP-自定义模板-学习笔记

    1.  开始 这几天,看了李炎恢老师的<PHP第二季度视频>中的“章节7:创建TPL自定义模板”,做一个学习笔记,通过绘制架构图.UML类图和思维导图,来对加深理解. 2.  整体架构图 ...

  2. dubbox微服务实例及引发的“血案”

    Dubbo 是阿里巴巴公司开源的一个高性能优秀的服务框架,使得应用可通过高性能的 RPC 实现服务的输出和输入功能,可以和 Spring框架无缝集成. 主要核心部件: Remoting: 网络通信框架 ...

  3. ASP.NET WebApi OWIN 实现 OAuth 2.0

    OAuth(开放授权)是一个开放标准,允许用户让第三方应用访问该用户在某一网站上存储的私密的资源(如照片,视频,联系人列表),而无需将用户名和密码提供给第三方应用. OAuth 允许用户提供一个令牌, ...

  4. 自定义搭建PHP开发环境

    学习了一段时间php了,因为之前是刚接触php,所以用的是集成安装包(wamp).现在想进一步了解apache.mysql.php之间的关系以及提升自己所以进行自定义搭建PHP开发环境.废话不多说,请 ...

  5. 破解SQLServer for Linux预览版的3.5GB内存限制 (RHEL篇)

    微软发布了SQLServer for Linux,但是安装竟然需要3.5GB内存,这让大部分云主机用户都没办法尝试这个新东西 这篇我将讲解如何破解这个内存限制 要看关键的可以直接跳到第6步,只需要替换 ...

  6. 前端自动化构建工具gulp记录

    一.安装 1)安装nodejs 通过nodejs的npm安装gulp,插件也可以通过npm安装.windows系统是个.msi工具,只要一直下一步即可,软件会自动在写入环境变量中,这样就能在cmd命令 ...

  7. java设计模式之--单例模式

    前言:最近看完<java多线程编程核心技术>一书后,对第六章的单例模式和多线程这章颇有兴趣,我知道我看完书还是记不住多少的,写篇博客记录自己所学的只是还是很有必要的,学习贵在坚持. 单例模 ...

  8. c++ pair 使用

    1. 包含头文件: #include <utility> 2. pair 的操作: pair<T1,T2> p; pair<T1,T2> p(v1,v2); pai ...

  9. NodeJs支付宝移动支付签名及验签

    非常感谢 :http://www.jianshu.com/p/8513e995ff3a?utm_campaign=hugo&utm_medium=reader_share&utm_co ...

  10. Open-Test 测试驱动模式与版本号管理机制

    以测试用例驱动项目开发,coding/case俩条线并走模式.   1.开发人员只负责功能实现:   2.测试人员提供自测用例,研发人员jenkins持续集成项目后自动化执行自测用例,通过后方可转测试 ...