天天看点

Spring(5)AOP-Aspect Oriented Programming1 AOP简介2 原理+快速入门案例3 通知

1 AOP简介

①简介

AOP(Aspect Oriented Programming) 面向切面编程,是对一类或是所有对象编程。

核心:在不增加、改变代码的基础上,增加新的功能。

aop编程,在框架开发中,应用很多;在实际开发中,用的不是很多。

编程阶段:面向机器(汇编)->面向过程->面向对象->面向切面

②相关术语

  • 切面(aspect):要实现的交叉功能,是系统模块化的一个切面或领域。如日志记录。
  • 连接点:应用程序执行过程中插入切面的地点,可以使方法调用,异常抛出,或是要修改的字段。
  • 通知:切面的实际实现,他通知系统新的行为。如在日志通知包含了实现日志功能的代码,如向日志文件写内容。通知在连接点插入到应用系统中。
  • 切入点:定义了通知应该应用在哪些连接点,通知可以应用到AOP框架支持的任何连接点。
  • 引入:为类添加新的方法和属性
  • 目标对象:被通知的对象。既可以是你编写的类也可以是第三方类。
  • 代理:将通知应用到目标对象后创建的对象,应用系统的其他部分不用为了支持代理对象而改变。
  • 织入:将切面应用到目标对象,从而创建一个新的代理对象的过程。织入发生在目标对象生命周期的多个点上:

    编译期:切面在目标对象编译时织入,这需要一个特殊的编译器

    类装载期:切面在目标对象被装载入JVM时织入,这需要一个特殊的类装载器

    运行期:切面在应用系统运行时织入

    Spring(5)AOP-Aspect Oriented Programming1 AOP简介2 原理+快速入门案例3 通知

③AOP实现细节

spring的运行时通知对象;

spring在运行期间创建代理,不需要特殊的编译器。

spring有两种代理方式:

 1 若目标对象实现了若干接口

 spring使用JDK的java.lang.reflect.Proxy类代理。该类让spring动态产生一个新类,它实现了所需的接口,织入通知,并且代理对目标对象的所有请求。

 2 若目标没有实现任何接口

 spring使用CGLIB库生成目标对象的子类。使用该方式时需要注意。

  - 对接口创建代理优先对类创建代理,因为会产生更加松耦合的系统;对类代理是让遗留系统或无法实现接口的第三方类库同样可以得到通知。这种方式应该是备用方案。

  - 标记为final的方法不能够被通知。spring是为目标类产生子类。任何需要被通知的方法都被复写,将通知织入。final方法是不允许重写的。

spring实现了aop联盟接口。

**spring只支持方法连接点;不提供属性连接点。**spring的观点是属性拦截破坏了封装。面向对象的概念是对象自己处理工作,其他对象只能通过方法调用得到的结果。

④定义切入点,可以使用正则表达式

Spring(5)AOP-Aspect Oriented Programming1 AOP简介2 原理+快速入门案例3 通知
<!-- 定义前置通知的切入点 -->
<bean id="myMethodBeforeAdviceFilter" class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor">
    <property name="advice" ref="myMethodBeforeAdvice"/>
    <property name="mappedNames">
        <list>
            <!-- 只对sayBye方法通知 -->
            <value>say*</value>
        </list>
    </property>
</bean>
           

2 原理+快速入门案例

开发步骤:

- 定义接口

- 编写对象(被代理对象==目标对象)

- 编写通知(前置通知- - -目标方法调用前调用)

- 在beans.xml文件中配置

- 配置被代理对象

- 配置通知

- 配置代理对象,是ProxyFactoryBean的对象实例

  - - 代理接口集proxyInterfaces

  - - 织入通知interceptorNames

   - - 配置被代理对象target

①定义接口

public interface TestServiceInter {
    public void sayHello();
}
public interface TestServiceInter2 {
    public void sayBye();
}
           

②编写对象- - -被代理对象=目标对象

public class Test1Service implements TestServiceInter, TestServiceInter2 {

    private String name;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    // 连接点(静态)
    @Override
    public void sayHello() {
        System.out.println("hi " + name);
    }
    // 连接点(静态)
    @Override
    public void sayBye() {
        System.out.println("bye " + name);
    }

}
           

③编写通知

// - - -前置通知- -目标方法调用前调用
public class MyMethodBeforeAdvice implements MethodBeforeAdvice {
    /**
     * method:被调用的方法名
     * args:给method传递的参数
     * target:目标对象
     */
    public void before(Method method, Object[] args, Object target)
            throws Throwable {
        // 切面:该记录日志功能
        // 通知:切面的具体实现
        System.out.println("记录日志 " + method.getName() + " " + target.getClass());
    }
}
// - - -后置通知- -目标方法调用后调用
public class MyAfterReturningAdvice implements AfterReturningAdvice {

    @Override
    public void afterReturning(Object returnValue, Method method,
            Object[] args, Object target) throws Throwable {
        System.out.println("关闭资源  " + method.getName() + " " + target.getClass());

    }
}
           

④在beans.xml文件中配置

<?xml version="1.0" encoding="utf-8"?>

<beans>

<!-- 配置被代理的对象 -->
<bean id="test1Servece" class="com.test.aop.Test1Service">
    <property name="name" value="jiaozl"></property>
</bean>

<!-- 配置前置通知 -->
<bean id="myMethodBeforeAdvice" class="com.test.aop.MyMethodBeforeAdvice"/>
<!-- 配置后置通知 -->
<bean id="myAfterReturningAdvice" class="com.test.aop.MyAfterReturningAdvice"/>

<!-- 配置代理对象 

ProxyFactoryBean implements TestServiceInter {
    public void sayHello() {};
}

-->
<bean id="proxyFactoryBean" class="org.springframework.aop.framework.ProxyFactoryBean">
    <!-- 代理接口集 -->
    <property name="proxyInterfaces">
        <list>
            <value>com.test.aop.TestServiceInter</value>
            <value>com.test.aop.TestServiceInter2</value>
        </list>
    </property>
    <!-- 把通知织入到代理对象 -->
    <property name="interceptorNames">
        <list>
            <!-- 相当于把前置通知(也可以把通知看成拦截器)和代理对象关联起来 -->
            <value>myMethodBeforeAdvice</value>
            <value>myAfterReturningAdvice</value>
        </list>
    </property>
    <!-- 配置被代理对象,可以指定 -->
    <property name="target" ref="test1Servece"/>
</bean>

</beans>
           

⑤调用

public static void main(String[] args) {
    ApplicationContext ac = new ClassPathXmlApplicationContext("com/test/aop/beans.xml");

    TestServiceInter tsi =  (TestServiceInter) ac.getBean("proxyFactoryBean");
    // 通知织入后,连接点变为-->切入点(动态)
    tsi.sayHello();
    TestServiceInter2 tsi2 =  (TestServiceInter2) tsi;
    tsi2.sayBye();
}
           

3 通知

----------------------------

通知类型  接口         描述

----------------------------

Around  MethodInterceptor  拦截对目标方法调用

Before  MethodBeforeAdvice  在目标方法调用前调用

After   AfterReturningAdvice 在目标方法调用后调用

Throws  ThrowsAdvice     当目标方法抛出异常时调用

-----------------------------

①Around通知:

该通知能够控制目标方法是否真的被调用;

同时也可以控制返回的对象。

public class MyMethodInterceptor implements MethodInterceptor {
    @Override
    public Object invoke(MethodInvocation arg0) throws Throwable {
        // TODO Auto-generated method stub
        System.out.println("调用前....");
        Object obj = arg0.proceed(); // 调用的方法
        System.out.println("调用后....");
        return obj;
    }
}
           

②Throws通知:

该接口是标识性接口,没有任何方法,但实现该接口的类必须要有如下形式的方法:

public void afterThrowing(Exception e);
public void afterThrowing(Method method, Object[] os, Object target, Exception e);
           

第一个方法只接受一个参数:需要抛出的异常。

第二个方法接受异常、被调用的方法、参数以及目标对象

public class MyThrowsAdvice implements ThrowsAdvice {
     public void afterThrowing(Exception e){
         System.out.println("000出大事了  " + e.getMessage());
     }
     public void afterThrowing(Method method, Object[] os, Object target, Exception e){
         System.out.println(method+ " 111出大事了  " + e.getMessage());
     }
}
           

③ 引入通知

以前定义的通知类型是在目标对象的方法被调用的周围织入。引入通知给目标对象添加新的方法和属性。

只需要在xml配置文件中进行配置:

<?xml version="1.0" encoding="utf-8"?>
<beans>

<!-- 配置被代理的对象 -->
<bean id="test1Servece" class="com.test.aop.Test1Service">
    <property name="name" value="jiaozl"></property>
</bean>

<!-- 配置前置通知 -->
<bean id="myMethodBeforeAdvice" class="com.test.aop.MyMethodBeforeAdvice"/>
<!-- 配置后置通知 -->
<bean id="myAfterReturningAdvice" class="com.test.aop.MyAfterReturningAdvice"/>
<bean id="myMethodInterceptor" class="com.test.aop.MyMethodInterceptor"/>
<bean id="myThrowsAdvice" class="com.test.aop.MyThrowsAdvice"/>
<!-- 定义前置通知的切入点 -->
<bean id="myMethodBeforeAdviceFilter" class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor">
    <property name="advice" ref="myMethodBeforeAdvice"/>
    <property name="mappedNames">
        <list>
            <!-- 只对sayBye方法通知 -->
            <value>sayBye</value>
        </list>
    </property>
</bean>

<!-- 配置代理对象 

ProxyFactoryBean implements TestServiceInter {
    public void sayHello() {};
}

-->
<bean id="proxyFactoryBean" class="org.springframework.aop.framework.ProxyFactoryBean">
    <!-- 代理接口集 -->
    <property name="proxyInterfaces">
        <list>
            <value>com.test.aop.TestServiceInter</value>
            <value>com.test.aop.TestServiceInter2</value>
        </list>
    </property>
    <!-- 把通知织入到代理对象 -->
    <property name="interceptorNames">
        <list>
            <!-- 使用自定义切入点来控制前置通知 -->
            <value>myMethodBeforeAdviceFilter</value>
            <value>myAfterReturningAdvice</value>
            <value>myMethodInterceptor</value>
            <value>myThrowsAdvice</value>

        </list>
    </property>
    <!-- 配置被代理对象,可以指定 -->
    <property name="target" ref="test1Servece"/>
</bean>

</beans>