對於Spring AOP的一點理解

  • AOP的概述

AOP Aspect Oriented Programming 面向切面編程

AOP採取了橫向抽取機制,取代了傳統縱向繼承體系重複性代碼(性能監視、事務管理、安全檢查、緩存)

Spring AOP採用純JAVA實現,不需要專門的編譯過程和類加載器,在運行期通過代理方式向目標類織入增強代碼。

  • AOP相關術語

Joinpoint(連接點):所謂連接點是指那些被攔截到的點。在Spring中,這些點指的是方法,因爲Spring只支持方法類型的連接點

Pointcut(切入點):所謂切入點是指我們要對哪些JoinPoint進行攔截的定義。

Advice(通知/增強):所謂通知是指攔截到JoinPoint之後所要做的事情就是通知。

通知分爲前置通知、後置通知、異常通知、最終通知、環繞通知(切面要完成的功能)

Introduction(引介):引介是一種特殊的通知在不修改類代碼的前提下,

Introduction可以在運行期爲類動態地添加一些方法或Field。

Target(目標對象):代理的目標對象

Weaving(織入):是指把增強應用到目標對象來創建新的代理對象的過程。

Spring採用動態代理織入,而AspectJ採用編譯期間織入和類裝載期織入

Proxy(代理):一個類被AOP織入增強後,就產生一個結果代理類

Aspect(切面):是切入點和通知(引介)的結合

Spring有兩種代理機制分別爲JDK動態代理和CGLIB生成代理,下面我分別用代碼實現下

JDK動態代理實現代碼:

首先創建一個UserDao接口,代碼如下所示:



public interface UserDao {
    public void save();

    public void update();

    public void delete();

    public void find();
}

然後創建接口實現類UserDaoImpl,代碼如下所示:
 



public class UserDaoImpl implements UserDao {
    public void save() {
        System.out.println("保存用戶...");
    }

    public void update() {
        System.out.println("修改用戶...");
    }

    public void delete() {
        System.out.println("刪除用戶...");
    }

    public void find() {
        System.out.println("查詢用戶...");
    }
}

創建JDK的動態代理類MyJDKProxy類,代碼如下所示:



import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class MyJdkProxy implements InvocationHandler{
    private UserDao userDao;

    public MyJdkProxy(UserDao userDao){
        this.userDao = userDao;
    }

    public Object createProxy(){
        Object proxy = Proxy.newProxyInstance(userDao.getClass().getClassLoader(),userDao.getClass().getInterfaces(),this);
        return proxy;
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if("save".equals(method.getName())){
            System.out.println("權限校驗...");
            return method.invoke(userDao,args);
        }
        return method.invoke(userDao,args);
    }
}

然後創建一個測試單元進行測試,代碼如下:


import org.junit.Test;

public class SpringDemo1 {
    @Test
    public void demo1(){
        UserDao userDao = new UserDaoImpl();

        UserDao proxy = (UserDao)new MyJdkProxy(userDao).createProxy();
        proxy.save();
        proxy.update();
        proxy.delete();
        proxy.find();
    }
}

使用cglib生成代理

  • 對於不使用接口的業務類,無法使用JDK動態代理
  • CGlib採用非常底層字節碼技術,可以爲一個類創建子類,解決無接口代理問題

cglib的動態代理實現:

首先創建一個ProductDao類,代碼如下所示:



public class ProductDao {
    public void save(){
        System.out.println("保存商品...");
    }

    public void update(){
        System.out.println("修改商品...");
    }

    public void delete(){
        System.out.println("刪除商品...");
    }

    public void find(){
        System.out.println("查詢商品...");
    }
}

然後創建cglib實現類,代碼實現如下:


import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class MyCglibProxy implements MethodInterceptor{

    private ProductDao productDao;

    public MyCglibProxy(ProductDao productDao){
        this.productDao = productDao;
    }

    public Object createProxy(){
        // 1.創建核心類
        Enhancer enhancer = new Enhancer();
        // 2.設置父類
        enhancer.setSuperclass(productDao.getClass());
        // 3.設置回調
        enhancer.setCallback(this);
        // 4.生成代理
        Object proxy = enhancer.create();
        return proxy;
    }

    public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        if("save".equals(method.getName())){
            System.out.println("權限校驗===================");
            return methodProxy.invokeSuper(proxy,args);
        }
        return methodProxy.invokeSuper(proxy,args);
    }
}

最後,創建測試單元進行測試,代碼如下:

import org.junit.Test;

public class SpringDemo2 {

    @Test
    public void demo1(){
        ProductDao productDao = new ProductDao();

        ProductDao proxy = (ProductDao) new MyCglibProxy(productDao).createProxy();
        proxy.save();
        proxy.update();
        proxy.delete();
        proxy.find();
    }
}

代理知識總結

Spring在運行期,生成動態代理對象,不需要特殊的編譯器

Spring AOP的底層就是通過JDK動態代理或CGLib動態代理技術爲目標Bean執行橫向織入

  1. 若目標對象實現了若干接口,spring使用JDK的java.lang.reflect.Proxy類代理
  2. 若目標對象沒有實現任何接口,spring使用CGLIB庫生成目標對象的子類。

 

  • 程序中應優先對接口創建代理,便於程序解耦維護
  • 標記爲final的方法,不能被代理,因爲無法覆蓋

JDK動態代理,是針對接口生成子類,接口中方法不能使用final修飾

CGLib是針對目標類生產子類,因此類或方法不能使用final的

  • Spring只支持連接點,不支持屬性連接

Spring AOP增強類型

AOP聯盟爲通知Advice定義了org.aopalliance.aop.Interface.Advice

Spring按照通知Advice在目標類方法的連接點位置,可以分爲5類

  • 前置通知org.springframework.aop.MethodBeforeAdvice 在目標方法執行前實施增強
  • 後置通知org.springframework.aop.AfterReturningAdvice 在目標方法執行後實施增強
  • 環繞通知org.aopalliance.intercept.MethodInterceptor 在目標方法執行前後實施增強
  • 異常拋出通知 org.springframework.aop.ThrowsAdvice 在方法拋出異常後實施增強
  • 引介通知 org.springframework.aop.IntroductionInterceptor 在目標類中添加一些新的方法和屬性

下面我用具體的demo實現演示下AOP增強類型的概念:

創建一個StudentDao接口,代碼如下:


public interface StudentDao {
    public void find();

    public void save();

    public void update();

    public void delete();
}

接着,創建StudentDao接口的實現類StudentDaoImpl,代碼如下:



public interface StudentDao {
    public void find();

    public void save();

    public void update();

    public void delete();
}

接着,我們創建一個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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!--配置目標類=======================-->
    <bean id="studentDao" class="com.imooc.aop.demo3.StudentDaoImpl"/>

    <!--前置通知類型=====================-->
    <bean id="myBeforeAdvice" class="com.imooc.aop.demo3.MyBeforeAdvice"/>

    <!--Spring的AOP 產生代理對象-->
    <bean id="studentDaoProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
        <!--配置目標類-->
        <property name="target" ref="studentDao"/>
        <!--實現的接口-->
        <property name="proxyInterfaces" value="com.imooc.aop.demo3.StudentDao"/>
        <!--採用攔截的名稱-->
        <property name="interceptorNames" value="myBeforeAdvice"/>
        <property name="optimize" value="true"></property>
    </bean>
</beans>

然後,我們創建一個通知類型類,代碼如下所示:

package com.imooc.aop.demo3;

import org.springframework.aop.MethodBeforeAdvice;

import java.lang.reflect.Method;

public class MyBeforeAdvice implements MethodBeforeAdvice {
    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println("前置增強======================");
    }
}

最後,我們創建一個測試單元進行測試,代碼如下所示:

package com.imooc.aop.demo3;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import javax.annotation.Resource;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class SpringDemo3 {

    // @Resource(name="studentDao")
    @Resource(name="studentDaoProxy")
    private StudentDao studentDao;

    @Test
    public void demo1(){
        studentDao.find();
        studentDao.save();
        studentDao.update();
        studentDao.delete();
    }
}

Spring AOP切面類型

  • Advisor:代表一般切面,Advice本身就是一個切面,對目標類所有方法進行攔截
  • PointcutAdvisor:代表具有切點的切面,可以指定攔截目標類哪些方法
  • IntroductionAdvisor:代表引介切面,針對引介通知而使用切面

代碼實現

首先,創建一個customerDao類,代碼實現如下:

package com.imooc.aop.demo4;

public class CustomerDao {
    public void find(){
        System.out.println("查詢客戶...");
    }

    public void save(){
        System.out.println("保存客戶...");
    }

    public void update(){
        System.out.println("修改客戶...");
    }

    public void delete(){
        System.out.println("刪除客戶...");
    }
}

接着,創建一個xml配置文件把custormerDao放入到Spring容器中

<?xml version="1.0" encoding="UTF-8"?>
<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">

    <!--配置目標類============-->
    <bean id="customerDao" class="com.imooc.aop.demo4.CustomerDao"/>

    <!--配置通知============== -->
    <bean id="myAroundAdvice" class="com.imooc.aop.demo4.MyAroundAdvice"/>

    <!--一般的切面是使用通知作爲切面的,因爲要對目標類的某個方法進行增強就需要配置一個帶有切入點的切面-->
    <bean id="myAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
        <!--pattern中配置正則表達式:.任意字符  *任意次數 -->
    <!--<property name="pattern" value=".*save.*"/>-->
        <property name="patterns" value=".*save.*,.*delete.*"/>
    <property name="advice" ref="myAroundAdvice"/>
</bean>

<!-- 配置產生代理 -->
    <bean id="customerDaoProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
        <property name="target" ref="customerDao"/>
        <property name="proxyTargetClass" value="true"/>
        <property name="interceptorNames" value="myAdvisor"/>
    </bean>
</beans>

創建一個增強類型類,代碼如下:
 

package com.imooc.aop.demo4;

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

public class MyAroundAdvice implements MethodInterceptor {
    public Object invoke(MethodInvocation invocation) throws Throwable {
        System.out.println("環繞前增強===================");

        Object obj = invocation.proceed();

        System.out.println("環繞後增強===================");
        return obj;
    }
}

最後,創建一個測試單元類,進行測試:

package com.imooc.aop.demo4;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import javax.annotation.Resource;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext2.xml")
public class SpringDemo4 {

    // @Resource(name="customerDao")
    @Resource(name="customerDaoProxy")
    private CustomerDao customerDao;

    @Test
    public void demo1(){
        customerDao.find();
        customerDao.save();
        customerDao.update();
        customerDao.delete();
    }
}

ProxyFactoryBean常用的可配置屬性

——target:代理的目標對象

——proxyInterfaces:代理要實現的接口

如果多個接口可以使用一下格式賦值

<list>

<value></value>

...

</list>

——proxyTargetClass:是否對類代理而不是接口,設置爲true時,使用CGLib代理

——interceptorNames:需要織入目標的Advice

——singleton:返回代理是否爲單實例,默認爲單例

——optimize:當設置爲true時,強制使用CGLib

 

PointcutAdvisor切點切面

使用普通Advice作爲切面,將對目標類所有方法進行攔截,不夠靈活,在實際開發中常採用帶有切點的切面

常用PointcutAdvisor實現類

——DefaultPointcutAdvisor最常用的切面類型,它可以通過任意Pointcut和Advice組合定義切面

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章