且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

Spring XML 文件配置层次帮助/说明

更新时间:2022-11-03 14:03:15

名为applicationContext.xml"的文件没有什么特别之处,只是 Spring 倾向于将其命名为默认配置文件.使用一个名为 that 的文件或多个名为dog.xml"、cat.xml"和alien.xml"的文件的工作方式完全相同.您遇到的麻烦来自同时使用多个 ApplicationContext,而不是来自多个 XML 文件.我最近回答了一些因不理解这些概念而遇到问题的人的问题.查看这些答案,看看您还有哪些问题:

There's nothing special about the file named "applicationContext.xml" except that it's the name Spring tends to expect as its default configuration file. Using one file named that or multiple files named "dog.xml", "cat.xml", and "alien.xml" will work exactly the same way. The trouble you're having comes from having multiple ApplicationContexts in use at the same time, not from having multiple XML files. I've recently answered a couple of questions from people who had problems caused by not understanding these concepts. Check out those answers, and see what questions you still have:

在父上下文中声明 Spring Bean vs子上下文

Spring-MVC:什么是上下文"和命名空间"?

针对您的新问题:

我的 servlet.xml 中有一个 <context:component-scan base-package="com.myapp"/> 标记.

I had a <context:component-scan base-package="com.myapp"/> tag in my servlet.xml.

我猜这个servlet.xml"文件的名字类似于foo-servlet.xml,其中你的web.xml中配置的DispatcherServlet被命名为foo",比如

I'm guessing this "servlet.xml" file is named like foo-servlet.xml, where the DispatcherServlet configured in your web.xml is named "foo", like

<servlet>
    <servlet-name>foo</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
</servlet>

按照惯例,当这个 DispatcherServlet 启动时,它会创建一个新的 ApplicationContext,它由文件 foo-servlet.xml 配置,派生自 servlet-name.现在,由于您在其中放置了 context:component-scan,它将递归扫描给定的包并为所有带注释的类创建 bean.您提供的包 com.myapp 看起来像是整个应用程序的基础包,因此 Spring 将从应用程序中所有的带注释的类创建 bean,包括数据访问的,在这个与 DispatcherServlet 关联的 ApplicationContext 中.通常,这个上下文应该只有视图层的东西和直接支持其中的 DispatcherServlet 的 bean,所以这是一个错误配置.

By convention, when this DispatcherServlet starts, it'll create a new ApplicationContext that's configured by the file foo-servlet.xml, derived from the servlet-name. Now, since you put a context:component-scan in there, it's going to recursively scan the given package and create beans for all annotated classes. The package you gave it, com.myapp, looks like it's the base package for your entire app, so Spring will create beans from all of the annotated classes in your app, including the data access ones, in this one ApplicationContext that's associated to the DispatcherServlet. Typically, this context should only have view-layer stuff and beans that directly support the DispatcherServlet in it, so this was something of a misconfiguration.

在我的 data.xml 文件中,我有数据源 bean,仅此而已.没有其他 bean,其他所有内容都是自动装配和注释的.

In my data.xml file I had data source beans and that was it. No other beans, everything else was autowired and annotated.

据推测,这个data.xml"文件就是您在 contextConfigLocation 上下文参数中列出的文件.假设您还将 ContextLoaderListener 添加到您的 web.xml 中,例如

Presumably, this "data.xml" file is the one you listed in the contextConfigLocation context-param. Assuming you'd also added the ContextLoaderListener to your web.xml, like

<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

然后该文件将用于创建第二个 ApplicationContext——根上下文.这就是这个听众所做的.请注意,它实际上是从 contextConfigLocation 中列出的所有文件构建上下文,如果您还在该列表中包含了servlet.xml",那么您已经加载了它配置两次:这里在根上下文以及与 DipatcherServlet 关联的上下文中.希望您现在看到了 XML 配置文件和它们配置的 ApplicationContext 之间的明显区别.可以轻松地使用同一个 XML 文件来配置两个不同的上下文.这样做是否正确是另一个问题.在这种特殊情况下,它不是.

then that file will be used to create a second ApplicationContext--the root context. That's what this listener does. Note that it actually builds the context from all the files listed in contextConfigLocation, and if you also included your "servlet.xml" in that list, then you've loaded that config twice: here in the root context as well as in the context associated with the DipatcherServlet. Hopefully you see now how there's a distinct division between the XML configuration files and the ApplicationContexts that they configure. The same XML file can easily be used to configure two different contexts. Whether doing so is correct or not is another question. In this particular case, it isn't.

我描述这两个上下文的顺序实际上是倒序的.我只是按照你对你所做的事情的描述.作为 ServletContextListener 的 ContextLoaderListener 将始终在执行之前执行任何 servlet 启动.这意味着首先创建根上下文,然后创建另一个上下文.这是设计使然,当 DispatcherServlet 创建其上下文时,它可以将该上下文添加为根上下文的子级.我在其他帖子中描述了这种关系.这样做的最重要的效果是根上下文中的 bean 可通过 DispatcherServlet 的上下文使用.这也适用于自动装配的关系.这很重要,因为 DispatcherServlet 在其关联的上下文中查找它需要的 bean,例如控制器实例.但是,您的控制器显然必须连接支持 bean.因此,传统上,控制器位于 DispatcherServlet 的上下文中,支持 bean 位于根上下文中.

The order I've described these two contexts in is actually backwards. I was just following your description of what you did. The ContextLoaderListener, being a ServletContextListener, will always execute before any servlet starts up. This means the root context is created first, and the other context second. This is by design so that when the DispatcherServlet creates its context, it can add that context as a child of the root context. I've described this relationship in those other posts. The most important effect of this is that beans in the root context are available to and via the DispatcherServlet's context. That applies to autowired relationships, too. That's important because the DispatcherServlet only looks in its associated context for beans that it needs, like controller instances. Your controllers, though, obviously have to be wired with supporting beans. Thus, traditionally, the controllers live in the DispatcherServlet's context, and the supporting beans live in the root context.

然后我尝试将 @Transacational 添加到我的服务 bean 中,但它不会持续存在.

I then tried to add @Transacational to my service bean and it wouldn't persist.

为了让@Transactional 工作,您必须在注释 bean 所在的 ApplicationContext 的配置中包含 <tx:annotation-driven/> 标记.诀窍是弄清楚它住在哪里"部分.子级中的 Bean 可以覆盖父级上下文中的 Bean.因此——我只是在这里猜测——如果你像我上面描述的那样将所有 bean 加载到 DispatcherServlet 上下文中,但将 <tx:annotation-driven/> 放在根上下文中,您可能在根上下文中有一个正确事务性的 bean,但它不是正在使用的 bean,因为副本更接近"父/子层次结构中的 servlet,并且它所在的上下文没有得到 配置.

In order for @Transactional to work, you must include the <tx:annotation-driven/> tag in the configuration of the ApplicationContext where the annotated bean lives. The trick is figuring out the "where it lives" part. Beans in a child can override beans in a parent context. Therefore--I'm just guessing here--if you loaded all your beans into the DispatcherServlet context as I described above but put the <tx:annotation-driven/> in the root context, you might have a bean in the root context that's correctly transactional, but it's not the one being used because the duplicate is "closer" to the servlet in the parent/child hierarchy, and the context it's in didn't get a <tx:annotation-driven/> configuration.

当我将 servlet context:component-scan 标记更改为指向 com.myapp.web,然后将 context:component-scan 标记添加到 data.xml 文件时,一切正常.

When I changed the servlet context:component-scan tag to instead point at com.myapp.web and then added a context:component-scan tag to the data.xml file, everything worked.

这仍然在一定程度上取决于您将哪些配置文件包含在哪些 ApplicationContext 中,但至少我可以说,通过这样做,您从 DispatcherServlet 的上下文中删除了许多导致问题的 bean.特别是,您在根上下文中正确配置的 @Transactional bean 将不再被子上下文中的 bean 遮蔽,并且会被注入到您的控制器中,这样您的持久性内容就会起作用.

It still depends somewhat on exactly which config files you were including in which ApplicationContexts, but at the very least I can say that by doing this, you removed a lot of beans from the DispatcherServlet's context which were causing problems. In particular, your correctly-configured @Transactional beans in the root context would no longer be shadowed by beans in the child context and would be injected into your controllers, so your persistence stuff would work then.

所以...要带走的主要内容是您有两个相关的 ApplicationContext.您必须始终意识到这一事实,并控制哪些 bean 出现在哪个上下文中.

So... the main thing to take away is that you have two related ApplicationContexts. You have to remain aware of that fact and stay in control of which beans go in which context.

这是否涵盖所有内容?