好记性不如烂笔头98-spring3学习(19)-实施spring AOP事务的方法的漏网之鱼

Spring的事务增强管理,主要是基于各种动态代理或者动态字节码技术;
对于基于接口动态代理的AOP事务来说,因为接口都是要求public的,因此这个问题并不突出;
对于基于CGLIb动态字节码技术的方案来说,因为使用final,static,private修饰的方法不能够被子类覆盖,这些方法都无法进行AOP增强。

因此,在需要事务的场景,一定要注意是否有漏网之鱼。这种隐藏的错误是非常难被发现的。

1) 前提和准备
我们需要利用log4j的debug模式来观察,因此我们需要系统能支持log4j运行;
我们需要观察事务,因此我们要访问一个数据库。

create table FFM_USER
(
  USERNAME        VARCHAR2(64),
  PASSWORD        VARCHAR2(64),
  LAST_LOGON_TIME VARCHAR2(64),
  CHARGE          INTEGER
)

里面有一条数据: 在username字段对应“ffm”
2) 业务场景
UserService里面有很多的方法,有的方法能够被AOP增强,有的不行。
能够被增强的会启动一个事务,如果没有启动事务,说明无法被AOP增强

3) 实现简单的查看漏网之鱼的源代码
基础父类

package com.spring.special;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**  
 * 模拟spring事务嵌套调用的父类
 * @author 范芳铭
 */ 
public class BaseService {
    protected static final Logger log = LoggerFactory.getLogger(BaseService.class);
}

模拟被spring的AOP事务增强的漏网之鱼

package com.spring.special;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;


/**  
 * 模拟被spring的AOP事务增强的漏网之鱼
 * private, final,static都不能被AOP事务增强,public和protected可以
 * @author 范芳铭
 */ 
@Service("userService")
public class UserService extends BaseService{
    @Autowired private JdbcTemplate jdbcTemplate;

    private void logon_private(String userName){
        updateLastLogonTime(userName);
        String name = Thread.currentThread().getStackTrace()[1].getMethodName();
        System.out.println("当前运行的方法:" + name);
    }

    public final void logon_final(String userName){
        updateLastLogonTime(userName);
        String name = Thread.currentThread().getStackTrace()[1].getMethodName();
        System.out.println("当前运行的方法:" + name);
    }

    public static void logon_static(UserService user){

        user.updateLastLogonTime("ffm");
        String name = Thread.currentThread().getStackTrace()[1].getMethodName();
        System.out.println("当前运行的方法:" + name);
    }

    public  void logon_public(String userName){
        updateLastLogonTime(userName);
        String name = Thread.currentThread().getStackTrace()[1].getMethodName();
        System.out.println("当前运行的方法:" + name);
    }

    protected void logon_protected(String userName){
        updateLastLogonTime(userName);
        String name = Thread.currentThread().getStackTrace()[1].getMethodName();
        System.out.println("当前运行的方法:" + name);
    }


    public void updateLastLogonTime(String userName){
        String sql = "update ffm_user u set u.last_logon_time = ? where username =? ";
        jdbcTemplate.update(sql, System.currentTimeMillis(),userName);
    }


    //模拟容器运行
    public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("b_special.xml");
        UserService user = (UserService)ctx.getBean("userService");

        user.logon_private("ffm");
        user.logon_final("ffm");

        //静态方法比较特殊,特殊处理下
        logon_static(user);

        user.logon_public("ffm");
        user.logon_protected("ffm");


    }

}

4)配置文件b_special.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"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:p="http://www.springframework.org/schema/p"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:task="http://www.springframework.org/schema/task"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context-3.0.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
        http://www.springframework.org/schema/task
        http://www.springframework.org/schema/task/spring-task.xsd">

   <context:component-scan base-package="com.spring.special"/>    

   <context:property-placeholder location="classpath:dbconfig.properties" />
   <bean id = "dataSource"  class="org.apache.commons.dbcp.BasicDataSource"
        destroy-method = "close"
        p:driverClassName="${jdbc.o2o.driverClassName}"
        p:url="${jdbc.o2o.url}"
        p:username="${jdbc.o2o.username}"
        p:password="${jdbc.o2o.password}"
   />

   <bean id="jdbcTemplate" class ="org.springframework.jdbc.core.JdbcTemplate" 
        p:dataSource-ref="dataSource" />

   <bean id="jdbcManager" 
    class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
    p:dataSource-ref="dataSource"    />

   <tx:advice id="jdbcAdvice" transaction-manager="jdbcManager">
    <tx:attributes>
        <tx:method name = "*" />
    </tx:attributes>    
   </tx:advice> 

   <aop:config proxy-target-class="true">
    <aop:pointcut id="serviceJdbcMethod" expression="within(com.spring.special.BaseService+)" />
    <aop:advisor pointcut-ref="serviceJdbcMethod" advice-ref="jdbcAdvice" order="0" />
   </aop:config>


</beans>

5)观察和结论
开启DEBUG模式,启动“模拟容器运行”应用。查看日志。
利用“Creating new transaction”在一大堆日志中查找,我们能看到
Creating new transaction with name [com.spring.special.UserService.logon_public]:

Creating new transaction with name [com.spring.special.UserService.logon_protected]:

就只有这两个启动了新的事务。
其他的,就是日志操作方法本身的事务的信息。
Creating new transaction with name [com.spring.special.UserService.updateLastLogonTime]

结论
A被private, final,static修饰的方法都不能被AOP事务增强;
B 被public和protected修饰的方法可以被AOP事务增强。
如果在一个方法中,包括了A,也包括了B,那么最终以A为准。

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。