天天看點

Spring的AOP面向切面程式設計

一、AOP的概述

AOP是基于反射與動态代理的實作,java有反射的中間層,讓AOP的實作更加簡單,而C++想實作相比會較為困難。

AOP為:(aspect oriented programming),面向切面程式設計。是對OOP的補充,彌補OOP的不足。

作用:在不修改源碼的前提下,給項目增加新的公共功能,如:權限,日志,事務(資料庫中同時成功或同時失敗的操作的集合)。

二、AOP的組成要數

分别有以下幾點:

  • advice:通知。即新增的功能。比如寫日志。
  • target:目标。通知加入的目标類。
  • joinpoint:切點。通知加入的目标類的具體位置。
  • pointcut:切線。所有切點的集合。
  • aspect:切面。切線+通知。
  • weaving:動詞。織入。
  • introduction:在不修改代碼的前提下,引入可以在運作期為類動态地添加一些方法或字段。

三、AOP原理

基于設計模式之“代理模式”。agent/proxy。

代碼實作上面分為:靜态代理 和 動态代理。

代理模式的原理:代理對象和被代理對象實作同樣的業務接口,由代理對象代替其

完成業務行為,并增加“附加行為”。

例如:我想去動物園買票,傳統方式是直接到動物園門口購票。而現在我可以找個人代替我買票,那個人就是代理人,而他也要向我索取金錢(成本+代理費。。)之類的附加行為,就是代理業務。

而現在的AOP就是那個代理人,替我們去做事的代理人。

注意:spring實作aop采用的”動态代理”的方式。動态代理意思是,去幫你做事的代理對象不是固定的,是用Spring動态建立的。

四、Spring對AOP的支援

Spring中AOP代理由Spring的IOC容器負責生成、管理,其依賴關系也由IOC容器負責管理。是以,AOP代理可以直接使用容器中的其它bean執行個體作為目标,這種關系可由IOC容器的依賴注入提供。

AOP通知日志的簡單執行個體,建立一個User接口,具有登陸與注冊兩個方法:

public interface User {
    void login();
    void register();
}
           

建立一個代理對象UserImpl,實作User接口:

public class UserImpl implements User {

    private String role;

    public String getRole() {
        return role;
    }

    public void setRole(String role) {
        this.role = role;
    }

    @Override
    public void login() {
        System.out.println("完成登入的業務方法!");
    }

    @Override
    public void register()  {
        System.out.println("完成注冊的業務方法!");
    }
}
           

建立一個通知類對象MyAdvice:

public class MyAdvice {

    public void beforelog()
    {
        System.out.println("這是前置通知的日志。");
    }

    public void afterlog()
    {
        System.out.println("這是後置通知的日志。");
    }

    public void aroundlog(ProceedingJoinPoint p) throws Throwable
    {
        System.out.println("環繞前的日志!");
        p.proceed();  //讓原來的業務方法執行!
        System.out.println("環繞後的日志!");
    }


    public void throwlog()
    {
        System.out.println("這是異常通知的日志");
    }
}
           

在applicationContext.xml檔案配置頭部:

<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"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
    http://www.springframework.org/schema/aop
    http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
           

接着配置其代理對象,和通知類對象及織入關系

<!-- 建立1個業務實作類對象 -->
    <bean id="user" class="com.sunsharing.entity.UserImpl">
    </bean>

    <!--建立1個自定義通知類對象 -->
    <bean id="myadvice" class="com.sunsharing.entity.MyAdvice">

    </bean>

    <!-- 配置 織入關系 -->
    <aop:config>
    <!-- 定義1條切線 -->
      <aop:pointcut expression="execution(* com.sunsharing.entity.UserImpl.login(..))" id="mypc"/> <!--這裡監聽的是login方法-->
      <!--配置切面 ,引用自定義通知對象-->
      <aop:aspect ref="myadvice">
         <aop:before method="beforelog" pointcut-ref="mypc"/> 

         <aop:after method="afterlog" pointcut-ref="mypc"/>

         <aop:around method="aroundlog" pointcut-ref="mypc"/>

         <aop:after-throwing method="throwlog" pointcut-ref="mypc"/>
      </aop:aspect>
    </aop:config>
           

最後,在TestUser測試類中進行日志測試:

import com.sunsharing.entity.User;

import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TestUser {

    @org.junit.Test
    public void test() {
        BeanFactory fac = new ClassPathXmlApplicationContext("applicationContext.xml");
        User user = (User) fac.getBean("user");
        user.login();
    }
}
           

輸出結果:

Spring的AOP面向切面程式設計

其中還有個抛出異常的日志監聽,在register方法抛出個自定義異常試試。

@Override
    public void register()  {
        throw new RuntimeException("這是注冊的異常!");
        //System.out.println("完成注冊的業務方法!");
    }
           

然後把applicationContext中的監聽方法改為rigister

測試類中運作後,輸出的結果為:

Spring的AOP面向切面程式設計

以上是日志功能的實作,接下來再講個權限功能的實作

五、AOP的權限功能

沿用以上的例子,加個周四無法注冊的權限控制。

建立一個MyRegAdvice的自定義通知類:

package com.sunsharing.entity;

import java.util.Calendar;

import org.aspectj.lang.ProceedingJoinPoint;

public class MyRegAdvice
{
    //這是環繞通知
    public void checkday(ProceedingJoinPoint p) throws Throwable
    {
        Calendar c = Calendar.getInstance();

        int day = c.get(Calendar.DAY_OF_WEEK);
        if(day==)//周日是第一天為1,是以周四是5,親測!
        {
            System.out.println("周四無法注冊!");
            return;
        }

        p.proceed(); //其他情況,允許往前走。
    }
}
           

建立一個applicationContext_register.xml配置檔案:

<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"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
    http://www.springframework.org/schema/aop
    http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">

    <!-- 建立1個業務實作類對象 -->
    <bean id="user" class="com.sunsharing.entity.UserImpl">

    </bean>

    <!--建立1個自定義通知類對象 -->
    <bean id="myadvice" class="com.sunsharing.entity.MyRegAdvice">

    </bean>

    <!-- 配置 織入關系 -->
    <aop:config>
    <!-- 定義1條切線 -->
      <aop:pointcut expression="execution(* com.sunsharing.entity.UserImpl.reg*(..))" id="mypc"/>
      <!--配置切面 ,引用自定義通知對象-->
      <aop:aspect ref="myadvice">
        <aop:around method="checkday" pointcut-ref="mypc"/>
      </aop:aspect>
    </aop:config>
</beans>
           

最後在測試類進行測試,測試類完全不用改動。

輸出結果為:

Spring的AOP面向切面程式設計