day12-ApplicationContext與Spring AOP(千峰逆戰(zhàn))

<meta charset="utf-8">

一、ApplicationContext

ApplicationContext被稱為Spring上下文,Application Context 是 Spring 中較高級(jí)的容器。和 BeanFactory 類似,它可以加載配置文件中定義的 bean,將所有的 bean 集中在一起,當(dāng)有請(qǐng)求的時(shí)候分配 bean。 另外,它增加了企業(yè)所需要的功能,比如,從屬性文件中解析文本信息和將事件傳遞給所指定的監(jiān)聽器。

1、BeanFactory容器

這是一個(gè)最簡(jiǎn)單的容器,它主要的功能是為依賴注入 (DI) 提供支持,這個(gè)容器接口在 org.springframework.beans.factory.BeanFactor 中被定義。BeanFactory 和相關(guān)的接口,比如BeanFactoryAware、DisposableBean、InitializingBean,仍舊保留在 Spring 中,主要目的是向后兼容已經(jīng)存在的和那些 Spring 整合在一起的第三方框架。在資源寶貴的移動(dòng)設(shè)備或者基于 applet 的應(yīng)用當(dāng)中, BeanFactory 會(huì)被優(yōu)先選擇。否則,一般使用的是 ApplicationContext。

這個(gè)接口是Spring中最底層的接口,提供了最簡(jiǎn)單的容器的功能,只提供了實(shí)例化對(duì)象和取對(duì)象的功能。

2、ApplicationContext與BeanFactory的關(guān)系

2.1 聯(lián)系

在IDEA中可以查看兩者的繼承關(guān)系

image

從上圖可以看出,AllicationContext這個(gè)接口繼承了ListableBeanFactory與HierarchicalBeanFactory這兩個(gè)接口,而這兩個(gè)接口都繼承了BeanFactory這個(gè)接口。所以它們都可以用來配置XML屬性,也支持屬性的自動(dòng)注入。

2.2 區(qū)別

1)功能

BeanFactory是Spring中最底層的容器,提供的功能較為簡(jiǎn)單,ApplicationContext繼承了兩個(gè)接口,相對(duì)而言可以提供更多功能:

  • 國(guó)際化(i18n)(MessageSource)

  • 訪問資源,如URL和文件(ResourceLoader)

  • 載入多個(gè)(有繼承關(guān)系)上下文 ,使得每一個(gè)上下文都專注于一個(gè)特定的層次,比如應(yīng)用的web層

  • 消息發(fā)送、響應(yīng)機(jī)制(ApplicationEventPublisher)

  • AOP(攔截器)

2)裝載Bean

BeanFactory在啟動(dòng)的時(shí)候不會(huì)去實(shí)例化Bean,中有從容器中拿Bean的時(shí)候才會(huì)去實(shí)例化;ApplicationContext在啟動(dòng)的時(shí)候就把所有的Bean全部實(shí)例化了。它還可以為Bean配置lazy-init=true來讓Bean延遲實(shí)例化;

  • 延遲實(shí)例化的優(yōu)點(diǎn):(BeanFactory

應(yīng)用啟動(dòng)的時(shí)候占用資源很少;對(duì)資源要求較高的應(yīng)用,比較有優(yōu)勢(shì);

  • 不延遲實(shí)例化的優(yōu)點(diǎn): (ApplicationContext

    • 所有的Bean在啟動(dòng)的時(shí)候都加載,系統(tǒng)運(yùn)行的速度快;
  • 在啟動(dòng)的時(shí)候所有的Bean都加載了,我們就能在系統(tǒng)啟動(dòng)的時(shí)候,盡早的發(fā)現(xiàn)系統(tǒng)中的配置問題

    • 建議web應(yīng)用,在啟動(dòng)的時(shí)候就把所有的Bean都加載了。(把費(fèi)時(shí)的操作放到系統(tǒng)啟動(dòng)中完成)

3 、重要的實(shí)現(xiàn)類

image

ApplicationContext的實(shí)現(xiàn)類如上圖所示,重要的實(shí)現(xiàn)類有兩個(gè):ClassPathXmlApplicationContext與FileSystemXmlApplicationContext

  • ClassPathXmlApplicationContext:從classpath(resources目錄等同于classpath)中讀取xml文件加載已經(jīng)被定義的bean。(注意,使用這個(gè)類的對(duì)象讀取xml文件后需要手動(dòng)調(diào)用close()(繼承自抽象類AbstractApplicationContext)方法,否則會(huì)引發(fā)警告)
  • FileSystemXmlApplicationContext:從系統(tǒng)文件中讀取xml文件加載已經(jīng)被定義的bean。

二、Spring AOP

AOP(Aspect Oriented Programming)稱為面向切面編程,在程序開發(fā)中主要用來解決一些系統(tǒng)層面上的問題,比如日志,事務(wù),權(quán)限等待,Struts2的攔截器設(shè)計(jì)就是基于AOP的思想,是個(gè)比較經(jīng)典的例子。

在不改變?cè)械倪壿嫷幕A(chǔ)上,增加一些額外的功能。代理也是這個(gè)功能,讀寫分離也能用aop來做。與AOP有關(guān)的兩個(gè)概念是OOP(面向?qū)ο缶幊?與POP(面向過程編程)。AOP的核心技術(shù)是代理。在介紹AOP之前有必要了解一下Java的動(dòng)態(tài)代理。

1、動(dòng)態(tài)代理

在程序運(yùn)行過程中產(chǎn)生的這個(gè)對(duì)象,而程序運(yùn)行過程中產(chǎn)生對(duì)象其實(shí)就是我們剛才反射講解的內(nèi)容,所以,動(dòng)態(tài)代理其實(shí)就是通過反射來生成一個(gè)代理對(duì)象,動(dòng)態(tài)代理可以讓我們?cè)诓恍薷脑创a的情況下增加新的功能。Java的動(dòng)態(tài)代理有兩種方式

1.1 使用java.lang.reflect包

這種方式要求被代理的類必須實(shí)現(xiàn)某個(gè)接口,實(shí)現(xiàn)原理是利用對(duì)象的類的字節(jié)碼接口,寫出一個(gè)新的類到本地區(qū),通過編譯器直接編譯成.class文件,再通過類加載器加載進(jìn)來。最重要的一步是:

MyInterface p =  (MyInterface) Proxy.newProxyInstance(Student.class.getClassLoader(), Student.class.getInterfaces(), new InvocationHandler() {

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable       {
            System.out.println("你好");
            method.invoke(student);
            return null;
        }
    });

其中,Student類實(shí)現(xiàn)了MyInterface這個(gè)接口,student是被代理的對(duì)象。

1.2 使用cglib

這是非Java原生的動(dòng)態(tài)代理,效率更高,限制更小,而且不需要被代理的類實(shí)現(xiàn)接口。

public static void main(String[] args) {
    //導(dǎo)入包   cglib-core  asm     ant     ant-launcher
    //創(chuàng)建運(yùn)行器
    MethodInterceptor mi = new MethodInterceptor() {

        @Override
        public Object intercept(Object arg0, Method arg1, Object[] arg2, MethodProxy arg3) throws Throwable {
            System.out.println("運(yùn)行前");
            arg3.invokeSuper(arg0, arg2);
            System.out.println("運(yùn)行后");
            return null;
        }
    };
    //獲取代理類
    Enhancer enhancer = new Enhancer();
    //設(shè)置父類
    enhancer.setSuperclass(Demo.class);
    //運(yùn)行任務(wù)
    enhancer.setCallback(mi);
    //創(chuàng)建代理對(duì)象
    Demo d = (Demo)enhancer.create();
    d.method();
}

cglib實(shí)現(xiàn)動(dòng)態(tài)代理的原理是代理類去繼承目標(biāo)類,然后重寫其中目標(biāo)類的方法,這樣每次調(diào)用代理類的方法都會(huì)被方法攔截器攔截,在攔截器中才調(diào)用目標(biāo)類的該方法。

2、第一種實(shí)現(xiàn)方式

第一種實(shí)現(xiàn)方式就是使用Java原生的動(dòng)態(tài)代理,需要四個(gè)類一個(gè)接口:

Person接口,這個(gè)接口是目標(biāo)類的實(shí)現(xiàn)接口

package com.qianfeng.aop01;

public interface Person {
    void eat();
    void drink();
}

Student類,這個(gè)類是目標(biāo)類

package com.qianfeng.aop01;

public class Student implements Person {
    @Override
    public void eat() {
        System.out.println("I can eat");
    }

    @Override
    public void drink() {
        System.out.println("I can run");
    }
}

MyAspect類,我們想要進(jìn)行切面的類,可以擴(kuò)展我們想要增加的功能

package com.qianfeng.aop01;

public class MyAspect {
    public void before(){
        System.out.println("---------before----------");
    }
    public void after(){
        System.out.println("---------after----------");
    }
}

AOP01Test類,測(cè)試類

package com.qainfeng.aop01;

import com.qianfeng.aop01.Person;
import com.qianfeng.aop01.PersonFactory;
import org.junit.Test;

public class AOP01Test {
    @Test
    public void testStudent(){
        Person p = PersonFactory.getPerson();
        p.eat();
        p.drink();
    }
}

PersonFactory類,工廠類,用于生成Person對(duì)象

package com.qianfeng.aop01;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class PersonFactory {
    public static Person getPerson(){
        //創(chuàng)建被代理對(duì)象(目標(biāo)對(duì)象)
        Person p = new Student();
        //創(chuàng)建自定義切面類對(duì)象
        MyAspect ma = new MyAspect();

        //創(chuàng)建代理對(duì)象
        Person p1 = (Person) Proxy.newProxyInstance(PersonFactory.class.getClassLoader(), p.getClass().getInterfaces(), new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                ma.before();
                Object obj = method.invoke(p,args);
                ma.after();
                return obj;
            }
        });
        //返回代理對(duì)象
        return p1;
    }
}

需要重點(diǎn)說明的是PersonFactory類中的Proxy.newProxyInstance()方法,這個(gè)方法用來實(shí)現(xiàn)對(duì)目標(biāo)對(duì)象的代理,這個(gè)方法需要三個(gè)參數(shù),第一個(gè)參數(shù)是目標(biāo)類的加載器、第二個(gè)參數(shù)是目標(biāo)類的所有接口,第三個(gè)參數(shù)是一個(gè)實(shí)現(xiàn)了InvocationHandler接口的類的對(duì)象,首先,我們查看InvocationHandler的注釋:

/**
 * {@code InvocationHandler} is the interface implemented by
 * the <i>invocation handler</i> of a proxy instance.
 *
 * <p>Each proxy instance has an associated invocation handler.
 * When a method is invoked on a proxy instance, the method
 * invocation is encoded and dispatched to the {@code invoke}
 * method of its invocation handler.
 */

InvocationHanler是被一個(gè)代理實(shí)例的調(diào)用處理程序?qū)崿F(xiàn)的接口,每一個(gè)代理實(shí)例都有一個(gè)關(guān)聯(lián)的調(diào)用處理程序,當(dāng)一個(gè)代理實(shí)例的方法被調(diào)用的時(shí)候,方法調(diào)用被編碼分派到它的調(diào)用處理程序的invoke方法。通過閱讀注釋我們知道每次調(diào)用代理實(shí)例的方法時(shí)都會(huì)調(diào)用invoke方法。再來看一下invoke方法的注釋:

/**
     * Processes a method invocation on a proxy instance and returns
     * the result.  This method will be invoked on an invocation handler
     * when a method is invoked on a proxy instance that it is
     * associated with.
     *
     * @param   proxy the proxy instance that the method was invoked on
     *
     * @param   method the {@code Method} instance corresponding to
     * the interface method invoked on the proxy instance.  The declaring
     * class of the {@code Method} object will be the interface that
     * the method was declared in, which may be a superinterface of the
     * proxy interface that the proxy class inherits the method through.
     *
     * @param   args an array of objects containing the values of the
     * arguments passed in the method invocation on the proxy instance,
     * or {@code null} if interface method takes no arguments.
     * Arguments of primitive types are wrapped in instances of the
     * appropriate primitive wrapper class, such as
     * {@code java.lang.Integer} or {@code java.lang.Boolean}.
     */

這個(gè)方法處理一個(gè)代理對(duì)象的方法調(diào)用然后返回結(jié)果。參數(shù)proxy是方法被調(diào)用的代理實(shí)例。參數(shù)method是與接口實(shí)例中被調(diào)用的接口方法一直的方法實(shí)例,參數(shù)args是傳過來的參數(shù),是一個(gè)對(duì)象數(shù)組。最后再來看一下Proxy類中的newProxyInstance方法:

/**
     * Returns an instance of a proxy class for the specified interfaces
     * that dispatches method invocations to the specified invocation
     * handler.
     *
     * <p>{@code Proxy.newProxyInstance} throws
     * {@code IllegalArgumentException} for the same reasons that
     * {@code Proxy.getProxyClass} does.
     *
     * @param   loader the class loader to define the proxy class
     * @param   interfaces the list of interfaces for the proxy class
     *          to implement
     * @param   h the invocation handler to dispatch method invocations to
     * @return  a proxy instance with the specified invocation handler of a
     *          proxy class that is defined by the specified class loader
     *          and that implements the specified interfaces
     * @throws  IllegalArgumentException if any of the restrictions on the
     *          parameters that may be passed to {@code getProxyClass}
     *          are violated
     */

這個(gè)方法返回指定接口的代理類的實(shí)例。參數(shù)loader是定義代理類的類加載器,參數(shù)interfaces是代理類將要實(shí)現(xiàn)的接口集合。參數(shù)h是一個(gè)調(diào)用處理程序。

3、第二種實(shí)現(xiàn)方式

第二種實(shí)現(xiàn)方式使用了cglib,由于不需要實(shí)現(xiàn)接口,這里只需要四個(gè)類:Student、AOP02Test、MyAspect、StudentFactory,其中前三個(gè)類與第一種實(shí)現(xiàn)方式基本一致,只不過不需要實(shí)現(xiàn)接口,這里詳細(xì)解釋一下第四個(gè)類

package com.qianfeng.aop02;

import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;

public class StudentFactory {
    public static Student getStudent(){
        Enhancer en = new Enhancer();
        //設(shè)置父類
        en.setSuperclass(Student.class);
        Student student = new Student();
        MyAspect ma = new MyAspect();
        //設(shè)置回調(diào)方法
        en.setCallback(new MethodInterceptor() {
            @Override
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                ma.before();
                Object obj = method.invoke(student,objects);
                ma.after();
                return obj;
            }
        });
        //將動(dòng)態(tài)生成的代理類的對(duì)象強(qiáng)轉(zhuǎn)為目標(biāo)類的對(duì)象
        Student student1 = (Student) en.create();
        return student1;
    }
}

Enhancer類是一個(gè)字節(jié)碼增強(qiáng)器,用來動(dòng)態(tài)生成代理類的對(duì)象,這里看一下注釋:

/**
 * Generates dynamic subclasses to enable method interception. This
 * class started as a substitute for the standard Dynamic Proxy support
 * included with JDK 1.3, but one that allowed the proxies to extend a
 * concrete base class, in addition to implementing interfaces. The dynamically
 * generated subclasses override the non-final methods of the superclass and
 * have hooks which callback to user-defined interceptor
 * implementations.
 */

生成動(dòng)態(tài)的子類用來確保方法攔截,這個(gè)類作為一種標(biāo)準(zhǔn)動(dòng)態(tài)代理支持的替代,但是允許代理去擴(kuò)展一個(gè)具體的基類,并且實(shí)現(xiàn)接口。動(dòng)態(tài)生成的子類重寫了父類的非final方法。

MethodInterceptor是一個(gè)接口,實(shí)現(xiàn)MethodInterceptor 接口,在調(diào)用目標(biāo)對(duì)象的方法時(shí),就可以實(shí)現(xiàn)在調(diào)用方法之前、調(diào)用方法過程中、調(diào)用方法之后對(duì)其進(jìn)行控制。intercept方法和第一種實(shí)現(xiàn)方式中的invoke方法有點(diǎn)類似,不同的是多了一個(gè)參數(shù)MethodProxy,這個(gè)參數(shù)是對(duì)當(dāng)前被調(diào)用方法的代理。

4、第三種實(shí)現(xiàn)方式

第三種實(shí)現(xiàn)方式基于xml文件,使用了昨天學(xué)過的IoC,需要四個(gè)文件:Student.java、MyAspect.java、Person.java(這個(gè)是接口)、AOC03Test.java與beans.xml,最重要的是beans.xml和MyAspect類,這個(gè)類需要實(shí)現(xiàn)一個(gè)接口:

package com.qianfeng.aop03;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

public class MyAspect implements MethodInterceptor {
    public void before(){
        System.out.println("---------before----------");
    }
    public void after(){
        System.out.println("---------after----------");
    }
    //重寫invoke方法,注意實(shí)現(xiàn)類
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        before();
        Object obj = invocation.proceed();
        after();
        return obj;
    }
}

AOC03Test.java

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class AOP03Test {
    @Test
    public void testStudent(){
        ApplicationContext ac = new ClassPathXmlApplicationContext("beans2.xml");
        Student student = ac.getBean("proxy",Student.class);
        student.eat();
        student.drink();
    }
}

beans.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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="st" class="com.qianfeng.aop03.Student" />
    <bean id="my" class="com.qianfeng.aop03.MyAspect" />
    <bean id="proxy" class="org.springframework.aop.framework.ProxyFactoryBean">
        <property name="interfaces" value="com.qianfeng.aop03.Person" />
        <property name="target" ref="st" />
        <property name="interceptorNames" value="my" />
        <property name="optimize" value="true" />
    </bean>
</beans>

ProxyFactoryBean代理的FactoryBean對(duì)象,我們現(xiàn)在要代理的是st包含四個(gè)屬性注入:

  1. interfaces: 接口對(duì)象們
    <list>
    <value>com.qfedu.aop03.IUserService</value>
    <value>com.qfedu.aop03.IUserService</value>
    <value>com.qfedu.aop03.IUserService</value>
    </list>
  2. target:目標(biāo)對(duì)象,哪個(gè)對(duì)象將被以代理的方式創(chuàng)建
  3. interceptorNames:攔截對(duì)象的名稱,自定義的MethodInterceptor對(duì)象,注意它的包結(jié)構(gòu)組成,和第二種方法中的不一樣。注意使用的是value因?yàn)檫@里要的是名稱而不是對(duì)象,所以不使用ref。
  4. optimize:boolean類型的值:
    true:強(qiáng)制使用cglib的動(dòng)態(tài)代理方式
    false:使用jdk自帶的動(dòng)態(tài)代理

cglib:code generation library,代碼生成庫(kù),性能更高

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容