【问题标题】:Logging bean id into log4j logfile without BeanNameAware interface在没有 BeanNameAware 接口的情况下将 bean id 记录到 log4j 日志文件中
【发布时间】:2011-09-06 16:35:59
【问题描述】:

给定一组由 spring 连接在一起的类。在环境中的多个实例中,有几个类与不同的配置一起使用。他们当然有不同的豆子。

问题:

  • 当他们制作日志条目时,我们不知道究竟是哪个 bean 制作了日志,因为 log4j 只显示类名
  • 我知道我可以使用 spring InitializationBean+BeanNameAware 接口方法实例化的记录器,但我不想这样做,因为我不想在所有类中实现它们

解决方案可能是:

  • 对 bean factory 将 bean 的 id 存储在带有 bean 引用的映射中具有一些影响(key 是 ref,name 是 value)
  • 创建一个应用于每个方法的方面,这将在调用之前在 Log4j 中设置一个“BeanName”MDC 条目,并在调用之后将其恢复为之前的值。同时,之前的 beannames 可以存储在堆栈中的线程本地中。

问题:

  • 如何更改/配置 bean 工厂来为我完成这个技巧?有什么自定义点可以用来实现这个目标吗?
  • 如何避免 beanid 注册表中的映射内存泄漏?也许根本不需要注册表,如果 spring 可以以某种方式查找 id 以供参考。
  • 你有什么更好的主意,不会导致一万个班级的变化?

提前致谢。

更新: - 有人有原型 bean 的解决方案吗?

【问题讨论】:

    标签: java spring logging javabeans


    【解决方案1】:

    我已经设法根据this Spring AOP Example 破解了一些东西。

    我还没有跟上 Spring 3 的速度,所以我使用 Spring 2.5 实现了这一点——我敢说有更优雅的方式来实现你想要的。为了简单起见,我使用 System.out 实现了这一点,但这些可以很容易地转换为 log4j 调用。

    最初,我在 Spring 的 bean 名称和对象的字符串表示形式 (InitBean) 之间创建了一个映射。此映射在 MethodInterceptor 内部使用 - 我确实尝试将 MethodInterceptor 设为 InitializingBeanMethodInterceptor 停止工作某种原因。

    在通过 MethodInterceptor 传入的 bean 和应用程序上下文中的其他 bean 之间执行 equals 不起作用。例如在 MethodInterceptor 中使用类似“ctx.getBeansOfType(GoBean.class)”的东西。我认为这是因为通过 MethodInvocation 传入的对象是一个 GoBean 而此时从应用程序上下文获得的对象是代理的(例如像 example.GoBean$$EnhancerByCGLIB$$bd27d40e)。

    这就是为什么我不得不求助于对象字符串表示的比较(这并不理想)。此外,我特别不想在对象上调用“toString”方法时激活 MethodInterceptor 逻辑(因为我使用的是 toString其他地方会导致无限循环和 StackOverflow)。

    希望对你有用,

    applicationContext.xml

    <beans>
    
        <bean name="initBean" class="example.InitBean"/>
    
        <bean name="methodLoggingInterceptor" class="example.MethodLoggingInterceptor">
            <property name="initBean" ref="initBean"/>
        </bean>
    
        <bean name="proxyCreator" class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
            <property name="beanNames">
                <list>
                    <value>go*</value>
                </list>
            </property>
            <property name="interceptorNames">
                <list>
                    <value>methodLoggingInterceptor</value>
                </list>
            </property>
        </bean>
    
        <bean name="goBean1" class="example.GoBean" />
        <bean name="goBean2" class="example.GoBean" />   
        <bean name="goBean3" class="example.GoBean" />  
    
    </beans>
    

    GoBean.java

    public class GoBean {
        public void execute(){
            System.out.println(new Date());
        }        
    }    
    

    SimpleTestClass.java

    public static void main( String[] args ){
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
    
        ArrayList<GoBean> goBeans = new ArrayList<GoBean>();
        goBeans.add((GoBean) ctx.getBean("goBean1"));
        goBeans.add((GoBean) ctx.getBean("goBean2"));
        goBeans.add((GoBean) ctx.getBean("goBean3"));
    
        for(GoBean g: goBeans){
            g.execute();
        }
    }
    

    InitBean.java

    public class InitBean implements ApplicationContextAware, InitializingBean {
        private ApplicationContext ctx;
        private Map<String, String> beanMap = new HashMap<String,String>();
    
        public void setApplicationContext(ApplicationContext ac) throws BeansException {
            ctx = ac;
        }
    
        public void afterPropertiesSet() throws Exception {
            for(String beanName: ctx.getBeanNamesForType(GoBean.class)){
                beanMap.put(ctx.getBean(beanName).toString(), beanName);
            }
        }
    
        public Map<String,String> getBeanMap(){
            return beanMap;
        }    
    }
    

    MethodLoggingInterceptor.java

    public class MethodLoggingInterceptor implements MethodInterceptor{
    
        private InitBean initBean;
    
        public Object invoke(MethodInvocation method) throws Throwable {
            if (!"toString".equals(method.getMethod().getName())) {
                StringBuilder sb = new StringBuilder();
                Object obj = method.getThis();
                if (obj instanceof GoBean) {
                    Map<String,String> beanMap = initBean.getBeanMap();
                    String objToString = obj.toString();
                    if (beanMap.containsKey(objToString)) {
                        System.out.println(beanMap.get(objToString));
                        sb.append("bean: ");
                        sb.append(beanMap.get(objToString));
                        sb.append(" : ");
                    }
                }
                sb.append(method.getMethod().getDeclaringClass());
                sb.append('.');
                sb.append(method.getMethod().getName());
                System.out.println(sb.toString() + " starts");
                Object result = method.proceed();
                System.out.println(sb.toString() + " finished");
                return result;
            } else {
                return method.proceed();
            }
    
        }
    
        public void setInitBean(InitBean ib) {
            this.initBean = ib;
        }        
    }
    

    【讨论】:

    • 感谢您提供一个很好的例子。缺少一个问题,即在原型 bean 的情况下如何防止泄漏?
    • 我认为我的解决方案不适用于原型 bean。 bean 不会正确记录在 InitializingBean (InitBean) 中,因此 MethodInterceptor 将无法识别它们。要捕获原型 bean 名称,您可以尝试通过 BeanFactory 实现某些东西,也许使用 BeanPostProcessor 的一些变体。我怀疑您也需要某种 BeanPostProcessor 来控制原型 bean 的生命周期(从而防止泄漏)。
    • InitBean 可以实现 BeanPostProcessor 接口,在这种情况下,一旦完全初始化,它就会收到对每个 Bean 对象的引用。这也将被原型 bean 调用。通过使用 Wea​​kHashMap,您还可以处理内存问题 - 一旦没有从其他任何地方引用 bean,下一个 GC 循环将从映射中删除相应的条目。
    猜你喜欢
    • 1970-01-01
    • 2011-06-25
    • 2011-06-12
    • 2010-09-22
    • 2018-11-16
    • 2015-12-25
    • 1970-01-01
    • 2012-07-24
    相关资源
    最近更新 更多