Spring(四):AOP

fan_rockrock發表於2016-07-06

一、AOP的概念理解    

        OOP(物件導向程式設計)使用的是從上到下、縱向的體系結構來解決重複程式碼的問題,重點關注的是與實際業務聯合緊密的模組。而AOP(面向切面程式設計)則使用的是橫向的體系來解決重複程式碼的問題,它重點關注的是與業務無關,卻為業務模組所共同呼叫(叫做切面Aspect)的邏輯,如執行業務模組的某一功能時,需要記錄操作日誌、要實現事務,保證業務操作的原子性等等。AOP將這些切面提取出來然後動態的新增到業務邏輯程式碼之中,即使這些Aspect的實現機制以及程式碼進行了修改,只需改動一處而不會影響原有業務邏輯程式碼,從而降低了切面與業務邏輯的耦合度 。 因此,AOP是用來在使用OOP解決問題的過程中增強解決問題的能力,是對OOP的一種補充,實現更好的模組化

二、代理機制

        AOP採用的是代理模式實現的

(1)靜態代理

Count.java 

/** 
 * 定義一個賬戶介面 
 */  
public interface Count {  
    // 檢視賬戶方法  
    public void queryCount();  
    // 修改賬戶方法  
    public void updateCount();  
}  
CountImpl.java 

/** 
 * 委託類(包含業務邏輯) 
 */  
public class CountImpl implements Count {  
    @Override  
    public void queryCount() {  
        System.out.println("檢視賬戶方法...");  
    }  
    @Override  
    public void updateCount() {  
        System.out.println("修改賬戶方法...");  
    }  
}  
CountProxy.java

/** 
 * 這是一個代理類(增強CountImpl實現類) 
 */  
public class CountProxy implements Count {  
    private CountImpl countImpl;  
  
    /** 
     * 覆蓋預設構造器 
     *  
     * @param countImpl 
     */  
    public CountProxy(CountImpl countImpl) {  
        this.countImpl = countImpl;  
    }  
  
    @Override  
    public void queryCount() {  
        System.out.println("事務處理之前");  
        // 呼叫委託類的方法;  
        countImpl.queryCount();  
        System.out.println("事務處理之後");  
    }  
  
    @Override  
    public void updateCount() {  
        System.out.println("事務處理之前");  
        // 呼叫委託類的方法;  
        countImpl.updateCount();  
        System.out.println("事務處理之後");  
  
    }  
  
}  
TestCount.java

/** 
 *測試Count類 
 */  
public class TestCount {  
    public static void main(String[] args) {  
        CountImpl countImpl = new CountImpl();  
 

觀察程式碼可以發現每一個代理類只能為一個介面服務,這樣一來程式開發中必然會產生過多的代理,而且,所有的代理操作除了呼叫的方法不一樣之外,其他的操作都一樣,則此時肯定是重複程式碼。解決這一問題最好的做法是可以通過一個代理類完成全部的代理功能,那麼此時就必須使用動態代理完成。 

(2)動態代理之JDK

BookFacade.java 

public interface BookFacade {  
    public void addBook();  
}  
BookFacadeImpl.java 

public class BookFacadeImpl implements BookFacade {  
    @Override  
    public void addBook() {  
        System.out.println("增加圖書方法。。。");  
    }  
}  
BookFacadeProxy.java

import java.lang.reflect.InvocationHandler;  
import java.lang.reflect.Method;  
import java.lang.reflect.Proxy;  
/** 
 * JDK動態代理代理類  
 */  
public class BookFacadeProxy implements InvocationHandler {  
    private Object target;  
    /** 
     * 繫結委託物件並返回一個代理類 
     * @param target 
     * @return 
     */  
    public Object bind(Object target) {  
        this.target = target;  
        //取得代理物件  
        return Proxy.newProxyInstance(target.getClass().getClassLoader(),  
                target.getClass().getInterfaces(), this);   //要繫結介面(這是一個缺陷,cglib彌補了這一缺陷)  
    }  
  
    @Override  
    /** 
     * 呼叫目標物件的任何一個方法 都相當於呼叫invoke(); 
     */  
    public Object invoke(Object proxy, Method method, Object[] args)  
            throws Throwable {  
        Object result=null;  
        System.out.println("事物開始");  
        //執行方法  
        result=method.invoke(target, args);  
        System.out.println("事物結束");  
        return result;  
    }  
  
}  
TestProxy.java 

import net.battier.dao.BookFacade;  
import net.battier.dao.impl.BookFacadeImpl;  
import net.battier.proxy.BookFacadeProxy;  
public class TestProxy {  
    public static void main(String[] args) {  
        BookFacadeProxy proxy = new BookFacadeProxy();  
        BookFacade bookProxy = (BookFacade) proxy.bind(new BookFacadeImpl());  
        bookProxy.addBook();  
    }  
}  
JDK的動態代理依靠介面實現,如果有些類並沒有實現介面,則不能使用JDK代理,這就要使用cglib動態代理了。

(3)動態代理之CGlib

cglib是針對類來實現代理的,他的原理是對指定的目標類生成一個子類,並覆蓋其中方法實現增強。

BookCadeImpl1.java 

/** 
 * 這個是沒有實現介面的實現類 
 */  
public class BookFacadeImpl1 {  
    public void addBook() {  
        System.out.println("增加圖書的普通方法...");  
    }  
}  
BookFacadeProxy.java 

import java.lang.reflect.Method;  
  
import net.sf.cglib.proxy.Enhancer;  
import net.sf.cglib.proxy.MethodInterceptor;  
import net.sf.cglib.proxy.MethodProxy;  
  
/** 
 * 使用cglib動態代理 
 */  
public class BookFacadeCglib implements MethodInterceptor {  
    private Object target;  
  
    /** 
     * 建立代理物件 
     */  
    public Object getInstance(Object target) {  
        this.target = target;  
        Enhancer enhancer = new Enhancer();  
        enhancer.setSuperclass(this.target.getClass());  
        // 回撥方法  
        enhancer.setCallback(this);  
        // 建立代理物件  
        return enhancer.create();  
    }  
  
    @Override  
    // 回撥方法  
    public Object intercept(Object obj, Method method, Object[] args,  
            MethodProxy proxy) throws Throwable {  
        System.out.println("事物開始");  
        proxy.invokeSuper(obj, args);  
        System.out.println("事物結束");  
        return null;  
    }  
}  
Testcglib.java

import net.battier.dao.impl.BookFacadeImpl1;  
import net.battier.proxy.BookFacadeCglib;  
public class TestCglib {  
    public static void main(String[] args) {  
        BookFacadeCglib cglib=new BookFacadeCglib();  
        BookFacadeImpl1 bookCglib=(BookFacadeImpl1)cglib.getInstance(new BookFacadeImpl1());  
        bookCglib.addBook();  
    }  
}  
Spring框架中的AOP,如果類實現了介面,就使用JDK的動態代理生成代理物件,如果這個類沒有實現任何介面,使用CGLIB生成代理物件.

三、AOP概念


四、Spring中基於AspectJ的AOP

AspectJ是一個基於Java語言的AOP框架,Spring2.0以後新增了對AspectJ切點表示式支援。

AspectJ表示式:
* 語法:execution(表示式)
execution(<訪問修飾符>?<返回型別><方法名>(<引數>)<異常>)
       execution(“* cn.itcast.spring3.demo1.dao.*(..)”) ---只檢索當前包
       execution(“* cn.itcast.spring3.demo1.dao..*(..)”) ---檢索包及當前包的子包.
        execution(* cn.itcast.dao.GenericDAO+.*(..)) ---檢索GenericDAO及子類

AspectJ增強:
     @Before 前置通知
     @AfterReturning 後置通知
     @Around 環繞通知
     @AfterThrowing丟擲通知
     @After 最終final通知,不管是否異常,該通知都會執行

1、基於註解的AOP

第一步:引入相應jar包.
 spring-aspects-3.2.0.RELEASE.jar
 com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar

第二步:編寫被增強的類:
UserDao.java

package cn.itcast.spring3.demo1;

public class UserDao {
	public void add(){
		System.out.println("新增使用者");
	}
	public int update(){
		System.out.println("修改使用者");
	}
	public void delete(){
		System.out.println("刪除使用者");
	}
	public void find(){
		System.out.println("查詢使用者");
	}
}
第三步:使用AspectJ註解形式編寫切面類:

/**
 * 切面類:就是切點與增強結合
 */
@Aspect
public class MyAspect {
	@Before("execution(* cn.itcast.spring3.demo1.UserDao.add(..))")
	public void before(){
		System.out.println("前置增強....");
	}
}
第四步:建立applicationContext.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"
       xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
	<!-- 自動生成代理-->
	<aop:aspectj-autoproxy />
	<bean id="userDao" class="cn.itcast.spring3.demo1.UserDao"></bean>
	<bean id="myAspect" class="cn.itcast.spring3.demo1.MyAspect"></bean>
</beans>
第五步:測試

package cn.itcast.spring3.demo1;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class SpringTest1 {
	@Autowired
	@Qualifier("userDao")
	private UserDao userDao;
	
	@Test
	public void demo1(){
		userDao.add();
	}
}

2、基於XML的AOP

第一步:編寫被增強的類:

package cn.itcast.spring3.demo2;
public class ProductDao {
	public int add(){
		System.out.println("新增商品...");
		int d = 10/0;
		return 100;
	}
	public void update(){
		System.out.println("修改商品...");
	}
	public void delete(){
		System.out.println("刪除商品...");
	}
	public void find(){
		System.out.println("查詢商品...");
	}
}
第二步:定義切面

package cn.itcast.spring3.demo2;

import org.aspectj.lang.ProceedingJoinPoint;

/**
 * 切面類
 */
public class MyAspectXML {	
	public void before(){
		System.out.println("前置通知...");
	}
	public void afterReturing(Object returnVal){
		System.out.println("後置通知...返回值:"+returnVal);
	}	
	public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
		System.out.println("環繞前增強....");
		Object result = proceedingJoinPoint.proceed();
		System.out.println("環繞後增強....");
		return result;
	}	
	public void afterThrowing(Throwable e){
		System.out.println("異常通知..."+e.getMessage());
	}	
	public void after(){
		System.out.println("最終通知....");
	}
}
第三步:配置applicationContext.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"
       xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
	<!-- 定義被增強的類 -->
	<bean id="productDao" class="cn.itcast.spring3.demo2.ProductDao"></bean>
	
	<!-- 定義切面 -->
	<bean id="myAspectXML" class="cn.itcast.spring3.demo2.MyAspectXML"></bean>
	
	<!-- 定義aop配置 -->
	<aop:config>
		<!-- 定義切點: -->
		<aop:pointcut expression="execution(* cn.itcast.spring3.demo2.ProductDao.add(..))" id="mypointcut"/>
		<aop:aspect ref="myAspectXML">
			<!-- 前置通知 -->
			<aop:before method="before" pointcut-ref="mypointcut"/> 
			<!-- 後置通知 -->
			<aop:after-returning method="afterReturing" pointcut-ref="mypointcut" returning="returnVal"/> 
			<!-- 環繞通知 -->
			<aop:around method="around" pointcut-ref="mypointcut"/> 
			<!-- 異常通知 -->
		    <aop:after-throwing method="afterThrowing" pointcut-ref="mypointcut" throwing="e"/> 
			<!-- 最終通知 -->
			<aop:after method="after" pointcut-ref="mypointcut"/>
		</aop:aspect>
	</aop:config>
</beans>

第四步:測試類

package cn.itcast.spring3.demo2;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

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

	@Autowired
	@Qualifier("productDao")
	private ProductDao productDao;
	
	@Test
	public void demo1(){
		productDao.add();
		productDao.find();
		productDao.update();
		productDao.delete();
	}
}