【问题标题】:Accessing spring beans in static method以静态方法访问spring bean
【发布时间】:2012-09-14 07:24:08
【问题描述】:

我有一个带有静态方法的 Util 类。在我的 Util 类中,我想使用 spring beans,所以我将它们包含在我的 util 类中。 据我所知,将 spring bean 用作静态字段并不是一个好习惯。 但是有什么方法可以在静态方法中访问 spring bean 吗?

我的例子:

public class TestUtils {

   private static TestBean testBean;

   public void setTestBean(TestBean testBean) {
     TestUtils.testBean = testBean;
   }

  public static String getBeanDetails() {
    return beanName = testBean.getDetails();
  }
}

我在许多论坛上看到这不是最佳做法。有人可以告诉我如何处理这种情况吗?

我的配置文件:

<bean id="testUtils" class="com.test.TestUtils">
 <property name="testBean" ref="testBean" />
</bean>

【问题讨论】:

  • 为什么将spring bean用作静态字段不是一个好习惯?
  • @user59290:因为静态字段不受 Spring 的控制,它们受类加载器的约束。 spring 不能像管理对象那样拆除类。

标签: java spring


【解决方案1】:

我的方法是让希望访问的 bean 实现 InitializingBean 或使用 @PostConstruct,并包含对其自身的静态引用。

例如:

@Service
public class MyBean implements InitializingBean {
    private static MyBean instance;

    @Override
    public void afterPropertiesSet() throws Exception {
        instance = this;
    }

    public static MyBean get() {
        return instance;
    }
}

因此,您的静态类中的用法就是:

MyBean myBean = MyBean.get();

这样,不需要 XML 配置,你不需要将 bean 作为构造函数参数传入,调用者不需要知道或关心 bean 是使用 Spring 连接的(即,没有需要凌乱的ApplicationContext 变量)。

【讨论】:

  • slick - 是否有一个好地方可以为 Spring 中此类反复出现的问题找到正确的解决方法..?
  • 如果您使用方面,这是一个问题。 Spring 没有机会将您的实例包装在拦截器中。
【解决方案2】:

静态方法的结果应该只取决于传递给方法的参数,因此不需要调用任何bean。

如果您需要调用另一个 bean,那么您的方法应该是独立 bean 的成员方法。

其他答案为您提供了可行的解决方案,但可以做到的事实并不意味着应该做到。

【讨论】:

  • 是的,我觉得我们应该把它作为参数,而不是直接在静态类中设置。根据标准,它说我们不应该将 bean 用作静态的,尽管我们可以用不同的方式来做到这一点,如上面的响应所示。感谢您的回复。
  • “静态方法的结果应该只取决于传递给方法的参数”,不错!
【解决方案3】:

你也可以实现ApplicationContextAware接口,像这样:

@Component
public class TestUtils implements ApplicationContextAware {

  private static ApplicationContext ac;

  public static String getBeanDetails() {
    return beanName = ((TestBean) ac.getBean("testBean")).getDetails();
  }

  @Override
  public void setApplicationContext(ApplicationContext ac) {
    TestUtils.ac = ac;
  }

}

【讨论】:

  • 乍一看这是个好主意,但this.ac = ac; 是不对的。应该是TestUtils.ac = ac; 。或者您可以为private static ApplicationContext utilAc 定义不同的名称,然后为utilAc = ac;
  • ac 可以为 null,并导致 NPE 问题吗?
【解决方案4】:

这对我有用。

使用 xml 配置定义您的 bean(old school):

<bean id="someBean1" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    <property name="driverClassName"><value>${db.driver}</value></property>     
    <property name="url"><value>${db.url}</value></property>
    <property name="username"><value>${db.username_seg}</value></property>
    <property name="password"><value>${db.password_seg}</value></property>
</bean> 

或者用java代替xml定义(新学校

@Bean(name = "someBean2")
public MySpringComponent loadSomeSpringComponent() {

  MySpringComponent bean = new MySpringComponent();
  bean.setSomeProperty("1.0.2");
  return bean;
}

静态方法访问spring bean

import org.springframework.web.context.ContextLoader;
import org.springframework.web.context.WebApplicationContext;

public class TestUtils {

  public static void getBeansFromSpringContext() {
    WebApplicationContext context = ContextLoader.getCurrentWebApplicationContext();
    //for spring boot apps
    //ApplicationContext context = SpringApplication.run(Application.class, args)
    DataSource datasource  = (DataSource)context.getBean("someBean1");
    MySpringComponent springBean  = (MySpringComponent)context.getBean("someBean2");
  }
}   

HTH

【讨论】:

    【解决方案5】:

    与@nullPainter 的响应类似,但我们做了以下操作。无需构建后逻辑。它只是在注入期间直接设置静态成员(在@Autowired 方法中)。

    @Service
    public class MyUtil {
    
        private static MyManager myManager;
    
        @Autowired(required = true)
        public void setMyManager(MyManager manager) {
            myManager = manager;
        }
    
        public static MyManager getMyManager() {
            return myManager;
        }
    }
    

    【讨论】:

    • 不需要传递required=true 参数,因为这是@Autowired 的默认值。所以只有@Autowired public void setMyManager(MyManager manager) { myManager = manager; } 就足够了。
    • 需要@Service 注释,这没什么意义
    【解决方案6】:

    注意:这不是 Spring 的预期使用方式,并且会产生严重的副作用(难以解决的错误等)。请尝试重构您的代码,使其不再需要它!

    通用解决方案

    您可以创建一个允许从静态上下文访问任何 Bean 的类。 此处的大多数其他答案仅显示如何静态访问单个类。

    添加了下面代码中的代理,以防有人在自动装配 ApplicationContext 之前调用 getBean() 方法(因为这会导致空指针)。 此处发布的其他解决方案均未处理该空指针。

    我的博客上的详细信息:https://tomcools.be/post/apr-2020-static-spring-bean/

    用法

    UserRepository userRepo = StaticContextAccessor.getBean(UserRespository.class)
    

    StaticContextAccessor 的完整代码:

    @Component
    public class StaticContextAccessor {
    
        private static final Map<Class, DynamicInvocationhandler> classHandlers = new HashMap<>();
        private static ApplicationContext context;
    
        @Autowired
        public StaticContextAccessor(ApplicationContext applicationContext) {
            context = applicationContext;
        }
    
        public static <T> T getBean(Class<T> clazz) {
            if (context == null) {
                return getProxy(clazz);
            }
            return context.getBean(clazz);
        }
    
        private static <T> T getProxy(Class<T> clazz) {
            DynamicInvocationhandler<T> invocationhandler = new DynamicInvocationhandler<>();
            classHandlers.put(clazz, invocationhandler);
            return (T) Proxy.newProxyInstance(
                    clazz.getClassLoader(),
                    new Class[]{clazz},
                    invocationhandler
            );
        }
    
        //Use the context to get the actual beans and feed them to the invocationhandlers
        @PostConstruct
        private void init() {
            classHandlers.forEach((clazz, invocationHandler) -> {
                Object bean = context.getBean(clazz);
                invocationHandler.setActualBean(bean);
            });
        }
    
        static class DynamicInvocationhandler<T> implements InvocationHandler {
    
            private T actualBean;
    
            public void setActualBean(T actualBean) {
                this.actualBean = actualBean;
            }
    
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                if (actualBean == null) {
                    throw new RuntimeException("Not initialized yet! :(");
                }
                return method.invoke(actualBean, args);
            }
        }
    }
    

    【讨论】:

      【解决方案7】:

      这就是我从 spring 注入静态字段的方式。

      <bean id="..." class="...">
       <property name="fieldToBeInjected">
                  <util:constant static-field="CONSTANT_FIELD" />
              </property>
      </bean>
      

      也许这对你也有帮助。

      【讨论】:

        【解决方案8】:

        您概述的方法是我所看到的用于将 Spring bean 注入实用程序类的方法。

        <bean id="testUtils" class="com.test.TestUtils">
         <property name="testBean" ref="testBean" />
        </bean>
        

        另一种选择是:

        <bean name="methodInvokingFactoryBean" class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
                <property name="staticMethod" value="TestUtils.setInstance"/>
                <property name="arguments">
                    <list>
                        <ref bean="testBean"/>
                    </list>
               </property>
        </bean>
        

        与:

        public class TestUtils {
        
           private static testBean;
        
           public static void setInstance(TestBean anInstance) {
             testBean = anInstance;
           }
        
          public static String getBeanDetails() {
            return testBean.getDetails();
          }
        }
        

        更多细节是herehere

        【讨论】:

        • 我不清楚.. 你能添加 testBean 配置吗?
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2014-04-12
        • 1970-01-01
        • 1970-01-01
        • 2021-05-08
        • 1970-01-01
        • 2018-05-30
        • 2015-07-22
        相关资源
        最近更新 更多