Spring AOP详解

一、AOP基本概念

AOP(Aspect Oriented Programming),即面向切面编程,可以说是OOP(Object Oriented Programming,面向对象编程)的补充和完善。OOP引入封装、继承、多态等概念来建立一种对象层次结构,用于模拟公共行为的一个集合。不过OOP允许开发者定义纵向的关系,但并不适合定义横向的关系,例如日志功能。日志代码往往横向地散布在所有对象层次中,而与它对应的对象的核心功能毫无关系对于其他类型的代码,如安全性、异常处理和透明的持续性也都是如此,这种散布在各处的无关的代码被称为横切(cross cutting),在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。

AOP技术恰恰相反,它利用一种称为"横切"的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为"Aspect",即切面。所谓"切面",简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。

使用"横切"技术,AOP把软件系统分为两个部分:核心关注点横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处基本相似,比如权限认证、日志、事物。AOP的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。

二、AOP核心概念

1、横切关注点

  对哪些方法进行拦截,拦截后怎么处理,这些关注点称之为横切关注点。

2、切面(aspect)

  类是对物体特征的抽象,切面就是对横切关注点的抽象。

3、连接点(joinpoint)

  被拦截到的点,因为Spring只支持方法类型的连接点,所以在Spring中连接点指的就是被拦截到的方法,实际上连接点还可以是字段或者构造器。

4、切入点(pointcut)

  对连接点进行拦截的定义。

5、通知(advice)

  所谓通知指的就是指拦截到连接点之后要执行的代码,通知分为前置、后置、异常、最终、环绕通知五类。

6、目标对象

  代理的目标对象。

7、织入(weave)

  将切面应用到目标对象并导致代理对象创建的过程。

8、引入(introduction)

  在不修改代码的前提下,引入可以在运行期为类动态地添加一些方法或字段。

三、AOP代理模式

1、静态代理:需要增强原有类的哪个方法,就需要对在代理类中包装哪个方法。 

  1.1、需要知道核心类(被代理类)是哪一个类,并且有哪些方法。 

  1.2、非核心的代码需要重复写多次,显得代码的结构臃肿,形成代码冗余。

  1.3、非核心类(代理类)需要实现核心类(被代理类)实现的接口,也就是他们需要实现共同的接口,但是以核心类实现的接口(被代理类)为准。

静态代理代码示例:

  示例介绍:假定学生为核心类,学生的家人是代理类。学生需要做的核心业务有:在家学习(studyAtHome)、在学校学习(studyAtHome);家人需要做的非核心业务为:准备饭菜(炒菜、煮饭)。收拾碗筷(洗碗、擦桌)。

  1)创建学生接口

 1 package com.czr.aop;
 2 
 3 /**
 4  * 定义学生接口
 5  */
 6 public interface Student {
 7     //在家里学习
 8     public void studyAtHome(String name);
 9     //在学校学习
10     public void studyAtSchool(String name);
11     
12 }

  2)创建学生实现类

package com.czr.aop;

/**
 * 学生实现类
 */
public class StudentImpl implements Student {

    @override
    public void studyAtHome(String name) {
        System.out.println(name + "在家里学习");
    }

    @override
    public void studyAtSchool(String name) {
        System.out.println(name + "在学校学习");
    }

}

  3)创建学生代理package com.czr.aop;/** * 学生代理类

*/
public class StudentProxy implements Student {
    //定义一个学生
    private Student student;

    public StudentProxy() {
        student = new StudentImpl();
    }

    public void studyAtHome(String name){
    
//这个是代理准备饭菜的需要做的流程:     System.out.println("代理:开始炒菜");     System.out.println("代理:开始煮饭");     System.out.println("-----------------");     //调用这个学生实例的在家里学习方法     student.studyAtHome(name);     //这个是代理准备收拾需要做的流程:
    System.out.println("-----------------");     System.out.println("代理 :开始洗碗");     System.out.println("代理 :开始擦桌");
    System.out.println(); }
public void studyAtSchool(String name) {     //这个是代理准备饭菜的需要做的流程:     System.out.println("代理:开始炒菜");     System.out.println("代理:开始煮饭");     System.out.println("-----------------");     //调用这个学生实例的在学校学习方法     student.studyAtSchool(name);     //这个是代理准备收拾需要做的流程:     System.out.println("-----------------");     System.out.println("代理 :开始洗碗");     System.out.println("代理 :开始擦桌");
    System.out.println(); } }

  4)创建一个测试类

package com.czr.aop;

public class Test {
    public static void main(String[] args) {
        //创建一个代理对象,并且传入相对应的参数构造出具体的实例
        Student student = new StudentProxy("小明");

        //通过这个代理对象执行相关的方法
        student.studyAtHome();
        student.studyAtSchool();
    }

}

  5)测试输出结果如下:

代理:开始炒菜
代理:开始煮饭
-----------------
小明在家里学习
-----------------
代理 :开始洗碗
代理 :开始擦桌

代理:开始炒菜
代理:开始煮饭
-----------------
小明在学校学习
-----------------
代理 :开始洗碗
代理 :开始擦桌

  总结:由上面的示例我们可以简单模拟一个静态代理的实例,我们从中可以发现,这个代理做的事情会因为核心对象业务多而变得多起来,而且这些代理做的事情都是相同的没有变化的,如果我们要修改这些流程中的某一个流程的时候会发现要改多处,而且都是相同的代码,所以这个时候使用动态代理就比较适合,静态代理适用于明确知道切入点且各切入点单一不冗余。

2、动态代理:使用反射机制,方法和对象都是传入的变量,就可以经过传入的对象和方法而动态调用被代理对象的任何方法,jdk中提供了实现此动态代理的api,被代理类必须实现接口。 

  2.1、不需要知道核心类(被代理类)具体是什么类。

  2.2、非核心类(代理类)需要实现InvocationHandler接口。 

以下做出一个基于Spring AOP动态代理实现示例:

  1)先导入Jar包:aopalliance.jar、aspectjweaver.jar

  2)定义测试接口

packege com.czr.aop.model;

public interface TestModel{
    public void printTime();
}

  3)定义测试接口实现类

packege com.czr.aop.model;

public class TestModelImpl implements TestModel{
    public void printTime(){
        System.out.println("调用 TestModelImpl.printTime() 方法");
    }

}    

  4)定义横切关注点

packege com.czr.aop;

public class TimeAop {
    public void printTime() {
        System.out.println("CurrentTime = " + System.currentTimeMillis());
    }
}

  5)定义Spring AOP aop.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:aop="http://www.springframework.org/schema/aop"
  xmlns:tx="http://www.springframework.org/schema/tx"
  xsi:schemaLocation="http://www.springframework.org/schema/beans
  http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
  http://www.springframework.org/schema/aop
  http://www.springframework.org/schema/aop/spring-aop-4.2.xsd">
        
  <bean id="testModel" class="com.czr.aop.model.TestModelImpl" />
  <bean id="timeAop" class="com.czr.aop.TimeAop" />
        
  <aop:config>
    <aop:aspect id="aspect" ref="timeAop">
      <aop:pointcut id="printTime" expression="execution(* com.czr.aop.model.*(..))" />
      <aop:before method="printTime" pointcut-ref="printTime" />
      <aop:after method="printTime" pointcut-ref="printTime" />
    </aop:aspect>
  </aop:config>
</beans>

  6)定义测试类

package com.czr.aop;

public class Test {
    public static void main(String[] args) {
        ApplicationContext ctx = 
            new ClassPathXmlApplicationContext("aop.xml");
        
        TestModel tm = (TestModel)ctx.getBean("testModel");
        tm .printTime();
    }

}

  7)测试输出结果:

CurrentTime = 1556179611998
调用 TestModelImpl.printTime() 方法
CurrentTime = 1556179612005

 

posted @ 2018-11-14 18:23  小码哥**  阅读(470)  评论(0编辑  收藏  举报