ApplicationContext与Spring AOP

一、ApplicationContext

ApplicationContext被称为Spring上下文,Application Context 是 Spring 中较高级的容器。和 BeanFactory 类似,它可以加载配置文件中定义的 bean,将所有的 bean 集中在一起,当有请求的时候分配 bean。 另外,它增加了企业所需要的功能,比如,从属性文件中解析文本信息和将事件传递给所指定的监听器。

1、BeanFactory容器

这是一个最简单的容器,它主要的功能是为依赖注入 (DI) 提供支持,这个容器接口在 org.springframework.beans.factory.BeanFactor 中被定义。BeanFactory 和相关的接口,比如BeanFactoryAware、DisposableBean、InitializingBean,仍旧保留在 Spring 中,主要目的是向后兼容已经存在的和那些 Spring 整合在一起的第三方框架。在资源宝贵的移动设备或者基于 applet 的应用当中, BeanFactory 会被优先选择。否则,一般使用的是 ApplicationContext。

这个接口是Spring中最底层的接口,提供了最简单的容器的功能,只提供了实例化对象和取对象的功能。

2、ApplicationContext与BeanFactory的关系

2.1 联系

在IDEA中可以查看两者的继承关系

image.png

从上图可以看出,AllicationContext这个接口继承了ListableBeanFactory与HierarchicalBeanFactory这两个接口,而这两个接口都继承了BeanFactory这个接口。所以它们都可以用来配置XML属性,也支持属性的自动注入。

2.2 区别

1)功能

BeanFactory是Spring中最底层的容器,提供的功能较为简单,ApplicationContext继承了两个接口,相对而言可以提供更多功能:

  • 国际化(i18n)(MessageSource)

  • 访问资源,如URL和文件(ResourceLoader)

  • 载入多个(有继承关系)上下文 ,使得每一个上下文都专注于一个特定的层次,比如应用的web层

  • 消息发送、响应机制(ApplicationEventPublisher)

  • AOP(拦截器)

2)装载Bean

BeanFactory在启动的时候不会去实例化Bean,中有从容器中拿Bean的时候才会去实例化;ApplicationContext在启动的时候就把所有的Bean全部实例化了。它还可以为Bean配置lazy-init=true来让Bean延迟实例化;

  • 延迟实例化的优点:(BeanFactory

应用启动的时候占用资源很少;对资源要求较高的应用,比较有优势;

  • 不延迟实例化的优点: (ApplicationContext

    • 所有的Bean在启动的时候都加载,系统运行的速度快;
  • 在启动的时候所有的Bean都加载了,我们就能在系统启动的时候,尽早的发现系统中的配置问题

    • 建议web应用,在启动的时候就把所有的Bean都加载了。(把费时的操作放到系统启动中完成)

3 、重要的实现类

image.png

ApplicationContext的实现类如上图所示,重要的实现类有两个:ClassPathXmlApplicationContext与FileSystemXmlApplicationContext

  • ClassPathXmlApplicationContext:从classpath(resources目录等同于classpath)中读取xml文件加载已经被定义的bean。(注意,使用这个类的对象读取xml文件后需要手动调用close()(继承自抽象类AbstractApplicationContext)方法,否则会引发警告)
  • FileSystemXmlApplicationContext:从系统文件中读取xml文件加载已经被定义的bean。

二、Spring AOP

AOP(Aspect Oriented Programming)称为面向切面编程,在程序开发中主要用来解决一些系统层面上的问题,比如日志,事务,权限等待,Struts2的拦截器设计就是基于AOP的思想,是个比较经典的例子。

在不改变原有的逻辑的基础上,增加一些额外的功能。代理也是这个功能,读写分离也能用aop来做。与AOP有关的两个概念是OOP(面向对象编程)与POP(面向过程编程)。AOP的核心技术是代理。在介绍AOP之前有必要了解一下Java的动态代理。

1、动态代理

在程序运行过程中产生的这个对象,而程序运行过程中产生对象其实就是我们刚才反射讲解的内容,所以,动态代理其实就是通过反射来生成一个代理对象,动态代理可以让我们在不修改源码的情况下增加新的功能。Java的动态代理有两种方式

1.1 使用java.lang.reflect包

这种方式要求被代理的类必须实现某个接口,实现原理是利用对象的类的字节码接口,写出一个新的类到本地区,通过编译器直接编译成.class文件,再通过类加载器加载进来。最重要的一步是:

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类实现了MyInterface这个接口,student是被代理的对象。

1.2 使用cglib

这是非Java原生的动态代理,效率更高,限制更小,而且不需要被代理的类实现接口。

public static void main(String[] args) {
    //导入包   cglib-core  asm     ant     ant-launcher
    //创建运行器
    MethodInterceptor mi = new MethodInterceptor() {
        
        @Override
        public Object intercept(Object arg0, Method arg1, Object[] arg2, MethodProxy arg3) throws Throwable {
            System.out.println("运行前");
            arg3.invokeSuper(arg0, arg2);
            System.out.println("运行后");
            return null;
        }
    };
    //获取代理类
    Enhancer enhancer = new Enhancer();
    //设置父类
    enhancer.setSuperclass(Demo.class);
    //运行任务
    enhancer.setCallback(mi);
    //创建代理对象
    Demo d = (Demo)enhancer.create();
    d.method();
}

cglib实现动态代理的原理是代理类去继承目标类,然后重写其中目标类的方法,这样每次调用代理类的方法都会被方法拦截器拦截,在拦截器中才调用目标类的该方法。

2、第一种实现方式

第一种实现方式就是使用Java原生的动态代理,需要四个类一个接口:

Person接口,这个接口是目标类的实现接口

package com.qianfeng.aop01;

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

Student类,这个类是目标类

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类,我们想要进行切面的类,可以扩展我们想要增加的功能

package com.qianfeng.aop01;

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

AOP01Test类,测试类

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对象

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(){
        //创建被代理对象(目标对象)
        Person p = new Student();
        //创建自定义切面类对象
        MyAspect ma = new MyAspect();

        //创建代理对象
        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;
            }
        });
        //返回代理对象
        return p1;
    }
}

需要重点说明的是PersonFactory类中的Proxy.newProxyInstance()方法,这个方法用来实现对目标对象的代理,这个方法需要三个参数,第一个参数是目标类的加载器、第二个参数是目标类的所有接口,第三个参数是一个实现了InvocationHandler接口的类的对象,首先,我们查看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是被一个代理实例的调用处理程序实现的接口,每一个代理实例都有一个关联的调用处理程序,当一个代理实例的方法被调用的时候,方法调用被编码分派到它的调用处理程序的invoke方法。通过阅读注释我们知道每次调用代理实例的方法时都会调用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}.
     */

这个方法处理一个代理对象的方法调用然后返回结果。参数proxy是方法被调用的代理实例。参数method是与接口实例中被调用的接口方法一直的方法实例,参数args是传过来的参数,是一个对象数组。最后再来看一下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
     */

这个方法返回指定接口的代理类的实例。参数loader是定义代理类的类加载器,参数interfaces是代理类将要实现的接口集合。参数h是一个调用处理程序。

3、第二种实现方式

第二种实现方式使用了cglib,由于不需要实现接口,这里只需要四个类:Student、AOP02Test、MyAspect、StudentFactory,其中前三个类与第一种实现方式基本一致,只不过不需要实现接口,这里详细解释一下第四个类

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();
        //设置父类
        en.setSuperclass(Student.class);
        Student student = new Student();
        MyAspect ma = new MyAspect();
        //设置回调方法
        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;
            }
        });
        //将动态生成的代理类的对象强转为目标类的对象
        Student student1 = (Student) en.create();
        return student1;
    }
}

Enhancer类是一个字节码增强器,用来动态生成代理类的对象,这里看一下注释:

/**
 * 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.
 */

生成动态的子类用来确保方法拦截,这个类作为一种标准动态代理支持的替代,但是允许代理去扩展一个具体的基类,并且实现接口。动态生成的子类重写了父类的非final方法。

MethodInterceptor是一个接口,实现MethodInterceptor 接口,在调用目标对象的方法时,就可以实现在调用方法之前、调用方法过程中、调用方法之后对其进行控制。intercept方法和第一种实现方式中的invoke方法有点类似,不同的是多了一个参数MethodProxy,这个参数是对当前被调用方法的代理。

4、第三种实现方式

第三种实现方式基于xml文件,使用了昨天学过的IoC,需要四个文件:Student.java、MyAspect.java、Person.java(这个是接口)、AOC03Test.java与beans.xml,最重要的是beans.xml和MyAspect类,这个类需要实现一个接口:

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方法,注意实现类
    @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对象,我们现在要代理的是st包含四个属性注入:

  1. interfaces: 接口对象们
    <list>
    <value>com.qfedu.aop03.IUserService</value>
    <value>com.qfedu.aop03.IUserService</value>
    <value>com.qfedu.aop03.IUserService</value>
    </list>
  2. target:目标对象,哪个对象将被以代理的方式创建
  3. interceptorNames:拦截对象的名称,自定义的MethodInterceptor对象,注意它的包结构组成,和第二种方法中的不一样。注意使用的是value因为这里要的是名称而不是对象,所以不使用ref。
  4. optimize:boolean类型的值:
    true:强制使用cglib的动态代理方式
    false:使用jdk自带的动态代理

cglib:code generation library,代码生成库,性能更高

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343