根据类的包进行注入

时间:2013-06-28 14:21:48

标签: java spring spring-ioc

我有2个包含类的模块:

blog.model.ArticleDAO
blog.model.CategoryDAO

users.model.UserDAO
users.model.UserGroupDAO

所有这些DAO都依赖于相同的服务,但我需要根据包注入不同的实例。

我的意思是模块博客应该有MyService的特定实例,模块用户应该有另一个MyService实例。

我不想创建2个命名服务,因为有一天我可能想为所有DAO使用相同的服务。或者我也想为特定的类注入另一个特定的实例......

有没有办法根据类的包来注入服务?

一种说法:

  • fooMyService的实例)注入blog.*
  • 中的类
  • barMyService的实例)注入users.*
  • 中的类

但保持我的所有课程都没有意识到这一点!他们的配置只应说明“注入MyService的实例”。

1 个答案:

答案 0 :(得分:0)

首先我想说,我发现这是一个奇怪的要求。我也想知道为什么你的DAO需要服务。在正常的分层设计中,这是相反的(服务使用DAO)。

但是我发现挑战很有趣,我尝试使用FactoryBean创建一个Java Proxy类,它将根据调用程序包在运行时重定向到MyService的正确实例。这是代码:

public class CallerPackageAwareProxyFactoryBean implements
        FactoryBean<MyService>, ApplicationContextAware {

    private Class<?> targetServiceType;
    private ApplicationContext applicationContext;

    private InvocationHandler invocationHandler = new InvocationHandler() {
        public Object invoke(Object proxy, Method method, Object[] args)
                throws Throwable {
            if (ReflectionUtils.isEqualsMethod(method)) {
                // Only consider equal when proxies are identical.
                return (proxy == args[0]);
            } else if (ReflectionUtils.isHashCodeMethod(method)) {
                // Use hashCode of service locator proxy.
                return System.identityHashCode(proxy);
            } else if (ReflectionUtils.isToStringMethod(method)) {
                return "Service dispatcher: " + targetServiceType.getName();
            } else {
                String callerPackageFirstLevel = getCallerPackageFirstLevel();
                Map<String, ?> beans = applicationContext
                        .getBeansOfType(targetServiceType);
                for (Map.Entry<String, ?> beanEntry : beans.entrySet()) {
                    if (beanEntry.getKey().startsWith(callerPackageFirstLevel)) {
                        return method.invoke(beanEntry.getValue(), args);
                    }
                }
                throw new IllegalArgumentException(
                        String.format(
                                "Could not find any valid bean to forward call for method %s.",
                                method.getName()));
            }
        }

        private String getCallerPackageFirstLevel() {
            Throwable t = new Throwable();
            StackTraceElement[] elements = t.getStackTrace();

            String callerClassName = elements[3].getClassName();
            return callerClassName.split("\\.")[0];
        }
    };

    public MyService getObject() throws Exception {
        return (MyService) Proxy.newProxyInstance(Thread.currentThread()
                .getContextClassLoader(), new Class<?>[] { MyService.class },
                invocationHandler);
    }

    public Class<?> getObjectType() {
        return MyService.class;
    }

    public boolean isSingleton() {
        return true;
    }

    public void setApplicationContext(ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
    }

    public void setTargetServiceType(Class<?> targetServiceType) {
        this.targetServiceType = targetServiceType;
    }

}

我没有必要改变Dao或Service配置。我只需要在Spring上下文中添加FactoryBean的创建:

<bean id="myService" class="stackoverflow.CallerPackageAwareProxyFactoryBean">
    <property name="targetServiceType" value="a.b.c.MyService" />
</bean>

也许有几条评论:

  • 调用程序包只能通过创建异常并查看堆栈跟踪来获取。
  • InvocationHandler的代码受ServiceLocatorFactoryBean启发。
  • 我仍然想知道是否有更简单的方法,但我认为没有。
  • 您可以替换部分InvocationHandler以使用配置Map(package =&gt; MyService bean name)
  • 我会不推荐在生产环境中使用此类代码。