Spring 由 bean 工厂创建的对象上的 AOP

Spring AOP on objects created by factories which are beans

我有一条 Spring AOP 建议,它在调用 auth(..) 方法时没有触发:

@AfterReturning(pointcut="execution(* auth(..))",
        returning="result")
public void trigger(final Boolean result) {
    System.out.println("I was triggered " + result);
}

这是因为带有 Auth 方法的 Authenticator 对象不是 Spring bean,而是由 Spring bean 工厂创建的:

    <bean id="passwordUserAuthFact" class="foo.bar.PasswordAuthFactory">
    ...
    </bean>

    <bean id="server" class="foo.bar.Server">
       <property name="authFactory" ref="passwordUserAuthFact"/>
       ...
    </bean>

所以当应用程序执行时:

 session.authenticator = server.getAuthFactory().create();

...session.authenticator 是一个未代理的普通 PasswordAuthFactory,AOP 不会发生在它上面。

在我看来,应该可以告诉 Spring 哪些 bean 是工厂,这样它就可以将工厂包装在代理中,这样 create() 返回的对象就是他们自己包装在 AOP 代理中。

也就是说,Spring 会像这样在我的工厂周围动态创建一个包装器:

public Foo create() {
    Foo instance = target.create();
    return wrapWithAopProxy(instance);
}

但我看不到现成的方法。

我不想让我的 classes Spring-aware。工厂 class 本身是第三方的,所以我更喜欢不更改其来源的解决方案。

是否有实现此目的的通用模式?或者更好的解决问题的方法?

您的主要替代方案是使用编程方面定义,因为您无法使用源代码。

实施一个新的身份验证工厂装饰器来包装您的旧身份验证工厂。前者主要将验证器创建委托给后者,然后用 ProxyBean 包装返回的对象并注册所需的建议。

Sprig 使这些 bean 松散耦合,authenticator POJO 成为非托管 bean。在下面的示例中,我的目的只是提供一个关于如何完成的概览,将实现细节留给您的应用程序:

  • 下面是假的Authenticator:

    public class Authenticator
    {
        private String name;
    
        public Authenticator( String name )
        {
            this.name = name;
        }
    
        public void authenticate( Object subject )
        {
            System.out.println( subject + " is being authenticated by: " + name );
        }
    }
    
  • 假设您的 AuthFactory 界面如下所示:

    public interface AuthFactory 
    {
        Authenticator create();
    }
    
  • 这是遗留实现,提供非托管身份验证的实现如下:

    public class AuthenticationFactory implements AuthFactory
    {
        @Override
        public Authenticator create()
        {
            return new Authenticator("mocked-authenticator");
        }
    }
    
  • 创建一个新的 MethodInterceptor(注意您可能需要 aopalliance 依赖项)来封装您的建议逻辑:

    public class AuthenticationAdvise implements MethodInterceptor
    {
        @Override
        public Object invoke( MethodInvocation methodInvocation ) throws Throwable
        {
            System.out.println("Before invocation..."); // Your advice logic goes here
            return methodInvocation.proceed();
        }
    }
    
  • 创建一个新的身份验证提供程序装饰器,它将是一个 Spring 托管 bean:

    public class AuthenticationFactoryDecorator implements AuthFactory {
    
        private AuthFactory authenticationFactoryDelegate;
    
        private MethodInterceptor interceptor;
    
        public AuthenticationFactoryDecorator( final AuthFactory authenticationFactoryDelegate, final MethodInterceptor interceptor )
        {
            this.authenticationFactoryDelegate = authenticationFactoryDelegate;
            this.interceptor = interceptor;
        }
    
        @Override
        public Authenticator create()
        {
             // Create the needed pointcut
            NameMatchMethodPointcut pc = new NameMatchMethodPointcut();
            pc.addMethodName("authenticate");
            // Get an authenticator from your legacy class
            Authenticator auth = authenticationFactoryDelegate.create();
            // Create a new Proxy wrapping your authFactory with the needed pointcut and advice
            ProxyFactory proxyFactory = new ProxyFactory(auth);
            proxyFactory.addAdvice(interceptor);
            return (Authenticator) proxyFactory.getProxy();
        }
    }
    
  • 将新的 AuthFactory bean 注册为 Spring 组件并将其与您的建议和遗留工厂 bean(例如 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="legacyAuthFactory" class="some.package.AuthenticationFactory"/>
    
        <bean id="interceptor" class="some.package.AuthenticationFactoryAdvise"/>
    
        <bean id="authFactory" class="some.package.AuthenticationFactoryDecorator">
            <constructor-arg name="authenticationFactoryDelegate" ref="legacyAuthFactory"/>
            <constructor-arg name="interceptor" ref="interceptor"/>
        </bean>
    
    </beans>
    

现在您可以自由地从 Spring 应用程序上下文中提取 authFactory bean 并使用它来实例化新的身份验证器对象:

public class MainAuthentication
{
    public static void main( String[] args )
    {
        ApplicationContext  applicationContext = new ClassPathXmlApplicationContext("classpath:META-INF/beans.xml");
        AuthFactory factory = applicationContext.getBean("authFactory", AuthFactory.class);
        Authenticator authenticator = factory.create();
        authenticator.authenticate(new MockSubject());
    }

    private static class MockSubject
    {
        @Override
        public String toString() {
            return "MockSubject";
        }
    }
}

在 main class 下执行,请注意,您正在处理一个新的代理身份验证器实例,该实例包含在您的建议逻辑中,如输出所示:

Before invocation...

MockSubject is being authenticated!!!