澳门新浦京娱乐游戏手把手教你用Java实现AOP

澳门新浦京娱乐游戏 16

介绍

众所周知,AOP(面向切面编程)是Spring框架的特色功能之一。通过设置横切关注点(cross
cutting
concerns),AOP提供了极高的扩展性。那AOP在Spring中是怎样运作的呢?当你只能使用core
java,却需要AOP技术时,这个问题的解答变得极为关键。不仅如此,在高级技术岗位的面试中,此类问题也常作为考题出现。这不,我的朋友最近参加了一个面试,就被问到了这样一个棘手的问题——如何在不使用Spring及相关库,只用core
Java的条件下实现AOP。因此,我将在本文中提供一份大纲,帮助大家了解如何只用core
Java实现一个AOP(当然啦,这种AOP在功能上有一定的局限性)。注意,本文不是一篇有关Spring
AOP与Java AOP的对比研究,而是有关在core Java中借助固有的设计模式实现AOP的教程。

想必读者已经知道AOP是什么,也知道在Spring框架中如何使用它,因此本文只着眼于如何在不用Spring的前提下实现AOP。首先,我们得知道,Spring是借助了JDK
proxy和CGlib两种技术实现AOP的。JDK dynamic
proxy提供了一种灵活的方式来hook一个方法并执行指定的操作,但执行操作时得有一个限制条件:必须先提供一个相关的接口以及该接口的实现类。实践出真知,让我们透过一个案例来理解这句吧!现在有一个计算器程序,用于完成一些数学运算。让我们来考虑下除法功能,此时的问题是:如果core
framework
已经具备了一份实现除法的代码,我们能否在代码执行时劫持(highjack)它并执行额外的校验呢?答案是肯定的,我将用下面提供的代码片段来证明这点。首先来看基础接口的代码:

 public interface Calculator {
    public int calculate( int a , int b);
}

该接口实现类的代码如下:

 public class CalculatorImpl implements Calculator {
    @Override
    public int calculate(int a, int b) {
        return a/b;
    }
}

假设我们既不能修该上面的代码,也不能对核心库进行任何改动,怎样才能完美地实现校验功能呢?不如试下JDK
dynamic proxy的功能吧。

public class SomeHandler implements InvocationHandler {

// Code omitted for simplicity…..

    @Override
    public Object invoke(Object proxy, Method method, Object[] params) throws Throwable {
// Your complex business validation and logic
        Object result = method.invoke(targetObject ,params);
        return result;
    }

}

让我们通过测试类来看看由JDK dynamic proxy实现的校验功能的效果如何。

public static void main(String[] args) {
        CalculatorImpl calcImpl = new CalculatorImpl();
        Calculator proxied = (Calculator)ProxyFactory.getProxy (Calculator.class, calcImpl, 
                new SomeHandler(calcImpl));
        int result = proxied.calculate(20, 10);
        System.out.println("FInal Result :::" + result);
    }

从结果可以看出,简单地实现功能强大的InvocationHandler接口,我们便能得到一个hooking
implementation。按照JDK文档的描述,InvocationHandler接口是借助一个代理实例(proxy
instance)来处理一个方法调用的。

现在我们已经知道,InvocationHandler的invoke()方法能够帮助我们解决问题。那么再来解决一个新问题——怎样才能在方法执行的前后执行操作呢?说的更具体一些,我们能通过添加多个aop(before、after、around)来hook一个方法吗(译注:原文为add
multiple
aops,但我认为Handler是充当Aspect的角色)?答案同样是肯定的。按照以下的步骤建立一个精简的代码模板便能满足这样的需求:

  1. 创建一个抽象类,用于将aop应用于目标对象上。
  2. 创建名为BeforeHandler 和
    AfterHandler的两个aop。前者在方法执行之前工作,而后者则在方法执行结束后工作。
  3. 创建一个代理类,使所有的aop
    handler和目标对象只需作为参数传入,就能创建一个hook。
  4. 加入你自己的业务逻辑或者横切关注点。
  5. 最后,通过传入相关的参数创建代理对象(proxy object)。

什么是AOP

解释AOP

  • AOP(Aspect-OrientedProgramming,面向方面编程),可以说是OOP(Object-Oriented
    Programing,面向对象编程)的补充和完善。OOP允许你定义从上到下的关系,但并不适合定义从左到右的关系。
  • 将那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可操作性和可维护性。

澳门新浦京娱乐游戏 1

技术实现概要

(译注:此处是核心代码片段,如果想运行该实例,需进入下方提供的链接下载完整代码)

创建一个handler的抽象类:

public abstract class AbstractHandler implements InvocationHandler {

    private Object targetObject;

    public void setTargetObject(Object targetObject) {
        this.targetObject = targetObject;
    }
}

创建名为BeforeHandler和AfterHandler的两个易扩展的handler抽象类:

public abstract class BeforeHandler extends AbstractHandler {
    public abstract void handleBefore(Object proxy, Method method, Object[] args);
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        handleBefore(proxy, method, args);
        return method.invoke(getTargetObject(), args);
    }
}

public abstract class AfterHandler extends AbstractHandler {
    public abstract void handleAfter(Object proxy, Method method, Object[] args);
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object result = method.invoke(getTargetObject(), args);
        handleAfter(proxy, method, args);
        return result;
    }
}

创建Proxy的工厂类:

public class ProxyFactory {

    public static Object getProxy(Object targetObject,
            List handlers) {
            //Code to get the proxy
            return proxyObject;
        } else {
            return targetObject;
        }
    }
}

以下为测试代码:

CalculatorImpl calcImpl = new CalculatorImpl();
BeforeHandler before = new BeforeHandlerImpl();
AfterHandler after = new AfterHandlerImpl();
List<AbstractHandler> handlers = new ArrayList<AbstractHandler>();
handlers.add(before);
handlers.add(after);
Calculator proxy = (Calculator) ProxyFactory.getProxy(calcImpl,
                handlers);
int result = proxy.calculate(20, 10);

Aspect Oriented Programming:面向切面编程

传统编码方式与使用aop的区别

澳门新浦京娱乐游戏 2

配置

以上的代码片段简明扼要地解释了AOP在结构上的实现(structural
implementation)。当然,如果能通过实际的测试将其运用到现实中去,那就再好不过了。读者可在下面的链接中获取完整的工程文件,并在Java编辑器中配置它们,最后通过其中的测试类来检验效果。

什么时候会出现面向切面编程的需求?

两种代理:

  • JDK:基于接口
    • 原理:动态代理 + 反射
    • 定义一个类XxHandler,实现InvocationHandler接口,里边包含真实对象的实例(实际上是一个Object,具体真实对象在运行期赋值给Object),并使用反射调用真实对象的指定方法
    • 使用Proxy.newProxyInstance(ClassLoader, interfaces,
      InvocationHandler)创建代理对象,之后调用相应的真实对象的方法即可。由于该方法的第二个对象是interface,所以JDK只能基于接口实现动态代理。
  • CGLib:基于类
    • 原理:动态生成字节码技术,即为将要拦截的类动态生成子类,然后在子类中拦截所有父类的调用并顺势织入横切逻辑。
    • 由于是创建子类,所以不能代理目标类中的private和final方法。

总结

希望这篇简短的有关AOP文章能够帮助到大家。需说明的是,本文只实现了before和after两种aop,而另外两种,即“Around”和“Throw”,则希望读者自行完成。

按照软件重构的思想,如果多个类中出现重复的代码,就应该考虑定义一个共同的抽象类,将这些共同的代码提取到抽象类中,比如Teacher,Student都有username,那么就可以把username及相关的get、set方法抽取到SysUser中,这种情况,我们称为纵向抽取。

比较:

  • CGLib创建代理对象花费的时间长于JDK,因为要动态生成子类
  • CGLib创建好的代理对象的性能由于JDK

但是如果,我们的情况是以下情况,又该怎么办?

术语

切点(Pointcut)、连接点(Join
point)、切面(Aspect)、引入(Introduction)、织入(Weaving)、通知(Advice)。

澳门新浦京娱乐游戏 3

给所有的类方法添加性能检测,事务控制,该怎么抽取?

Aspect 切面

AOP核心就是切面,它将多个类的通用行为封装成可重用的模块,该模块含有一组API提供横切功能。比如,一个日志模块可以被称作日志的AOP切面。根据需求的不同,一个应用程序可以有若干切面。在Spring
AOP中,切面通过带有@Aspect注解的类实现。

PerformanceMonitor

在Spring AOP 中,关注点和横切关注的区别是什么

  • 关注点是应用中一个模块的行为,一个关注点可能会被定义成一个我们想实现的一个功能。
  • 横切关注点是一个关注点,此关注点是整个应用都会使用的功能,并影响整个应用,比如日志,安全和数据传输,几乎应用的每个模块都需要的功能。因此这些都属于横切关注点。

TransactionManager

连接点

连接点代表一个应用程序的某个位置,在这个位置我们可以插入一个AOP切面,它实际上是个应用程序执行Spring
AOP的位置。

AOP就是希望将这些分散在各个业务逻辑代码中的相同代码,通过横向切割的方式抽取到一个独立的模块中,让业务逻辑类依然保存最初的单纯。

通知

  • 通知是个在方法执行前或执行后要做的动作,实际上是程序执行时要通过SpringAOP框架触发的代码段。
  • Spring切面可以应用五种类型的通知:
    • before:前置通知,在一个方法执行前被调用。
    • after: 在方法执行之后调用的通知,无论方法执行是否成功。
    • after-returning: 仅当方法成功完成后执行的通知。
    • after-throwing: 在方法抛出异常退出时执行的通知。
    • around: 在方法执行之前和之后调用的通知。

澳门新浦京娱乐游戏 4

切点

切入点是一个或一组连接点,通知将在这些位置执行。可以通过表达式或匹配的方式指明切入点。

抽取出来简单,难点就是如何将这些独立的逻辑融合到业务逻辑中,完成跟原来一样的业务逻辑,这就是AOP解决的主要问题。

什么是引入?

引入允许我们在已存在的类中增加新的方法和属性。

AOP术语

什么是目标对象?

被一个或者多个切面所通知的对象。它通常是一个代理对象。也指被通知(advised)对象。

l连接点(Joinpoint)

什么是代理?

代理是通知目标对象后创建的对象。从客户端的角度看,代理对象和目标对象是一样的。

程序执行的某个特定位置,如某个方法调用前,调用后,方法抛出异常后,这些代码中的特定点称为连接点。简单来说,就是在哪加入你的逻辑增强

有几种不同类型的自动代理?

  • BeanNameAutoProxyCreator
  • DefaultAdvisorAutoProxyCreator
  • Metadata autoproxying

l切点(PointCut)

什么是织入

织入是将切面和到其他应用类型或对象连接或创建一个被通知对象的过程。
织入可以在编译时,加载时,或运行时完成。

每个程序的连接点有多个,如何定位到某个感兴趣的连接点,就需要通过切点来定位。

切面例子

@Aspect
public class TransactionDemo {

    @Pointcut(value="execution(* com.yangxin.core.service.*.*.*(..))")
    public void point(){

    }

    @Before(value="point()")
    public void before(){
        System.out.println("transaction begin");
    }

    @AfterReturning(value = "point()")
    public void after(){
        System.out.println("transaction commit");
    }

    @Around("point()")
    public void around(ProceedingJoinPoint joinPoint) throws Throwable{
        System.out.println("transaction begin");
        joinPoint.proceed();
        System.out.println("transaction commit");
    }
}

比如,连接点–数据库的记录,切点–查询条件

spring aop原理

AOP 代理其实是由 AOP
框架动态生成的一个对象,该对象可作为目标对象使用。AOP
代理包含了目标对象的全部方法,但 AOP
代理中的方法与目标对象的方法存在差异:AOP
方法在特定切入点添加了增强处理,并回调了目标对象的方法。

澳门新浦京娱乐游戏 5

Spring 的 AOP 代理由 Spring 的 IoC 容器负责生成、管理,其依赖关系也由
IoC 容器负责管理。因此,AOP 代理可以直接使用容器中的其他 Bean
实例作为目标,这种关系可由 IoC 容器的依赖注入提供。
aop开发时,其中需要程序员参与的只有 3 个部分:

  • 定义普通业务组件。
  • 定义切入点,一个切入点可能横切多个业务组件。
  • 定义增强处理,增强处理就是在 AOP 框架为普通业务组件织入的处理动作。

Spring提供了两种方式来生成代理对象:
JDKProxy和Cglib,具体使用哪种方式生成由AopProxyFactory根据AdvisedSupport对象的配置来决定。默认的策略是如果目标类是接口,则使用JDK动态代理技术,否则使用Cglib来生成代理。

  • JDK动态代理
    JDK动态代理主要涉及到java.lang.reflect包中的两个类:Proxy和InvocationHandler。InvocationHandler是一个接口,通过实现该接口定义横切逻辑,并通过反射机制调用目标类的代码,动态将横切逻辑和业务逻辑编制在一起。
    Proxy利用InvocationHandler动态创建一个符合某一接口的实例,生成目标类的代理对象。

  • CGLib动态代理
    CGLib全称为Code Generation
    Library,是一个强大的高性能,高质量的代码生成类库,可以在运行期扩展Java类与实现Java接口,CGLib封装了asm,可以再运行期动态生成新的class。和JDK动态代理相比较:JDK创建代理有一个限制,就是只能为接口创建代理实例,而对于没有通过接口定义业务方法的类,则可以通过CGLib创建动态代理。

l增强(Advice)

aop与拦截器、过滤器是什么关系

先来回顾一下拦截器与过滤器。如下图一网友的测试,在web.xml中注册了TestFilter1和TestFilter2。然后在spring的配置文件中配置了BaseInterceptor和TestInterceptor。得到的结果如下图所示。从图中可以看出,拦截器和过滤器都横切了业务方法,看似符合aop的思想。

澳门新浦京娱乐游戏 6

  • Filter过滤器:拦截web访问url地址
  • Interceptor拦截器:拦截以 .action结尾的url,拦截Action的访问
  • Spring AOP拦截器:只能拦截Spring管理Bean的访问(业务层Service)

澳门新浦京娱乐游戏 7


Ref:https://my.oschina.net/u/3080373/blog/1503693

增强是织入到目标类连接点上的一段程序代码。

在Spring中,像BeforeAdvice等还带有方位信息

l目标对象(Target)

需要被加强的业务对象

l织入(Weaving)

织入就是将增强添加到对目标类具体连接点上的过程。

l代理类(Proxy)

一个类被AOP织入增强后,就产生了一个代理类。

l切面(Aspect)

切面由切点和增强组成,它既包括了横切逻辑的定义,也包括了连接点的定义,SpringAOP就是将切面所定义的横切逻辑织入到切面所制定的连接点中。

userBiz add update del

couseBiz add update del

定义一个范围给哪些类的哪些方法

增强:加事务的控制

AOP实现者

lAspectJ

AspectJ是语言级的AOP实现,2001发布,扩展了Java语言,定义了AOP语法,能够在编译期通过提供横切代码的织入,所以它有一个专门的编译器用来生成遵守Java字节码规范的class文件

lSpringAOP

SpringAOP使用纯Java实现,在运行期通过代理的方式向目标类织入增强代码。

SpringAOP代理机制

基于JDK的动态代理,接口

基于CGlib的动态代理,类

问题

现有一个UserDao的实现,想在原有的实现基础上添加新的业务处理,怎么办?

假如有多个实现

什么是代理模式

澳门新浦京娱乐游戏 8

我们假设有一个UserDao和一个TransactionManager类

澳门新浦京娱乐游戏 9

静态代理的问题:

1,假如还有其他的dao,则还需要再创建新的proxy

2,如果目标对象的方法发生改变,比如方法名做了修改,则代理类也要做修改

基于JDK的动态代理

关键类:

InvocationHandler:其作用就是将横切逻辑代码和业务逻辑代码编织到一起的编织器。

Proxy则通过newProxyInstance根据handler来创建一个符合第三方接口的代理类

基于接口的模式

基于CGlib的动态代理

CGlib采用非常底层的字节码技术,可以为一个类创建子类,并在子类中采用方法拦截技术拦截所有父类方法的调用,并织入横切逻辑

扩展

如果要多层怎么办?

AOP小结

这种实现方式存在三个明显需要改进的地方:

1.目标类的所有方法都添加了性能监视横切逻辑,而有时,这并不是我们所期望的,我们可能只希望对业务类中的某些特定方法添加横切逻辑;

2.我们通过硬编码的方式指定了织入横切逻辑的织入点,即在目标类业务方法的开始和结束前织入代码;

3.我们手工编写代理实例的创建过程,为不同类创建代理时,需要分别编写相应的创建代码,无法做到通用。

Spring AOP的主要工作就是围绕以上三点展开:Spring

AOP通过Pointcut(切点)指定在哪些类的哪些方法上织入横切逻辑,通过Advice(增强)描述横切逻辑和方法的具体织入点(方法前、方法后、方法的两端等)。此外,Spring通过Advisor(切面)将Pointcut和Advice两者组装起来。有了Advisor的信息,Spring就可以利用JDK或CGLib的动态代理技术采用统一的方式为目标Bean创建织入切面的代理对象了。

创建增强

Spring使用增强类定义横切逻辑,同时由于Spring只支持方法连接点,增强还包括了在方法的哪一点加入横切代码的方位信息,所以增强既包含横切逻辑,还包含部分连接点的信息

增强类型

AOP联盟为增强定义了org.aopalliance.aop.Advice接口,Spring支持5种类型的增强

前置增强:org.springframework.aop.BeforeAdvice代表前置增强,因为Spring只支持方法级的增强,所以MethodBeforeAdvice是目前可用的前置增强,表示在目标方法执行前实施增强,而BeforeAdvice是为了将来版本扩展需要而定

后置增强:org.springframework.aop.AfterReturningAdvice代表后增强,表示在目标方法执行后实施增强;

环绕增强:org.aopalliance.intercept.MethodInterceptor代表环绕增强,表示在目标方法执行前后实施增强;

异常抛出增强:org.springframework.aop.ThrowsAdvice代表抛出异常增强,表示在目标方法抛出异常后实施增强;

案例:

创建切面

我们可能注意到一个问题:增强被织入到目标类的所有方法中,假设我们希望有选择地织入到目标类某些特定的方法中,就需要使用切点进行目标连接点的定位了。描述连接点是进行AOP编程最主要的工作

增强提供了连接点方位信息:如织入到方法前面、后面等,而切点进一步描述织入到哪些类的哪些方法上。

Spring通过org.springframework.aop.Pointcut接口描述切点,Pointcut由ClassFilter和MethodMatcher构成,它通过ClassFilter定位到某些特定类上,通过MethodMatcher定位到某些特定方法上,这样Pointcut就拥有了描述某些类的某些特定方法的能力。

Spring使用org.springframework.aop.Advisor接口表示切面的概念,一个切面同时包含横切代码和连接点信息

从广义上说,增强其实就是一种最简单的切面,它既包括横切代码也包括切点信息,只不过它的切点只是简单的方法相对位置的信息。所以增强一般需要和切点联合才可以表示一个更具实用性的切面。

AspectJ

Spring
AOP,它包括基于XML配置的AOP和基于@AspcetJ注解的AOP,这两种方法虽然在配置切面时的表现方式不同,但底层都是采用动态代理技术(JDK代理或CGLib代理)。Spring可以集成AspectJ,但AspectJ本身并不属于Spring
AOP的范畴。

Spring在处理@Aspect注解表达式时,需要将Spring的asm模块添加到类路径中。asm是轻量级的字节码处理框架,因为Java的反射机制无法获取入参名,Spring就利用asm处理@AspectJ中所描述的方法入参名。

澳门新浦京娱乐游戏 10

澳门新浦京娱乐游戏 11

也可以通过配置的方式来生成代理对象

澳门新浦京娱乐游戏 12

澳门新浦京娱乐游戏 13

工作原理:

首先,在配置文件中引入aop命名空间,然后通过aop命名空间的自动为Spring容器中那些匹配@AspectJ切面的Bean创建代理,完成切面织入。

澳门新浦京娱乐游戏 14

澳门新浦京娱乐游戏 15

澳门新浦京娱乐游戏 16

You can leave a response, or trackback from your own site.

Leave a Reply

网站地图xml地图