6.2.4 代理接口
当目标Bean的实现类实现了接口后,Spring AOP可以为其创建JDK动态代理,而无须使用CGLIB创建的代理,这种代理称为代理接口。
创建AOP代理必须指定两个属性:目标Bean和处理。实际上,很多AOP框架都以拦截器作为处理。因为Spring AOP与IoC容器的良好整合,因此配置代理Bean时,完全可以利用依赖注入来管理目标Bean和拦截器Bean。
下面的示例演示了基于AOP的权限认证,它是简单的TestService接口,该接口模拟Service组件,该组件内包含两个方法:
   ● 查看数据。
   ● 修改数据。
接口的源代码如下:
//Service组件接口
public interface TestService
{
    //查看数据
    void view();
    //修改数据
    void modify();
}
该接口的实现类实现两个方法。因为篇幅限制,本示例并未显示出完整的查看数据和修改数据的持久层操作,仅仅在控制台打印两行信息。实际的项目实现中,两个方法的实现则改成对持久层组件的调用,这不会影响示例程序的效果。实现类的源代码如下:
TestService接口的实现类
public class TestServiceImpl implements TestService
{
    //实现接口必须实现的方法
    public void view()
    {
        System.out.println("用户查看数据");
    }
    //实现接口必须实现的方法
    public void modify()
    {
        System.out.println("用户修改数据");
    }
}
示例程序采用Around 处理作为拦截器,拦截器中使用依赖注入获得当前用户名。实际Web应用中,用户应该从session中读取。这不会影响示例代码的效果。拦截器源代码如下:
public class AuthorityInterceptor implements MethodInterceptor
{
    //当前用户名
    private String user;
    //依赖注入所必需的setter方法
    public void setUser(String user)
    {
        this.user = user;
    }
    public Object invoke(MethodInvocation invocation) throws Throwable
    {
        //获取当前拦截的方法名
        String methodName = invocation.getMethod().getName();
        //下面执行权限检查
        //对既不是管理员,也不是注册用户的情况
        if (!user.equals("admin") && !user.equals("registedUser"))
        {
            System.out.println("您无权执行该方法");
            return null;
        }
        //对仅仅是注册用户,调用修改数据的情况
        else if (user.equals("registedUser") && methodName.equals 
        ("modify"))
        {
            System.out.println("您不是管理员,无法修改数据");
            return null;
        }
        //对管理员或注册用户,查看数据的情况
        else
        {
            return invocation.proceed();
        }
    }
}
TestAction类依赖TestService。因篇幅关系,此处不给出TestAction的接口的源代码,TestActionImpl的源代码如下:
public class TestActionImpl
{
    //将TestService作为成员变量,面向接口编程
    private TestService ts;
    //依赖注入的setter方法
    public void setTs(TestService ts)
    {
        this.ts = ts;
    }
    //修改数据
    public void modify()
    {
        ts.modify();
    }
    //查看数据
    public void view()
    {
        ts.view();
    }
}
配置文件如下:
<?xml version="1.0" encoding="GBK"?>
<!-- 指定Spring配置文件的根元素,以及Spring配置文件的Schema信息 -->
<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 -->
    <bean id="serviceTarget" class="lee.TestServiceImpl"/>
    <!-- 配置拦截器,拦截器作为处理使用 -->
    <bean id="authorityInterceptor" class="lee.AuthorityInterceptor">
        <property name="user" value="admin"/>
    </bean>
    <!-- 配置代理工厂Bean,负责生成AOP代理 -->
    <bean id="service" class="org.springframework.aop.framework. 
    ProxyFactoryBean">
        <!-- 指定AOP代理所实现的接口 -->
        <property name="proxyInterfaces" value="lee.TestService"/>
        <!-- 指定AOP代理所代理的目标Bean -->
        <property name="target" ref="serviceTarget"/>
        <!-- AOP代理所需要的拦截器列表 -->
        <property name="interceptorNames">
            <list>
                <value>authorityInterceptor</value>
            </list>
        </property>
    </bean>
    <!-- 配置Action Bean,该Action依赖TestService Bean -->
    <bean id="testAction" class="lee.TestActionImpl">
        <!-- 此处注入的是依赖代理Bean -->
        <property name="ts" ref="service"/>
    </bean>
</beans>
主程序请求testAction Bean,然后调用该Bean的两个方法,主程序如下:
public class BeanTest
{
    public static void main(String[] args)throws Exception
    {
        //创建Spring容器实例
        ApplicationContext ctx = new FileSystemXmlApplicationContext 
        ("bean.xml");
        //获得TestAction bean
        TestAction ta = (TestAction)ctx.getBean("testAction");
        //调用bean的两个测试方法
        ta.view();
        ta.modify();
    }
}
程序执行结果如下:
[java] 用户查看数据
[java] 用户修改数据
代理似乎没有发挥任何作用。因为配置文件中的当前用户是admin,admin用户具备访问和修改数据的权限,因此代理并未阻止访问。将配置文件中的admin修改成registed- User,再次执行程序,得到如下结果:
[java] 用户查看数据
[java] 您不是管理员,无法修改数据
代理阻止了registedUser修改数据,查看数据可以执行。将registedUser修改成其他用户,执行程序,看到如下结果:
[java] 您无权执行该方法
[java] 您无权执行该方法
代理阻止用户对两个方法的执行。基于AOP的权限检查,可以降低程序的代码量,因为无须每次调用方法之前,手动编写权限检查代码;同时,权限检查与业务逻辑分离,提高了程序的解耦。
示例中的目标Bean被暴露在容器中,可以被客户端代码直接访问。为了避免客户端代码直接访问目标Bean,可以将目标Bean定义成代理工厂的嵌套Bean,修改后的配置文件如下:
<?xml version="1.0" encoding="GBK"?>
<!-- 指定Spring配置文件的根元素,以及Spring配置文件的Schema信息 -->
<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="authorityInterceptor" class="lee.AuthorityInterceptor">
        <property name="user" value="admin"/>
    </bean>
    <!-- 配置代理工厂Bean,该工厂Bean将负责创建目标Bean的代理 -->
    <bean id="service" class="org.springframework.aop.framework. 
    ProxyFactoryBean">
        <!-- 指定AOP代理所实现的接口 -->
        <property name="proxyInterfaces" value="lee.TestService"/>
        <property name="target">
            <!-- 以嵌套Bean的形式定义目标Bean,避免客户端直接访问目标Bean -->
            <bean class="lee.TestServiceImpl"/>
        </property>
        <!-- AOP代理所需要的拦截器列表 -->
        <property name="interceptorNames">
            <list>
                <value>authorityInterceptor</value>
            </list>
        </property>
    </bean>
    <!-- 配置Action Bean,该Action依赖TestService Bean -->
    <bean id="testAction" class="lee.TestActionImpl">
        <!-- 此处注入的是依赖代理Bean -->
        <property name="ts" ref="service"/>
    </bean>
</beans>
由上面介绍的内容可见,Spring的AOP是对JDK动态代理模式的深化。通过Spring AOP组件,允许通过配置文件管理目标Bean和AOP所需的处理。
下面将继续介绍如何为没有实现接口的目标Bean创建CGLIB代理。
6.2.5 代理类
如果目标类没有实现接口,则无法创建JDK动态代理,只能创建CGLIB代理。如果需要没有实现接口的Bean实例生成代理,配置文件中应该修改如下两项:
   ● 去掉<property name="proxyInterfaces"/>声明。因为不再代理接口,因此,此处的配置没有意义。
   ● 增加<property name="proxyTargetClass">子元素,并设其值为true,通过该元素强制使用CGLIB代理,而不是JDK动态代理。
注意:最好面向接口编程,不要面向类编程。同时,即使在实现接口的情况下,也可强制使用CGLIB代理。
CGLIB代理在运行期间产生目标对象的子类,该子类通过装饰器设计模式加入到Advice中。因为CGLIB代理是目标对象的子类,则必须考虑保证如下两点:
   ● 目标类不能声明成final,因为final类不能被继承,无法生成代理。
   ● 目标方法也不能声明成final,final方法不能被重写,无法得到处理。
当然,为了需要使用CGLIB代理,应用中应添加CGLIB二进制Jar文件。
6.2.6 使用BeanNameAutoProxyCreator自动创建代理
这是一种自动创建事务代理的方式,一旦在容器中配置了BeanNameAutoProxyCreator实例,该实例将会对指定名字的Bean实例自动创建代理。实际上,BeanNameAutoProxyCreator是一个Bean后处理器,理论上它会对容器中所有的Bean进行处理,实际上它只对指定名字的Bean实例创建代理。
BeanNameAutoProxyCreator根据名字自动生成事务代理,名字匹配支持通配符。
与ProxyFactoryBean一样,BeanNameAutoProxyCreator需要一个interceptorNames属性,该属性名虽然是“拦截器”,但并不需要指定拦截器列表,它可以是Advisor或任何处理类型。
下面是使用BeanNameAutoProxyCreator的配置片段:
<!-- 定义事务拦截器bean -->
<bean id="transactionInterceptor"
    class="org.springframework.transaction.interceptor.
    TransactionInterceptor">
    <!-- 事务拦截器bean需要依赖注入一个事务管理器 -->
    <property name="transactionManager" ref="transactionManager"/>
    <property name="transactionAttributes">
        <!-- 下面定义事务传播属性 -->
        <props>
            <prop key="insert*">PROPAGATION_REQUIRED </prop>
            <prop key="find*">PROPAGATION_REQUIRED,readOnly</prop>
            <prop key="*">PROPAGATION_REQUIRED</prop>
        </props>
    </property>
    </bean>
<!-- 定义BeanNameAutoProxyCreator Bean,它是一个Bean后处理器,
        负责为容器中特定的Bean创建AOP代理 -->
<bean class="org.springframework.aop.framework.autoproxy. 
    BeanNameAutoProxyCreator">
    <!-- 指定对满足哪些bean name的bean自动生成业务代理 -->
    <property name="beanNames">
        <list>
            <!-- 下面是所有需要自动创建事务代理的Bean -->
            <value>core-services-applicationControllerSevice</value>
            <value>core-services-deviceService</value>
            <value>core-services-authenticationService</value>
            <value>core-services-packagingMessageHandler</value>
            <value>core-services-sendEmail</value>
            <value>core-services-userService</value>
            <!-- 此处可增加其他需要自动创建事务代理的Bean -->
        </list>
    </property>
    <!-- 下面定义BeanNameAutoProxyCreator所需的拦截器 -->
    <property name="interceptorNames">
        <list>
            <value>transactionInterceptor</value>
            <!-- 此处可增加其他新的Interceptor -->
        </list>
    </property>
</bean>
上面的片段是使用BeanNameAutoProxyCreator自动创建事务代理的片段。Transaction- Interceptor用来定义事务拦截器,定义事务拦截器时传入事务管理器Bean,也指定事务传播属性。
通过BeanNameAutoProxyCreator定义Bean后处理器,定义该Bean后处理器时,通过beanNames属性指定有哪些目标Bean生成事务代理;还需要指定“拦截器链”,该拦截器链可以由任何处理组成。
定义目标Bean还可使用通配符,使用通配符的配置片段如下所示:
<!-- 定义BeanNameAutoProxyCreator Bean,它是一个Bean后处理器,
        负责为容器中特定的Bean创建AOP代理 -->
<bean class="org.springframework.aop.framework.autoproxy. 
BeanNameAutoProxyCreator">
    <!-- 指定对满足哪些bean name的bean自动生成业务代理 -->
    <property name="beanNames">
        <!-- 此处使用通配符确定目标bean -->
        <value>*DAO,*Service,*Manager</value>
    </property>
    <!-- 下面定义BeanNameAutoProxyCreator所需的事务拦截器 -->
    <property name="interceptorNames">
        <list>
            <value>transactionInterceptor</value>
            <!-- 此处可增加其他新的Interceptor -->
        </list>
    </property>
</bean>
上面的配置片段中,所有名字以DAO、Service、Manager结尾的bean,将由该“bean后处理器”为其创建事务代理。目标bean不再存在,取而代之的是目标bean的事务代理。通过这种方式,不仅可以极大地降低配置文件的繁琐,而且可以避免客户端代码直接调用目标bean。
注意:虽然上面的配置片段是为目标对象自动生成事务代理。但这不是唯一的,如果有需要,可以为目标对象生成任何的代理。BeanNameAutoProxyCreator为目标对象生成怎样的代理,取决于传入怎样的处理Bean,如果传入事务拦截器,则生成事务代理Bean;否则将生成其他代理,在后面的实例部分,读者将可以看到大量使用BeanNameAutoProxy- Creator创建的权限检查代理。
6.2.7 使用DefaultAdvisorAutoProxyCreator自动创建代理
Spring还提供了另一个Bean后处理器,它也可为容器中的Bean自动创建代理。相比之下,DefaultAdvisorAutoProxyCreator是更通用、更强大的自动代理生成器。它将自动应用于当前容器中的advisor,不需要在DefaultAdvisorAutoProxyCreator定义中指定目标Bean的名字字符串。
这种定义方式有助于配置的一致性,避免在自动代理创建器中重复配置目标Bean 名。
使用该机制包括:
   ● 配置DefaultAdvisorAutoProxyCreator bean定义。
   ● 配置任何数目的Advisor,必须是Advisor,不仅仅是拦截器或其他处理。因为,必须使用切入点检查处理是否符合候选Bean定义。
DefaultAdvisorAutoProxyCreator计算Advisor包含的切入点,检查处理是否应该被应用到业务对象,这意味着任何数目的Advisor都可自动应用到业务对象。如果Advisor中没有切入点符合业务对象的方法,这个对象就不会被代理。如果增加了新的业务对象,只要它们符合切入点定义,DefaultAdvisorAutoProxyCreator将自动为其生成代理,无须额外   配置。
当有大量的业务对象需要采用相同处理,DefaultAdvisorAutoProxyCreator是非常有用的。一旦定义恰当,直接增加业务对象,而不需要额外的代理配置,系统自动为其增加     代理。
<beans>
    <!-- 定义Hibernate局部事务管理器,可切换到JTA全局事务管理器 -->
    <bean id="transactionManager"
         class="org.springframework.orm.hibernate3. 
        HibernateTransactionManager">
        <!-- 定义事务管理器时,依赖注入SessionFactory -->
         <property name="sessionFactory" ref bean="sessionFactory"/>
    </bean>
    <!-- 定义事务拦截器 -->
    <bean id="transactionInterceptor"
        class="org.springframework.transaction.interceptor. 
        TransactionInterceptor">
         <property name="transactionManager" ref="transactionManager"/>
         <property name="transactionAttributeSource">
            <props>
                <prop key="*">PROPAGATION_REQUIRED</prop>
                  <prop key="find*">PROPAGATION_REQUIRED,readOnly</prop>
            </props>
        </property>
    </bean>
    <!-- 定义DefaultAdvisorAutoProxyCreator Bean,这是一个Bean后处理器 -->
    <bean class="org.springframework.aop.framework.autoproxy. 
    DefaultAdvisorAutoProxyCreator"/>
    <!-- 定义事务Advisor -->
    <bean class="org.springframework.transaction.interceptor. 
    TransactionAttributeSourceAdvisor">
         <property name="transactionInterceptor" ref= 
        "transactionInterceptor"/>
    </bean>
    <!-- 定义额外的Advisor>
    <bean id="customAdvisor" class="lee.MyAdvisor"/>
</beans>
DefaultAdvisorAutoProxyCreator支持过滤和排序。如果需要排序,可让Advisor实现org.springframework.core.Ordered接口来确定顺序。TransactionAttributeSourceAdvisor已经实现Ordered接口,因此可配置其顺序,默认是不排序。
采用这样的方式,一样可以避免客户端代码直接访问目标Bean,而且配置更加简洁。只要目标Bean符合切入点检查,Bean后处理器自动为目标Bean创建代理,无须依次指定目标Bean名。
注意:Spring也支持以编程方式创建AOP代理,但这种方式将AOP代理所需要的目标对象和处理Bean等对象的耦合降低到代码层次,因此不推荐使用。如果读者需要深入了解如何通过编程方式创建AOP代理,请参阅笔者所著的《Spring2.0宝典》。
	posted on 2009-07-19 10:16 
jadmin 阅读(100) 
评论(0)  编辑  收藏