Spring 5 中文解析核心篇-IoC容器之AOP編程(下)

5.5 基于Schema的AOP支持

如果你更喜歡基于XML的格式宋雏,Spring還提供了使用新的aop名稱空間標(biāo)簽定義切面的支持闽颇。支持與使用@AspectJ樣式時(shí)完全相同的切入點(diǎn)表達(dá)式和通知類型厢岂。因此绳瘟,在本節(jié)中易茬,我們將重點(diǎn)放在新語法上茧妒,并使讀者參考上一節(jié)中的討論(@AspectJ支持)萧吠,以了解編寫切入點(diǎn)表達(dá)式和通知參數(shù)的綁定。

要使用本節(jié)中描述的aop名稱空間標(biāo)簽桐筏,你需要導(dǎo)入spring-aop模式纸型,如基于XML Schema的配置中所述。有關(guān)如何在aop名稱空間中導(dǎo)入標(biāo)簽的信息梅忌,請參見AOP schema狰腌。

在你的Spring配置中,所有切面和advisor元素都必須放在<aop:config>元素內(nèi)(在應(yīng)用程序上下文配置中可以有多個(gè)<aop:config>元素)牧氮。<aop:config>元素可以包含切入點(diǎn)琼腔,advisoraspect元素(請注意,必須按此順序聲明它們)踱葛。

<aop:config>的配置樣式大量使用了Spring的自動(dòng)代理機(jī)制丹莲。如果你已經(jīng)通過BeanNameAutoProxyCreator或類似的工具使用了顯式的自動(dòng)代理,那么這可能會導(dǎo)致一些問題(比如沒有編織通知)尸诽。推薦的用法模式是僅使用<aop:config>樣式或僅使用AutoProxyCreator樣式并且不要混合使用甥材。

5.5.1 聲明切面

使用schema支持時(shí),切面是在Spring應(yīng)用程序上下文中定義為Bean的常規(guī)Java對象性含。狀態(tài)和行為在對象的字段和方法中捕獲洲赵,切入點(diǎn)和通知信息在XML中捕獲。

你可以通過使用<aop:aspect>元素來聲明一個(gè)切面,并通過使用ref屬性來引用后臺bean板鬓,如下面的示例所示:

<aop:config>
    <aop:aspect id="myAspect" ref="aBean">
        ...
    </aop:aspect>
</aop:config>

<bean id="aBean" class="...">
    ...
</bean>

支持切面的bean(在本例中為aBean)當(dāng)然可以像配置其他Spring bean一樣進(jìn)行配置并注入依賴項(xiàng)悲敷。

5.5.2 聲明切入點(diǎn)

你可以在<aop:config>元素內(nèi)聲明一個(gè)命名的切入點(diǎn)究恤,讓切入點(diǎn)定義多個(gè)切面和advisors之間共享俭令。

可以定義代表服務(wù)層中任何業(yè)務(wù)服務(wù)的執(zhí)行的切入點(diǎn):

<aop:config>

    <aop:pointcut id="businessService"
        expression="execution(* com.xyz.myapp.service.*.*(..))"/>

</aop:config>

請注意,切入點(diǎn)表達(dá)式本身使用的是@AspectJ支持中所述的AspectJ切入點(diǎn)表達(dá)式語言部宿。如果使用基于schema的聲明樣式抄腔,則可以引用在切入點(diǎn)表達(dá)式中的類型(@Aspects)中定義的命名切入點(diǎn)。定義上述切入點(diǎn)的另一種方法如下:

<aop:config>

    <aop:pointcut id="businessService"
        expression="com.xyz.myapp.SystemArchitecture.businessService()"/>

</aop:config>

假定你具有“共享通用切入點(diǎn)定義”中所述的SystemArchitecture方面理张。

然后赫蛇,在切面內(nèi)聲明切入點(diǎn)與聲明頂級切入點(diǎn)非常相似,如以下示例所示:

<aop:config>

    <aop:aspect id="myAspect" ref="aBean">

        <aop:pointcut id="businessService"
            expression="execution(* com.xyz.myapp.service.*.*(..))"/>

        ...

    </aop:aspect>

</aop:config>

@AspectJ切面幾乎相同雾叭,通過使用基于schema的定義樣式聲明的切入點(diǎn)可以收集連接點(diǎn)上下文悟耘。例如,以下切入點(diǎn)收集此對象作為連接點(diǎn)上下文织狐,并將其傳遞給通知:

<aop:config>

    <aop:aspect id="myAspect" ref="aBean">

        <aop:pointcut id="businessService"
            expression="execution(* com.xyz.myapp.service.*.*(..)) && this(service)"/>

        <aop:before pointcut-ref="businessService" method="monitor"/>

        ...

    </aop:aspect>

</aop:config>

必須聲明該通知以通過包含匹配名稱的參數(shù)來接收收集的連接點(diǎn)上下文暂幼,如下所示:

public void monitor(Object service) {
    // ...
}

在組合切入點(diǎn)子表達(dá)式時(shí),&&在XML文檔中是不合適的移迫,所以你可以分別使用and旺嬉、ornot關(guān)鍵字來代替&&||!厨埋。例如邪媳,上一個(gè)切入點(diǎn)可以更好地編寫如下:

<aop:config>

    <aop:aspect id="myAspect" ref="aBean">

        <aop:pointcut id="businessService"
            expression="execution(* com.xyz.myapp.service.*.*(..)) and this(service)"/>

        <aop:before pointcut-ref="businessService" method="monitor"/>

        ...
    </aop:aspect>
</aop:config>

請注意,以這種方式定義的切入點(diǎn)由其XML ID引用荡陷,并且不能用作命名切入點(diǎn)以形成復(fù)合切入點(diǎn)雨效。因此,基于schema的定義樣式中的命名切入點(diǎn)支持比@AspectJ樣式所提供的更受限制废赞。

5.5.3 聲明通知

基于schema的AOP支持使用與@AspectJ樣式相同的五種通知设易,并且它們具有完全相同的語義。

前置通知

在執(zhí)行匹配的方法之前蛹头,先運(yùn)行通知顿肺。使用<aop:before>元素在<aop:aspect>中聲明它,如以下示例所示:

<aop:aspect id="beforeExample" ref="aBean">

    <aop:before
        pointcut-ref="dataAccessOperation"
        method="doAccessCheck"/>

    ...

</aop:aspect>

在這里渣蜗,dataAccessOperation是在最高(<aop:config>)級別定義的切入點(diǎn)ID屠尊。要定義內(nèi)聯(lián)切入點(diǎn),請使用以下方法將pointcut-ref屬性替換為pointcut屬性:

<aop:aspect id="beforeExample" ref="aBean">

    <aop:before
        pointcut="execution(* com.xyz.myapp.dao.*.*(..))"
        method="doAccessCheck"/>

    ...

</aop:aspect>

正如我們在@AspectJ樣式的討論中所指出的那樣耕拷,使用命名的切入點(diǎn)可以顯著提高代碼的可讀性讼昆。method屬性標(biāo)識提供通知正文的方法(doAccessCheck)。必須為包含通知的Aspect元素所引用的bean定義此方法骚烧。在執(zhí)行數(shù)據(jù)訪問操作(與切入點(diǎn)表達(dá)式匹配的方法執(zhí)行連接點(diǎn))之前浸赫,將調(diào)用方面bean上的doAccessCheck方法闰围。

返回后通知

返回的通知在匹配的方法執(zhí)行正常完成時(shí)運(yùn)行。它以與前置通知相同的方式在<aop:aspect>中聲明既峡。以下示例顯示了如何聲明它:

<aop:aspect id="afterReturningExample" ref="aBean">

    <aop:after-returning
        pointcut-ref="dataAccessOperation"
        method="doAccessCheck"/>

    ...

</aop:aspect>

@AspectJ樣式一樣羡榴,你可以在通知正文中獲取返回值。為此运敢,使用returning屬性指定返回值應(yīng)傳遞到的參數(shù)的名稱校仑,如以下示例所示:

<aop:aspect id="afterReturningExample" ref="aBean">

    <aop:after-returning
        pointcut-ref="dataAccessOperation"
        returning="retVal"
        method="doAccessCheck"/>

    ...

</aop:aspect>

doAccessCheck方法必須聲明一個(gè)名為retVal的參數(shù)。該參數(shù)的類型以與@AfterReturning中所述相同的方式約束匹配传惠。例如迄沫,你可以按以下方式聲明方法簽名:

public void doAccessCheck(Object retVal) {...

異常通知

當(dāng)匹配的方法執(zhí)行通過拋出異常退出時(shí)執(zhí)行通知時(shí),拋出通知卦方。通過使用after-throwing元素在<aop:aspect>中聲明它羊瘩,如以下示例所示:

<aop:aspect id="afterThrowingExample" ref="aBean">

    <aop:after-throwing
        pointcut-ref="dataAccessOperation"
        method="doRecoveryActions"/>

    ...

</aop:aspect>

@AspectJ樣式一樣,你可以在通知正文中獲取引發(fā)的異常盼砍。為此尘吗,請使用throwing屬性指定異常應(yīng)傳遞到的參數(shù)的名稱,如以下示例所示:

<aop:aspect id="afterThrowingExample" ref="aBean">

    <aop:after-throwing
        pointcut-ref="dataAccessOperation"
        throwing="dataAccessEx"
        method="doRecoveryActions"/>

    ...

</aop:aspect>

doRecoveryActions方法必須聲明一個(gè)名為dataAccessEx的參數(shù)衬廷。該參數(shù)的類型以與@AfterThrowing中所述相同的方式約束匹配摇予。例如,方法簽名可以聲明如下:

public void doRecoveryActions(DataAccessException dataAccessEx) {...

最終通知

無論最終如何執(zhí)行匹配的方法吗跋,最終通知都會運(yùn)行侧戴。你可以使用after元素聲明它,如以下示例所示:

<aop:aspect id="afterFinallyExample" ref="aBean">

    <aop:after
        pointcut-ref="dataAccessOperation"
        method="doReleaseLock"/>

    ...

</aop:aspect>

環(huán)繞通知

最后一種通知是環(huán)繞通知跌宛。環(huán)繞通知在匹配的方法執(zhí)行“環(huán)繞”運(yùn)行酗宋。它有機(jī)會在方法執(zhí)行之前和之后執(zhí)行工作,并確定何時(shí)疆拘、如何執(zhí)行蜕猫,甚至是否真的執(zhí)行方法。環(huán)繞通知通常用于以線程安全的方式(例如哎迄,啟動(dòng)和停止計(jì)時(shí)器)在方法執(zhí)行之前和之后共享狀態(tài)回右。總是使用最弱的形式的通知來滿足你的要求(備注:最小范圍使用)漱挚。不要使用環(huán)繞的通知翔烁,如果前置通知可以做的工作。

你可以使用<aop:around>元素聲明環(huán)繞通知旨涝。通知方法的第一個(gè)參數(shù)必須是ProceedingJoinPoint類型蹬屹。在通知的正文中,在ProceedingJoinPoint上調(diào)用proceed()會使底層方法執(zhí)行。還可以使用Object []調(diào)用proceed方法慨默。數(shù)組中的值用作方法執(zhí)行時(shí)的參數(shù)贩耐。有關(guān)調(diào)用Object []的注意事項(xiàng),請參見“環(huán)繞通知”厦取。以下示例顯示了如何在XML中環(huán)繞通知進(jìn)行聲明:

<aop:aspect id="aroundExample" ref="aBean">

    <aop:around
        pointcut-ref="businessService"
        method="doBasicProfiling"/>

    ...

</aop:aspect>

doBasicProfiling通知的實(shí)現(xiàn)可以與@AspectJ示例完全相同(當(dāng)然要去掉注解)潮太,像以下示例所示:

public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
    // start stopwatch
    Object retVal = pjp.proceed();
    // stop stopwatch
    return retVal;
}

通知參數(shù)

基于schema的聲明樣式以與@AspectJ支持相同的方式支持完全類型的通知,即通過名稱與通知方法參數(shù)匹配切入點(diǎn)參數(shù)來實(shí)現(xiàn)蒜胖。有關(guān)詳細(xì)信息消别,請參見通知參數(shù)抛蚤。如果你希望顯式指定通知方法的參數(shù)名稱(不依賴于先前描述的檢測策略台谢,則可以通過使用advice元素的arg-names屬性來實(shí)現(xiàn),該屬性的處理方式與argNames屬性相同在通知注解中(如確定參數(shù)名稱中所述)岁经。以下示例顯示如何在XML中指定參數(shù)名稱:

<aop:before
    pointcut="com.xyz.lib.Pointcuts.anyPublicMethod() and @annotation(auditable)"
    method="audit"
    arg-names="auditable"/>

arg-names屬性接受以逗號分隔的參數(shù)名稱列表朋沮。

以下基于XSD的方法中涉及程度稍高的示例顯示了一些與一些強(qiáng)類型參數(shù)結(jié)構(gòu)使用的建議:

package x.y.service;

public interface PersonService {

    Person getPerson(String personName, int age);
}

public class DefaultFooService implements FooService {

    public Person getPerson(String name, int age) {
        return new Person(name, age);
    }
}

接下來是切面。請注意profile(..)方法接受許多強(qiáng)類型參數(shù)的事實(shí)缀壤,其中第一個(gè)恰好是用于進(jìn)行方法調(diào)用的連接點(diǎn)樊拓。此參數(shù)的存在表明profile(..)將用作通知,如以下示例所示:

package x.y;

import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.util.StopWatch;

public class SimpleProfiler {

    public Object profile(ProceedingJoinPoint call, String name, int age) throws Throwable {
        StopWatch clock = new StopWatch("Profiling for '" + name + "' and '" + age + "'");
        try {
            clock.start(call.toShortString());
            return call.proceed();
        } finally {
            clock.stop();
            System.out.println(clock.prettyPrint());
        }
    }
}

最后塘慕,以下示例XML配置影響了特定連接點(diǎn)的上述通知的執(zhí)行:

<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 https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- this is the object that will be proxied by Spring's AOP infrastructure -->
    <bean id="personService" class="x.y.service.DefaultPersonService"/>

    <!-- this is the actual advice itself -->
    <bean id="profiler" class="x.y.SimpleProfiler"/>

    <aop:config>
        <aop:aspect ref="profiler">

            <aop:pointcut id="theExecutionOfSomePersonServiceMethod"
                expression="execution(* x.y.service.PersonService.getPerson(String,int))
                and args(name, age)"/>

            <aop:around pointcut-ref="theExecutionOfSomePersonServiceMethod"
                method="profile"/>

        </aop:aspect>
    </aop:config>

</beans>

考慮以下驅(qū)動(dòng)程序腳本:

import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import x.y.service.PersonService;

public final class Boot {

    public static void main(final String[] args) throws Exception {
        BeanFactory ctx = new ClassPathXmlApplicationContext("x/y/plain.xml");
        PersonService person = (PersonService) ctx.getBean("personService");
        person.getPerson("Pengo", 12);
    }
}

有了這樣的Boot類筋夏,我們將在標(biāo)準(zhǔn)輸出上獲得類似于以下內(nèi)容的輸出:

StopWatch 'Profiling for 'Pengo' and '12'': running time (millis) = 0
-----------------------------------------
ms     %     Task name
-----------------------------------------
00000  ?  execution(getFoo)

通知順序

當(dāng)需要在同一連接點(diǎn)(執(zhí)行方法)上執(zhí)行多個(gè)通知時(shí),排序規(guī)則如“通知順序”中所述图呢。切面之間的優(yōu)先級是通過將Order注解添加到支持切面的Bean或通過使Bean實(shí)現(xiàn)Ordered接口來確定的条篷。

5.5.4 引入

簡介(在AspectJ中稱為類型間聲明)使切面可以聲明通知的對象實(shí)現(xiàn)給定的接口,并代表那些對象提供該接口的實(shí)現(xiàn)蛤织。

你可以通過在<aop:aspect/>中使用<aop:declare-parents/>元素進(jìn)行引入赴叹。你可以使用<aop:declare-parents/>元素聲明匹配類型具有新的父類(因此而得名)。例如指蚜,給定一個(gè)名為UsageTracked的接口和該接口名為DefaultUsageTracked的實(shí)現(xiàn)乞巧,以下切面聲明服務(wù)接口的所有實(shí)現(xiàn)者也都實(shí)現(xiàn)UsageTracked接口。(例如摊鸡,為了通過JMX公開統(tǒng)計(jì)信息绽媒。)

<aop:aspect id="usageTrackerAspect" ref="usageTracking">

    <aop:declare-parents
        types-matching="com.xzy.myapp.service.*+"
        implement-interface="com.xyz.myapp.service.tracking.UsageTracked"
        default-impl="com.xyz.myapp.service.tracking.DefaultUsageTracked"/>

    <aop:before
        pointcut="com.xyz.myapp.SystemArchitecture.businessService()
            and this(usageTracked)"
            method="recordUsage"/>

</aop:aspect>

支持usageTracking bean的類將包含以下方法:

public void recordUsage(UsageTracked usageTracked) {
    usageTracked.incrementUseCount();
}

要實(shí)現(xiàn)的接口由Implement-interface屬性確定。類型匹配屬性的值是AspectJ類型模式免猾。匹配類型的任何bean都實(shí)現(xiàn)UsageTracked接口是辕。請注意,在前面示例的之前通知中掸刊,服務(wù)Bean可以直接用作UsageTracked接口的實(shí)現(xiàn)免糕。要以編程方式訪問bean,可以編寫以下代碼:

UsageTracked usageTracked = (UsageTracked) context.getBean("myService");
5.5.5 切面實(shí)例化模式

模式定義切面唯一受支持的實(shí)例化模型是單例模型。在將來的版本中可能會支持其他實(shí)例化模型石窑。

5.5.6 Advisors

`

advisors的概念來自于Spring中定義的AOP支持牌芋,在AspectJ中沒有直接的對等物。advisors就像一個(gè)小的自包含的切面松逊,只有一條通知躺屁。通知本身由bean表示,并且必須實(shí)現(xiàn)Spring的“通知類型”中描述的通知接口之一经宏。advisors可以利用AspectJ切入點(diǎn)表達(dá)式犀暑。

Spring通過<aop:advisor>元素支持advisors概念。你通常會看到它與事務(wù)通知結(jié)合使用烁兰,事務(wù)通知在Spring中也有其自己的名稱空間支持耐亏。以下示例顯示advisors

<aop:config>

    <aop:pointcut id="businessService"
        expression="execution(* com.xyz.myapp.service.*.*(..))"/>

    <aop:advisor
        pointcut-ref="businessService"
        advice-ref="tx-advice"/>

</aop:config>

<tx:advice id="tx-advice">
    <tx:attributes>
        <tx:method name="*" propagation="REQUIRED"/>
    </tx:attributes>
</tx:advice>

除了在前面的示例中使用的pointcut-ref屬性之外,你還可以使用pointcut屬性內(nèi)聯(lián)定義一個(gè)pointcut表達(dá)式沪斟。

要定義advisor的優(yōu)先級以便通知可以參與排序广辰,可以使用order屬性來定義advisor的排序值。

5.5.7 AOP Schema例子

本節(jié)將展示AOP示例中的并發(fā)鎖定失敗重試示例在使用模式支持重寫時(shí)的例子主之。

有時(shí)由于并發(fā)問題(例如择吊,死鎖失敗者),業(yè)務(wù)服務(wù)的執(zhí)行可能會失敗槽奕。如果重試該操作几睛,則很可能在下一次嘗試中成功。對于適合在這種情況下重試的業(yè)務(wù)(不需要為解決沖突而需要返回給用戶冪等操作)粤攒,我們希望透明地重試該操作所森,以避免客戶端看到PessimisticLockingFailureException。這項(xiàng)要求明確地跨越了服務(wù)層中的多個(gè)服務(wù)琼讽,因此非常適合通過一個(gè)切面實(shí)現(xiàn)必峰。

因?yàn)槲覀兿胫卦囋摬僮鳎晕覀冃枰褂弥車ㄗh钻蹬,以便可以多次調(diào)用proceed吼蚁。下面的清單顯示了基本的切面實(shí)現(xiàn)(它是一個(gè)使用schema支持的常規(guī)Java類)

public class ConcurrentOperationExecutor implements Ordered {

    private static final int DEFAULT_MAX_RETRIES = 2;

    private int maxRetries = DEFAULT_MAX_RETRIES;
    private int order = 1;

    public void setMaxRetries(int maxRetries) {
        this.maxRetries = maxRetries;
    }

    public int getOrder() {
        return this.order;
    }

    public void setOrder(int order) {
        this.order = order;
    }

    public Object doConcurrentOperation(ProceedingJoinPoint pjp) throws Throwable {
        int numAttempts = 0;
        PessimisticLockingFailureException lockFailureException;
        do {
            numAttempts++;
            try {
                return pjp.proceed();
            }
            catch(PessimisticLockingFailureException ex) {
                lockFailureException = ex;
            }
        } while(numAttempts <= this.maxRetries);
        throw lockFailureException;
    }

}

請注意,切面實(shí)現(xiàn)了Ordered接口问欠,因此我們可以將切面的優(yōu)先級設(shè)置為高于事務(wù)通知(每次重試時(shí)都希望有新的事務(wù))肝匆。maxRetriesorder屬性均由Spring配置。主要操作發(fā)生在通知方法周圍的doConcurrentOperation中顺献。我們試著繼續(xù)旗国。如果我們因?yàn)橐粋€(gè)PessimisticLockingFailureException異常失敗了,我們會再次嘗試注整,除非我們已經(jīng)耗盡了所有的重試嘗試能曾。

該類與@AspectJ示例中使用的類相同度硝,但是除去了注解。

相應(yīng)的Spring配置如下:

<aop:config>

    <aop:aspect id="concurrentOperationRetry" ref="concurrentOperationExecutor">

        <aop:pointcut id="idempotentOperation"
            expression="execution(* com.xyz.myapp.service.*.*(..))"/>

        <aop:around
            pointcut-ref="idempotentOperation"
            method="doConcurrentOperation"/>

    </aop:aspect>

</aop:config>

<bean id="concurrentOperationExecutor"
    class="com.xyz.myapp.service.impl.ConcurrentOperationExecutor">
        <property name="maxRetries" value="3"/>
        <property name="order" value="100"/>
</bean>

請注意寿冕,目前我們假設(shè)所有業(yè)務(wù)服務(wù)都是冪等的蕊程。如果不是這種情況,我們可以改進(jìn)切面驼唱,以便通過引入等冪注解并使用注解來注釋服務(wù)操作的實(shí)現(xiàn)藻茂,使其僅重試真正的冪等操作,如以下示例所示:

@Retention(RetentionPolicy.RUNTIME)
public @interface Idempotent {
    // marker annotation
}

切面更改為僅重試冪等操作涉及更改切入點(diǎn)表達(dá)式玫恳,以便僅@Idempotent操作匹配辨赐,如下所示:

<aop:pointcut id="idempotentOperation"
        expression="execution(* com.xyz.myapp.service.*.*(..)) and
        @annotation(com.xyz.myapp.service.Idempotent)"/>
5.6 選擇哪一種AOP聲明格式使用

一旦確定切面是實(shí)現(xiàn)給定需求的最佳方法,你如何在使用Spring AOP或AspectJ以及在Aspect語言(代碼)樣式京办,@AspectJ注解樣式或Spring XML樣式之間做出選擇掀序?這些決定受許多因素影響、包括應(yīng)用程序需求臂港、開發(fā)工具和團(tuán)隊(duì)對AOP的熟悉程度森枪。

5.6.1 Spring AOP 或 完整的AspectJ视搏?

使用最簡單的方法即可审孽。 Spring AOP比使用完整的AspectJ更簡單,因?yàn)椴恍枰陂_發(fā)和構(gòu)建過程中引入AspectJ編譯器/編織器浑娜。如果你只需要通知在Spring bean上執(zhí)行操作佑力,則Spring AOP是正確的選擇。如果你需要通知不受Spring容器管理的對象(通常是領(lǐng)域?qū)ο螅┙钤猓瑒t需要使用AspectJ打颤。如果你希望通知除簡單方法執(zhí)行之外的連接點(diǎn)(例如,字段get或set連接點(diǎn)等)漓滔,則還需要使用AspectJ (備注:對字段屬性進(jìn)行通知使用AspectJ )编饺。

使用AspectJ時(shí),可以選擇AspectJ語言語法(也稱為“代碼樣式”)或@AspectJ注解樣式响驴。顯然透且,如果你不使用Java 5+赶熟,則已經(jīng)為你做出了選擇:使用代碼樣式缘回。如果切面在你的設(shè)計(jì)中起著重要作用,并且你能夠使用Eclipse的AspectJ開發(fā)工具(AJDT)插件档押,則AspectJ語言語法是首選琳骡。它更干凈锅论、更簡單,因?yàn)樵撜Z言是專為編寫切面而設(shè)計(jì)的楣号。如果你不使用Eclipse或只有少數(shù)幾個(gè)切面在你的應(yīng)用程序中最易,那么你可能要考慮使用@AspectJ樣式怒坯,在IDE中堅(jiān)持常規(guī)Java編譯,并向其中添加切面編織階段你的構(gòu)建腳本藻懒。

5.6.2 Spring AOP中使用@AspectJXML選擇敬肚?

如果你選擇使用Spring AOP,則可以選擇@AspectJXML樣式束析。有各種折衷考慮艳馒。

XML樣式可能是現(xiàn)有Spring用戶最熟悉的,并且得到了真正的POJO的支持员寇。當(dāng)使用AOP作為配置企業(yè)服務(wù)的工具時(shí)弄慰,XML是一個(gè)不錯(cuò)的選擇(一個(gè)很好的嘗試是你是否將切入點(diǎn)表達(dá)式視為你可能希望獨(dú)立更改的配置的一部分)。使用XML樣式蝶锋,可以說從你的配置中可以更清楚地了解系統(tǒng)中存在哪些切面陆爽。

XML樣式有兩個(gè)缺點(diǎn)。首先扳缕,它沒有完全將要解決的需求的實(shí)現(xiàn)封裝在一個(gè)地方慌闭。DRY原則說,系統(tǒng)中的任何知識都應(yīng)該有一個(gè)單一躯舔、明確驴剔、權(quán)威的表示形式。當(dāng)使用XML樣式時(shí)粥庄,需求如何實(shí)現(xiàn)的知識在支持bean類的聲明和配置文件中的XML中被分割開來丧失。當(dāng)你使用@AspectJ樣式時(shí),此信息將封裝在一個(gè)模塊中:切面惜互。其次布讹,與@AspectJ樣式相比,XML樣式在表達(dá)能力上有更多限制:僅支持“單例”切面實(shí)例化模型训堆,并且無法組合以XML聲明的命名切入點(diǎn)描验。例如,使用@AspectJ樣式坑鱼,你可以編寫如下內(nèi)容:

@Pointcut("execution(* get*())")
public void propertyAccess() {}

@Pointcut("execution(org.xyz.Account+ *(..))")
public void operationReturningAnAccount() {}

@Pointcut("propertyAccess() && operationReturningAnAccount()")
public void accountPropertyAccess() {}

在XML樣式中膘流,你可以聲明前兩個(gè)切入點(diǎn):

<aop:pointcut id="propertyAccess"
        expression="execution(* get*())"/>

<aop:pointcut id="operationReturningAnAccount"
        expression="execution(org.xyz.Account+ *(..))"/>

XML方法的缺點(diǎn)是你無法通過組合這些定義來定義accountPropertyAccess切入點(diǎn)(備注:不能組合多個(gè)切面)。

@AspectJ樣式支持其他實(shí)例化模型和更豐富的切入點(diǎn)組合姑躲。它具有將切面保持為模塊化單元的優(yōu)勢睡扬。它還具有的優(yōu)點(diǎn)是,Spring AOP和AspectJ都可以理解@AspectJ方面黍析。因此卖怜,如果你以后決定需要AspectJ的功能來實(shí)現(xiàn)其他要求,則可以輕松地遷移到AspectJ設(shè)置阐枣÷砜浚總而言之奄抽,Spring在自定義方面更喜歡@AspectJ樣式,而不是簡單地配置企業(yè)服務(wù)甩鳄。

5.7 混合切面類型

通過使用自動(dòng)代理支持逞度,模式定義的<aop:aspect>切面、<aop:advisor>聲明的advisors妙啃,甚至是同一配置中其他樣式的代理和攔截器档泽,完全可以混合@AspectJ樣式的切面。所有這些都是通過使用相同的底層支持機(jī)制實(shí)現(xiàn)的揖赴,并且可以毫無困難地共存馆匿。

5.8 代理機(jī)制

Spring AOP使用JDK動(dòng)態(tài)代理或CGLIB創(chuàng)建給定目標(biāo)對象的代理。JDK動(dòng)態(tài)代理內(nèi)置在JDK中燥滑,而CGLIB是常見的開源類定義庫(重新包裝到spring-core中)渐北。

如果要代理的目標(biāo)對象實(shí)現(xiàn)至少一個(gè)接口,則使用JDK動(dòng)態(tài)代理铭拧。代理了由目標(biāo)類型實(shí)現(xiàn)的所有接口赃蛛。如果目標(biāo)對象未實(shí)現(xiàn)任何接口,則將創(chuàng)建CGLIB代理搀菩。

如果要強(qiáng)制使用CGLIB代理(例如呕臂,代理為目標(biāo)對象定義的每個(gè)方法,而不僅是由其接口實(shí)現(xiàn)的方法)秕磷,可以這樣做诵闭。但是,你應(yīng)該考慮以下問題:

  • 使用CGLIB澎嚣,不能通知final方法,因?yàn)椴荒茉谶\(yùn)行時(shí)生成的子類中覆蓋它們瘟芝。
  • 從Spring 4.0開始易桃,由于CGLIB代理實(shí)例是通過Objenesis創(chuàng)建的,因此不再調(diào)用代理對象的構(gòu)造函數(shù)兩次锌俱。只有在你的JVM不允許繞過構(gòu)造函數(shù)的情況下晤郑,你才可能從Spring的AOP支持中得到兩次調(diào)用和相應(yīng)的調(diào)試日志條目。

要強(qiáng)制使用CGLIB代理贸宏,請將<aop:config>元素的proxy-target-class屬性的值設(shè)置為true造寝,如下所示:

<aop:config proxy-target-class="true">
    <!-- other beans defined here... -->
</aop:config>

要在使用@AspectJ自動(dòng)代理支持時(shí)強(qiáng)制CGLIB代理,請將<aop:aspectj-autoproxy>元素的proxy-target-class屬性設(shè)置為true吭练,如下所示:

<aop:aspectj-autoproxy proxy-target-class="true"/>

多個(gè)<aop:config/>部分在運(yùn)行時(shí)折疊到一個(gè)統(tǒng)一的自動(dòng)代理創(chuàng)建器中诫龙,該創(chuàng)建器將應(yīng)用任何<aop:config />部分(通常來自不同的XML bean定義文件)指定的最強(qiáng)的代理設(shè)置。這也適用于<tx:annotation-driven/><aop:aspectj-autoproxy/>元素鲫咽。

為了清楚起見签赃,在<tx:annotation-driven/>谷异、<aop:aspectj-autoproxy/><aop:config/>元素上使用proxy-target-class =“true”會強(qiáng)制對所有三個(gè)元素使用CGLIB代理其中。

5.8.1 理解AOP代理

Spring AOP是基于代理的锦聊。在編寫自己的切面或使用Spring Framework隨附的任何基于Spring AOP的切面之前歹嘹,掌握最后一條語句實(shí)際含義的語義至關(guān)重要。

首先考慮以下情況:你有一個(gè)普通的孔庭、未經(jīng)代理的尺上、無特殊要求的直接對像引用,如以下代碼片段所示:

public class SimplePojo implements Pojo {

    public void foo() {
        // 調(diào)用當(dāng)前對象的bar方法
        this.bar();
    }

    public void bar() {
        // some logic...
    }
}

如果在對象引用上調(diào)用方法圆到,則直接在該對象引用上調(diào)用該方法,如下圖清單所示:

aop proxy plain pojo call
public class Main {

    public static void main(String[] args) {
        Pojo pojo = new SimplePojo();
        // this is a direct method call on the 'pojo' reference
        pojo.foo();
    }
}

當(dāng)客戶端代碼具有的引用是代理時(shí)己单,情況會稍有變化廷痘⌒中桑考慮以下圖表和代碼片段:

aop proxy call
public class Main {

    public static void main(String[] args) {
        ProxyFactory factory = new ProxyFactory(new SimplePojo());
        factory.addInterface(Pojo.class);
        factory.addAdvice(new RetryAdvice());

        Pojo pojo = (Pojo) factory.getProxy();
        // this is a method call on the proxy!
        pojo.foo();
    }
}

此處要理解的關(guān)鍵是讶迁,Main類的main(..)方法內(nèi)部的客戶端代碼具有對代理的引用坯汤。這意味著該對象引用上的方法調(diào)用是代理上的調(diào)用搓幌。因此拐揭,代理可以委托給與特定方法調(diào)用相關(guān)的所有攔截器(通知)盟猖。然而,一旦調(diào)用最終到達(dá)目標(biāo)對象(本例中是SimplePojo风秤,即引用)碍沐,它可能對自身進(jìn)行的任何方法調(diào)用朽褪,比如this.bar()或this.foo(),都將針對this引用而不是代理進(jìn)行調(diào)用。這具有重要意義呛凶。這意味著自調(diào)用不會導(dǎo)致與方法調(diào)用相關(guān)的通知得到執(zhí)行的機(jī)會(備注:通過代理調(diào)用才能觸發(fā)相關(guān)的通知)崭捍。

Okay匀们,那么該怎么辦?最佳方法(在這里寬松地使用術(shù)語“最佳”)是重構(gòu)代碼陌兑,以免發(fā)生<u>自調(diào)用</u>。這確實(shí)需要你做一些工作慧瘤,但這是最好的,侵入性最小的方法固该。下一種方法絕對可怕锅减,我們正要指出這一點(diǎn),恰恰是因?yàn)樗侨绱丝膳路セ怠D憧梢?這對我們來說很痛苦)將類中的邏輯完全綁定到Spring AOP怔匣,如下面的示例所示:

public class SimplePojo implements Pojo {

    public void foo() {
        // this works, but... gah!
        ((Pojo) AopContext.currentProxy()).bar();
    }

    public void bar() {
        // some logic...
    }
}

這將你的代碼完全耦合到Spring AOP,并且使類本身意識到在AOP上下文中使用的事實(shí)桦沉,而AOP上下文卻是這樣每瞒。創(chuàng)建代理時(shí),還需要一些其他配置纯露,如以下示例所示:

public class Main {

    public static void main(String[] args) {
        ProxyFactory factory = new ProxyFactory(new SimplePojo());
        factory.addInterface(Pojo.class);
        factory.addAdvice(new RetryAdvice());
        //需要指定暴露代理
        factory.setExposeProxy(true);

        Pojo pojo = (Pojo) factory.getProxy();
        // this is a method call on the proxy!
        pojo.foo();
    }
}

最后独泞,必須注意,AspectJ沒有此自調(diào)用問題苔埋,因?yàn)樗皇腔诖淼腁OP框架。

5.9 編程創(chuàng)建 @AspectJ代理

除了通過使用<aop:config><aop:aspectj-autoproxy>聲明配置中的各個(gè)切面外蜒犯,還可以通過編程方式創(chuàng)建通知目標(biāo)對象的代理组橄。有關(guān)Spring的AOP API的完整詳細(xì)信息荞膘,請參閱下一章。在這里玉工,我們要重點(diǎn)介紹使用@AspectJ切面自動(dòng)創(chuàng)建代理的功能羽资。

你可以使用org.springframework.aop.aspectj.annotation.AspectJProxyFactory類為一個(gè)或多個(gè)@AspectJ切面通知的目標(biāo)對象創(chuàng)建代理。此類的基本用法非常簡單遵班,如以下示例所示:

// 創(chuàng)建一個(gè)工廠屠升,這個(gè)工廠可以為給定對象生成代理
AspectJProxyFactory factory = new AspectJProxyFactory(targetObject);

// 增加切面,這個(gè)切面必須被標(biāo)注@AspectJ注解
// you can call this as many times as you need with different aspects
factory.addAspect(SecurityManager.class);

// 添加存在的切面狭郑,這個(gè)切面對象背心支持@AspectJ
factory.addAspect(usageTracker);

// now get the proxy object...
MyInterfaceType proxy = factory.getProxy();

有關(guān)更多信息腹暖,請參見javadoc

5.10 在Spring應(yīng)用中使用AspectJ

到目前為止翰萨,本章介紹的所有內(nèi)容都是純Spring AOP脏答。在本節(jié)中,我們將研究如果你的需求超出了Spring AOP所提供的功能亩鬼,那么如何使用AspectJ編譯器或weaver代替Spring AOP或除Spring AOP之外使用殖告。

Spring附帶了一個(gè)小的AspectJ切面庫,該庫在你的發(fā)行版中可以作為spring-aspects.jar獨(dú)立使用雳锋。你需要將其添加到類路徑中才能使用其中的切面黄绩。使用AspectJ依賴于Spring和AspectJ的其他Spring切面來注入域?qū)ο?/a>以及AspectJ討論該庫的內(nèi)容以及如何使用它。使用Spring IoC配置AspectJ切面討論了如何依賴注入使用AspectJ編譯器編織的AspectJ切面玷过。最后爽丹,Spring Framework中使用AspectJ進(jìn)行的加載時(shí)編織為使用AspectJ的Spring應(yīng)用順序提供了加載時(shí)編織的介紹。

5.10.1 在Spring中使用AspectJ去依賴注入領(lǐng)域?qū)ο?/h6>

Spring容器實(shí)例化并配置在你的應(yīng)用程序上下文中定義的bean冶匹。給定包含要應(yīng)用的配置的Bean定義的名稱习劫,也可以要求Bean工廠配置預(yù)先存在的對象。spring-aspects.jar包含注解驅(qū)動(dòng)的切面嚼隘,該切面利用此功能允許依賴項(xiàng)注入任何對象诽里。該支撐旨在用于在任何容器的控制范圍之外創(chuàng)建的對象。領(lǐng)域?qū)ο笸ǔ儆诖祟惙捎迹驗(yàn)樗鼈兺ǔJ峭ㄟ^數(shù)據(jù)庫查詢的結(jié)果由new操作或ORM工具以編程方式創(chuàng)建的谤狡。

@Configurable注解將一個(gè)類標(biāo)記為符合Spring驅(qū)動(dòng)的配置。在最簡單的情況下卧檐,你可以將其純粹用作標(biāo)記注解墓懂,如以下示例所示:

package com.xyz.myapp.domain;

import org.springframework.beans.factory.annotation.Configurable;

@Configurable
public class Account {
    // ...
}

當(dāng)以這種方式作為標(biāo)記接口使用時(shí),Spring通過使用與完全限定類型名(com.xyz.myapp.domain.Account)同名的bean定義(通常是原型作用域)來配置注解類型(在本例中為Account)的新實(shí)例霉囚。由于bean的默認(rèn)名稱是其類型的全限定名捕仔,因此聲明原型定義的一種方便的方法是省略id屬性,如下面的示例所示:

<bean class="com.xyz.myapp.domain.Account" scope="prototype">
    <property name="fundsTransferService" ref="fundsTransferService"/>
</bean>

如果要顯式指定要使用的原型bean定義的名稱,則可以直接在注解中這樣做如以下示例所示:

package com.xyz.myapp.domain;

import org.springframework.beans.factory.annotation.Configurable;

@Configurable("account")
public class Account {
    // ...
}

Spring現(xiàn)在查找名為account的bean定義榜跌,并將其用作配置新Account實(shí)例的定義闪唆。

你也可以使用自動(dòng)裝配來避免完全指定專用的bean定義。要讓Spring應(yīng)用自動(dòng)裝配钓葫,請使用@Configurable注解的autowire屬性悄蕾。你可以指定@Configurable(autowire = Autowire.BY_TYPE)@Configurable(autowire = Autowire.BY_NAME)分別按類型或名稱進(jìn)行自動(dòng)裝配〈「。或者帆调,最好在字段或方法級別通過@Autowired@Inject@Configurable bean指定顯式的,注解驅(qū)動(dòng)的依賴項(xiàng)注入(有關(guān)更多詳細(xì)信息豆同,請參見基于注釋的容器配置)番刊。最后,你可以使用dependencyCheck屬性(例如诱告,@Configurable(autowire = Autowire.BY_NAME撵枢,dependencyCheck = true))為新創(chuàng)建和配置的對象中的對象引用啟用Spring依賴檢查。如果該屬性設(shè)置為true精居,則Spring在配置后驗(yàn)證所有屬性(不是原生類型或集合)是否已經(jīng)設(shè)置锄禽。

請注意,單獨(dú)使用注解不會執(zhí)行任何操作靴姿。spring-aspects.jar中的AnnotationBeanConfigurerAspect會對注解的存在起作用沃但。本質(zhì)上,切面的意思是佛吓,在初始化一個(gè)帶有@Configurable注解的類型的新對象之后宵晚,使用Spring根據(jù)注解的屬性配置新創(chuàng)建的對象。在這種情況下维雇,“初始化”是指新實(shí)例化的對象(例如淤刃,用new運(yùn)算符實(shí)例新的對象)以及正在進(jìn)行反序列化(例如,通過readResolve()的可序列化的對象)吱型。

上述段落中的一個(gè)關(guān)鍵短語是in essence(本質(zhì))逸贾。在大多數(shù)情況下,“從新對象的初始化返回后”的確切語義是可以的津滞。在這種情況下铝侵,“初始化之后”是指在構(gòu)造對象之后注入依賴項(xiàng)。這意味著該依賴項(xiàng)不可在類的構(gòu)造函數(shù)體中使用触徐。如果你希望在構(gòu)造函數(shù)主體執(zhí)行之前注入依賴項(xiàng)咪鲜,從而可以在構(gòu)造函數(shù)主體中使用這些依賴項(xiàng),則需要在@Configurable聲明中對此進(jìn)行定義撞鹉,如下所示:

@Configurable(preConstruction = true)

你可以在《 AspectJ編程指南》的此附錄中找到有關(guān)各種切入點(diǎn)類型的語言語義的更多信息疟丙。

為此颖侄,必須將帶注解的類型與AspectJ編織器編織在一起。你可以使用構(gòu)建時(shí)的Ant或Maven任務(wù)來執(zhí)行此操作(例如隆敢,參見《 AspectJ開發(fā)環(huán)境指南》)发皿,也可以使用加載時(shí)編織(請參見Spring Framework中的使用AspectJ進(jìn)行加載時(shí)編織)。Spring需要配置AnnotationBeanConfigurerAspect自身(以便獲得對將用于配置新對象Bean工廠的引用)拂蝎。如果使用基于Java的配置,則可以將@EnableSpringConfigured添加到任何@Configuration類中惶室,如下所示:

@Configuration
@EnableSpringConfigured
public class AppConfig {
}

如果你更喜歡基于XML的配置温自,則Spring context namespace定義了一個(gè)方便的context:spring-configured元素,你可以按以下方式使用它:

<context:spring-configured/>

在配置切面之前創(chuàng)建的@Configurable對象實(shí)例導(dǎo)致向調(diào)試日志發(fā)出消息皇钞,并且不進(jìn)行對象配置悼泌。一個(gè)例子可能是Spring配置中的一個(gè)bean,當(dāng)它由Spring初始化時(shí)會創(chuàng)建域?qū)ο蠹薪纭T谶@種情況下馆里,你可以使用depends-on bean屬性來手動(dòng)指定bean依賴于配置切面。下面的示例顯示如何使用depends-on屬性:

<bean id="myService"
        class="com.xzy.myapp.service.MyService"
        depends-on="org.springframework.beans.factory.aspectj.AnnotationBeanConfigurerAspect">

    <!-- ... -->

</bean>

除非你真的想在運(yùn)行時(shí)依賴它的語義可柿,否則不要通過bean configurer激活@Configurable處理鸠踪。特別是,請確保不要在通過容器注冊為常規(guī)Spring bean的bean類上使用@Configurable复斥。這樣做會導(dǎo)致兩次初始化营密,一次是通過容器,一次是通過切面目锭。

單元測試@Configurable對象

@Configurable支持的目標(biāo)之一是實(shí)現(xiàn)領(lǐng)域?qū)ο蟮莫?dú)立單元測試评汰,而不會需要復(fù)雜的硬編碼查找。如果AspectJ尚未編織@Configurable類型痢虹,則注解在單元測試期間不起作用被去。你可以在被測對象中設(shè)置mockstub屬性引用,然后照常進(jìn)行奖唯。如果AspectJ編織了@Configurable類型惨缆,你仍然可以像往常一樣在容器外部進(jìn)行單元測試,但是每次構(gòu)造@Configurable對象時(shí)臭埋,你都會看到一條警告消息踪央,指示該對象尚未由Spring配置。

使用多個(gè)應(yīng)用程序上下文

用于實(shí)現(xiàn)@Configurable支持的AnnotationBeanConfigurerAspect是AspectJ單例切面瓢阴。單例切面的范圍與靜態(tài)成員的范圍相同:每個(gè)類加載器都有一個(gè)切面實(shí)例定義類型畅蹂。這意味著,如果你在同一個(gè)類加載器層次結(jié)構(gòu)中定義多個(gè)應(yīng)用程序上下文則需要考慮在何處定義@EnableSpringConfigured bean荣恐,以及在哪里將spring-aspects.jar放置在類路徑上液斜。

考慮一個(gè)典型的Spring Web應(yīng)用程序配置累贤,該配置具有一個(gè)共享的父應(yīng)用程序上下文,該上下文定義了通用的業(yè)務(wù)服務(wù)少漆、支持那些服務(wù)所需的一切臼膏、以及每個(gè)Servlet的一個(gè)子應(yīng)用程序上下文(其中包含該Servlet的特定定義)。所有這些上下文共存于同一類加載器層次結(jié)構(gòu)中示损,因此AnnotationBeanConfigurerAspect只能保存對其中一個(gè)的引用渗磅。在這種情況下,我們建議在共享(父)應(yīng)用程序上下文中定義@EnableSpringConfigured bean检访。這定義了你可能想注入領(lǐng)域?qū)ο蟮姆?wù)始鱼。結(jié)果是,你無法使用@Configurable機(jī)制來配置領(lǐng)域?qū)ο蟠喙螅擃I(lǐng)域?qū)ο笠玫氖窃谧樱ㄌ囟ㄓ趕ervlet的)上下文中定義的Bean的引用(無論如何医清,這可能不是你想要做的)。

在同一容器中部署多個(gè)Web應(yīng)用程序時(shí)卖氨,請確保每個(gè)Web應(yīng)用程序通過使用其自己的類加載器(例如会烙,將spring-aspects.jar放置在“ WEB-INF/lib”中)將其類型加載到spring-aspects.jar中。如果將spring-aspects.jar僅添加到容器級的類路徑中(并因此由共享的父類加載器加載)筒捺,則所有Web應(yīng)用程序都共享相同的切面實(shí)例(可能不是你想要的)柏腻。

5.10.2 AspectJ的其他Spring切面

除了@Configurable切面之外,spring-aspects.jar還包含一個(gè)AspectJ切面焙矛,你可以使用該切面來驅(qū)動(dòng)Spring的事務(wù)管理葫盼,以使用@Transactional注解來注釋類型和方法。這主要適用于希望在Spring容器之外使用Spring Framework的事務(wù)支持的用戶村斟。

解析@Transactional注解的切面是AnnotationTransactionAspect贫导。使用此切面時(shí),必須注解實(shí)現(xiàn)類(或該類中的方法或兩者)蟆盹,而不是注釋實(shí)現(xiàn)類所實(shí)現(xiàn)的接口(如果有)孩灯。AspectJ遵循Java的規(guī)則,即不繼承接口上的注解逾滥。

類上的@Transactional注解指定用于執(zhí)行該類中任何公共操作的默認(rèn)事務(wù)語義峰档。可以注解任何可見性的方法寨昙,包括私有方法讥巡。直接注解非公共方法是執(zhí)行此類方法而獲得事務(wù)劃分的唯一方法。

從Spring Framework 4.2開始舔哪,spring-aspects提供了一個(gè)相似的切面欢顷,為標(biāo)準(zhǔn)javax.transaction.Transactional注解提供了完全相同的功能。檢查JtaAnnotationTransactionAspect了解更多詳細(xì)信息捉蚤。

對于希望使用Spring配置和事務(wù)管理支持但又不想(或不能)使用注解的AspectJ編程者抬驴,spring-aspects.jar也包含抽象切面炼七,你可以擴(kuò)展它們以提供自己的切入點(diǎn)定義。有關(guān)更多信息布持,請參見AbstractBeanConfigurerAspectAbstractTransactionAspect切面的資源豌拙。作為示例,以下摘錄顯示了如何編寫切面來使用與完全限定的類名匹配的類型bean定義來配置域模型中定義的對象的所有實(shí)例:

public aspect DomainObjectConfiguration extends AbstractBeanConfigurerAspect {

    public DomainObjectConfiguration() {
        setBeanWiringInfoResolver(new ClassNameBeanWiringInfoResolver());
    }

    // the creation of a new bean (any object in the domain model)
    protected pointcut beanCreation(Object beanInstance) :
        initialization(new(..)) &&
        SystemArchitecture.inDomainModel() &&
        this(beanInstance);
}
5.10.3 通過使用Spring IoC配置AspectJ切面

當(dāng)你將AspectJ切面與Spring應(yīng)用程序一起使用時(shí)题暖,既自然又希望能夠使用Spring配置這些切面按傅。AspectJ運(yùn)行時(shí)本身負(fù)責(zé)切面的創(chuàng)建,并且通過Spring配置AspectJ創(chuàng)建的切面的方法取決于切面所使用的AspectJ實(shí)例化模型(per-xxx子句)芙委。

AspectJ的大多數(shù)切面都是單例切面逞敷。這些切面的配置很容易。你可以創(chuàng)建一個(gè)bean定義灌侣,該bean定義按常規(guī)引用切面類型,并包括factory-method =“aspectOf” bean屬性裂问。這樣可以確保Spring通過向AspectJ獲取實(shí)例侧啼,而不是嘗試自己創(chuàng)建實(shí)例。以下示例顯示如何使用factory-method =“aspectOf”屬性:

<bean id="profiler" class="com.xyz.profiler.Profiler"
        factory-method="aspectOf"> //1

    <property name="profilingStrategy" ref="jamonProfilingStrategy"/>
</bean>
  1. 注意factory-method =“aspectOf”屬性

非單例切面更難配置堪簿。但是痊乾,可以通過創(chuàng)建原型Bean定義并使用spring-aspects.jar中的@Configurable支持來實(shí)現(xiàn),一旦它們由AspectJ運(yùn)行時(shí)創(chuàng)建了Bean椭更,就可以配置切面實(shí)例哪审。

如果你有一些要與AspectJ編織的@AspectJ切面(例如,對域模型類型使用加載時(shí)編織)以及要與Spring AOP一起使用的其他@AspectJ切面虑瀑,那么這些切面都已在Spring中配置湿滓,你需要告訴Spring AOP @AspectJ自動(dòng)代理支持,應(yīng)使用配置中定義的@AspectJ切面的確切子集進(jìn)行自動(dòng)代理舌狗。你可以通過在<aop:aspectj-autoproxy />聲明中使用一個(gè)或多個(gè)<include />元素來做到這一點(diǎn)叽奥。每個(gè)<include />元素都指定一個(gè)名稱模式,只有名稱與至少一個(gè)模式匹配的bean才可用于Spring AOP自動(dòng)代理配置痛侍。以下示例顯示了如何使用<include />元素:

<aop:aspectj-autoproxy>
    <aop:include name="thisBean"/>
    <aop:include name="thatBean"/>
</aop:aspectj-autoproxy>

不要被<aop:aspectj-autoproxy />元素的名稱所迷惑朝氓。使用它可以創(chuàng)建Spring AOP代理。這里使用的是@AspectJ樣式的切面聲明主届,但不涉及AspectJ運(yùn)行時(shí)赵哲。

5.10.4 在Spring Framework中使用AspectJ進(jìn)行加載時(shí)編織

加載時(shí)編織(LTW)是指在將AspectJ切面加載到應(yīng)用程序的類文件中時(shí)將其編織到Java虛擬機(jī)(JVM)中的過程。本節(jié)的重點(diǎn)是在Spring框架的特定上下文中配置和使用LTW君丁。本節(jié)不是LTW的一般介紹枫夺。有關(guān)LTW的詳細(xì)信息以及僅使用AspectJ配置LTW(完全不涉及Spring)的詳細(xì)信息請參閱《 AspectJ開發(fā)環(huán)境指南》的LTW部分。Spring框架為AspectJ LTW帶來的價(jià)值在于能夠?qū)幙椷^程進(jìn)行更精細(xì)的控制谈截】曷牛“ Vanilla” AspectJ LTW通過使用Java(5+)代理來實(shí)現(xiàn)涧偷,該代理在啟動(dòng)JVM時(shí)通過指定VM參數(shù)來切換。因此毙死,它是一個(gè)JVM范圍的設(shè)置燎潮,在某些情況下可能很好,但通常有點(diǎn)過于粗糙扼倘。啟用spring的LTW允許你在每個(gè)類加載器的基礎(chǔ)上打開LTW确封,這是更細(xì)粒度的,在“單jvm -多應(yīng)用程序”環(huán)境中更有意義(比如在典型的應(yīng)用程序服務(wù)器環(huán)境中)再菊。

此外爪喘,在某些環(huán)境中,此支持激活裝載時(shí)編織不需要應(yīng)用程序服務(wù)器的啟動(dòng)腳本進(jìn)行任何修改纠拔,也不需要添加-javaagent:path/to/aspectjweaver.jar(如本節(jié)稍后所述)或-javaagent:path/to/spring-instrument.jar秉剑。開發(fā)人員將應(yīng)用程序上下文配置為啟用加載時(shí)編織,而不是依賴通常負(fù)責(zé)部署配置(例如啟動(dòng)腳本)的管理員稠诲。

現(xiàn)在介紹結(jié)束了侦鹏,讓我們首先瀏覽一個(gè)使用Spring的AspectJ LTW的快速示例,然后詳細(xì)介紹示例中引入的元素臀叙。有關(guān)完整的示例略水,請參見Petclinic示例應(yīng)用程序

第一個(gè)例子

假設(shè)你是一位負(fù)責(zé)診斷系統(tǒng)中某些性能問題的原因的應(yīng)用程序開發(fā)人員劝萤。我們將打開一個(gè)簡單的配置切面渊涝,而不是使用配置文件工具,使我們能夠快速獲取一些性能指標(biāo)床嫌。然后跨释,我們可以立即在該特定區(qū)域應(yīng)用更細(xì)粒度的分析工具。

此處提供的示例使用XML配置既鞠。你還可以配置@AspectJ并將其與Java配置一起使用煤傍。具體來說,你可以使用@EnableLoadTimeWeaving注解替代<context:load-time-weaver />(有關(guān)詳細(xì)信息嘱蛋,請參見下文)蚯姆。

下面的示例顯示了配置切面的信息,這并不理想洒敏。這是一個(gè)基于時(shí)間的探查器龄恋,它使用@AspectJ樣式的切面聲明:

package foo;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.util.StopWatch;
import org.springframework.core.annotation.Order;

@Aspect
public class ProfilingAspect {

    @Around("methodsToBeProfiled()")
    public Object profile(ProceedingJoinPoint pjp) throws Throwable {
        StopWatch sw = new StopWatch(getClass().getSimpleName());
        try {
            sw.start(pjp.getSignature().getName());
            return pjp.proceed();
        } finally {
            sw.stop();
            System.out.println(sw.prettyPrint());
        }
    }

    @Pointcut("execution(public * foo..*.*(..))")
    public void methodsToBeProfiled(){}
}

我們還需要?jiǎng)?chuàng)建一個(gè)META-INF/aop.xml文件,以通知AspectJ編織者我們希望將ProfilingAspect編織到類中凶伙。此文件約定郭毕,即在Java類路徑上稱為META-INF/aop.xml的文件,是標(biāo)準(zhǔn)的AspectJ函荣。以下示例顯示aop.xml文件:

<!DOCTYPE aspectj PUBLIC "-//AspectJ//DTD//EN" "https://www.eclipse.org/aspectj/dtd/aspectj.dtd">
<aspectj>

    <weaver>
        <!-- only weave classes in our application-specific packages -->
        <include within="foo.*"/>
    </weaver>

    <aspects>
        <!-- weave in just this aspect -->
        <aspect name="foo.ProfilingAspect"/>
    </aspects>

</aspectj>

現(xiàn)在显押,我們可以繼續(xù)進(jìn)行配置中特定于Spring的部分扳肛。我們需要配置一個(gè)LoadTimeWeaver(稍后說明)。該加載時(shí)織布器是必不可少的組件乘碑,負(fù)責(zé)將一個(gè)或多個(gè)META-INF/aop.xml文件中的切面配置編織到應(yīng)用程序的類中挖息。好處是,它不需要很多配置(你可以指定一些其他選項(xiàng)兽肤,但是稍后會詳細(xì)介紹)套腹,如以下示例所示:

<?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:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <!-- a service object; we will be profiling its methods -->
    <bean id="entitlementCalculationService"
            class="foo.StubEntitlementCalculationService"/>

    <!-- this switches on the load-time weaving -->
    <context:load-time-weaver/>
</beans>

現(xiàn)在,所有必需的組件(切面,META-INF/aop.xml文件和Spring配置)均已就緒,我們可以使用main(..)方法創(chuàng)建以下驅(qū)動(dòng)程序類鸽凶,以演示LTW的實(shí)際作用:

package foo;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public final class Main {

    public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml", Main.class);

        EntitlementCalculationService entitlementCalculationService =
                (EntitlementCalculationService) ctx.getBean("entitlementCalculationService");

        // the profiling aspect is 'woven' around this method execution
        entitlementCalculationService.calculateEntitlement();
    }
}

我們還有最后一件事要做。本節(jié)的引言確實(shí)說過尖飞,可以使用Spring在每個(gè)ClassLoader的基礎(chǔ)上選擇性地打開LTW,這是事實(shí)店雅。但是葫松,對于此示例,我們使用Java代理(Spring提供)打開LTW底洗。我們使用以下命令來運(yùn)行前面顯示的Main類:

java -javaagent:C:/projects/foo/lib/global/spring-instrument.jar foo.Main

-javaagent是一個(gè)標(biāo)志,用于指定和啟用代理以對在JVM上運(yùn)行的程序進(jìn)行檢測咕娄。Spring框架附帶了這樣的代理工具InstrumentationSavingAgent亥揖,該代理文件打包在spring-instrument.jar中,在上一示例中作為-javaagent參數(shù)的值提供圣勒。

執(zhí)行Main程序的輸出類似于下一個(gè)示例费变。 (我在calculateEntitlement()實(shí)現(xiàn)中引入了Thread.sleep(..)語句,以便探查器實(shí)際上捕獲的不是0毫秒的內(nèi)容(01234毫秒不是AOP引入的開銷)圣贸。以下清單顯示了運(yùn)行分析器時(shí)得到的輸出:

Calculating entitlement

StopWatch 'ProfilingAspect': running time (millis) = 1234
------ ----- ----------------------------
ms     %     Task name
------ ----- ----------------------------
01234  100%  calculateEntitlement

由于此LTW是通過使用成熟的AspectJ來實(shí)現(xiàn)的挚歧,因此我們不僅限于為Spring Bean提供通知。在Main程序上進(jìn)行以下細(xì)微改動(dòng)會產(chǎn)生相同的結(jié)果:

package foo;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public final class Main {

    public static void main(String[] args) {
        new ClassPathXmlApplicationContext("beans.xml", Main.class);

        EntitlementCalculationService entitlementCalculationService =
                new StubEntitlementCalculationService();

        // the profiling aspect will be 'woven' around this method execution
        entitlementCalculationService.calculateEntitlement();
    }
}

請注意吁峻,在前面的程序中滑负,我們?nèi)绾我龑?dǎo)Spring容器,然后完全在Spring上下之外創(chuàng)建StubEntitlementCalculationService的新實(shí)例用含。剖析通知仍會被應(yīng)用矮慕。

誠然,這個(gè)例子很簡單啄骇。但是痴鳄,在前面的示例中已經(jīng)介紹了Spring對LTW支持的基礎(chǔ),本節(jié)的其余部分詳細(xì)解釋了每個(gè)配置和用法背后的“原因”缸夹。

在此示例中使用的ProfilingAspect可能是基本的痪寻,但它非常有用螺句。它是開發(fā)時(shí)間方面的一個(gè)很好的例子,開發(fā)人員可以在開發(fā)期間使用它橡类,然后很容易地從部署到UAT或生產(chǎn)中的應(yīng)用程序的構(gòu)建中排除它蛇尚。

切面

你在LTW中使用的切面必須是AspectJ切面。你可以使用AspectJ語言本身來編寫它們猫态,也可以使用@AspectJ風(fēng)格來編寫切面佣蓉。這樣,你的切面是有效的AspectJ和Spring AOP方面亲雪。此外勇凭,編譯的切面類需要在類路徑上可用。

'META-INF/aop.xml'

通過使用Java類路徑上的一個(gè)或多個(gè)META-INF/aop.xml文件(直接或通常在jar文件中)來配置AspectJ LTW基礎(chǔ)結(jié)構(gòu)义辕。

該文件的結(jié)構(gòu)和內(nèi)容在AspectJ參考文檔的LTW部分中進(jìn)行了詳細(xì)說明虾标。由于aop.xml文件是100%的AspectJ,因此在此不再贅述灌砖。

所需的庫(JARS)

至少璧函,你需要使用以下庫來使用Spring Framework對AspectJ LTW的支持:

  • spring-aop.jar
  • aspectjweaver.jar

如果使用Spring提供的代理來啟用檢測,則還需要:

  • spring-instrument.jar

Spring配置

Spring的LTW支持的關(guān)鍵組件是LoadTimeWeaver接口(在org.springframework.instrument.classloading包中)基显,以及Spring發(fā)行版附帶的眾多實(shí)現(xiàn)蘸吓。LoadTimeWeaver負(fù)責(zé)在運(yùn)行時(shí)將一個(gè)或多個(gè)java.lang.instrument.ClassFileTransformers添加到ClassLoader,這為各種有趣的應(yīng)用程序打開了大門撩幽,其中之一就是方面的LTW库继。

如果你不熟悉運(yùn)行時(shí)類文件轉(zhuǎn)換的概念,請?jiān)诶^續(xù)操作之前參閱java.lang.instrument的javadoc API文檔窜醉。盡管該文檔并不全面宪萄,但至少你可以看到關(guān)鍵的接口和類(在你通讀本節(jié)文檔作為參考)。

為特定的ApplicationContext配置LoadTimeWeaver就像添加一行一樣容易榨惰。(請注意拜英,你幾乎肯定需要將ApplicationContext用作Spring容器-通常,僅BeanFactory是不夠的琅催,因?yàn)長TW支持使用BeanFactoryPostProcessors居凶。)

要啟用Spring Framework的LTW支持,你需要配置一個(gè)LoadTimeWeaver恢暖,通常通過使用@EnableLoadTimeWeaving注解來完成排监,如下所示:

@Configuration
@EnableLoadTimeWeaving
public class AppConfig {
}

另外,如果你喜歡基于XML的配置杰捂,請使用<context:load-time-weaver />元素舆床。注意,該元素是在上下文名稱空間中定義的。以下示例顯示了如何使用<context:load-time-weaver />

<?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:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <context:load-time-weaver/>

</beans>

前面的配置會自動(dòng)為你定義并注冊許多LTW特定的基礎(chǔ)結(jié)構(gòu)Bean挨队,例如LoadTimeWeaverAspectJWeavingEnabler谷暮。默認(rèn)的LoadTimeWeaverDefaultContextLoadTimeWeaver類,它嘗試裝飾自動(dòng)檢測到的LoadTimeWeaver盛垦∈遥“自動(dòng)檢測到”的LoadTimeWeaver的確切類型取決于你的運(yùn)行時(shí)環(huán)境。下表總結(jié)了各種LoadTimeWeaver實(shí)現(xiàn):

運(yùn)行時(shí)環(huán)境 LoadTimeWeaver實(shí)現(xiàn)
運(yùn)行在 Apache Tomcat TomcatLoadTimeWeaver
運(yùn)行在 GlassFish (限于EAR部署) GlassFishLoadTimeWeaver
運(yùn)行在 Red Hat的 JBoss ASWildFly JBossLoadTimeWeaver
運(yùn)行在 IBM的 WebSphere WebSphereLoadTimeWeaver
運(yùn)行在 Oracle的 WebLogic WebLogicLoadTimeWeaver
JVM從Spring開始InstrumentationSavingAgent(java -javaagent:path/to/spring-instrument.jar) InstrumentationLoadTimeWeaver
期望基礎(chǔ)ClassLoader遵循通用約定 (即addTransformer和可選的getThrowawayClassLoader方法) ReflectiveLoadTimeWeaver

請注意腾夯,該表僅列出使用DefaultContextLoadTimeWeaver時(shí)自動(dòng)檢測到的LoadTimeWeavers颊埃。你可以確切指定要使用的LoadTimeWeaver實(shí)現(xiàn)。

要使用Java配置指定特定的LoadTimeWeaver蝶俱,請實(shí)現(xiàn)LoadTimeWeavingConfigurer接口并覆該getLoadTimeWeaver()方法班利。

以下示例指定了ReflectiveLoadTimeWeaver

@Configuration
@EnableLoadTimeWeaving
public class AppConfig implements LoadTimeWeavingConfigurer {

    @Override
    public LoadTimeWeaver getLoadTimeWeaver() {
        return new ReflectiveLoadTimeWeaver();
    }
}

如果使用基于XML的配置,則可以在<context:load-time-weaver />元素上將全限定的類名指定為weaver-class屬性的值榨呆。同樣罗标,以下示例指定了ReflectiveLoadTimeWeaver

<?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:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <context:load-time-weaver
            weaver-class="org.springframework.instrument.classloading.ReflectiveLoadTimeWeaver"/>

</beans>

稍后可以使用眾所周知的名稱loadTimeWeaver從Spring容器中檢索由配置定義和注冊的LoadTimeWeaver。請記住积蜻,LoadTimeWeaver僅作為Spring LTW基礎(chǔ)結(jié)構(gòu)添加一個(gè)或多個(gè)ClassFileTransformers的一種機(jī)制而存在闯割。執(zhí)行LTW的實(shí)際ClassFileTransformerClassPreProcessorAgentAdapter(來自org.aspectj.weaver.loadtime包)類。有關(guān)更多詳細(xì)信息竿拆,請參見ClassPreProcessorAgentAdapter類的類級javadoc宙拉,因?yàn)閷?shí)際上如何實(shí)現(xiàn)編織的細(xì)節(jié)不在本文檔的討論范圍之內(nèi)。

剩下要討論的配置的最后一個(gè)屬性:aspectjWeaving屬性(如果使用XML丙笋,則為Aspectj-weaving)鼓黔。此屬性控制是否啟用LTW。它接受三個(gè)可能的值之一不见,如果屬性不存在,則默認(rèn)值為autodetect崔步。如果屬性不存在稳吮。下表總結(jié)了三個(gè)可能的值:

注解值 XML 值 Explanation
ENABLED on AspectJ正在編織,并且在加載時(shí)適當(dāng)?shù)鼐幙椓饲忻妗?/td>
DISABLED off LTW已關(guān)閉井濒。加載時(shí)不會編織任何切面灶似。
AUTODETECT autodetect 如果Spring LTW基礎(chǔ)結(jié)構(gòu)可以找到至少一個(gè)META-INF / aop.xml文件,則AspectJ編織已啟動(dòng)瑞你。否則酪惭,它關(guān)閉。這是默認(rèn)值者甲。

特定環(huán)境配置

最后一部分包含在應(yīng)用程序服務(wù)器和Web容器等環(huán)境中使用Spring的LTW支持時(shí)所需的任何其他設(shè)置和配置春感。

Tomcat、JBoss、WebSphere鲫懒、WebLogic

Tomcat嫩实、JBoss/WildFlyIBM WebSphere Application ServerOracle WebLogic Server都提供了通用應(yīng)用程序ClassLoader窥岩,該應(yīng)用程序能夠進(jìn)行本地檢測甲献。Spring的本地LTW可以利用這些ClassLoader實(shí)現(xiàn)來提供AspectJ編織。如前所述颂翼,你可以簡單地啟用加載時(shí)編織晃洒。具體來說,你無需修改JVM啟動(dòng)腳本即可添加-javaagent:path/to/spring-instrument.jar朦乏。請注意球及,在JBoss上,你可能需要禁用應(yīng)用服務(wù)器掃描集歇,以防止它在應(yīng)用程序?qū)嶋H啟動(dòng)之前加載類桶略。一個(gè)快速的解決方法是將一個(gè)名為WEB-INF/jboss-scanning.xml的文件添加到你的構(gòu)件中,其中包含以下內(nèi)容:

<scanning xmlns="urn:jboss:scanning:1.0"/>

通用Java應(yīng)用程序

如果特定LoadTimeWeaver實(shí)現(xiàn)不支持的環(huán)境中需要類檢測诲宇,則JVM代理是通用解決方案际歼。對于這種情況,Spring提供了InstrumentationLoadTimeWeaver姑蓝,它需要特定于Spring(但非常通用)的JVM代理spring-instrument.jar鹅心,并由常見@EnableLoadTimeWeaving<context:load-time-weaver />設(shè)置自動(dòng)檢測到。

要使用它纺荧,必須通過提供以下JVM選項(xiàng)來使用Spring代理啟動(dòng)虛擬機(jī):

-javaagent:/path/to/spring-instrument.jar

請注意旭愧,這需要修改JVM啟動(dòng)腳本,這可能會阻止你在應(yīng)用程序服務(wù)器環(huán)境中使用它(取決于你的服務(wù)器和你的操作策略)宙暇。也就是說输枯,對于每個(gè)JVM一個(gè)應(yīng)用程序的部署(例如獨(dú)立的Spring Boot應(yīng)用程序),無論如何占贫,你通常都可以控制整個(gè)JVM的設(shè)置桃熄。

5.11 更多資源

可以在AspectJ網(wǎng)站上找到有關(guān)AspectJ的更多信息。

作者

個(gè)人從事金融行業(yè)型奥,就職過易極付瞳收、思建科技、某網(wǎng)約車平臺等重慶一流技術(shù)團(tuán)隊(duì)厢汹,目前就職于某銀行負(fù)責(zé)統(tǒng)一支付系統(tǒng)建設(shè)螟深。自身對金融行業(yè)有強(qiáng)烈的愛好。同時(shí)也實(shí)踐大數(shù)據(jù)烫葬、數(shù)據(jù)存儲界弧、自動(dòng)化集成和部署凡蜻、分布式微服務(wù)、響應(yīng)式編程夹纫、人工智能等領(lǐng)域咽瓷。同時(shí)也熱衷于技術(shù)分享創(chuàng)立公眾號和博客站點(diǎn)對知識體系進(jìn)行分享。關(guān)注公眾號:青年IT男 獲取最新技術(shù)文章推送舰讹!

博客地址: http://youngitman.tech

CSDN: https://blog.csdn.net/liyong1028826685

微信公眾號:

image

技術(shù)交流群:

image

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末茅姜,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子月匣,更是在濱河造成了極大的恐慌钻洒,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,635評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件锄开,死亡現(xiàn)場離奇詭異素标,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)萍悴,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,543評論 3 399
  • 文/潘曉璐 我一進(jìn)店門头遭,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人癣诱,你說我怎么就攤上這事计维。” “怎么了撕予?”我有些...
    開封第一講書人閱讀 168,083評論 0 360
  • 文/不壞的土叔 我叫張陵鲫惶,是天一觀的道長。 經(jīng)常有香客問我实抡,道長欠母,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,640評論 1 296
  • 正文 為了忘掉前任吆寨,我火速辦了婚禮赏淌,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘啄清。我一直安慰自己猜敢,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,640評論 6 397
  • 文/花漫 我一把揭開白布盒延。 她就那樣靜靜地躺著,像睡著了一般鼠冕。 火紅的嫁衣襯著肌膚如雪添寺。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,262評論 1 308
  • 那天懈费,我揣著相機(jī)與錄音计露,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛票罐,可吹牛的內(nèi)容都是我干的叉趣。 我是一名探鬼主播,決...
    沈念sama閱讀 40,833評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼该押,長吁一口氣:“原來是場噩夢啊……” “哼疗杉!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起蚕礼,我...
    開封第一講書人閱讀 39,736評論 0 276
  • 序言:老撾萬榮一對情侶失蹤烟具,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后奠蹬,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體朝聋,經(jīng)...
    沈念sama閱讀 46,280評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,369評論 3 340
  • 正文 我和宋清朗相戀三年囤躁,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了冀痕。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,503評論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡狸演,死狀恐怖言蛇,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情严沥,我是刑警寧澤猜极,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站消玄,受9級特大地震影響跟伏,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜翩瓜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,870評論 3 333
  • 文/蒙蒙 一受扳、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧兔跌,春花似錦勘高、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,340評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至仅乓,卻和暖如春赖舟,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背夸楣。 一陣腳步聲響...
    開封第一講書人閱讀 33,460評論 1 272
  • 我被黑心中介騙來泰國打工宾抓, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留子漩,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,909評論 3 376
  • 正文 我出身青樓石洗,卻偏偏與公主長得像幢泼,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子讲衫,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,512評論 2 359