Spring 代理浅析

Spring提供了结合了java自带的代理方式和Cglib的代理方式,提供了多种构造代理的入口。

1、ProxyFactoryBean

ProxyFactoryBean,可理解为产生代理的FactoryBean。具体使用方法:
声明一个Animal接口:
package org.antstudio;

public interface Animal {

    public String say();

    public String name();

}

声明一个Cat实现Animal接口:
package org.antstudio;

import org.springframework.stereotype.Component;

@Component
public class Cat implements Animal{

    @Override
    public String say() {
        System.out.println("miao.");
        return "Miao";
    }

    @Override
    public String name() {
        return "Cat";
    }
}

在声明一个Dog,具有Animal的方法,但是不实现于Animal,用于测试没有接口的代理实现:
package org.antstudio;

import org.springframework.stereotype.Component;

/**
 * 这里不实现Animal接口,以测试无接口时的代理
 */
@Component
public class Dog {

    public String say(){
        System.out.println("Wang.");
        return "Wang";
    }

    public String name(){
        return "Dog";
    }
}

创建一个通知,用于代理拦截目标对象方法时执行:
package org.antstudio.org.antstudio.proxy;

import org.springframework.aop.MethodBeforeAdvice;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

@Component
public class AnimalBeforeAdvice implements MethodBeforeAdvice {


    @Override
    public void before(Method method, Object[] objects, Object o) throws Throwable {
        System.out.println("Before execute "+ method.getName());
    }
}


配置代理生成:
    <bean id="personProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
        <property name="interfaces">
            <list>
                <value>org.antstudio.Animal</value>
            </list>
        </property>
        <property name="interceptorNames">
            <list>
                <value>animalBeforeAdvice</value>
            </list>
        </property>
        <property name="target">
            <ref bean="cat"></ref>
        </property>
    </bean>

对Dog类的代理配置:
<bean id="dogProxy" class="org.springframework.aop.framework.ProxyFactoryBean">

        <property name="interceptorNames">
            <list>
                <value>animalBeforeAdvice</value>
            </list>
        </property>
        <property name="target">
            <ref bean="dog"></ref>
        </property>
    </bean>

创建测试类:
package org.antstudio;

import junit.framework.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import javax.annotation.Resource;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:spring-config.xml")
public class ProxyFactoryBeanTests {

    @Autowired
    private BeanFactory beanFactory;

    @Test
    public void testCatProxyFactory() {
        Animal p = (Animal)beanFactory.getBean("catProxy") ;
        System.out.println(p.getClass());//class com.sun.proxy.$Proxy11
        Assert.assertEquals(p.say(), "Miao");
    }

    @Test
    public void testDogProxyFactory() {
        Dog d = (Dog)beanFactory.getBean("dogProxy") ;
        System.out.println(d.getClass());//class org.antstudio.Dog$$EnhancerByCGLIB$$d23a9052
        Assert.assertEquals(d.say(), "Wang");
    }
}

可以看到,当被代理的目标对象有实现了接口时,Spring采用java自带的动态代理,而没有接口实现时,则采用Cglib进行代理生成。 使用 ProxyFactoryBean进行代理生成,需要显示的指定代理对象,并显示的去获取改代理。对于需要多个或大量的代理时,该方法就会增加非常的多配置或代码,比较麻烦。

2.BeanNameAutoProxyCreator

BeanNameAutoProxyCreator可以通过bean的名字自动进行代理,名字可以采用表达式匹配,可用于批量的代理生成。
 <bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
        <property name="beanNames">
            <list>
                <value>cat*</value>
                <value>dog*</value>
            </list>
        </property>
        <property name="interceptorNames">
            <list>
                <value>animalBeforeAdvice</value>
            </list>
        </property>
    </bean>

这里我们采用cat*和dog*匹配cat开头和dog开头的beanname,在本示例中则匹配Cat和Dog两个类的bean示例。
创建测试类:
package org.antstudio;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import javax.annotation.Resource;

/**
 * @author Gavin 
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:spring-config.xml")
public class BeanNameAutoProxyCreatoryTests {

    @Resource
    private BeanFactory beanFactory;

    @Resource
    private Dog dog;

    @Test
    public void testCat(){
        Animal cat = (Animal) beanFactory.getBean("cat");
        cat.say();
        cat.name();
    }

    @Test
    public void testDog(){
        dog.say();
    }

}
注意:上例中,如果对于Cat采用Dog的注入方式,即:
@Resource
private Cat cat;

会抛出类型转换错误,因为对于Cat(实现了Animal接口),Spring采用java自带的动态代理生成,因此生成的代理类型为接口Animal类型,而非具体实现类Cat。而Dog未实现接口,采用Cglib。Cglib使用继承Dog的形式创建Dog的代理。由此可知,如果我们将Dog声明为final(不可继承),那么将无法完成代理的生成。

3. DefaultAdvisorAutoProxyCreator

BeanNameAutoProxyCreator可以批量的通过bean的名字进行代理生成,其控制粒度为bean实例。然而有的时候只需要需要对bean中的部分方法进行代理,将控制粒度细化到方法级别。DefaultAdvisorAutoProxyCreator提供了基于通知的代理方式,在通知中可以控制相应的切入点,可精准到方法级别。
 <bean class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
        <property name="patterns">
            <list>
                <value>.*say</value>
            </list>
        </property>
        <property name="advice">
            <ref bean="animalAfterAdvice"></ref>
        </property>
    </bean>

这里我们只对say方法进行代理,而不对其他方法,如name方法进行代理。此时调用相应的代理类时,则只会对切入点say方法进行代理。测试类,可参考 BeanNameAutoProxyCreator的测试类。

Spring提供了丰富的通知方式,前置后置环绕等等,也有很多的切入点方式。不是本文讨论的重点,就不一一列出。
源码:https://github.com/gavincook/springProxy

你可能感兴趣的:(spring,代理,proxy)