spring內部使用了jdk動態代理、cglib(經過生成字節碼的方式,繼承目標類擴展目標類功能)兩種方式實現了AOP框架。本篇先詳細介紹spring內部的AOP概念實體、以後介紹spring AOP的使用方式和原理java
spring內部使用了jdk動態代理、cglib這兩種機制構建了整個AOP框架的基礎正則表達式
咱們能夠經過反射技術,爲須要代理的目標對象,創造一個代理類出來,而且在代理類中執行咱們所須要的邏輯,如:統計方法執行時間、打印日誌。
相對於cglib技術,JDK動態代理存在兩個比較明顯的缺點:spring
// 一個須要進行代理的接口
public interface Greeting {
void sayHi(String name);
void sayByte(String name);
}
// 接口實現類,即目標對象。
// 咱們須要在不改變該實現類代碼的基礎上,在執行接口方法時,進行一些額外的功能
public class GreetingImpl implements Greeting {
@Override
public void sayHi(String name) {
System.out.println(name);
}
@Override
public void sayByte(String name) {
System.out.println(name);
}
}
// 實現一個InvocationHandler,用於執行額外功能,而且調用目標對象的方法
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/** * created by luhuancheng on 2018/11/17 * @author luhuancheng */
public class LogInvocationHandler implements InvocationHandler {
private Object target;
public LogInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Before execute"); // 在執行目標對象方法以前,打印日誌
Object result = method.invoke(target, args); // 執行目標對象的方法
System.out.println("After execute"); // 在執行目標對象方法以後,打印日誌
return result;
}
}
// 建立動態代理
public class DynamicProxy {
public static void main(String[] args) {
Greeting greeting = new GreetingImpl();
Object proxyInstance = Proxy.newProxyInstance(DynamicProxy.class.getClassLoader(), new Class[]{Greeting.class}, new LogInvocationHandler(greeting));
Greeting proxy = (Greeting) proxyInstance;
proxy.sayHi("luhuancheng");
proxy.sayByte("luhuancheng");
}
}
複製代碼
當咱們要代理的目標對象,並非由一個接口實現時。咱們沒法經過JDK動態代理來進行代理對象的建立,這時候就須要cglib這種字節碼技術的登場了。緩存
// 須要被代理的目標類,該類沒有實現任何一個接口
public class Requestable {
public void request() {
System.out.println("request in Requestable without implementint any interface");
}
}
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
// 實現cglib的方法攔截器接口
public class RequestableCallback implements MethodInterceptor {
private static final String INTERCEPTOR_METHOD_NAME = "request";
@Override
public Object intercept(Object target, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
if (INTERCEPTOR_METHOD_NAME.equals(method.getName())) {
System.out.println("Before execute");
Object result = methodProxy.invokeSuper(target, args);
System.out.println("After execute");
return result;
}
return null;
}
}
import net.sf.cglib.proxy.Enhancer;
// cglib代理實現-動態字節碼生成技術擴展對象行爲(對目標對象進行繼承擴展)
public class CglibProxy {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Requestable.class);
enhancer.setCallback(new RequestableCallback());
Requestable proxy = (Requestable) enhancer.create();
proxy.request();
}
}
複製代碼
JoinPoint,能夠理解爲執行切面動做的一個時機。如:構造方法調用時,字段設置時,方法調用時,方法執行時。但在spring AOP中,僅支持方法執行時的JoinPoint。app
Pointcut,是咱們在開發AOP功能時,定義的一個幫助咱們捕捉系統中的相應JoinPoint的規則。框架
Advice,定義了AOP功能中的橫切邏輯less
橫切邏輯將在相應的JoinPoint執行以前執行ide
橫切邏輯將在相應的JoinPoint執行以後執行。該接口又派生了兩個子接口ThrowsAdvice、AfterReturningAdvicepost
橫切邏輯將在相應的JoinPoint執行異常時執行性能
// ThrowsAdvice是一個沒有定義方法的標記接口,可是在橫切邏輯執行時,
// spring AOP內部會經過反射的方式,檢測ThrowsAdvice實現類的方法。
// 咱們能夠定義如下三個方法,分別處理不一樣的異常
public class ExceptionBarrierThrowsAdvice implements ThrowsAdvice {
public void afterThrowing(Throwable t) {
// 普通異常處理邏輯
}
public void afterThrowing(RuntimeException e) {
// 運行時異常處理邏輯
}
public void afterThrowing(Method method, Object[] args, Object target, ApplicationException e) {
// 處理應用程序生成的異常
}
}
/** * 業務異常類 */
class ApplicationException extends RuntimeException {
}
複製代碼
橫切邏輯將在相應的JoinPoint正常執行返回時執行
做爲Spring AOP的環繞方法(Around Advice),能夠攔截相應的JoinPoint方法執行,從而在JoinPoint執行的前、正常返回、執行後這三個地方進行橫切邏輯的切入
public class PerformanceMethodInterceptor implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
long start = System.currentTimeMillis();
try {
// 調用目標方法
return invocation.proceed();
} finally {
System.out.println(String.format("cost time: %d", System.currentTimeMillis() - start));
}
}
}
複製代碼
Spring AOP框架中實現Introduction的接口。Introduction功能能夠在不改變類的定義的狀況下,爲目標類增長新的接口或屬性
Advisor,表明了spring中的Aspect。
分爲兩大類:PointcutAdvisor、IntroductionAdvisor
PointcutAdvisor,默認存在三個實現類DefaultPointcutAdvisor、NameMatchMethodPointcutAdvisor、RegexpMethodPointcutAdvisor
DefaultPointcutAdvisor做爲PointcutAdvisor比較通用的一個實現類,咱們能夠爲其設置Pointcut、Advice。僞代碼以下:
Pointcut pointcut = ...
Advice advice = ...
DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor();
advisor.setPointcut(pointcut);
advisor.setAdvice(advice);
複製代碼
NameMatchMethodPointcutAdvisor,其內部持有一個NameMatchMethodPointcut實例做爲pointcut。能夠經過setMappedName(String mappedName)或者setMappedNames(String... mappedNames)操做pointcut,設置匹配的Pointcut方法名
RegexpMethodPointcutAdvisor,其內部持有一個AbstractRegexpMethodPointcut實現類的實例pointcut(默認爲JdkRegexpMethodPointcut),使用setPattern(String pattern)或者setPatterns(String... patterns)設置正則表達式用於匹配JoinPoint方法名
DefaultBeanFactoryPointcutAdvisor,間接繼承了BeanFactoryAware,其內部持有beanfactory。在指定advice時,能夠經過方法setAdviceBeanName(String adviceBeanName)指定advice在beanfactory中的惟一name。以後在須要Advice時,將從beanfactory中獲取,減小了容器啓動初期Advice和Advisor之間的耦合
DefaultIntroductionAdvisor做爲惟一的Introduction類型的Advice,只能使用於Introduction場景。
在spring AOP中,能夠經過ProxyFactory、ProxyFactoryBean、AbstractAutoProxyCreator(及其實現類)來執行織入
咱們來看看ProxyFactory這個最基本的織入器是如何工做的。其步驟大體分爲如下幾步:
/** * 基於接口的織入 - JDK動態代理 */
private static void forInterface() {
// 1. 須要被攔截的接口實現類
ITask task = new TaskImpl();
// 2. 實例化一個執行織入操做的ProxyFactory
ProxyFactory weaver = new ProxyFactory(task);
// 咱們也可讓基於接口的織入,使用cglib的方式
weaver.setProxyTargetClass(true);
weaver.setOptimize(true);
// 3. 指定須要織入的接口
weaver.setInterfaces(ITask.class);
// 4. 定義切面
NameMatchMethodPointcutAdvisor advisor = new NameMatchMethodPointcutAdvisor();
advisor.setMappedName("execute");
// 5. 指定切面邏輯
advisor.setAdvice(new PerformanceMethodInterceptor());
// 6. 將切面添加到ProxyFactory實例中
weaver.addAdvisor(advisor);
// 7. 執行織入,返回織入後的代理實例
ITask proxyObject = (ITask) weaver.getProxy();
// 8. 調用接口,此時的執行邏輯中織入了切面邏輯
proxyObject.execute();
System.out.println(proxyObject);
System.out.println(task);
}
複製代碼
/** * 基於類的織入 - CGLIB */
private static void forClass() {
// 1. 實例化一個執行織入操做的ProxyFactory,做爲織入器
ProxyFactory weaver = new ProxyFactory(new Excutable());
// 2. 實例化Advisor(切面)
NameMatchMethodPointcutAdvisor advisor = new NameMatchMethodPointcutAdvisor();
advisor.setMappedName("execute");
// 3. 爲Advisor設置切面邏輯
advisor.setAdvice(new PerformanceMethodInterceptor());
// 4. 爲織入器設置切面
weaver.addAdvisor(advisor);
// 5. 執行織入
Excutable proxyObject = (Excutable) weaver.getProxy();
// 6. 調用接口,此時的執行邏輯中織入了切面邏輯
proxyObject.execute(); // 使用cglib,第一次執行須要動態生成字節碼,效率比動態代理低。
proxyObject.execute(); // 再次使用cglib,直接執行第一次調用生成的字節碼,效率比動態代理高
System.out.println(proxyObject.getClass());
}
複製代碼
private static void forIntroduction() {
ProxyFactory weaver = new ProxyFactory(new DevloperImpl());
weaver.setInterfaces(IDevloper.class, ITester.class);
TesterFeatureIntroductionInterceptor advice = new TesterFeatureIntroductionInterceptor();
weaver.addAdvice(advice);
// DefaultIntroductionAdvisor advisor = new DefaultIntroductionAdvisor(advice);
// weaver.addAdvisor(advisor);
Object proxy = weaver.getProxy();
((ITester) proxy).test();
((IDevloper) proxy).develSoftware();
}
複製代碼
AopProxy對使用不一樣實現機制的代理進行了抽象。提供兩個接口用於生成代理對象
Object getProxy();
Object getProxy(ClassLoader classLoader);
複製代碼
提供了5個屬性用於配置控制代理對象生成的過程
private boolean proxyTargetClass = false;
private boolean optimize = false;
boolean opaque = false;
boolean exposeProxy = false;
private boolean frozen = false;
複製代碼
提供接口用於配置生成代理的必要配置信息,好比Advice、Advisor等
做爲AopProxy的抽象工廠
AopProxyFactory默認實現,經過接口根據AdvisedSupport提供的配置信息建立代理
AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException;
複製代碼
做爲基本的織入器,繼承了ProxyCreatorSupport(爲了統一管理生成不一樣類型的AopProxy,將生成邏輯抽象到了這個類中)。ProxyCreatorSupport持有了一個AopProxyFactory類型的實例,默認爲DefaultAopProxyFactory
使用ProxyFactory咱們能夠獨立於spring的IOC容器來使用spring AOP框架。可是便於咱們管理Pointcut、Advice等相關的bean,咱們通常利用IOC容器來進行管理。在IOC容器中,使用ProxyFactoryBean來進行織入
public class ProxyFactoryBean extends ProxyCreatorSupport implements FactoryBean<Object>, BeanClassLoaderAware, BeanFactoryAware {
private synchronized void initializeAdvisorChain() throws AopConfigException, BeansException {
if (this.advisorChainInitialized) {
return;
}
// 當攔截器名稱列表不爲空時,初始化advisor chain
if (!ObjectUtils.isEmpty(this.interceptorNames)) {
// 須要根據interceptorName從beanfactory中取到對應advisor的實例,因此beanfactory不能爲null
if (this.beanFactory == null) {
throw new IllegalStateException("No BeanFactory available anymore (probably due to serialization) " +
"- cannot resolve interceptor names " + Arrays.asList(this.interceptorNames));
}
// Globals can't be last unless we specified a targetSource using the property...
if (this.interceptorNames[this.interceptorNames.length - 1].endsWith(GLOBAL_SUFFIX) &&
this.targetName == null && this.targetSource == EMPTY_TARGET_SOURCE) {
throw new AopConfigException("Target required after globals");
}
// Materialize interceptor chain from bean names.
for (String name : this.interceptorNames) {
if (logger.isTraceEnabled()) {
logger.trace("Configuring advisor or advice '" + name + "'");
}
// 若是name以符號"*"結尾,則從beanfactory中獲取beanname爲name(去掉*)開頭的全部Advisor、Interceptor類型的bean,註冊爲Advisor
if (name.endsWith(GLOBAL_SUFFIX)) {
if (!(this.beanFactory instanceof ListableBeanFactory)) {
throw new AopConfigException(
"Can only use global advisors or interceptors with a ListableBeanFactory");
}
addGlobalAdvisor((ListableBeanFactory) this.beanFactory,
name.substring(0, name.length() - GLOBAL_SUFFIX.length()));
}
// 普通的name(即非*號匹配的bean),則直接從beanfactory獲取,添加到advisor chain中
else {
// If we get here, we need to add a named interceptor.
// We must check if it's a singleton or prototype.
Object advice;
if (this.singleton || this.beanFactory.isSingleton(name)) {
// Add the real Advisor/Advice to the chain.
advice = this.beanFactory.getBean(name);
}
else {
// It's a prototype Advice or Advisor: replace with a prototype.
// Avoid unnecessary creation of prototype bean just for advisor chain initialization.
advice = new PrototypePlaceholderAdvisor(name);
}
addAdvisorOnChainCreation(advice, name);
}
}
}
// 標記已初始化
this.advisorChainInitialized = true;
}
private synchronized Object getSingletonInstance() {
if (this.singletonInstance == null) {
// 根據targetName從beanfactory中獲取目標對象
this.targetSource = freshTargetSource();
if (this.autodetectInterfaces && getProxiedInterfaces().length == 0 && !isProxyTargetClass()) {
// Rely on AOP infrastructure to tell us what interfaces to proxy.
Class<?> targetClass = getTargetClass();
if (targetClass == null) {
throw new FactoryBeanNotInitializedException("Cannot determine target class for proxy");
}
// 自動識別目標對象的接口,設置到interfaces屬性中
setInterfaces(ClassUtils.getAllInterfacesForClass(targetClass, this.proxyClassLoader));
}
// Initialize the shared singleton instance.
super.setFrozen(this.freezeProxy);
// 生成代理對象
this.singletonInstance = getProxy(createAopProxy());
}
return this.singletonInstance;
}
@Override
public Object getObject() throws BeansException {
// 初始化advisor chain
initializeAdvisorChain();
if (isSingleton()) {
// 獲取單例的代理
return getSingletonInstance();
}
else {
if (this.targetName == null) {
logger.warn("Using non-singleton proxies with singleton targets is often undesirable. " +
"Enable prototype proxies by setting the 'targetName' property.");
}
// 獲取原型的代理(每次都會從新生成代理對象)
return newPrototypeInstance();
}
}
}
複製代碼
使用BeanNameAutoProxyCreator的僞代碼
TargetClass target1 = new TargetClass();
TargetClass target2 = new TargetClass();
BeanNameAutoProxyCreator autoProxyCreator = new BeanNameAutoProxyCreator();
// 指定目標實例的beanname
autoProxyCreator.setBeanNames("target1", "target2");
// 指定Advice(切面邏輯)
autoProxyCreator.setInterceptorNames("a interceptor name");
// 完成以上配置的BeanNameAutoProxyCreator,註冊到IOC容器時,將自動完成對target一、target2兩個bean進行織入切面邏輯
複製代碼
總結一下:
如下內容就是spring aop框架的實現原理,能夠看到建立aop的過程至關的繁瑣,而且若是使用這種方式來建立代理類,織入切面邏輯的話,存在大量的模板代碼。在spring2.0中,使用了一種全新的方法來簡化咱們開發AOP的流程。咱們在下篇文章進行分析吧