Announcement Announcement Module
Collapse
No announcement yet.
Transaction initialized for interface-based but not class-based pointcuts Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • Transaction initialized for interface-based but not class-based pointcuts

    Iíve been playing around with Spring 2.0 declarative transactions to better understand them, with a view to converting all our existing EJB services to POJOs. Iím running into behavior I donít understand where a service method is intercepted if the pointcut is defined on its interface, but not if itís defined on its class. In both cases, logs suggest proxies are correctly set up based on interface (documented behavior for a class-based pointcut where CGLIB isnít used, as is so here). Is it expected behavior for the class-pointcut that a proxy would be set up for the interface but wouldn't kick in for the bean? I'm really trying to get a good handle on this before converting over all our services.

    Hereís what Iíve gotÖ

    Iíve an extremely basic service and service interface:
    Code:
    public interface FakeService {
        public void doOperation();
    }
    
    public class FakeConcreteService implements FakeService {
        public void doOperation() {
        }
    }
    For which Iím attempting to check a transaction starts based on the following Spring configuration:

    Code:
    	<bean id="serviceBean" class="com.s1.arch.test.FakeConcreteService"/>
    
    	<tx:advice id="serviceTxAdvice">
    		<tx:attributes>
    			<tx:method name="*" propagation="REQUIRED"/>
    		</tx:attributes>
    	</tx:advice>
    	
    	<aop:config>
    		<aop:pointcut id="allServiceOperations"
    			expression="execution(* com.s1.arch.test.FakeConcreteService.*(..))" />
    		<aop:advisor advice-ref="serviceTxAdvice"
    			pointcut-ref="allServiceOperations" />
    	</aop:config>
    	
    	<bean id="transactionManager"
    		class="com.s1.arch.test.FakeSpringTransactionManager">
    	</bean>
    Iíve an extremely basic fake PlatformTransactionManager extending AbstractPlatformTransactionManager which counts how many times doBegin is invoked and dummies out all abstract operations:

    Code:
    public class FakeSpringTransactionManager extends
            AbstractPlatformTransactionManager {
        static private int doBeginCalls;
    
        static private TransactionDefinition doBeginCallArg;
    
        static public int getDoBeginCalls() {
            return doBeginCalls;
        }
    
        static public TransactionDefinition getDoBeginCallArg() {
            return doBeginCallArg;
        }
    
        static public void reset() {
            doBeginCalls = 0;
            doBeginCallArg = null;
        }
    
        protected Object doGetTransaction() throws TransactionException {
            return null;
        }
    
        protected void doBegin(Object transaction, TransactionDefinition definition)
                throws TransactionException {
            doBeginCalls++;
            doBeginCallArg = definition;
        }
    
        protected void doCommit(DefaultTransactionStatus status)
                throws TransactionException {
        }
    
        protected void doRollback(DefaultTransactionStatus status)
                throws TransactionException {
        }
    }
    And a JUnit test that pulls this together:

    Code:
    public void testTransactionAdviceApplied() {
            FakeSpringTransactionManager.reset();
            BeanFactory beanFactory = new ClassPathXmlApplicationContext("spring-config//test-springproducer-upstream.xml");
            FakeService bean = (FakeService) beanFactory.getBean("serviceBean");
            bean.doOperation();
            assertEquals(1, FakeSpringTransactionManager.getDoBeginCalls());
            assertEquals(TransactionDefinition.PROPAGATION_REQUIRED,
                    FakeSpringTransactionManager.getDoBeginCallArg()
                            .getPropagationBehavior());
        }
    When I run this test, the first assert fails as FakeSpringTransactionManager.getDoBeginCalls() == 0, plus there are no additional transaction initiation logs. If I change my Spring configuration to use an interface-based pointcut, the test succeeds and I can see transaction initialization in the logs:

    Code:
    <aop:pointcut id="allServiceOperations" 
    	expression="execution(* com.s1.arch.test.FakeService.*(..))" />
    The crux seems to be line 79 in AdvisorChainFactoryUtils. calculateInterceptorsAndDynamicInterceptionAdvice, which returns true for the interface-pointcut but false for the class-based pointcut:

    Code:
    MethodMatcher mm = pointcutAdvisor.getPointcut().getMethodMatcher();
    if (mm.matches(method, targetClass)) {
    Ö etc. Ö
    If anybody could explain why this happens this way, that would be brilliant. Thanks!

  • #2
    It is the expected behavior

    For the given scenario, you are observing the expected behavior. The execution() pointcut matches methods based on the proxy-object (and not the target proxied object). Since the proxy object is of the FakeService type, but not of the FakeConcreteService type, only the interface-based pointcut works.

    -Ramnivas

    Comment


    • #3
      But they both proxy the interface so what's different?

      Thanks for the reply Ramnivas!

      But I'm afraid I still don't understand...

      I think what you're saying is that when an execution pointcut is class-based (FakeConcreteService), Spring will create a proxy based on its interface (FakeService). It will then use this interface-proxy to decide whether an invokation on a target object (serviceBean) matches the pointcut or not. When an execution pointcut is interface-based (FakeService), Spring will create a proxy based on the interface (FakeService) directly and use it to decide whether the pointcut matches.

      But both end up proxy-ing the interface and use it decide whether an invokation on a target object matches the pointcut or not. The only difference is the initial decision about what to proxy, but they end up with the same answer. So I don't understand the different behaviour.

      Do class-based pointcuts just not work unless you use CGLIB? But why wouldn't Spring just report that in the logs, instead of attempting to proxy the interface for a class-based pointcut if it knows it won't kick in for the class anyway?

      Sorry if I'm being dim! All help much appreciated

      Barbara
      Last edited by bemmi; Aug 29th, 2006, 04:58 AM. Reason: adding title

      Comment


      • #4
        Originally posted by bemmi
        Thanks for the reply Ramnivas!

        But I'm afraid I still don't understand...
        As far as I understand you would obtain desired behavior if you will replace "execution" expression in your pointcut with "target" expression.

        "Execution" expression is calculated against proxy, and "target" one - against proxied class, see 6.2.3.1c and 6.2.3.4 in the reference manual.

        Oleksandr Alesinskyy
        Last edited by al0; Aug 29th, 2006, 04:58 PM.

        Comment


        • #5
          I think you have two different questions and it is best to handle them separately.

          1. Why doesn't execution(* FakeConcreteService.*(..)) select any join points?

          The execution() pointcut matches methods based on the proxy object. When not using CGLIB, the proxy object is an interface (of the FakeService type, in your case). Therefore, no method matches the "* FakeConcreteService.*(..)" pattern.

          2. Why the auto-proxy mechanism creates a proxy when there will not be any matched join points with the execution(* FakeConcreteService.*(..)) pointcut?

          Creating proxies even where they could have been avoided is an optimization issue. In this case, the proxy could have been avoided after considering that CGLIB use is disabled and the pointcut uses the class, which would make it impossible to select any join points.

          Nonetheless, the externally observable behavior remains correct. The proxy in this case will act as pass-thru.

          I hope this answers your question.

          -Ramnivas

          Comment


          • #6
            Ok, I'm happy

            Yes it answers my questions very well - thanks!

            My confusion boiled down to that I wouldn't have been surprised by (1) if I hadn't seen evidence of the proxies in the logs caused by (2) as well as documentation around that implied it was a design feature rather than just something that happens. Now I can put that down as an optimization question I can move on

            Thanks again!

            Barbara

            Comment

            Working...
            X