關於Spring aop

一、概念

AOP(Aspect Oriented Programming),即面向切面編程,能夠說是OOP(Object Oriented Programming,面向對象編程)的補充和完善。OOP引入封裝、繼承、多態等概念來創建一種對象層次結構,用於模擬公共行爲的一個集合。不過OOP容許開發者定義縱向的關係,但並不適合定義橫向的關係,例如日誌功能。日誌代碼每每橫向地散佈在全部對象層次中,而與它對應的對象的核心功能毫無關係對於其餘類型的代碼,如安全性、異常處理和透明的持續性也都是如此,這種散佈在各處的無關的代碼被稱爲橫切(cross cutting),在OOP設計中,它致使了大量代碼的重複,而不利於各個模塊的重用。java

AOP技術偏偏相反,它利用一種稱爲"橫切"的技術,剖解開封裝的對象內部,並將那些影響了多個類的公共行爲封裝到一個可重用模塊,並將其命名爲"Aspect",即切面。所謂"切面",簡單說就是那些與業務無關,卻爲業務模塊所共同調用的邏輯或責任封裝起來,便於減小系統的重複代碼,下降模塊之間的耦合度,並有利於將來的可操做性和可維護性。git

使用"橫切"技術,AOP把軟件系統分爲兩個部分:核心關注點橫切關注點。業務處理的主要流程是核心關注點,與之關係不大的部分是橫切關注點。橫切關注點的一個特色是,他們常常發生在覈心關注點的多處,而各處基本類似,好比權限認證、日誌、事物。AOP的做用在於分離系統中的各類關注點,將核心關注點和橫切關注點分離開來。spring

二、相關術語

一、橫切關注點:對哪些方法進行攔截,攔截後怎麼處理,這些關注點稱之爲橫切關注點編程

二、切面(aspect):類是對物體特徵的抽象,切面就是對橫切關注點的抽象安全

三、鏈接點(joinpoint):被攔截到的點,由於Spring只支持方法類型的鏈接點,因此在Spring中鏈接點指的就是被攔截到的方法,實際上鍊接點還能夠是字段或者構造器app

四、切入點(pointcut):對鏈接點進行攔截的定義ide

五、通知(advice)(interceptor):所謂通知指的就是指攔截到鏈接點以後要執行的代碼,通知分爲前置、後置、異常、最終、環繞通知五類oop

六、目標對象:代理的目標對象測試

七、織入(weave):將切面應用到目標對象並致使代理對象建立的過程this

八、引入(introduction):在不修改代碼的前提下,引入能夠在運行期爲類動態地添加一些方法或字段

三、 AOP的實現機制 

3.一、JDK動態代理

3.1.一、動態代理    

    Java在JDK1.3後引入的動態代理機制,使咱們能夠在運行期動態的建立代理類。使用動態代理實現AOP須要有四個角色:被代理的類,被代理類的接口,織入器,和InvocationHandler,而織入器使用接口反射機制生成一個代理類,而後在這個代理類中織入代碼。被代理的類是AOP裏所說的目標,InvocationHandler是切面,它包含了Advice和Pointcut。 

3.1.二、代碼實現(相關代碼來自Spring.3.x企業應用開發實戰)

// 使用動態代理必須涉及到接口
// 動態代理自己就是基於接口的代理機制
public interface ForumService {
    void removeTopic(int topicId);  
    void removeForum(int forumId);
}

// 接口實現類
public class ForumServiceImpl implements ForumService {

    @Override
    public void removeTopic(int topicId) {
        // TODO Auto-generated method stub
        System.out.println("模擬刪除Topic記錄:" + topicId);
        try {
            Thread.currentThread().sleep(20);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void removeForum(int forumId) {
        // TODO Auto-generated method stub
        System.out.println("模擬刪除Forum記錄:" + forumId);
        try {
            Thread.currentThread().sleep(40);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}

// 切面
// 在切入點上須要加入的額外處理
public class PerformanceMonitor {
    
    private static ThreadLocal<MethodPerformance> performanceRecord = new ThreadLocal<MethodPerformance>();

    public static void begin(String method) {
        System.out.println("begin monitor...");
        MethodPerformance mp = new MethodPerformance(method);
        performanceRecord.set(mp);
    }

    public static void end() {
        System.out.println("end monitor...");
        MethodPerformance mp = performanceRecord.get();
        mp.printPerformance();
    }
}

// 動態代理
public class TestForumService {
    public static void test() {
    	
    	// 建立好要被代理的對象,再經過動態代理機制生成代理對象   
        final ForumService target = new ForumServiceImpl();
        
        // proxy 並非target對象,可是都是實現自ForumService接口的實例
        ForumService proxy = (ForumService) Proxy.newProxyInstance(target.getClass().getClassLoader(),
                target.getClass().getInterfaces(), new InvocationHandler() {
            // InvocationHandler對象        
        	@Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        
        				// 執行該切入點方法以前,須要切面處理
        				PerformanceMonitor.begin(target.getClass().getName() + method.getName());
                        Object obj = method.invoke(target, args);
                        PerformanceMonitor.end();
                        return obj;
                    }
                });
        proxy.removeForum(10);
        proxy.removeTopic(1012);
    }

    public static void main(String[] args) {
        test();
    }
}

3.1.三、小結

從代碼實現的角度來講,JDK動態代理技術其實很是簡單,關鍵的代碼是:

public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h);

 而動態代理自己的原理不外呼就是將咱們執行的對象轉爲執行經過動態代理生成的InvocationHandler類型的實例對象的invoke方法。至於要細緻到底層實現原理,建議經過代碼去分析,本文關注點在於瞭解aop,並瞭解如何去實現,沒有考慮到具體的代碼分析,因此不作贅述。

3.二、 動態字節碼生成

3.2.一、動態字節碼

使用動態字節碼生成技術實現AOP原理是在運行期間目標字節碼加載後,生成目標類的子類,將切面邏輯加入到子類中,因此使用Cglib實現AOP不須要基於接口。

3.2.二、代碼實現(相關代碼來自Spring.3.x企業應用開發實戰)

// CGlib代理機制則是實現自接口MethodInterceptor 
// 能夠理解爲'攔截器'
// 
public class CglibProxy implements MethodInterceptor {
    private Enhancer enhancer = new Enhancer();

    /**
     * 動態建立帶有橫切邏輯的代理實例
     * 
     * @param enhancer 加強對象(織入器 )
     * @param method
     * @param args
     * @param methodProxy 攔截目標對象類的全部方法的子類
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Object enhancer, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        PerformanceMonitor.begin(enhancer.getClass().getName() + method.getName());
        Object obj = methodProxy.invokeSuper(enhancer, args);// 經過代理子類調用父類的方法
        PerformanceMonitor.end();
        return obj;
    }

    public Object getProxy(Class clazz) {
        enhancer.setSuperclass(clazz);// 目標對象類
        enhancer.setCallback(this);
        return enhancer.create();// 經過字節碼技術建立目標對象類的子類實例做爲代理
    }
}

public class TestForumService {
    public static void testCglib() {
    	// 該代理實現過程並未涉及接口
    	// 也就是說cglib技術並不要求必定要經過實現接口的方式來作切面
        ForumServiceImpl proxy = (ForumServiceImpl) new CglibProxy().getProxy(ForumServiceImpl.class);
        proxy.removeForum(10);
        proxy.removeTopic(1012);
    }

    public static void main(String[] args) {
        testCglib();
    }
}
}

3.2.三、小結

本質上,cglib是生成了一個擴展自ForumServiceImpl 的子類,而後經過子類調用父類的方式來實現的動態代理。因此它沒有了JDK動態代理的基於接口的限制。

四、Spring aop

4.一、經過ProxyFactoryBean來使用spring aop功能

// 在切面中切入日誌控制
public class LoggerBeforeAdvice implements MethodBeforeAdvice {

	@Override
	public void before(Method method, Object[] args, Object target) throws Throwable {
		System.out.println("=======保存更新日誌=========");
	}

}

// 在切面中切入權限控制
public class SecurityInterceptor implements MethodInterceptor {

	@Override
	public Object invoke(MethodInvocation methodInvocation) throws Throwable {
		System.out.println("==========執行安全校驗====================");
		return methodInvocation.proceed();
	}

}

// 注意看兩個類分別實現了兩個不一樣的接口
// 一個是advicor,一個是interceptor


/**
 * 測試代理工廠類
 * 
 * @author Administrator
 *
 */
public class TestProxyFactory {

	public static void main(String[] args) {
		test();
	}

    public static void test() {
		ForumService target = new ForumServiceImpl();
		// 2.AOP 代理工廠
		ProxyFactory proxyFactory = new ProxyFactory(target);
		// 3.裝配Advice
		proxyFactory.addAdvice(new SecurityInterceptor());
		proxyFactory.addAdvice(new LoggerBeforeAdvice());
		//proxyFactory.addAdvisor(new DefaultPointcutAdvisor(new LoggerBeforeAdvice()));
		//// 4.獲取代理對象
		ForumService proxy = (ForumService) proxyFactory.getProxy();
		proxy.removeForum(1);
		proxy.removeTopic(2);
	}

}

4.2 基於@AspectJ

// 實現方式很是簡單
// 可是要注意orderd的排序,由於有可能這個點有多個切面
// ordered能夠決定執行的順序
@Aspect
@Component
public class ForumLoggerAspect implements Ordered{

	// pointcut
	@Before("execution(* com.example.aop.common.*Impl.*(..))")
	public void before() {
		System.out.println("=======保存更新日誌=========");
	}

	@Override
	public int getOrder() {
		// TODO Auto-generated method stub
		return 1;
	}
}

4.3 使用BeanNameAutoProxyCreator建立代理

// 攔截器
public class LoggerMethodInterceptor implements MethodInterceptor {
	
	@Override
	public Object invoke(MethodInvocation invocation) throws Throwable {
		System.out.println("調用前,=======打印日誌=========");
		Object object = invocation.proceed();
		System.out.println("調用後,=======打印日誌=========");
		return object;
	}
}

@Configuration
public class AppConfig {
	// 要建立代理的目標Bean
	@Bean
	public ForumService forumService() {
		return new ForumServiceImpl();
	}

	// 建立Advice或Advisor(Interceptor)
	@Bean
	public Advice loggerMethodInterceptor() {
		return new LoggerMethodInterceptor();
	}

	// 使用BeanNameAutoProxyCreator來建立代理
	@Bean
	public BeanNameAutoProxyCreator beanNameAutoProxyCreator() {
		BeanNameAutoProxyCreator beanNameAutoProxyCreator = new BeanNameAutoProxyCreator();
		// 設置要建立代理的那些Bean的名字
		beanNameAutoProxyCreator.setBeanNames("forumService*");
		// 設置攔截鏈名字(這些攔截器是有前後順序的)
		beanNameAutoProxyCreator.setInterceptorNames("loggerMethodInterceptor");
		return beanNameAutoProxyCreator;
	}
}


public class TestBeanNameAutoProxyCreator {
	public static void main(String[] args) {
		ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
		ForumService forumService = applicationContext.getBean(ForumService.class);
		forumService.removeForum(1);
		forumService.removeTopic(2);
	}
}

4.四、使用DefaultAdvisorAutoProxyCreator建立代理

// 攔截器
public class LoggerMethodInterceptor implements MethodInterceptor {
	
	@Override
	public Object invoke(MethodInvocation invocation) throws Throwable {
		System.out.println("調用前,=======打印日誌=========");
		Object object = invocation.proceed();
		System.out.println("調用後,=======打印日誌=========");
		return object;
	}
}


@Configuration
public class AppConfig1 {

	// 要建立代理的目標Bean
	@Bean
	public ForumService forumService() {
		return new ForumServiceImpl();
	}

	// 建立Advice或Advisor(Interceptor)
	@Bean
	public Advice loggerMethodInterceptor() {
		return new LoggerMethodInterceptor();
	}

	// 使用Advice建立Advisor
	@Bean
	public NameMatchMethodPointcutAdvisor nameMatchMethodPointcutAdvisor() {
		NameMatchMethodPointcutAdvisor nameMatchMethodPointcutAdvisor = new NameMatchMethodPointcutAdvisor();
		nameMatchMethodPointcutAdvisor.setMappedNames("remove*");
		nameMatchMethodPointcutAdvisor.setAdvice(loggerMethodInterceptor());
		return nameMatchMethodPointcutAdvisor;
	}

	@Bean
	public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
		return new DefaultAdvisorAutoProxyCreator();
	}
}

public class TestDefaultAdvisorAutoProxyCreator {
	public static void main(String[] args) {
		ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig1.class);
		ForumService forumService = applicationContext.getBean(ForumService.class);
		forumService.removeForum(1);
		forumService.removeTopic(2);
	}
}

五、總結

本文並未涉及相關實現源碼以及底層相關技術細節的描述,目的是但願能夠簡單瞭解aop的相關技術(其實java aop上的實現並不僅是jdk動態代理和cglib,還有好比利用類加載,字節碼轉換器等等),由於spring 上對於aop的實現是基於這兩種,因此就簡單聊聊這兩種。後續若是對源碼有一點了解也會記錄下來。

總的來講,aop技術是對oop技術的一個完善,在實際開發中使用很是普遍,包括咱們經常使用的日誌,權限,事務後來咱們在處理多數據源上也是使用aop來作(後邊抽空整理一下Spring動態切換數據源技術)。

因此掌握aop技術是很是重要的!!!

相關spring-aop的代碼地址

參考文章:

《死磕Spring aop系列》

《spring aop》

《aop實現機制》

相關文章
相關標籤/搜索