ssm系列之SpringAop(面向切面編程)

一、簡介

1、Aop介紹
AOP爲Aspect Oriented Programming的縮寫,意爲:面向切面編程,通過預編譯方式和運行期動態代理實現程序功能的統一維護的一種技術,也是Spring框架的重要內容。AOP是基於IOC容器之上的,能對IOC容器做出更詳細的操作。利用AOP可以對業務邏輯的各個部分進行隔離,從而使得業務邏輯各部分之間的耦合度降低,提高程序的可重用性,同時提高了開發的效率。
2、名詞解釋
(1)、切面(Aspect):一個橫切功能的模塊化,也即通知所在的類,它能應用於多個業務類。
(2)、切入點(Pointcut):可以理解爲要插入某個方法。
(3)、通知(Advice):通知分爲前置通知、後置通知、異常通知、最終通知和環繞通知。其中環繞通知的功能是最強大的。
a、前置通知(Before Advice):在切入點執行之前插入的通知。
b、後置通知(After Returning Advice):在切入點執行完畢之後插入的通知。
c、異常通知(After Throwing Advice):當切入點拋出異常時插入的通知。
d、最終通知(After Finnally Advice):當切入點執行完畢時插入的通知(不論是正常返回還是異常退出)。
e、環繞通知(Around Advice):可以貫穿切入點執行的整個過程,可以獲取目標方法的全部控制權(目標方法是否執行、執行前、執行後、參數、返回值等),底層是通過攔截器實現的。
3、個人理解
我個人將實現AOP的類分爲業務類(一般情況下的類)與通知類。具體步驟爲在通知類中先實現通知,然後編寫好業務類,最後通過連接把通知方法與業務方法連接起來。這就是一個面向切面編程程序。通知都能獲取到業務方法的各種屬性,且獲取屬性的方式也各有不同,詳情請看第三大點。

二、實現通知的三種方式

(一)、通過使用接口實現通知

1、編寫類
1)、編寫通知類,實現通知類的接口
2)、編寫業務類,確定給什麼方法添加通知

2、Ioc容器配置
1)、將業務類、通知納入springIoc容器
2)、確定連接線
3)、定義切入點(連接線的一端)、定義通知類(連接線的另一端)
4)、通過pointcut-ref將兩端連接起來

3、不同通知需要實現的接口與方法
1)、前置通知
a、編寫MethodBeforeAdvice接口的實現類,
b、重寫before方法

2)、後置通知
a、編寫AfterReturningAdvice接口的實現類,
b、重寫afterReturning方法

3)、異常通知
a、編寫ThrowsAdvice接口的實現類,
b、”編寫“ afterThrowing方法
public void afterThrowing([Method, args, target], ThrowableSubclass)

4)、環繞通知
a、編寫MethodInterceptor接口的實現類,
b、重寫invoke方法

4、部分代碼
配置代碼

<!-- 配置業務類 -->
    <!-- addStudent()方法所在的類 -->
    <bean id="studentDao" class="impl.StudentDaoimpl">
    </bean>
    <bean id="studentService" class="service.StudentServicelmpl">
        <property name="studentDao" ref="studentDao"></property>
    </bean>
    <!-- 前置通知類 -->
    <!-- 將通知納入springIoc容器 =============
    連接線的一方:業務類的具體方法=============-->
    <bean id="LogBefore" class="aop.LogBefore"></bean>
     <!--addStudent()和通知進行關聯-->
    <aop:config>
        <!-- 配置切入點(在哪裏執行通知)
        expression中添加要連接的方法
        =============連接線的另一端:通知類=============-->
        <aop:pointcut id="pointcut" expression="execution(public void service.StudentServicelmpl.addStudent(cn.chern.Student)) or execution(public void service.StudentServicelmpl.deleteStudentByNo(int))"/>
        <!-- ============advisor:連接線,連接切入點和切面的線==============-->
        <aop:advisor advice-ref="LogBefore" pointcut-ref="pointcut"></aop:advisor>
    </aop:config>

通知類代碼

//前置通知
public class LogBefore implements MethodBeforeAdvice {
    @Override
    public void before(Method method, Object[] objects, Object o) throws Throwable {
        System.out.println("前置通知即將執行。。。。");
    }
}

(二)、使用註解實現通知

1、編寫類
1)、編寫通知類,在類名前加註解:@Component()@Aspect
@Component():在括號中寫下該類的命名,在IOC容器中添加掃描器後就等於將該類納入IOC容器中。
@Aspect:聲明該類是一個通知
2)、編寫業務類,確定給什麼方法添加通知

2、Ioc容器配置
1)、開啓註解對AOP的支持
2)、將該類所在的包導入掃描器中

3、不同的通知需要添加的註解(通知都是通過方法實現的)
1)、前置通知:在方法前添加@Before()註解
2)、後置通知:在方法前添加@AfterReturning()註解
3)、異常通知:在方法前添加@AfterThrowing註解
4)、最終通知:在方法前添加@After註解
5)、環繞通知:在方法前添加@Around註解
()裏添加需要連接的方法

(三)、通過配置實現通知

1、概念:也即基於Schema配置,Schema就是頭文件
2、實現步驟
1)、編寫一個普通類
2)、將該類通過配置轉爲一個通知
3)、編寫一個業務類
4)、在IOC容器中連接兩個類

三、具體代碼

xml配置文件
spring-config.xml

<?xml version="1.0" encoding="UTF-8"?>
<!--配置掃描器必須引入的xmlns 屬性: xmlns:context="http://www.springframework.org/schema/context" -->
<!--配置aop的通知必須引入的xmlns 屬性: xmlns:aop="http://www.springframework.org/schema/aop" -->
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"
       
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:aop="http://www.springframework.org/schema/aop">

//現在業務類中注入值
<bean id="student" class="cn.chern.Student">
            <property name="stuNo" value="2"></property>
            <property name="stuName" value="ls"></property>
            <property name="stuAge" value="24"></property>
        </bean>



<!--========================================== 接口實現通知 ===============================================-->

    <!-- 配置業務類 -->
    <!-- addStudent()方法所在的類 -->
    <bean id="studentDao" class="impl.StudentDaoimpl">
    </bean>
    <bean id="studentService" class="service.StudentServicelmpl">
        <property name="studentDao" ref="studentDao"></property>
    </bean>
    <!-- 前置通知類 -->
    <!-- 將通知納入springIoc容器 =============
    連接線的一方:業務類的具體方法=============-->
    <bean id="LogBefore" class="aop.LogBefore"></bean>
     <!--addStudent()和通知進行關聯-->
    <aop:config>
        <!-- 配置切入點(在哪裏執行通知)
        =============連接線的另一端:通知類=============-->
        <aop:pointcut id="pointcut" expression="execution(public void service.StudentServicelmpl.addStudent(cn.chern.Student)) or execution(public void service.StudentServicelmpl.deleteStudentByNo(int))"/>
        <!-- ============advisor:連接線,連接切入點和切面的線==============-->
        <aop:advisor advice-ref="LogBefore" pointcut-ref="pointcut"></aop:advisor>
    </aop:config>

    <!-- 後置通知類 -->
    <bean id="LogAfter" class="aop.LogAfter"></bean>
    <aop:config>
        <aop:pointcut id="pointcut2" expression="execution(public void service.StudentServicelmpl.addStudent(cn.chern.Student))"/>
        <aop:advisor advice-ref="LogAfter" pointcut-ref="pointcut2"></aop:advisor>
    </aop:config>

    <!-- 異常通知類 -->
    <bean id="LogException" class="aop.LogException"></bean>
    <aop:config>
        <aop:pointcut id="pointcut3" expression="execution(public void service.StudentServicelmpl.addStudent(cn.chern.Student))"/>
        <aop:advisor advice-ref="LogException" pointcut-ref="pointcut3"></aop:advisor>
    </aop:config>

    <!-- 環繞通知類 -->
    <bean id="LogAround" class="aop.LogAround"></bean>
    <aop:config>
        <aop:pointcut id="pointcut4" expression="execution(public void service.StudentServicelmpl.addStudent(cn.chern.Student))"/>
        <aop:advisor advice-ref="LogAround" pointcut-ref="pointcut4"></aop:advisor>
    </aop:config>



    <!--========================================== 註解實現通知 ===============================================-->
        <!-- 配置掃描器 -->
    <context:component-scan base-package="aop"></context:component-scan>
    <!-- 開啓註解對AOP的支持 -->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>




    <!--========================================== schema配置實現通知 ===============================================-->

    <!-- 將通知納入springIoc容器
        =============連接線的一方:業務類的具體方法=============-->
    <bean id="LogSchema" class="aop.LogSchema"></bean>
    <!--addStudent()和通知進行關聯-->
    <aop:config>
        <!-- 配置切入點(在哪裏執行通知)
        =============連接線的另一端:通知類=============-->
        <aop:pointcut id="pcSchema" expression="execution(public void service.StudentServicelmpl.addStudent(cn.chern.Student))"/>
        <aop:aspect ref="LogSchema">
            <!-- 連接線:連接業務addStudent 和 通知before -->
            <aop:before method="before" pointcut-ref="pcSchema"></aop:before>
            <!-- 連接線:連接業務addStudent 和 通知afterReturning -->
            <aop:after-returning method="afterReturning" pointcut-ref="pcSchema" returning="returningValue"></aop:after-returning>
            <aop:after-throwing method="whenException" pointcut-ref="pcSchema" throwing="e"></aop:after-throwing>
            <aop:around method="around" pointcut-ref="pcSchema"></aop:around>


        </aop:aspect>
    </aop:config>
</beans>

業務類
Student.java

package cn.chern;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Student {
    private int stuNo;
    private String stuName;
    private int stuAge;

    public int getStuNo() {
        return stuNo;
    }

    public void setStuNo(int stuNo) {
        this.stuNo = stuNo;
    }

    public String getStuName() {
        return stuName;
    }

    public void setStuName(String stuName) {
        this.stuName = stuName;
    }

    public int getStuAge() {
        return stuAge;
    }

    public void setStuAge(int stuAge) {
        this.stuAge = stuAge;
    }
}

使用接口實現通知的四個java文件
LogBefore.java

package aop;
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;
//前置通知
public class LogBefore implements MethodBeforeAdvice {
    @Override
    public void before(Method method, Object[] objects, Object o) throws Throwable {
        System.out.println("【接口】前置通知即將執行。。。。");
    }
}

LogAfter.java

package aop;
import org.springframework.aop.AfterReturningAdvice;
import java.lang.reflect.Method;

public class LogAfter implements AfterReturningAdvice {
    @Override
    public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable {
        //o1爲調用的目標對象,method爲調用的方法,objects爲調用的方法的參數個數,o爲方法的返回值
        System.out.println("【接口】後置通知:目標對象"+o1+"調用的方法名:"+method.getName()+",方法的參數個數:"+objects.length+"方法的返回值爲:"+o);
    }
}

LogException.java

package aop;
import org.springframework.aop.ThrowsAdvice;
import java.lang.reflect.Method;

public class LogException implements ThrowsAdvice {
    //異常通知的具體方法
    public void afterThrowing(Method method, Object[] args, Object target, Throwable ex){
        System.out.println("【接口】異常通知:目標對象"+target+"調用的方法名:"+method.getName()+",方法的參數個數:"+args.length+",異常類型:"+ex.getMessage());
    }
}

LogAround .java

package aop;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

public class LogAround implements MethodInterceptor {
    @Override
    public Object invoke(MethodInvocation methodInvocation) throws Throwable {
        Object result = null;
        try{
            System.out.println("【接口-環繞】前置通知。。。");
            //methodInvocation.proceed()方法前的代碼爲:前置通知
            result = methodInvocation.proceed();//控制着目標方法執行,不寫的話目標方法不執行
            //methodInvocation.proceed()方法後的代碼爲:後置通知
            System.out.println("【接口-環繞】後置通知:");
            System.out.println("目標對象"+methodInvocation.getThis()+"\n調用的方法名:"+methodInvocation.getMethod().getName()+"\n方法的參數個數:"+methodInvocation.getArguments().length+"\n方法的返回值爲:"+result);

        }catch (Exception e){
            //異常通知
            System.out.println("【接口-環繞】異常通知。。。。");
        }
        return result;//目標方法的返回值
    }
}

使用註解實現通知

package aop;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

import java.sql.Array;
import java.util.Arrays;

@Component("LogAnnotation") //等同於將該類納入springIoc容器中
//@Aspect:聲明該類是一個通知
@Aspect
public class LogAnnotation {
    //通知與業務類直接連接,無需在springIoc容器配置
    //前置通知
    //獲取目標對象參數需要使用的對象:
	//環繞通知爲:ProceedingJoinPoint,其他通知爲:JoinPoint
    @Before("execution(public void service.StudentServicelmpl.addStudent(cn.chern.Student))")//屬性:定義切點
    public void myBefore(JoinPoint jp){
        System.out.println("【註解】前置通知:目標對象"+jp.getTarget()+",方法名"+jp.getSignature().getName()+",參數列表"+ Arrays.toString(jp.getArgs()));
    }

    //後置通知
    @AfterReturning(pointcut = "execution(public void service.StudentServicelmpl.addStudent(cn.chern.Student))",returning = "returningValue")
    public void myAfterreturing(JoinPoint jp,Object returningValue){//returingValue是返回值,但需要告訴spring
        System.out.println("【註解】後置通知:目標對象"+jp.getTarget()+",方法名"+jp.getSignature().getName()+",參數列表"+ Arrays.toString(jp.getArgs())+",返回值爲:"+returningValue);
    }

    環繞通知
    @Around("execution(public void service.StudentServicelmpl.addStudent(cn.chern.Student))")
    public void myAround(ProceedingJoinPoint jp){
        //方法執行之前:前置通知
        System.out.println("【註解-環繞】前置通知");
        try{
            //方法執行時
            jp.proceed();
            //方法執行之後:後置通知
            System.out.println("【註解-環繞】後置通知");
        }catch (Throwable e){
            //發生異常時:異常通知
            System.out.println("【註解-環繞】異常通知");
        }finally {
            //最終通知
            System.out.println("【註解-環繞】最終通知");
        }
    }
    //異常通知:如果只捕獲特定類型的異常,則可以通過第二個參數實現
    @AfterThrowing("execution(public void service.StudentServicelmpl.addStudent(cn.chern.Student))")
    public void myException(){
        System.out.println("【註解】異常通知");
    }
    //最終通知
    @After("execution(public void service.StudentServicelmpl.addStudent(cn.chern.Student))")
    public void myAfter(){
        System.out.println("【註解】最終通知");
    }
}

使用Schema配置實現通知
LogSchema .java

package aop;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;

import java.lang.reflect.Method;
import java.util.Arrays;

public class LogSchema {
    //前置通知方法
    public void before() throws Throwable {
        System.out.println("【Schema】前置通知");
    }
    //後置通知方法
    public void afterReturning(JoinPoint jp, Object returningValue) throws Throwable {
        System.out.println("【Schema】後置通知,目標對象"+jp.getTarget()+",方法名"+jp.getSignature().getName()+",參數列表"+ Arrays.toString(jp.getArgs())+",返回值爲:"+returningValue);
    }
    //異常通知方法
    public void whenException(NullPointerException e){
        System.out.println("【Schema】異常通知:"+e.getMessage());
    }
    //環繞通知配置
    public void around(ProceedingJoinPoint jp){
        //方法執行之前:前置通知
        System.out.println("【Schema-環繞】前置通知");
        try{
            //方法執行時
            jp.proceed();
            //方法執行之後:後置通知
            System.out.println("【Schema-環繞】後置通知");
        }catch (Throwable e){
            //發生異常時:異常通知
            System.out.println("【Schema-環繞】異常通知");
        }finally {
            //最終通知
            System.out.println("【Schema-環繞】最終通知");
        }
    }

}

主方法
Test.java

package cn.chern;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Test {
    public static void testAop(){
        ApplicationContext conext = new ClassPathXmlApplicationContext("spring-config.xml");
        IStudentService studentService = (IStudentService) conext.getBean("studentService");
        Student student = new Student();
        student.setStuAge(23);
        student.setStuName("zs");
        studentService.addStudent(student);
        studentService.deleteStudentByNo(1);
    }
    public static void main(String[] args) {
        testAop();
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章