Spring AOP實現源碼分析

Spring AOP實現

概念簡介

  • Aspect:系統中,功能模塊或者類的橫切面的一種抽象。舉例來說,web應用常見的事務管理即是典型的Aspect。各個服務功能(或類方法),都需要實現事務管理(一般指數據庫事務),這種橫跨多個功能的相同邏輯,可以稱之為Aspect。
  • Join Point:系統功能的執行點,方法調用或者方法拋出的異常處理,都可以稱之為Join Point。AOP的目的就是在方法某處執行自定義的功能,歸納為在【哪里】執行【什么功能】,這個【哪里】就是指Join Point。Spring中,Join Point指的是方法調用,從方法層級來切入。
  • Advice:在Join Point執行的具體動作,即【什么功能】。Advice分為多種類型,around、before、after等。通常,Advice以攔截器Interceptor的方式實現,某個方法的Advice的interceptor會組成一個鏈,來執行多個Advice的功能。
  • Pointcut:匹配Join Point的規則。例如,可以指定匹配帶有【add】前綴的所有方法。Advice要關聯Pointcut使用,來確定動作具體在哪里執行。Spring使用的是AspectJ的表達式。
  • Introduction:通常意義上的Advice都是附加在某個已有的方法上來指定額外的功能,Introduction可以在指定類上增加方法或者屬性。通過Introduction可以幫指定bean引入新的接口,實現新的功能。

Spring代理實現技術

? Spring代理的實現方式有兩種:

JDK代理:如果代理目標類實現了至少一個接口,那么接口的方法代理就可以使用JDK代理來實現。如果目標類沒有實現任何接口,那么將使用CGLIB來實現代理功能

CGLIB:主要實現方式是對生成一個目標類的子類,來對方法實行覆蓋,所以不可覆蓋的方法無法利用此方式來代理。

? Spring內部依賴于這兩種方式來實現代理,下面簡單舉例,說明一下二者的實現方式的區別

JDK代理

? 由于JDK代理需要接口的實現,所以先自定義一個簡單的接口:

public interface Fly {
    void fly();
}

? 定義一個簡單類實現接口:

public class Bird implements Fly{
    @Override
    public void fly() {
        System.out.println("this is bird flying");
    }
}

? JDK代理主要使用InvocationHandler接口來實現方法的代理:

public class FlyInvocationHandler implements InvocationHandler {
    /**
     * 代理目標類
     */
    private Object target;

    public FlyInvocationHandler(Object target) {
        super();
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 代理方式執行前后,自定義邏輯
        System.out.println("invocation interceptor, before");
        Object invokeResult = method.invoke(target, args);
        System.out.println("invocation interceptor, after");
        return invokeResult;
    }
    /**
     * 返回代理類對象
     *
     * @return the proxy
     * @author liuxinxing5 2018年11月30日 11:05
     */
    public Object getProxy() {
        return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
                target.getClass().getInterfaces(), this);
    }
}

? 測試代理類:

public class JdkProxyTest {
    public static void main(String[] args) {
        FlyInvocationHandler handler = new FlyInvocationHandler(new Bird());
        Fly fly = (Fly) handler.getProxy();
        fly.fly();
    }
}

? 測試輸出:

invocation interceptor, before
this is bird flying
invocation interceptor, after

CGLIB

public class EnhancerDemo {

    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        // 設置代理目標
        enhancer.setSuperclass(EnhancerDemo.class);
        // 設置代理的action,即Advice
        enhancer.setCallback(new MethodInterceptorImpl());
        EnhancerDemo demo = (EnhancerDemo) enhancer.create();
        demo.test(100);
    }

    public void test(int i) {
        System.out.println("inside enhancer demo " + i);
    }

    private static class MethodInterceptorImpl implements MethodInterceptor {

        @Override
        public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
            System.out.println("interceptor  begin   ");
            Object res = methodProxy.invokeSuper(o, objects);
            System.out.println("interceptor  end     ");
            return res;
        }
    }
}

? 測試輸出:

interceptor  begin   
inside enhancer demo 100
interceptor  end

Spring代理實現邏輯

簡單示例

? 首先先定義一個簡單的Bean:

public class TestBean {
    // 被代理方法
    public void test() {
        System.out.println("inside test bean");
    }
}

? test()方法是被代理的測試方法,下面需要定義代理的配置。Spring代理配置可以使用兩種方式,XML配置注解配置

XML配置

? 新增一個aop的配置類,XMLAop.java

public class XMLAop {
    // 用于before Advice測試
    public void before() {
        System.out.println("inside xml configured before method");
    }
    // 用于around Advice測試
    public Object around(ProceedingJoinPoint point) throws Throwable {
        System.out.println("inside xml configured around method 1");
        Object result = point.proceed(point.getArgs());
        System.out.println("inside xml configured around method 2");
        return result;
    }
}

? XML配置aop.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/>
<!--XMLAop配置為Bean-->
<bean id="xmlAop" class="com.sscon.aop.spring.XMLAop"/>
<aop:config>
    <!--Aspect配置-->
    <aop:aspect id="xmlAspect" ref="xmlAop">
        <!--定義pointcut匹配規則,用于匹配join point,這里直接匹配所有test方法-->
        <aop:pointcut id="somePointCut"
                      expression="execution(* com.sscon.aop.spring.*.test(..))" />
        <!--指定before的Advice action-->
        <aop:around method="around" pointcut-ref="somePointCut" />
        <!--指定around的Advice action-->
        <aop:before method="before" pointcut-ref="somePointCut" />
    </aop:aspect>
</aop:config>
<bean id="test" class="com.sscon.aop.spring.TestBean" />

? 測試test()方法輸出:

public static void main(String[] args) {
    ApplicationContext c = new ClassPathXmlApplicationContext("aop.xml");
    // 獲取test bean
    TestBean test = (TestBean)c.getBean("test");
    // 指定test方法
    test.test();
}

? 測試輸出:

inside xml configured around method 1
inside xml configured before method
inside test bean   
inside xml configured around method 2

注解配置

? 注解配置方式就是對XMLAop類作簡單修改,增加注解:

@Aspect
public class XMLAop {

    @Pointcut("execution(* com.sscon.aop.spring.*.test(..))")
    public void test() {
    }
    @Before("test()")
    public void before() {
        System.out.println("inside xml configured before method");
    }

    @Around("test()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        System.out.println("inside xml configured around method 1");
        Object result = point.proceed();
        System.out.println("inside xml configured around method 2");
        return result;
    }
}

? 關鍵注解:

@Aspect:表明此類是一個Aspect配置

@Pointcut:注解于void返回值的方法,用于定義join point的匹配規則

@Before:定義before類型的Advice,方法內指定執行的邏輯

@Aroud:定義around類型的Advice,方法內指定執行的邏輯,注解的方法的參數第一個必須為ProceedingJoinPoint ,使用ProceedingJoinPoint.proceed方法類實現對被代理目標方法的調用

...

? XML配置aop.xml移除aspect的相關配置:

<?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/>
<!--XMLAop配置為Bean-->
<bean id="xmlAop" class="com.sscon.aop.spring.XMLAop"/>
<bean id="test" class="com.sscon.aop.spring.TestBean" />

? 測試test()方法輸出:

inside xml configured around method 1
inside xml configured before method
inside test bean   
inside xml configured around method 2

源碼分析

AOP功能入口,創建ProxyCreator

? Spring xml配置文件中,<aop:aspectj-autoproxy>是開啟aop功能的入口,所以我們從此xml標簽的解析來分析Spring的處理流程。

? 在AopNamespaceHandler類中,Spring注冊了aop標簽配置的解析器,AspectJAutoProxyBeanDefinitionParser

// AopNamespaceHandler.java
public void init() {
    // In 2.0 XSD as well as in 2.1 XSD.
    registerBeanDefinitionParser("config", new ConfigBeanDefinitionParser());
    // 注冊處理aspectj-autoproxy標簽的處理器
    registerBeanDefinitionParser("aspectj-autoproxy", new AspectJAutoProxyBeanDefinitionParser());
    registerBeanDefinitionDecorator("scoped-proxy", new ScopedProxyBeanDefinitionDecorator());

    // Only in 2.0 XSD: moved to context namespace as of 2.1
    registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
}

? AspectJAutoProxyBeanDefinitionParser類實現了BeanDefinitionParser接口,BeanDefinitionParser是Spring用來解析<beans>直接子節點的接口,主要邏輯是parse方法:

// AspectJAutoProxyBeanDefinitionParser.java
public BeanDefinition parse(Element element, ParserContext parserContext) {
    AopNamespaceUtils.
    registerAspectJAnnotationAutoProxyCreatorIfNecessary(parserContext, element);
    extendBeanDefinition(element, parserContext);
    return null;
}
// AopNamespaceUtils.java
public static void registerAspectJAnnotationAutoProxyCreatorIfNecessary(
    ParserContext parserContext, Element sourceElement) {
    // 注冊代理的creator
    BeanDefinition beanDefinition = AopConfigUtils.
        registerAspectJAnnotationAutoProxyCreatorIfNecessary(
        parserContext.getRegistry(), parserContext.extractSource(sourceElement));
    // 處理proxy-target-class 和 expose-proxy屬性
    useClassProxyingIfNecessary(parserContext.getRegistry(), sourceElement);
    registerComponentIfNecessary(beanDefinition, parserContext);
}
// AopConfigUtils.java
public static BeanDefinition registerAspectJAnnotationAutoProxyCreatorIfNecessary(
    BeanDefinitionRegistry registry, Object source) {
    return registerOrEscalateApcAsRequired(
        AnnotationAwareAspectJAutoProxyCreator.class, registry, source);
}

? 跟蹤到AopConfigUtilsregisterOrEscalateApcAsRequired方法,可以看到Spring注冊代理creator的邏輯,其中參數clsAnnotationAwareAspectJAutoProxyCreator.class,這個方法的主要邏輯是注冊一個創建代理的工具類:

如果當前沒有注冊AUTO_PROXY_CREATOR_BEAN_NAME的代理Creator,那么使用cls參數指定的類作為工具創建

如果當前注冊了AUTO_PROXY_CREATOR_BEAN_NAME的Creator,那么要進行優先級排序,確定具體使用AnnotationAwareAspectJAutoProxyCreator還是已經創建的Creator

Spring默認使用AnnotationAwareAspectJAutoProxyCreator

// AopConfigUtils.java
public static final String AUTO_PROXY_CREATOR_BEAN_NAME =
            "org.springframework.aop.config.internalAutoProxyCreator";

static {
    APC_PRIORITY_LIST.add(InfrastructureAdvisorAutoProxyCreator.class);
    APC_PRIORITY_LIST.add(AspectJAwareAdvisorAutoProxyCreator.class);
    APC_PRIORITY_LIST.add(AnnotationAwareAspectJAutoProxyCreator.class);
}

private static BeanDefinition registerOrEscalateApcAsRequired(
    Class<?> cls, BeanDefinitionRegistry registry, Object source) {
    Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
    // 如果當前已注冊AUTO_PROXY_CREATOR_BEAN_NAME的creator,那么需要做一下選擇
    if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) {
        BeanDefinition apcDefinition = 
            registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME);
        if (!cls.getName().equals(apcDefinition.getBeanClassName())) {
            // 已注冊的creator和指定cls不同,根據優先級排序選擇
            int currentPriority = 
                // 當前已注冊的creator的優先級,優先級根據APC_PRIORITY_LIST中的
                // 序號來表示,AnnotationAwareAspectJAutoProxyCreator位于序號2
                // 是最高的優先級
                findPriorityForClass(apcDefinition.getBeanClassName());
            // cls指定的creator的優先級
            int requiredPriority = findPriorityForClass(cls);
            if (currentPriority < requiredPriority) {
                // 如果cls指定的優先級高,那么更換為cls的creator
                apcDefinition.setBeanClassName(cls.getName());
            }
        }
        return null;
    }
    RootBeanDefinition beanDefinition = new RootBeanDefinition(cls);
    beanDefinition.setSource(source);
    beanDefinition.getPropertyValues().add("order", Ordered.HIGHEST_PRECEDENCE);
    beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
    registry.registerBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME, beanDefinition);
    return beanDefinition;
}

代理配置處理

? 注冊完代理的Creator后,還需要處理指定的配置信息,讀取用戶的配置信息后,保存:

// AopNamespaceUtils.java
public static void registerAspectJAnnotationAutoProxyCreatorIfNecessary(
    ParserContext parserContext, Element sourceElement) {
    // 注冊代理的creator
    BeanDefinition beanDefinition = AopConfigUtils.
        registerAspectJAnnotationAutoProxyCreatorIfNecessary(
        parserContext.getRegistry(), parserContext.extractSource(sourceElement));
    // 處理proxy-target-class 和 expose-proxy屬性
    useClassProxyingIfNecessary(parserContext.getRegistry(), sourceElement);
    registerComponentIfNecessary(beanDefinition, parserContext);
}
// AopConfigUtils.java
private static void useClassProxyingIfNecessary(
    BeanDefinitionRegistry registry, Element sourceElement) {
    if (sourceElement != null) {
        // PROXY_TARGET_CLASS_ATTRIBUTE = "proxy-target-class"
        boolean proxyTargetClass = Boolean.parseBoolean(
            sourceElement.getAttribute(PROXY_TARGET_CLASS_ATTRIBUTE));
        if (proxyTargetClass) {
            AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
        }
        // EXPOSE_PROXY_ATTRIBUTE = "expose-proxy"
        boolean exposeProxy = Boolean.parseBoolean(
            sourceElement.getAttribute(EXPOSE_PROXY_ATTRIBUTE));
        if (exposeProxy) {
            AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
        }
    }
}
// AopConfigUtils.java
public static void forceAutoProxyCreatorToUseClassProxying(
    BeanDefinitionRegistry registry) {
    if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) {
        BeanDefinition definition = 
            registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME);
        definition.getPropertyValues().add("proxyTargetClass", Boolean.TRUE);
    }
}
// AopConfigUtils.java
public static void forceAutoProxyCreatorToExposeProxy(
    BeanDefinitionRegistry registry) {
    if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) {
        BeanDefinition definition = 
            registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME);
        definition.getPropertyValues().add("exposeProxy", Boolean.TRUE);
    }
}

? proxy-target-class與expose-proxy屬性:

proxy-target-class:Spring默認使用JDK代理來實現代理功能(前提是類至少實現了一個接口),該屬性可以強制指定使用CGLIB來實現代理功能

expose-proxy:被代理對象如果存在自我調用,例如a方法調用b方法,兩個方法都被代理,那么b方法是無法執行代理邏輯的。

被代理對象內部方法調用的問題

? 拿一個簡單的例子來描述這個問題,TestBean有方法被代理,test()test2(),test內部調用test2,test2方法配置的代理邏輯不會執行:

public class TestBean {
    // 被代理方法
    public void test() {
        System.out.println("inside test bean");
        test2();
    }
    // 被代理方法
    public void test2() {
        System.out.println("inside test2 bean   ");
    }
}

? 如果代理的配置如下:

@Aspect
public class XMLAop {
    // test test2方法都會匹配
    @Pointcut("execution(* com.sscon.aop.spring.*.test*(..))")
    public void test() {

    }
    @Before("test()")
    public void before() {
        System.out.println("inside xml configured before method");
    }
}

? 輸出如下:

inside xml configured before method
inside test bean   
inside test2 bean   

? 如果希望test2()方法也執行代理的邏輯,Spring給出一個解決辦法是,將expose-proxy修改為true嗎,暴露Spring生成的代理實體,且修改test()方法的邏輯:

public class TestBean {
    public void test() {
        System.out.println("inside test bean   ");
        // 拿到Spring生成的代理,指定test2()的方法,如果不修改expose-proxy配置,會報錯
        ((TestBean)AopContext.currentProxy()).test2();
    }
    public void test2() {
        System.out.println("inside test2 bean   ");
    }
}

使用代理Creator創建代理

? Spring默認使用AnnotationAwareAspectJAutoProxyCreator來對Spring管理的Bean做代理創建處理,此類實現了BeanPostProcessorBeanPostProcessor的關鍵方法是postProcessAfterInitialization。Bean初始化完成之后會調用此方法對Bean進行處理。該方法的實現在抽象父類AbstractAutoProxyCreator

// AbstractAutoProxyCreator.java
public Object postProcessAfterInitialization(Object bean, String beanName) 
    throws BeansException {
    if (bean != null) {
        Object cacheKey = getCacheKey(bean.getClass(), beanName);
        if (!this.earlyProxyReferences.contains(cacheKey)) {
            // 沒有緩存存在時,初始化代理
            return wrapIfNecessary(bean, beanName, cacheKey);
        }
    }
    return bean;
}

? 從上面的邏輯可以看到,當沒有緩存時,使用wrapIfNecessary方法進行代理初始化:

// AbstractAutoProxyCreator.java
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
    // 判斷是否已經處理過
    if (beanName != null && this.targetSourcedBeans.contains(beanName)) {
        return bean;
    }
    if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
        return bean;
    }
    if (isInfrastructureClass(bean.getClass()) 
        || shouldSkip(bean.getClass(), beanName)) {
        this.advisedBeans.put(cacheKey, Boolean.FALSE);
        return bean;
    }
    // 拿到Bean所有配置的interceptors
    Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(
        bean.getClass(), beanName, null);
    if (specificInterceptors != DO_NOT_PROXY) {
        this.advisedBeans.put(cacheKey, Boolean.TRUE);
        // 根據interceptors創建代理并緩存
        Object proxy = createProxy(
            bean.getClass(), beanName, specificInterceptors, 
            new SingletonTargetSource(bean));
        this.proxyTypes.put(cacheKey, proxy.getClass());
        return proxy;
    }

    this.advisedBeans.put(cacheKey, Boolean.FALSE);
    return bean;
}
獲取Bean所有配置的interceptors

? Spring內部使用Advisor類來保存Advice和Pointcut的配置信息,獲取Bean所有可用的Advisor的基本邏輯是:

首先拿到所有的advice配置,保留xml配置與注解配置

根據被代理類的實體,決定具體哪些advice可以應用于當前實體類

對advice排序,返回

// AbstractAdvisorAutoProxyCreator.java
protected Object[] getAdvicesAndAdvisorsForBean(Class<?> beanClass, String beanName,
                                                TargetSource targetSource) {
    List<Advisor> advisors = findEligibleAdvisors(beanClass, beanName);
    if (advisors.isEmpty()) {
        return DO_NOT_PROXY;
    }
    return advisors.toArray();
}

protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {
    // 查找所有可能的advice配置
    List<Advisor> candidateAdvisors = findCandidateAdvisors();
    // 過濾處理,拿到所有匹配的advice配置
    List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(
        candidateAdvisors, beanClass, beanName);
    extendAdvisors(eligibleAdvisors);
    if (!eligibleAdvisors.isEmpty()) {
        // 多個advisor需要進行排序,確定所有代理的advice邏輯的執行順序
        eligibleAdvisors = sortAdvisors(eligibleAdvisors);
    }
    return eligibleAdvisors;
}

? findCandidateAdvisors,主要負責讀取所有配置的Advice,包括兩個部分,一種是xml配置,一種的注解配置:

// AnnotationAwareAspectJAutoProxyCreator.java
protected List<Advisor> findCandidateAdvisors() {
    // 將父類讀取的advisor也保存到結果中,這里主要是xml配置的advice
    List<Advisor> advisors = super.findCandidateAdvisors();
    // 讀取注解配置的advice
    advisors.addAll(this.aspectJAdvisorsBuilder.buildAspectJAdvisors());
    return advisors;
}

? 查詢注解配置的advice信息的基本邏輯是:

首先獲取所有的Bean信息,遍歷

判斷Bean是否具有Aspect注解,如果有,獲取Advisor信息,其中包括配置的Pointcut規則,Advice配置等

緩存查詢結果,方便下次查詢

// BeanFactoryAspectJAdvisorsBuilder.java
public List<Advisor> buildAspectJAdvisors() {
    List<String> aspectNames = this.aspectBeanNames;

    if (aspectNames == null) {
        // 緩存結果
        synchronized (this) {
            aspectNames = this.aspectBeanNames;
            if (aspectNames == null) {
                List<Advisor> advisors = new LinkedList<Advisor>();
                aspectNames = new LinkedList<String>();
                // 查詢所有的bean
                String[] beanNames = 
                    BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
                    this.beanFactory, Object.class, true, false);
                for (String beanName : beanNames) {
                    // 是否合法,默認返回true
                    if (!isEligibleBean(beanName)) {
                        continue;
                    }
                    // 拿到Bean的Class對象
                    Class<?> beanType = this.beanFactory.getType(beanName);
                    if (beanType == null) {
                        continue;
                    }
                    // 如果有Aspect注解
                    if (this.advisorFactory.isAspect(beanType)) {
                        aspectNames.add(beanName);
                        AspectMetadata amd = new AspectMetadata(beanType, beanName);
                        if (amd.getAjType().getPerClause().getKind() 
                            == PerClauseKind.SINGLETON) {
                            MetadataAwareAspectInstanceFactory factory =
                                new BeanFactoryAspectInstanceFactory(
                                this.beanFactory, beanName);
                            // 獲取所有的advice信息并保存到列表
                            List<Advisor> classAdvisors = 
                                this.advisorFactory.getAdvisors(factory);
                            if (this.beanFactory.isSingleton(beanName)) {
                                this.advisorsCache.put(beanName, classAdvisors);
                            }
                            else {
                                this.aspectFactoryCache.put(beanName, factory);
                            }
                            advisors.addAll(classAdvisors);
                        }
                        else {
                            // Per target or per this.
                            if (this.beanFactory.isSingleton(beanName)) {
                                throw new IllegalArgumentException(
                                    "Bean with name '" + beanName +
              "' is a singleton, but aspect instantiation model is not singleton");
                            }
                            MetadataAwareAspectInstanceFactory factory =
                                new PrototypeAspectInstanceFactory(
                                this.beanFactory, beanName);
                            this.aspectFactoryCache.put(beanName, factory);         
                            advisors.addAll(this.advisorFactory
                                          .getAdvisors(factory));
                        }
                    }
                }
                this.aspectBeanNames = aspectNames;
                return advisors;
            }
        }
    }

    if (aspectNames.isEmpty()) {
        return Collections.emptyList();
    }
    // 從緩存中獲取advice信息并返回
    List<Advisor> advisors = new LinkedList<Advisor>();
    for (String aspectName : aspectNames) {
        List<Advisor> cachedAdvisors = this.advisorsCache.get(aspectName);
        if (cachedAdvisors != null) {
            advisors.addAll(cachedAdvisors);
        }
        else {
            MetadataAwareAspectInstanceFactory factory = 
                this.aspectFactoryCache.get(aspectName);
            advisors.addAll(this.advisorFactory.getAdvisors(factory));
        }
    }
    return advisors;
}

? 獲取Bean類的Advisor的邏輯:

遍歷Bean的方法,帶有@Pointcut注解的方法除外,因為@Pointcut配置的是join point的匹配規則,單獨處理

如果該方法有配置@Before等注解,那么構造Advisor,放入結果列表中

如果屬性有配置@DeclareParents,那么構造Advisor,放入結果列表中

// ReflectiveAspectJAdvisorFactory.java
public List<Advisor> getAdvisors(
    MetadataAwareAspectInstanceFactory aspectInstanceFactory) {
    Class<?> aspectClass = aspectInstanceFactory
        .getAspectMetadata().getAspectClass();
    String aspectName = aspectInstanceFactory
        .getAspectMetadata().getAspectName();
    validate(aspectClass);

    MetadataAwareAspectInstanceFactory lazySingletonAspectInstanceFactory = 
        new LazySingletonAspectInstanceFactoryDecorator(aspectInstanceFactory);
    // 保存Advisor信息
    List<Advisor> advisors = new LinkedList<Advisor>();
    // 遍歷當前Bean類的非Pointcut注解的方法,判斷是否有@Before等注解配置
    for (Method method : getAdvisorMethods(aspectClass)) {
        // 嘗試獲取方法的Advisor,如果當前類沒有@Pointcut配置或者方法沒有任何Advice配置
        // 都會直接返回null
        Advisor advisor = getAdvisor(method, lazySingletonAspectInstanceFactory, 
                                     advisors.size(), aspectName);
        if (advisor != null) {
            advisors.add(advisor);
        }
    }
    if (!advisors.isEmpty() 
        && lazySingletonAspectInstanceFactory.
        getAspectMetadata().isLazilyInstantiated()) {
        Advisor instantiationAdvisor = new 
            SyntheticInstantiationAdvisor(lazySingletonAspectInstanceFactory);
        advisors.add(0, instantiationAdvisor);
    }
    // 構造@DeclareParents注解的Advisor,這個配置是附加在屬性上,所以單獨處理
    // @DeclareParents主要是用于給Bean類增加新的方法,而不是在已有方法上附加行為
    for (Field field : aspectClass.getDeclaredFields()) {
        Advisor advisor = getDeclareParentsAdvisor(field);
        if (advisor != null) {
            advisors.add(advisor);
        }
    }
    return advisors;
}

? 接著上述邏輯,獲取方法的Advisor的邏輯為:

// ReflectiveAspectJAdvisorFactory.java
public Advisor getAdvisor(Method candidateAdviceMethod, 
                          MetadataAwareAspectInstanceFactory aspectInstanceFactory,
                          int declarationOrderInAspect, String aspectName) {
    validate(aspectInstanceFactory.getAspectMetadata().getAspectClass());
    // 獲取Pointcut配置
    AspectJExpressionPointcut expressionPointcut = getPointcut(
        candidateAdviceMethod, 
        aspectInstanceFactory.getAspectMetadata().getAspectClass());
    if (expressionPointcut == null) {
        return null;
    }
    // 根據查詢到的注解初初始化Advisor
    return new InstantiationModelAwarePointcutAdvisorImpl(
        expressionPointcut, candidateAdviceMethod,
        this, aspectInstanceFactory, declarationOrderInAspect, aspectName);
}

private AspectJExpressionPointcut getPointcut(
    Method candidateAdviceMethod, Class<?> candidateAspectClass) {
    // 獲取@Before等注解
    AspectJAnnotation<?> aspectJAnnotation =
        AbstractAspectJAdvisorFactory
        .findAspectJAnnotationOnMethod(candidateAdviceMethod);
    if (aspectJAnnotation == null) {
        return null;
    }

    AspectJExpressionPointcut ajexp =
        new AspectJExpressionPointcut(
        candidateAspectClass, new String[0], new Class<?>[0]);
    ajexp.setExpression(aspectJAnnotation.getPointcutExpression());
    ajexp.setBeanFactory(this.beanFactory);
    return ajexp;
}
// AbstractAspectJAdvisorFactory.java
protected static AspectJAnnotation<?> findAspectJAnnotationOnMethod(Method method) {
    Class<?>[] classesToLookFor = new Class<?>[] {
        Before.class, Around.class, After.class, 
        AfterReturning.class, AfterThrowing.class, Pointcut.class};
    for (Class<?> c : classesToLookFor) {
        AspectJAnnotation<?> foundAnnotation = 
            findAnnotation(method, (Class<Annotation>) c);
        if (foundAnnotation != null) {
            return foundAnnotation;
        }
    }
    return null;
}

? 由查詢到的注解配置信息構造Advisor(包含Advice,Pointcut等信息):

// InstantiationModelAwarePointcutAdvisorImpl.java
public InstantiationModelAwarePointcutAdvisorImpl(
    AspectJExpressionPointcut declaredPointcut,     
    Method aspectJAdviceMethod, AspectJAdvisorFactory aspectJAdvisorFactory,
    MetadataAwareAspectInstanceFactory aspectInstanceFactory, 
    int declarationOrder, String aspectName) {
        ...  
    if (aspectInstanceFactory.getAspectMetadata().isLazilyInstantiated()) {
        ...
    }
    else {
        this.pointcut = this.declaredPointcut;
        this.lazy = false;
        // 根據@Before等初始化Advice
        this.instantiatedAdvice = instantiateAdvice(this.declaredPointcut);
    }
}

private Advice instantiateAdvice(AspectJExpressionPointcut pcut) {
    return this.aspectJAdvisorFactory.getAdvice(
        this.aspectJAdviceMethod, pcut,
        this.aspectInstanceFactory, this.declarationOrder, this.aspectName);
}

? 初始化Advice的邏輯,可以看到是根據不同的注解生成不同的Advice對象,主要由工廠生成:

public Advice getAdvice(Method candidateAdviceMethod, AspectJExpressionPointcut expressionPointcut,
            MetadataAwareAspectInstanceFactory aspectInstanceFactory, int declarationOrder, String aspectName) {
    ....
    AbstractAspectJAdvice springAdvice;

    switch (aspectJAnnotation.getAnnotationType()) {
        case AtBefore:
            springAdvice = new AspectJMethodBeforeAdvice(
                candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
            break;
        case AtAfter:
            springAdvice = new AspectJAfterAdvice(
                candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
            break;
        case AtAfterReturning:
            springAdvice = new AspectJAfterReturningAdvice(
                candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
            AfterReturning afterReturningAnnotation = 
                (AfterReturning) aspectJAnnotation.getAnnotation();
            if (StringUtils.hasText(afterReturningAnnotation.returning())) {
                springAdvice.setReturningName(afterReturningAnnotation.returning());
            }
            break;
        ...
            return null;
        default:
            throw new UnsupportedOperationException(
                "Unsupported advice type on method: " + candidateAdviceMethod);
    }
    ...
}

? 到此邏輯,Spring已經可以拿到用戶配置的所有Aspect,接下來是遍歷這些配置,過濾出哪些Aspect配置可以應用于當前Bean類

過濾可應用于Bean的interceptors

? 上一步查詢出已經配置的所有的Aspect的配置,Advisor列表,根據Advisor中的Pointcut配置,來比對Bean中的方法,如果可以匹配,那么Advisor可應用于當前Bean:

// AbstractAdvisorAutoProxyCreator.java
protected List<Advisor> findAdvisorsThatCanApply(
    List<Advisor> candidateAdvisors, Class<?> beanClass, String beanName) {
    ProxyCreationContext.setCurrentProxiedBeanName(beanName);
    try {
        return AopUtils.findAdvisorsThatCanApply(candidateAdvisors, beanClass);
    }
    finally {
        ProxyCreationContext.setCurrentProxiedBeanName(null);
    }
}
// AopUtils.java
public static List<Advisor> findAdvisorsThatCanApply(
    List<Advisor> candidateAdvisors, Class<?> clazz) {
    if (candidateAdvisors.isEmpty()) {
        return candidateAdvisors;
    }
    List<Advisor> eligibleAdvisors = new LinkedList<Advisor>();
    for (Advisor candidate : candidateAdvisors) {
        // @DeclareParents類型
        if (candidate instanceof IntroductionAdvisor 
            && canApply(candidate, clazz)) {
            eligibleAdvisors.add(candidate);
        }
    }
    boolean hasIntroductions = !eligibleAdvisors.isEmpty();
    for (Advisor candidate : candidateAdvisors) {
        if (candidate instanceof IntroductionAdvisor) {
            // introduction類型已經處理
            continue;
        }
        if (canApply(candidate, clazz, hasIntroductions)) {
            eligibleAdvisors.add(candidate);
        }
    }
    return eligibleAdvisors;
}

? 主要判斷邏輯處于canApply方法,主要邏輯是判斷Bean的所有接口與Bean自己的所有方法是否可以應用Advisor中的Pointcut規則,如果任一方法可以,那就可以認為Advisor可以用于當前Bean:

// AopUtils.java
public static boolean canApply(Advisor advisor, Class<?> targetClass, boolean hasIntroductions) {
    if (advisor instanceof IntroductionAdvisor) {
        return ((IntroductionAdvisor) advisor)
            .getClassFilter().matches(targetClass);
    }
    else if (advisor instanceof PointcutAdvisor) {
        PointcutAdvisor pca = (PointcutAdvisor) advisor;
        return canApply(pca.getPointcut(), targetClass, hasIntroductions);
    }
    else {
        return true;
    }
}

public static boolean canApply(Pointcut pc, Class<?> targetClass, boolean hasIntroductions) {
    Assert.notNull(pc, "Pointcut must not be null");
    if (!pc.getClassFilter().matches(targetClass)) {
        return false;
    }
    // Pointcut配置的方法過濾為默認True類型,直接返回即可
    MethodMatcher methodMatcher = pc.getMethodMatcher();
    if (methodMatcher == MethodMatcher.TRUE) {
        return true;
    }
    IntroductionAwareMethodMatcher introductionAwareMethodMatcher = null;
    if (methodMatcher instanceof IntroductionAwareMethodMatcher) {
        introductionAwareMethodMatcher = 
            (IntroductionAwareMethodMatcher) methodMatcher;
    }
    // 存放代理目標類的所有接口類
    Set<Class<?>> classes = new LinkedHashSet<Class<?>>(
        ClassUtils.getAllInterfacesForClassAsSet(targetClass));
    // 把代理目標類也放入set
    classes.add(targetClass);
    // 上述所有類的任一方法可以匹配Pointcut,認為此Advisor可以應用于當前被代理類
    for (Class<?> clazz : classes) {
        Method[] methods = ReflectionUtils.getAllDeclaredMethods(clazz);
        for (Method method : methods) {
            if ((introductionAwareMethodMatcher != null 
                 && introductionAwareMethodMatcher.matches(
                     method, targetClass, hasIntroductions)) 
                || methodMatcher.matches(method, targetClass)) {
                return true;
            }
        }
    }
    return false;
}

? 至此,Spring已經拿到當前Bean可應用的Advisor列表,接下來就是利用Advisor和Bean來創建代理類

代理創建邏輯

? 前文提到的AbstractAutoProxyCreatorwrapIfNecessary方法中包含了實際代理創建的邏輯:

// AbstractAutoProxyCreator.java
protected Object createProxy(
            Class<?> beanClass, String beanName, Object[] specificInterceptors, TargetSource targetSource) {

    ... proxyFactory的初始化邏輯
    // 利用proxyFactory創建代理
    return proxyFactory.getProxy(getProxyClassLoader());
}
// ProxyFactory.java
public Object getProxy(ClassLoader classLoader) {
    return createAopProxy().getProxy(classLoader);
}
// ProxyCreatorSupport.java
protected final synchronized AopProxy createAopProxy() {
    if (!this.active) {
        activate();
    }
    return getAopProxyFactory().createAopProxy(this);
}
// DefaultAopProxyFactory.java
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
    if (config.isOptimize() || config.isProxyTargetClass() 
        || hasNoUserSuppliedProxyInterfaces(config)) {
        Class<?> targetClass = config.getTargetClass();
        if (targetClass == null) {
            throw new AopConfigException(
                "TargetSource cannot determine target class: " +                     
                "Either an interface or a target is required for proxy creation.");
        }
        // 這里判斷使用哪一種代理創建方式
        if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
            return new JdkDynamicAopProxy(config);
        }
        // ObjenesisCglibAopProxy是CglibAopProxy子類,做了一些邏輯優化
        return new ObjenesisCglibAopProxy(config);
    }
    else {
        return new JdkDynamicAopProxy(config);
    }
}

? 可以看到,Spring根據代理目標類是否實現接口,配置中是否強制開啟CGLIB,來決定使用哪一種方式來創建代理。

JdkDynamicAopProxy

? JdkDynamicAopProxy與JDK代理簡單實例中的一致,實現了InvocationHandler接口,主要邏輯位于invoke方法中,主要是將所有interceptor組成一個鏈,然后調用鏈執行,最后執行被代理方法:

// JdkDynamicAopProxy.java
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    MethodInvocation invocation;
    ...
    try {
        Object retVal;
        ...
        // interceptor組成一個鏈,鏈式調用
        List<Object> chain = this.advised.
            getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
        if (chain.isEmpty()) {
            Object[] argsToUse = AopProxyUtils.
                adaptArgumentsIfNecessary(method, args);
            retVal = AopUtils.
                invokeJoinpointUsingReflection(target, method, argsToUse);
        }
        else {
            invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
            retVal = invocation.proceed();
        }
        ...
        Class<?> returnType = method.getReturnType();
        if (retVal != null && retVal == target &&
            returnType != Object.class && returnType.isInstance(proxy) &&
            !RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) {
            return retVal;
        }
        finally {
            ...
        }
    }

? interceptor鏈執行:

// ReflectiveMethodInvocation.java
public Object proceed() throws Throwable {
    //  最后執行join point方法,也就是被代理方法
    if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
        return invokeJoinpoint();
    }

    Object interceptorOrInterceptionAdvice =
        this.interceptorsAndDynamicMethodMatchers
        .get(++this.currentInterceptorIndex);
    if (interceptorOrInterceptionAdvice 
        instanceof InterceptorAndDynamicMethodMatcher) {
        // 匹配當前方法,執行攔截的代理邏輯
        InterceptorAndDynamicMethodMatcher dm =
            (InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
        if (dm.methodMatcher.matches(this.method, 
                                     this.targetClass, this.arguments)) {
            return dm.interceptor.invoke(this);
        }
        else {
            // 不匹配當前方法,跳過
            return proceed();
        }
    }
    else {
        return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
    }
}
CglibAopProxy

? CGLIB構造代理的方式與示例類似,都是利用Enhancer類來進行構造:

// CglibAopProxy.java
public Object getProxy(ClassLoader classLoader) {
    try {
        ...
        // 配置Enhancer...
        Enhancer enhancer = createEnhancer();
        ...
        enhancer.setSuperclass(proxySuperClass);
        enhancer.setInterfaces(
            AopProxyUtils.completeProxiedInterfaces(this.advised));
        enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
        enhancer.setStrategy(
            new ClassLoaderAwareUndeclaredThrowableStrategy(classLoader));
        // 獲取關鍵的代理執行邏輯方法,callback方法
        Callback[] callbacks = getCallbacks(rootClass);
        Class<?>[] types = new Class<?>[callbacks.length];
        for (int x = 0; x < types.length; x++) {
            types[x] = callbacks[x].getClass();
        }
        // 設置CallBackFilter
        enhancer.setCallbackFilter(new ProxyCallbackFilter(
            this.advised.getConfigurationOnlyCopy(), 
            this.fixedInterceptorMap, this.fixedInterceptorOffset));
        enhancer.setCallbackTypes(types);
        // 設置CallBack方法
        return createProxyClassAndInstance(enhancer, callbacks);
    }...
}

? 獲取CallBack方法的主要邏輯為:

// CglibAopProxy.java
private Callback[] getCallbacks(Class<?> rootClass) throws Exception {
    boolean exposeProxy = this.advised.isExposeProxy();
    boolean isFrozen = this.advised.isFrozen();
    boolean isStatic = this.advised.getTargetSource().isStatic();

    // 獲取aop配置的CallBack
    Callback aopInterceptor = new DynamicAdvisedInterceptor(this.advised);

    // 一些優化策略
    ...
    Callback[] mainCallbacks = new Callback[] {
        aopInterceptor,  // xml和注解配置的aop代理邏輯
        targetInterceptor,  // 優化
        new SerializableNoOp(),  // 優化
        targetDispatcher, this.advisedDispatcher,
        new EqualsInterceptor(this.advised),
        new HashCodeInterceptor(this.advised)
    };
    Callback[] callbacks;
    // 一些優化策略
    ...
    return callbacks;
}

? DynamicAdvisedInterceptor實現了MethodInterceptor接口,也就是CGLIB示例中的CallBack方法的實現:

// DynamicAdvisedInterceptor.java
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
    Object oldProxy = null;
    boolean setProxyContext = false;
    Class<?> targetClass = null;
    Object target = null;
    try {
        if (this.advised.exposeProxy) {
            oldProxy = AopContext.setCurrentProxy(proxy);
            setProxyContext = true;
        }
        target = getTarget();
        if (target != null) {
            targetClass = target.getClass();
        }
        List<Object> chain = this.advised
            .getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
        Object retVal;
        if (chain.isEmpty() && Modifier.isPublic(method.getModifiers())) {
            // 直接調用目標類的方法
            Object[] argsToUse = AopProxyUtils
                .adaptArgumentsIfNecessary(method, args);
            retVal = methodProxy.invoke(target, argsToUse);
        }
        else {
            // 同樣是調用鏈的創建,CglibMethodInvocation直接繼承JDK代理使用的
            // ReflectiveMethodInvocation,主要邏輯沒有變化
            retVal = new CglibMethodInvocation(proxy, target, 
                                               method, args, targetClass, 
                                               chain, methodProxy).proceed();
        }
        retVal = processReturnType(proxy, target, method, retVal);
        return retVal;
    }
    finally {
        if (target != null) {
            releaseTarget(target);
        }
        if (setProxyContext) {
            AopContext.setCurrentProxy(oldProxy);
        }
    }
}

? 使用JDK或CGLIB創建代理后,Bean在AOP的Postprocess的流程就結束了。后續調用Bean方法時,就可以實現AOP方法的調用。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,646評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,595評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,560評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,035評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,814評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,224評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,301評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,444評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,988評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,804評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,998評論 1 370
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,544評論 5 360
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,237評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,665評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,927評論 1 287
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,706評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,993評論 2 374

推薦閱讀更多精彩內容

  • 本文是我自己在秋招復習時的讀書筆記,整理的知識點,也是為了防止忘記,尊重勞動成果,轉載注明出處哦!如果你也喜歡,那...
    波波波先森閱讀 12,309評論 6 86
  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,785評論 18 139
  • 轉自:https://javadoop.com/post/spring-aop-intro Spring AOP ...
    劍書藏于西閱讀 1,319評論 0 8
  • 概述 Spring是什么? Spring是一個開源框架,為了解決企業應用開發的復雜性而創建的,但是現在已經不止于企...
    瑯筑閱讀 1,181評論 2 8
  • 告吾友, 小船一盞, 苦樂潮起, 緣來緣去, 一槳進退不必思。 林中霧氣漸濃, 氣溫下降的很快, 折下來的脆樹枝放...
    青年書文閱讀 267評論 0 1