疯狂java


您现在的位置: 疯狂软件 >> 新闻资讯 >> 正文

AOP之SpringAOP实现原理


 

   

  OOP的完美点缀—AOP之SpringAOP实现原理

  前言

  OOP与AOP

  OOP(Object Oriented Programming,面向对象编程),通过封装、继承将程序抽象为各个层次的对象,进而组合为模块或者程序,达到了软件工程中的重用性、灵活性、扩展性。程序的运行笼统地可以看为各层次对象之间的相互调用。

  AOP(Aspect Oriented Programming,面向切面编程),将程序运行过程分解为一个个的切面,对特定的切面(某个步骤或者阶段)进行提取,达到解耦各种不同逻辑代码。

  OOP是在程序分块层面上进行考虑,而AOP则是在程序运行的切面上进行考虑。

  可以将AOP理解为一种无损伤型的”切面”激光手术刀。OOP使属性和行为被封装为了一个严实、密不透风的对象。想要改变由对象保护着的方法,就可以通过该激光手术刀,既不损伤对象(即是,不破坏对象的封装),又不添加任何冗余的代码,实现对原对象的方法的增强处理。

  不得不说,AOP实在是一种巧妙的编程思想!!!弥补了OOP中一些难以解决的问题。例如,

  1. 类应该是纯净的,不应含有与本身无关的逻辑。如日志跟踪的逻辑代码。这样的类就可以更好地重用,更有效地被AOP的切入更多的业务逻辑, 举例代码如下:

  /*

  * 假如需要记录某只柯基狗的日常,

  * 我们总不能让它自己来记录吧??

  * 如 下面的注释了的方法

  * 看起来是不是非常怪异,一只狗狗自己给自己写日记

  */

  class dog{

  void run(){

  /*note.write("散步了");*/

  }

  void sleep(){

  /*note.write("又睡懒觉了");*/

  }

  }

  2. OOP为不同类别的对象引入公共方法时,往往力不从心,造成大量的分散的重复代码,重用性真的很差,每次都要改动真的很麻烦。

  class dog{

  private Note note = new Note();

  void run(){

  note.write("散步了");

  }

  void sleep(){

  note.write("又睡懒觉了");

  }

  }

  本文中”特定处理”指的日志记录、事务管理、异常处理等等之类的各种通用的业务逻辑

  AOP实现原理

  主要分为两大类:

  一是采用动态代理,对被代理对象和特定处理进行修饰和封装,得到代理对象,从使得被代理对象中不需切入任何的代码,如图:

  简单的代理:实现不入侵原始对象而切入各种通用的业务逻辑(eg: 参数验证、日志记录方法等)

  interface Interface{

  void doSomething();

  }

  /*原始对象*/

  class RealObject implements Interface{

  @Override

  public void doSomething() {

  System.out.println("原始对象的行为");

  }

  }

  /*代理*/

  class SimplProxy implements Interface {

  private Interface proxied;

  public SimplProxy(Interface proxied){

  this.proxied = proxied;

  }

  public void doSomething(){

  System.out.println("处理一些通用的业务逻辑, 如参数校验等等");

  proxied.doSomething();

  }

  }

  /*调用者*/

  class Caller{

  public static void call(Interface iface){

  iface.doSomething();

  }

  public static void main(String[] args){

  call(new SimplProxy(new RealObject()));

  }

  }

  /*输出:*/

  1.处理一些通用的业务逻辑, 如参数校验等等

  2.原始对象的行为

  就这样,一些通用的业务逻辑被代理简单地切入到了原始对象之前执行

  二是采用静态织入,如AspectJ,使用其特定的语法创建切面,在编译期间将切面织入代码中。又如,通过AspectJ编译出来的class文件与普通编译出来的有很大区别,这块没有了解,不再赘述。

  AOP使用场景

  权限控制、异常处理、缓存、事务管理、日志记录、数据校验等等

  AOP基本概念

  切面(Aspect): 程序运行过程中的某个的步骤或者阶段

  连接点(Joinpoint): 程序运行过程中可执行特定处理(增强处理)的点, 如异常处理。而在SpringAOP中,方法调用是连接点。

  Advice(通知、处理、增强处理): 在符合的连接点进行的特定处理 (增强处理)

  切入点(Pointcut): 可切入进行增强处理的连接点。AOP核心之一就是如何用表达式来定义符合的切入点。在Spring中,默认使用AspectJ的切入点语法。

  由于Spring AOP只支持以Spring Bean的方法调用来作为连接点, 所以在这里切入点的定义包括:

  切入点表达式, 来限制该能作用的范围大小,即是,能匹配哪些bean的方法

  命名切入点

  @Pointcut("execution(* com.xxx.xxx.service.*.* (..)) ") /*切入点*/

  public void pointCutExpress(){   /*命名切入点*/

  }

  /*常见的 切入点表达式语法 如下:*/

  execution(返回值类型 方法所属类.方法名 方法形参列表 抛出的异常声明)

  例如:

  上面第一个 * 指匹配所有返回值类型

  第二个 * 指service包下的所有类

  第三个 * 指各个类中的所有方法

  (..)中的 .. 指零个或者多个(任意数量)的参数

  目标对象: 被进行增强处理的对象

  AOP代理: 是一个重新封装了(增强处理 + 被代理对象的方法 )方法的代理对象。

  织入(Weaving): 就如它名字表述的一样,增强处理切入目标对象以后,并获得代理对象(AOP代理)的这个过程,就是织入。按其作用的时间分类为,编译时织入与运行时织入。

  SpringAOP的使用方法

  基于注解的零配置方式:

  一、启动@AspectJ注解支持,旭在相应的容器内,加入如下片段:

  

  

  

  二、定义切面bean

  @Aspect

  @Compement

  public class Advice{

  /*定义切入点, 业务处理逻辑等等其他内容*/

  }

  三、定义@Before、@After、@Around等增强处理

  /*定义切入点表达式*/

  /*配置匹配service包下所有的类、所有的方法*/

  @Pointcut("execution(* com.xxx.xxx.service.*.*(..))")

  public void pointCutExpress(){

  }

  四、定义处理方法

  @After("pointCutExpress()")

  public void closeResource(){

  /*After注解,更适合于释放资源*/

  }

  通过注解和动态代理实现简单AOP

  一、切入点注解

  @Target(ElementType.METHOD)

  @Retention(RetentionPolicy.RUNTIME)

  @Documented

  public @interface MyPointCut {

  /*模拟简单的注解定义切入点*/

  public String expression();

  }

  二、原始对象及接口

  interface SomeMethods{

  void method1();

  void method2(String args);

  }

  public class Implementation implements SomeMethods{

  @Override

  public void method1() {

  System.out.println("原始对象的方法1");

  }

  @Override

  public void method2(String args) {

  System.out.println("原始对象的方法2及参数:" + args);

  }

  }

  三、动态代理工厂

  class MyAspect{

  @MyPointCut(expression = "com.auhnayuil.comm.Implementation.*")

  public void Logger(){

  System.out.println(">>>>>>>>>>>>>>>>>正在记载日志中<<<<<<<<<<<<<<<<<<<<<<<");

  }

  }

  class SimpleProxyFactory{

  /*简单代理工厂*/

  public static Object getProxyObject(final Object realObject, final Object aspectObject){/*代理对象 切面定义类*/

  final Class realObjectClass = realObject.getClass();

  final Class aspectObjectClass = aspectObject.getClass();

  return Proxy.newProxyInstance(

  realObjectClass.getClassLoader(),

  realObjectClass.getInterfaces(),

  new InvocationHandler(){

  /*模拟简单的@Before日志注解*/

  @Override

  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

  /*加载切入点信息. 这里的方法Logger被硬编码了, 后期可以根据注解来解决*/

  Method pointCutMethod = aspectObjectClass.getMethod("Logger", new Class[] {});

  MyPointCut myPointCut = pointCutMethod.getAnnotation(MyPointCut.class);

  /*判断切入点, 并执行其方法*/

  String expression = myPointCut.expression();

  String[] express = expression.split("\.");

  int exprLength = express.length;

  if("*".equals(express[exprLength - 1])){

  /*这里只演示一种情况*/

  pointCutMethod.invoke(aspectObject, new Class[] {});

  }

  /*执行原始对象的方法*/

  return method.invoke(realObject, args);

  }

  }

  );

  }

  }

  public class ProxyDemo {

  public static void main(String[] args){

  SomeMethods someMethods = new Implementation();

  SomeMethods proxyObject = (SomeMethods) SimpleProxyFactory.getProxyObject(someMethods, new MyAspect());

  proxyObject.method1();

  proxyObject.method2("auhnayuiL");

  }

  }

  四、输出结果

  >>>>>>>>>>>>>>>>>正在记载日志中<<<<<<<<<<<<<<<<<<<<<<<

  原始对象的方法1

  >>>>>>>>>>>>>>>>>正在记载日志中<<<<<<<<<<<<<<<<<<<<<<<

  原始对象的方法2及参数:auhnayuiL