AOP(面向切面編程) 筆記

AOP相關(guān)的概念

  • Aspect :切面宣谈,切入系統(tǒng)的一個切面尔破。比如事務(wù)管理是一個切面,權(quán)限管理也是一個切面莺匠;
  • Join point :連接點金吗,也就是可以進行橫向切入的位置;
  • Advice :通知趣竣,切面在某個連接點執(zhí)行的操作(分為: Before advice , After returning advice , After throwing advice , After (finally) advice , Around advice )摇庙;
  • Pointcut :切點,符合切點表達式的連接點遥缕,也就是真正被切入的地方卫袒;

AOP分為靜態(tài)AOP和動態(tài)AOP。

靜態(tài)AOP是指AspectJ實現(xiàn)的AOP单匣,他是將切面代碼直接編譯到Java類文件中夕凝。
動態(tài)AOP是指將切面代碼進行動態(tài)織入實現(xiàn)的AOP。如Spring AOP户秤,實現(xiàn)的技術(shù)為: JDK提供的動態(tài)代理技術(shù) 和 CGLIB(動態(tài)字節(jié)碼增強技術(shù))

Android平臺AOP

在Android平臺最常用的三劍客:APT码秉、AspectJ、Javassist

APT

APT針對的是編譯時注解
首先鸡号,APT(Annotation Processing Tool)是javac提供的一種工具转砖,它在編譯時掃描、解析膜蠢、處理注解堪藐。它會對源代碼文件進行檢測,找出用戶自定義的注解挑围,根據(jù)注解礁竞、注解處理器和相應的apt工具自動生成代碼。這段代碼是根據(jù)用戶編寫的注解處理邏輯去生成的杉辙。最終將生成的新的源文件與原來的源文件共同編譯(注意:APT并不能對源文件進行修改操作模捂,只能生成新的文件,例如往原來的類中添加方法)。代表框架:DataBinding,Dagger2, ButterKnife, EventBus3

流程概要

1狂男、創(chuàng)建自定義注解@interface综看;
2、創(chuàng)建并注冊注解處理器AbstractProcessor岖食,生成處理注解邏輯的.java文件红碑;
3、封裝一個供外部調(diào)用的API泡垃,用的是反射技術(shù)析珊,具體來說就是調(diào)用第二步中生成的代碼中的方法;
4蔑穴、在項目中使用忠寻,比如Activity、Fragment存和、Adapter

實現(xiàn)步驟

1.創(chuàng)建自定義注解
創(chuàng)建一個Java Library奕剃,名稱為annotation,作用是保存所有注解捐腿。

@Retention(RetentionPolicy.CLASS) //注解生命周期是編譯期纵朋,存活于.class文件,當jvm加載class時就不在了
@Target(ElementType.FIELD) //目標對象是變量 
public @interface BindView {

    /**
     * @return 控件變量的resourceId
     */
    int value();
}

2.實現(xiàn)注解處理器AbstractProcessor
創(chuàng)建一個Java Library茄袖,名稱為processor倡蝙,作用是掃描、解析绞佩、處理注解。
processor的Gradle配置如下:

apply plugin: 'java-library'

dependencies {
    implementation fileTree(include: ['*.jar'], dir: 'libs')

    implementation project(':annotation')
    //用于自動為 JAVA Processor 生成 META-INF 信息猪钮。
    implementation 'com.google.auto.service:auto-service:1.0-rc3'
    //快速生成.java文件的庫
    implementation 'com.squareup:javapoet:1.8.0'
}

創(chuàng)建自己的Processor來實現(xiàn)注解處理器

@AutoService(Processor.class)
public class ButterKnifeProcessor extends AbstractProcessor {
    /**
     * 生成文件的工具類
     */
    private Filer filer;
    /**
     * 打印信息
     */
    private Messager messager;
    /**
     * 元素相關(guān)
     */
    private Elements elementUtils;
    private Types typeUtils;
    private Map<String, ProxyInfo> proxyInfoMap = new HashMap<>();


    /**
     * 一些初始化操作品山,獲取一些有用的系統(tǒng)工具類
     *
     * @param processingEnv
     */
    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        filer = processingEnv.getFiler();
        messager = processingEnv.getMessager();
        elementUtils = processingEnv.getElementUtils();
        typeUtils = processingEnv.getTypeUtils();
    }


    /**
     * 設(shè)置支持的版本
     *
     * @return 這里用最新的就好
     */
    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }


    /**
     * 設(shè)置支持的注解類型
     *
     * @return
     */
    @Override
    public Set<String> getSupportedAnnotationTypes() {
        //添加支持的注解
        HashSet<String> set = new HashSet<>();
        set.add(BindView.class.getCanonicalName());
        return set;
    }

 /**
     * 注解內(nèi)部邏輯的實現(xiàn)
     * <p>
     * Element代表程序的一個元素,可以是package, class, interface, method.只在編譯期存在
     * TypeElement:變量烤低;TypeElement:類或者接口
     *
     * @param annotations
     * @param roundEnv
     * @return
     */
    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        messager.printMessage(Diagnostic.Kind.NOTE, "annotations size--->" + annotations.size());
        //1肘交、獲取要處理的注解的元素的集合
        Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(BindView.class);

        //process()方法會調(diào)用3次扮休,只有第一次有效奴璃,第2姻成,3次調(diào)用的話生成.java文件會發(fā)生異常
        if (elements == null || elements.size() < 1) {
            return true;
        }

        //2检诗、按類來劃分注解元素暮胧,因為每個使用注解的類都會生成相應的代理類
        for (Element element : elements) {
            checkAnnotationValid(element, BindView.class);

            //獲取被注解的成員變量
            //這里被注解的類型只能是變量享完,所以可以直接強轉(zhuǎn)
            VariableElement variableElement = (VariableElement) element;
            //獲取該元素的父元素政供,這里是父類
            TypeElement typeElement = (TypeElement) variableElement.getEnclosingElement();
            //獲取全類名
            String className = typeElement.getQualifiedName().toString();
            //獲取被注解元素的包名
            String packageName = elementUtils.getPackageOf(element).getQualifiedName().toString();
            //獲取注解的參數(shù)
            int resourceId = element.getAnnotation(BindView.class).value();

            //生成ProxyInfo對象
            //一個類里面的注解都在一個ProxyInfo中處理
            ProxyInfo proxyInfo = proxyInfoMap.get(className);
            if (proxyInfo == null) {
                proxyInfo = new ProxyInfo(typeElement, packageName);
                proxyInfoMap.put(className, proxyInfo);
            }
            proxyInfo.viewVariableElement.put(resourceId, variableElement);
        }

        //3趟紊、生成注解邏輯處理類
        for (String key : proxyInfoMap.keySet()) {
            ProxyInfo proxyInfo = proxyInfoMap.get(key);
            JavaFile javaFile = JavaFile.builder(proxyInfo.packageName, proxyInfo.generateProxyClass())
                    .addFileComment("auto generateProxyClass code,can not modify")
                    .build();
            try {
                javaFile.writeTo(filer);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return true;
    }

//通過javapoet生成注解邏輯代碼的具體步驟如下:
public TypeSpec generateProxyClass() {
        //代理類實現(xiàn)的接口
        ClassName viewInjector = ClassName.get("com.zx.inject_api", "IViewInjector");
        //原始的注解類
        ClassName className = ClassName.get(typeElement);
        //  泛型接口雄家,implements IViewInjector<MainActivity>
        ParameterizedTypeName parameterizedTypeName = ParameterizedTypeName.get(viewInjector, className);

        //生成接口的實現(xiàn)方法inject()
        MethodSpec.Builder bindBuilder = MethodSpec.methodBuilder("inject")
                .addModifiers(Modifier.PUBLIC)
                .addAnnotation(Override.class) //添加方法注解
                .addParameter(className, "target")
                .addParameter(Object.class, "source");

        for (int id : viewVariableElement.keySet()) {
            VariableElement element = viewVariableElement.get(id);
            String fieldName = element.getSimpleName().toString();
            bindBuilder.addStatement(" if (source instanceof android.app.Activity){target.$L = ((android.app.Activity) source).findViewById( $L);}" +
                    "else{target.$L = ((android.view.View)source).findViewById($L);}", fieldName, id, fieldName, id);
        }

        MethodSpec bindMethodSpec = bindBuilder.build();

        //創(chuàng)建類
        TypeSpec typeSpec = TypeSpec.classBuilder(proxyClassName)
                .addModifiers(Modifier.PUBLIC)
                .addSuperinterface(parameterizedTypeName) //實現(xiàn)接口
                .addMethod(bindMethodSpec)
                .build();

        return typeSpec;
    }

3.封裝一個供外部調(diào)用的API

public interface IViewInjector<T> {
    /**
     * 通過source.findViewById()
     *
     * @param target 泛型參數(shù)效诅,調(diào)用類 activity、fragment等
     * @param source Activity、View
     */
    void inject(T target, Object source);
}

/**
     * 根據(jù)使用注解的類和約定的命名規(guī)則乱投,通過反射找到動態(tài)生成的代理類(處理注解邏輯)
     * @param object 調(diào)用類對象
     */
    private static IViewInjector findProxyActivity(Object object) {
        String proxyClassName = object.getClass().getName() + PROXY;
        Log.e(TAG, "findProxyActivity: "+proxyClassName);
        Class<?> proxyClass = null;
        try {
            proxyClass = Class.forName(proxyClassName);
//            Constructor<?> constructor = proxyClass.getConstructor(object.getClass());
            return (IViewInjector) proxyClass.newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

/**
     * Activity調(diào)用
     */
    public static void bind(Activity activity) {
        findProxyActivity(activity).inject(activity, activity);
    }

    /**
     * Fragment咽笼、Adapter調(diào)用
     *
     * @param object
     * @param view
     */
    public static void bind(Object object, View view) {
        findProxyActivity(object).inject(object, view);
    }

4 在Activity、Fragment戚炫、Adapter等使用

implementation project(':annotation')
    implementation project(':inject_api')
    //gradle3.0以上apt的實現(xiàn)方式
    annotationProcessor project(':processor')
Q1:注解處理器processor為什么要在META-INF注冊剑刑?

在編譯時,java編譯器(javac)會去META-INF中查找實現(xiàn)了的AbstractProcessor的子類双肤,并且調(diào)用該類的process函數(shù)施掏,最終生成.java文件。其實就像activity需要注冊一樣杨伙,就是要到META-INF注冊 其监,javac才知道要給你調(diào)用哪個類來處理注解。

Q2:注解處理器processor是如何被系統(tǒng)調(diào)用的限匣?

在完成注解處理類Processor之后抖苦,需要做2件事情:
1、在META-INF目錄下注冊Processor米死;
2锌历、在項目中使用注解的地方添加apt工具annotationProcessor

Q3:注解申明和注解處理器為什么要分Module處理?

我們都知道processor的作用是:在編譯器解析注解峦筒、生成新的.java文件究西。這個lib只在編譯器用到,是不會被打包進apk的物喷。對于調(diào)用者來說卤材,你只是想使用這個注解,而不希望你已經(jīng)編譯好的項目中引進注解處理器相關(guān)的內(nèi)容峦失,所以為了不引入沒必要的文件扇丛,我們一般選擇將注解聲明和注解處理分開處理。

AspectJ

AspectJ的核心是ajc編譯器 (aspectjtools)和織入器 (aspectjweaver)尉辑。
ajc編譯器
基于Java編譯器之上的帆精,它是用來編譯.aj文件,aspectj在Java編譯器的基礎(chǔ)上增加了一些它自己的關(guān)鍵字和方法隧魄。因此卓练,ajc也可以編譯Java代碼。
weaver織入器:
它的工作原理是:通過Gradle Transform API购啄,在class文件生成后至dex文件生成前襟企,遍歷并匹配所有符合AspectJ文件中聲明的切點,然后將Aspect的代碼織入到目標.class狮含≌海織入代碼后的新.class會加入多個JoinPoint,這個JoinPoint會建立目標.class與Aspect代碼的連接拱撵,比如獲得執(zhí)行的對象、方法表蝙、參數(shù)等拴测。


Java動態(tài)代理

動態(tài)代理是指在運行時動態(tài)生成代理類。即府蛇,代理類的字節(jié)碼將在運行時生成并載入當前代理的 ClassLoader集索。與靜態(tài)處理類相比,動態(tài)類有諸多好處汇跨。
主要使用到 InvocationHandler 接口和 Proxy.newProxyInstance() 方法务荆。

①不需要寫一個形式上完全一樣的封裝類;

②使用一些動態(tài)代理的生成方法甚至可以在運行時制定代理類的執(zhí)行邏輯穷遂,從而大大提升系統(tǒng)的靈活性函匕。

java.lang.reflect.Proxy:這是生成代理類的主類,通過 Proxy 類生成的代理類都繼承了 Proxy 類蚪黑。
Proxy提供了用戶創(chuàng)建動態(tài)代理類和代理對象的靜態(tài)方法盅惜,它是所有動態(tài)代理類的父類。
java.lang.reflect.InvocationHandler:這里稱他為"調(diào)用處理器"忌穿,它是一個接口抒寂。當調(diào)用動態(tài)代理類中的方法時,將會直接轉(zhuǎn)接到執(zhí)行自定義的InvocationHandler中的invoke()方法掠剑。即我們動態(tài)生成的代理類需要完成的具體內(nèi)容需要自己定義一個類屈芜,而這個類必須實現(xiàn) InvocationHandler 接口,通過重寫invoke()方法來執(zhí)行具體內(nèi)容朴译。

Proxy提供了如下兩個方法來創(chuàng)建動態(tài)代理類和動態(tài)代理實例井佑。
static Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces) 返回代理類的java.lang.Class對象。第一個參數(shù)是類加載器對象(即哪個類加載器來加載這個代理類到 JVM 的方法區(qū))眠寿,第二個參數(shù)是接口(表明你這個代理類需要實現(xiàn)哪些接口)毅糟,第三個參數(shù)是調(diào)用處理器類實例(指定代理類中具體要干什么),該代理類將實現(xiàn)interfaces所指定的所有接口澜公,執(zhí)行代理對象的每個方法時都會被替換執(zhí)行InvocationHandler對象的invoke方法。

**static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) **返回代理類實例喇肋。參數(shù)與上述方法一致坟乾。

public class ProxyDemo {
    public static void main(String args[]){
        RealSubject subject = new RealSubject();
        Proxy p = new Proxy(subject);
        p.request();
    }
}

interface Subject{
    void request();
}

class RealSubject implements Subject{
    public void request(){
        System.out.println("request");
    }
}

class Proxy implements Subject{
    private Subject subject;
    public Proxy(Subject subject){
        this.subject = subject;
    }
    public void request(){
        System.out.println("PreProcess");
        subject.request();
        System.out.println("PostProcess");
    }
}

對應上述兩種方法創(chuàng)建動態(tài)代理對象的方式:

 //創(chuàng)建一個InvocationHandler對象
        InvocationHandler handler = new MyInvocationHandler(.args..);
        //使用Proxy生成一個動態(tài)代理類
        Class proxyClass = Proxy.getProxyClass(RealSubject.class.getClassLoader(),RealSubject.class.getInterfaces(), handler);
        //獲取proxyClass類中一個帶InvocationHandler參數(shù)的構(gòu)造器
        Constructor constructor = proxyClass.getConstructor(InvocationHandler.class);
        //調(diào)用constructor的newInstance方法來創(chuàng)建動態(tài)實例
        RealSubject real = (RealSubject)constructor.newInstance(handler);
//創(chuàng)建一個InvocationHandler對象
        InvocationHandler handler = new MyInvocationHandler(.args..);
        //使用Proxy直接生成一個動態(tài)代理對象
        RealSubject real =Proxy.newProxyInstance(RealSubject.class.getClassLoader(),RealSubject.class.getInterfaces(), handler);

newProxyInstance這個方法實際上做了兩件事:第一,創(chuàng)建了一個新的類【代理類】蝶防,這個類實現(xiàn)了Class[] interfaces中的所有接口甚侣,并通過你指定的ClassLoader將生成的類的字節(jié)碼加載到JVM中,創(chuàng)建Class對象间学;第二殷费,以你傳入的InvocationHandler作為參數(shù)創(chuàng)建一個代理類的實例并返回印荔。

動態(tài)代理模式的簡單實現(xiàn)

public class DynamicProxyDemo {
    public static void main(String[] args) {
        //1.創(chuàng)建目標對象
        RealSubject realSubject = new RealSubject();    
        //2.創(chuàng)建調(diào)用處理器對象
        ProxyHandler handler = new ProxyHandler(realSubject);    
       //3.動態(tài)生成代理對象
        Subject proxySubject = (Subject)Proxy.newProxyInstance(RealSubject.class.getClassLoader(),
                                                        RealSubject.class.getInterfaces(), handler);   
        //4.通過代理對象調(diào)用方法   
        proxySubject.request();    
    }
}

/**
 * 主題接口
 */
interface Subject{
    void request();
}

/**
 * 目標對象類
 */
class RealSubject implements Subject{
    public void request(){
        System.out.println("====RealSubject Request====");
    }
}
/**
 * 代理類的調(diào)用處理器
 */
class ProxyHandler implements InvocationHandler{
    private Subject subject;
    public ProxyHandler(Subject subject){
        this.subject = subject;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        //定義預處理的工作,當然你也可以根據(jù) method 的不同進行不同的預處理工作
        System.out.println("====before====");
       //調(diào)用RealSubject中的方法
        Object result = method.invoke(subject, args);
        System.out.println("====after====");
        return result;
    }
}

Retrofit 分析

動態(tài)代理详羡,讓Retrofit將我們的接口變?yōu)榫唧w的類

public <T> T create(final Class<T> service) {
    //做一些安全判斷仍律,判斷是否為接口,不是接口java原生的動態(tài)代理會報錯
    Utils.validateServiceInterface(service);
    if (validateEagerly) {
      eagerlyValidateMethods(service);
    }

    //create其實就是通過Java的動態(tài)代理將接口中定義的方法轉(zhuǎn)給了InvocationHandler的invoke方法
    //在通過loadServiceMethod調(diào)用invoke实柠,來發(fā)起網(wǎng)絡(luò)請求(下篇文章會講)
    return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service }
            , new InvocationHandler() {
              private final Platform platform = Platform.get();
              private final Object[] emptyArgs = new Object[0];

              @Override
              public @Nullable Object invoke(Object proxy, Method method,
                                             @Nullable Object[] args) throws Throwable {
                // If the method is a method from Object then defer to normal invocation.
                //如果該方法是Object自帶的方法水泉,那么我們直接反射出來這些方法就可以了
                //比如說是.equest() 、 toString()
                if (method.getDeclaringClass() == Object.class) {
                  return method.invoke(this, args);
                }
                // .isDefaultMethod是Java8的特性
                //它的意思是接口中定義的方法窒盐,因為java8支持了接口定義默認方法(default)這一特性
                //也就是說草则,這里的if是判斷上述特性,如果是就直接調(diào)用
                //如果不是蟹漓,就使用loadServiceMethod來獲取方法
                if (platform.isDefaultMethod(method)) {
                  return platform.invokeDefaultMethod(method, service, proxy, args);
                }
                //返回了 封裝了請求參數(shù)的一個接口炕横,它知道怎么發(fā)起網(wǎng)絡(luò)請求
               ServiceMethod<Object, Object> serviceMethod =
                (ServiceMethod<Object, Object>) loadServiceMethod(method);
            OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args);
              return serviceMethod.callAdapter.adapt(okHttpCall);
              }
            });
  }

newProxyInstance 看看它到底是怎么將接口進行代理,生成接口的代理類

//newProxyInstance傳入了ClassLoader 還有接口葡粒,我們可以想象得到
//他是通過ClassLoader來生成代理類的份殿。
public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
        ...
        /*
         * Look up or generate the designated proxy class.
            具體實現(xiàn)類代理的方法 getProxyClass0
         */
        Class<?> cl = getProxyClass0(loader, intfs);

        return cons.newInstance(new Object[]{h});
       ...
    }

private static Class<?> getProxyClass0(ClassLoader loader,
                                           Class<?>... interfaces) {
        if (interfaces.length > 65535) {
            throw new IllegalArgumentException("interface limit exceeded");
        }

        // If the proxy class defined by the given loader implementing
        // the given interfaces exists, this will simply return the cached copy;
        // otherwise, it will create the proxy class via the ProxyClassFactory
        return proxyClassCache.get(loader, interfaces);
    }
...
proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());

private static final class ProxyClassFactory
        implements BiFunction<ClassLoader, Class<?>[], Class<?>>{
        // prefix for all proxy class names
        private static final String proxyClassNamePrefix = "$Proxy";

        // next number to use for generation of unique proxy class names
        private static final AtomicLong nextUniqueNumber = new AtomicLong();

        @Override
        public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {

            Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
            for (Class<?> intf : interfaces) {
                /*
                 * Verify that the class loader resolves the name of this
                 * interface to the same Class object.
                 */
                Class<?> interfaceClass = null;
                try {
                    interfaceClass = Class.forName(intf.getName(), false, loader);
                } catch (ClassNotFoundException e) {
                }
                if (interfaceClass != intf) {
                    throw new IllegalArgumentException(
                        intf + " is not visible from class loader");
                }
                /*
                 * Verify that the Class object actually represents an
                 * interface.
                 */
                if (!interfaceClass.isInterface()) {
                    throw new IllegalArgumentException(
                        interfaceClass.getName() + " is not an interface");
                }
                /*
                 * Verify that this interface is not a duplicate.
                 */
                if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {
                    throw new IllegalArgumentException(
                        "repeated interface: " + interfaceClass.getName());
                }
            }

            String proxyPkg = null;     // package to define proxy class in
            int accessFlags = Modifier.PUBLIC | Modifier.FINAL;

            /*
             * Record the package of a non-public proxy interface so that the
             * proxy class will be defined in the same package.  Verify that
             * all non-public proxy interfaces are in the same package.
             */
            for (Class<?> intf : interfaces) {
                int flags = intf.getModifiers();
                if (!Modifier.isPublic(flags)) {
                    accessFlags = Modifier.FINAL;
                    String name = intf.getName();
                    int n = name.lastIndexOf('.');
                    String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
                    if (proxyPkg == null) {
                        proxyPkg = pkg;
                    } else if (!pkg.equals(proxyPkg)) {
                        throw new IllegalArgumentException(
                            "non-public interfaces from different packages");
                    }
                }
            }

            if (proxyPkg == null) {
                // if no non-public proxy interfaces, use the default package.
                proxyPkg = "";
            }

            {
                // Android-changed: Generate the proxy directly instead of calling
                // through to ProxyGenerator.
                List<Method> methods = getMethods(interfaces);
                Collections.sort(methods, ORDER_BY_SIGNATURE_AND_SUBTYPE);
                validateReturnTypes(methods);
                List<Class<?>[]> exceptions = deduplicateAndGetExceptions(methods);

                Method[] methodsArray = methods.toArray(new Method[methods.size()]);
                Class<?>[][] exceptionsArray = exceptions.toArray(new Class<?>[exceptions.size()][]);

                /*
                 * Choose a name for the proxy class to generate.
                 */
                long num = nextUniqueNumber.getAndIncrement();
                String proxyName = proxyPkg + proxyClassNamePrefix + num;

                return generateProxy(proxyName, interfaces, loader, methodsArray,
                                     exceptionsArray);
            }
        }
    }

generateProxy(proxyName, interfaces, loader, methodsArray, exceptionsArray)是一個native方法

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市塔鳍,隨后出現(xiàn)的幾起案子伯铣,更是在濱河造成了極大的恐慌,老刑警劉巖轮纫,帶你破解...
    沈念sama閱讀 218,941評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件腔寡,死亡現(xiàn)場離奇詭異,居然都是意外死亡掌唾,警方通過查閱死者的電腦和手機放前,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來糯彬,“玉大人凭语,你說我怎么就攤上這事×冒牵” “怎么了似扔?”我有些...
    開封第一講書人閱讀 165,345評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長搓谆。 經(jīng)常有香客問我炒辉,道長,這世上最難降的妖魔是什么泉手? 我笑而不...
    開封第一講書人閱讀 58,851評論 1 295
  • 正文 為了忘掉前任黔寇,我火速辦了婚禮,結(jié)果婚禮上斩萌,老公的妹妹穿的比我還像新娘缝裤。我一直安慰自己屏轰,他們只是感情好,可當我...
    茶點故事閱讀 67,868評論 6 392
  • 文/花漫 我一把揭開白布憋飞。 她就那樣靜靜地躺著霎苗,像睡著了一般。 火紅的嫁衣襯著肌膚如雪搀崭。 梳的紋絲不亂的頭發(fā)上叨粘,一...
    開封第一講書人閱讀 51,688評論 1 305
  • 那天,我揣著相機與錄音瘤睹,去河邊找鬼升敲。 笑死,一個胖子當著我的面吹牛轰传,可吹牛的內(nèi)容都是我干的驴党。 我是一名探鬼主播,決...
    沈念sama閱讀 40,414評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼获茬,長吁一口氣:“原來是場噩夢啊……” “哼港庄!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起恕曲,我...
    開封第一講書人閱讀 39,319評論 0 276
  • 序言:老撾萬榮一對情侶失蹤鹏氧,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后佩谣,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體把还,經(jīng)...
    沈念sama閱讀 45,775評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年茸俭,在試婚紗的時候發(fā)現(xiàn)自己被綠了吊履。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,096評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡调鬓,死狀恐怖艇炎,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情腾窝,我是刑警寧澤缀踪,帶...
    沈念sama閱讀 35,789評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站虹脯,受9級特大地震影響驴娃,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜归形,卻給世界環(huán)境...
    茶點故事閱讀 41,437評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望鼻由。 院中可真熱鬧暇榴,春花似錦厚棵、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至奸例,卻和暖如春彬犯,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背查吊。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評論 1 271
  • 我被黑心中介騙來泰國打工谐区, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人逻卖。 一個月前我還...
    沈念sama閱讀 48,308評論 3 372
  • 正文 我出身青樓宋列,卻偏偏與公主長得像,于是被迫代替她去往敵國和親评也。 傳聞我的和親對象是個殘疾皇子炼杖,可洞房花燭夜當晚...
    茶點故事閱讀 45,037評論 2 355

推薦閱讀更多精彩內(nèi)容

  • Java動態(tài)代理 引言 最近在看AOP代碼,其中利用到了Java動態(tài)代理機制來實現(xiàn)AOP織入盗迟。所以好好地把Java...
    草捏子閱讀 1,537評論 0 18
  • 前言 本文是我在學習代理模式時的一篇筆記坤邪,除了對代理模式、靜態(tài)和動態(tài)代理的概念和實現(xiàn)進行了描述外罚缕,還有關(guān)于動態(tài)代理...
  • 轉(zhuǎn)自https://blog.csdn.net/briblue/article/details/73928350前...
    扎Zn了老Fe閱讀 311評論 1 0
  • 感恩管理員~卍法蓮Serena卍師兄的主持感恩所有參加共修的師兄 感恩宋師兄感恩佛菩薩及護法金剛加持 感恩佛寶居道...
    瀞好如琳閱讀 243評論 0 0
  • 這篇文章說的是一個悶葫蘆是怎么演變成話嘮的艇纺。全篇約1000字,閱讀需5分鐘怕磨。 這三十多年喂饥,我是個不愛說話的人,...
    心循自在閱讀 149評論 0 1