1. 程式人生 > >SpringAOP詳解(第二次學習Spring,主要是JDK動態代理)

SpringAOP詳解(第二次學習Spring,主要是JDK動態代理)

    第一次學習Spring的時候只是覺得很好用,而且也只是停留在會用的階段,現在在公司實習,又叫看Spring,於是就有了這篇文章,這時才體會到Spring確實優秀。(博主只是一個自學一年不到的小白,如果有錯還望各位大佬批評指正)

 一.裝飾類    

        例子:在我們實現具體的業務需求操作的時候,我們為了保證業務安全,需要在業務方法前面開啟事務,方法完成之後再提交事務,如果業務出錯還需要回滾事務。如果有大量的業務方法,我們就會發現業務程式碼變得臃腫,而且全是重複事務的操作,這時候我們就會想要重構。因此首先我們是採用裝飾類來加強業務程式碼。(說明一下,為了直觀一點,我所有的bean都用spring的xml裝配,不採用@autowired自動裝配)

//這是業務方法service,只是模擬了儲存,和會出錯的更新
package com.swust.service.impl;
import com.swust.dao.IEmployeeDAO;
import com.swust.dao.impl.EmployeeDAOImpl;
import com.swust.domain.Employee;
import com.swust.service.IEmployeeService;

import lombok.Setter;

public class EmployeeServiceImpl implements IEmployeeService {
	@Setter
	private IEmployeeDAO dao;
 
	public void save(Employee employee) {
			dao.save(employee);
	}

	public void update(Employee employee) {
		dao.update(employee);
		throw new RuntimeException("故意出錯=============");
	}

}
package com.swust.service.impl; import com.swust.dao.IEmployeeDAO; import com.swust.dao.impl.EmployeeDAOImpl; import com.swust.domain.Employee; import com.swust.service.IEmployeeService; import lombok.Setter; public class EmployeeServiceImpl implements IEmployeeService { @Setter private IEmployeeDAO dao; public void save(Employee employee) { dao.save(employee); } public void update(Employee employee) { dao.update(employee); throw new RuntimeException("故意出錯============="); } }
//這是包裝類
package com.swust.test.wapper;
import com.swust.TranscantionManager;
import com.swust.domain.Employee;
import com.swust.service.IEmployeeService;
/**
 * @author Mr
 * 模仿包裝類,用事務包裝EmployeeServiceImpl,達到增強的目的
 */
public class EmployeeServiceWapper implements IEmployeeService{
	
	private IEmployeeService serviceImpl;
	private TranscantionManager tx;

	public EmployeeServiceWapper(IEmployeeService serviceImpl, TranscantionManager tx) {
		this.serviceImpl = serviceImpl;
		this.tx = tx;
	}
	public void save(Employee e) {
		try {
			tx.begin();
			serviceImpl.save(e);
			tx.commit();
		} catch (Exception e2) {
			e2.printStackTrace();
			tx.rollback();
		}
	}
	public void update(Employee e) {
		try {
			tx.begin();
			serviceImpl.update(e);
			tx.commit();
		} catch (Exception e2) {
			e2.printStackTrace();
			tx.rollback();
		}
	}
}
package com.swust.test.wapper;
import com.swust.TranscantionManager;
import com.swust.domain.Employee;
import com.swust.service.IEmployeeService;
/**
 * @author Mr
 * 模仿包裝類,用事務包裝EmployeeServiceImpl,達到增強的目的
 */
public class EmployeeServiceWapper implements IEmployeeService{
	
	private IEmployeeService serviceImpl;
	private TranscantionManager tx;

	public EmployeeServiceWapper(IEmployeeService serviceImpl, TranscantionManager tx) {
		this.serviceImpl = serviceImpl;
		this.tx = tx;
	}
	public void save(Employee e) {
		try {
			tx.begin();
			serviceImpl.save(e);
			tx.commit();
		} catch (Exception e2) {
			e2.printStackTrace();
			tx.rollback();
		}
	}
	public void update(Employee e) {
		try {
			tx.begin();
			serviceImpl.update(e);
			tx.commit();
		} catch (Exception e2) {
			e2.printStackTrace();
			tx.rollback();
		}
	}
}
package com.swust;
//需要增強的操作,可以是事務,log等

public class TranscantionManager {
	public void begin(){
		System.out.println("***********開啟事務***********");
	}
	public  void commit(){
		System.out.println("***********提交事務***********");
	}
	public void rollback(){
		System.out.println("***********回滾事務***********");
	}
}//需要增強的操作,可以是事務,log等

public class TranscantionManager {
	public void begin(){
		System.out.println("***********開啟事務***********");
	}
	public  void commit(){
		System.out.println("***********提交事務***********");
	}
	public void rollback(){
		System.out.println("***********回滾事務***********");
	}
}

我們可以看見,在包裝類中需要實現真實物件的介面,需要引入真實物件並通過構造器注入真實物件,接下來看一看測試類。

package com.swust.test.wapper;
import org.junit.Test;
import com.swust.TranscantionManager;
import com.swust.domain.Employee;
import com.swust.service.impl.EmployeeServiceImpl;

public class TestWapper {
	EmployeeServiceWapper wapper=new EmployeeServiceWapper(new EmployeeServiceImpl(), new  TranscantionManager());
	Employee e=new Employee();
	@Test
	public void testSave() throws Exception {
		wapper.save(e);	
	}
	@Test
	public void testUpdate() throws Exception {
		wapper.update(e);
	}
}

可以看見這種方法確實可以增強業務方法,但是它向外暴露的真實物件,不符合封裝,並且這個類不可複用,一個真實類需要一個包裝類。所以改進一下引出靜態代理。同樣是那些業務,來看看靜態代理類。

二.靜態代理

package com.swust.test.proxy.staticProxy;
import com.swust.TranscantionManager;
import com.swust.domain.Employee;
import com.swust.service.IEmployeeService;
import lombok.Setter;

/**
 * @author Mr
 * 模仿靜態代理類,使用spring容易管理bean,靜態代理沒有暴露真實物件,而包裝類會暴露
 */
public class EmployeeServiceStaticProxy implements IEmployeeService{
	@Setter
	private IEmployeeService target;
	@Setter
	private TranscantionManager tx;

	public void save(Employee e) {
		try {
			tx.begin();
			target.save(e);
			tx.commit();
		} catch (Exception e2) {
			e2.printStackTrace();
			tx.rollback();
		}	
	}
}
<!-- 配置事務管理 -->
	<bean id="txManager" class="com.swust.TranscantionManager"></bean>
	<!-- 配置DAO -->
	<bean id="employeeDAO" class="com.swust.dao.impl.EmployeeDAOImpl"></bean>

	<!-- 配置代理 -->
	<bean id="employeeServiceProxy" class="com.swust.test.proxy.staticProxy.EmployeeServiceStaticProxy">
		<property name="tx" ref="txManager"></property>
		<property name="target">
			<!-- 配置真實物件 -->
			<bean class="com.swust.service.impl.EmployeeServiceImpl">
				<property name="dao" ref="employeeDAO"></property>
			</bean>
		</property>
	</bean><bean class="com.swust.service.impl.EmployeeServiceImpl">
				<property name="dao" ref="employeeDAO"></property>
			</bean>
		</property>
	</bean>

可以看出靜態代理,並沒有向外暴露真實物件,因為他是無引數的構造器,在配置檔案中也可以隱藏起來,其他和裝飾類基本一樣。

三.JDK動態代理

        為了解決類的不可複用性,就引入了動態代理。下面來模仿一下JDK的動態代理(AOP基於動態代理)。

package com.swust.test.proxy.jdkProxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import com.swust.TranscantionManager;
import lombok.Setter;

public class TranscantionManagerHandler implements InvocationHandler{
	@Setter
	private Object target;//真實物件,可以通過spring注入
	@Setter
	private TranscantionManager tx;
	/**
	 * @return 代理物件
	 * 生成的代理類會繼承Proxy類
	 */
	@SuppressWarnings("unchecked")
	public <T>T getProxyObj(){
		Object object = Proxy.newProxyInstance(target.getClass().getClassLoader()//通過真實物件得到類載入器
				, target.getClass().getInterfaces()//真實物件的介面
				, this);//InvocationHandler h:這裡表示當前類物件,即代理輔助類物件,最終會呼叫加強後的invoke方法
		return (T) object;
	}
	/**
	 * 在真實物件上增強
	 * 
	 */
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		System.out.println("輔助類中的method是:"+method+"===args:"+args);
                System.out.println("輔助類中的args是:"+args);
		System.out.println("========前面做======增強");
		Object object = method.invoke(target, args);
		System.out.println("========後面做======增強");
		return object;
	}
}

假設還是需要事務增強,那麼這個類就必須實現InvocationHandler介面,這個介面中只有一個方法

package java.lang.reflect;
public interface InvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
}

然後實現介面的invoke方法。隨後還需要在類中寫一個獲取代理物件的方法(不在這兒寫也可以,獲取的方法一樣)。有兩種方法可以獲取代理物件,這裡使用Proxy.newProxyInstance來獲取。這裡需要傳入三個引數,一個是類載入器,一個是真實物件類實現的介面,一個就是代理輔助類的物件h。我們先看看Proxy的原始碼

public class Proxy implements java.io.Serializable {

     protected InvocationHandler h;  
 
     public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
        Objects.requireNonNull(h);

        final Class<?>[] intfs = interfaces.clone();
        final SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
        }
        Class<?> cl = getProxyClass0(loader, intfs);
        try {
            if (sm != null) {
                checkNewProxyPermission(Reflection.getCallerClass(), cl);
            }

            final Constructor<?> cons = cl.getConstructor(constructorParams);
            final InvocationHandler ih = h;
            if (!Modifier.isPublic(cl.getModifiers())) {
                AccessController.doPrivileged(new PrivilegedAction<Void>() {
                    public Void run() {
                        cons.setAccessible(true);
                        return null;
                    }
                });
            }
            return cons.newInstance(new Object[]{h});
        } 
    }


protected InvocationHandler h;  
 
     public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
        Objects.requireNonNull(h);

        final Class<?>[] intfs = interfaces.clone();
        final SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
        }
        Class<?> cl = getProxyClass0(loader, intfs);
        try {
            if (sm != null) {
                checkNewProxyPermission(Reflection.getCallerClass(), cl);
            }

            final Constructor<?> cons = cl.getConstructor(constructorParams);
            final InvocationHandler ih = h;
            if (!Modifier.isPublic(cl.getModifiers())) {
                AccessController.doPrivileged(new PrivilegedAction<Void>() {
                    public Void run() {
                        cons.setAccessible(true);
                        return null;
                    }
                });
            }
            return cons.newInstance(new Object[]{h});
        } 
    }


主要看顏色加深部分,可以看見原始碼中的Proxy維護了一個InvocationHandler 物件h,呼叫newProxyInstance方法的時候會先檢查輔助類TranscantionManagerHandler物件h是否為空,如果空,則丟擲空指標。之後利用傳進來的介面和類載入器獲取代理類(代理物件會實現該介面,注意代理類只存在記憶體中,如需存入硬碟需要改變JVM啟動引數,方法自行百度)。隨後根據代理類的含有InvocationHandler 物件h的構造器建立一個代理類的物件返回。之後我們看看測試方法。

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class JDKProxyTest {
	@Autowired
	private TranscantionManagerHandler handle;
	
	@Test
	public void testSave() throws Exception {
		IEmployeeService service = handle.getProxyObj();//得到的service為代理物件,代理類存在記憶體中
		service.save(new Employee());
                System.out.println("service物件的類是=======》"+service.getClass());
         }
}

通過注入代理輔助類物件呼叫 得到代理物件的方法getProxyObj() 得到代理物件。之後代理物件呼叫自身的業務方法(這裡是save),很多人看到這裡就很好奇,輔助類的TranscantionManagerHandler增強的invoke方法什麼時候呼叫的呢?先看看測試結果

service確實是代理類的物件,業務方法也得到了增強,而在代理輔助類invoke方法中的引數method可以看見是介面中的save方法。

需要知道什麼時候呼叫的invoke就需要反編譯生成的代理類位元組碼(調整虛擬機器引數將代理類位元組碼儲存在硬碟),在生成代理類中$Proxy11繼承了Proxy類,實現了需要被代理類的介面,也就是IEmployeeService,其構造器是如下,還含有三個Object的方法和真實物件的業務方法。

public $Proxy11(InvocationHandler invocationhandler) {  
        super(invocationhandler);  
    }
  static {  
        try {  
            m1 = Class.forName("java.lang.Object").getMethod("equals",  
                    new Class[] { Class.forName("java.lang.Object") });  
  
            m0 = Class.forName("java.lang.Object").getMethod("hashCode",  
                    new Class[0]);  
  
            m3 = Class.forName("***.RealSubject").getMethod("save",  //代理方法名稱
                    new Class[0]);  
  
            m2 = Class.forName("java.lang.Object").getMethod("toString",  
                    new Class[0]);  
  
    } //static   
public final void save() {  //代理方法
        try {  
            super.h.invoke(this, m3, null);  
            return;  
        } catch (Error e) {  
        } catch (Throwable throwable) {  
            throw new UndeclaredThrowableException(throwable);  
        }  

    } super.h.invoke(this, m3, null);  
            return;  
        } catch (Error e) {  
        } catch (Throwable throwable) {  
            throw new UndeclaredThrowableException(throwable);  
        }  

    } 

在這兒就可以看出了,當代理物件呼叫save方法時,會呼叫

super.h.invoke(this, m3, null);

而在父類Proxy中之前我們講過它維護了一個InvocationHandler的物件h,因此由於代理輔助類實現了一個InvocationHandler的介面,所以最終h會呼叫其實現類TranscantionManagerHandler的invoke方法,因此在

Object object = method.invoke(target, args);

前後就可以做想要的增強了。

                               總結一下

    JDK動態代理,含有增強方法的類需要實現InvocationHandler介面,重寫invoke方法。獲取代理物件的時候需要傳入介面,類載入器,因此需要代理的類必須實現介面。在記憶體中建立代理類的時候,通過傳的介面,建立一個代理類並實現該傳入的介面,繼承Proxy,在生成的代理類的業務方法中呼叫父類維護的InvocationHandler的h物件,並呼叫h.invoke()方法,而由於含有增強方法的代理輔助類實現了InvocationHandler介面,因此實際就會呼叫代理輔助類實現的invoke方法,最後通過代理輔助類裡面注入的真實物件使用method.invoke(target,args)就可以呼叫真實的業務方法了,在在方法前後就可以做增強。

注意:jdk動態代理必須要實現介面

四.CGlib動態代理

原理大部分和jdk動態代理一樣,只是獲取代理物件的方法不同,其餘都一樣。

public class TranscantionManagerHandlerCallbck implements org.springframework.cglib.proxy.InvocationHandler {
	@Setter
	private Object target;// 真實物件

	@SuppressWarnings("unchecked")
	public <T> T getProxyObj() {
		Enhancer enhancer = new Enhancer();
		enhancer.setSuperclass(target.getClass());//代理類需要繼承的父類
		enhancer.setCallback(this);//需要增強的物件,即該類的物件
		return (T) enhancer.create();
	}

	/**
	 * 在真實物件上增強
	 */
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		System.out.println("輔助類中的method是:" + method + "===args:" + args);
		System.out.println("輔助類中的args是:" + args);
		System.out.println("========前面做======增強");
		Object object = method.invoke(target, args);
		System.out.println("========後面做======增強");
		return object;
	}
}
Enhancer enhancer = new Enhancer();
		enhancer.setSuperclass(target.getClass());//代理類需要繼承的父類
		enhancer.setCallback(this);//需要增強的物件,即該類的物件
		return (T) enhancer.create();
	}

	/**
	 * 在真實物件上增強
	 */
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		System.out.println("輔助類中的method是:" + method + "===args:" + args);
		System.out.println("輔助類中的args是:" + args);
		System.out.println("========前面做======增強");
		Object object = method.invoke(target, args);
		System.out.println("========後面做======增強");
		return object;
	}
}

這裡實現的類是spring的cglib包裡的。另外需要cglib代理的類必須是可擴充套件的,因為cglib代理不需要實現介面,只需要繼承父類就可以(繼承需要被代理的類)。

五.AOP

  先貼一段aop的配置

        <!-- 配置aop:切面what,切入位置where,切入時機when -->
	<aop:config>
		<!-- what:做什麼增強 -->
		<aop:aspect ref="txManager">
			<!--where: 在哪些包下的哪些類和介面的哪些方法上做增強 -->
			<aop:pointcut expression="execution(* com.swust.common.service.*Service.*(..))" id="txPoint" />
			<!-- when:在什麼時候做增強(業務方法前/後/前後)第一個表示在之前做txManager中begin方法的增強 -->
			<aop:before method="begin" pointcut-ref="txPoint"/>
			<aop:after method="commit" pointcut-ref="txPoint"/>
			<aop:after-throwing method="rollback" pointcut-ref="txPoint"/>
		</aop:aspect>
	</aop:config>
	<!-- 配置事務管理 -->
	<bean id="txManager" class="com.swust.common.TranscantionManager"></bean>

(有很多配法)在這兒<aspect>裡面配的就相當於需要增強的類,相當於jdk代理中的代理輔助類。而pointcut表示切入點,意思就是我們需要在哪些類的哪些方法上做增強,相當於前面的save業務方法,在這兒指的是service包下的所以帶Service結尾的方法,execution是切的語法,需要引入相關aop和aspectj的包。而再下面就是增強的時機,需要在業務方法之前,之後還是環繞增強,並配置增強的方法。由於所有的bean都交給spring管理,spring會幫我們生成相關的類。關於sop還有其他的一些配置只有瞭解到了aop原理那麼配置那些都是很簡單的,需要了解可以去官方看看。另外,除了xml配置spring還支援註解配置。

注:Spring中AOP代理由Spring的IOC容器負責生成、管理,其依賴關係也由IOC容器負責管理。因此,AOP代理可以直接使用容器中的其它bean例項作為目標,這種關係可由IOC容器的依賴注入提供。Spring建立代理的規則為:

        1、預設使用Java動態代理來建立AOP代理,這樣就可以為任何介面例項建立代理了

        2、當需要代理的類不是代理介面的時候,Spring會切換為使用CGLIB代理,也可強制使用CGLIB