使用 Java注解配置 Spring bean

众所周知,Spring 框架是控制反转 (IOC) 或依赖性注入 (DI) 模式的推动因素,而这种推动是通过基于容器的配置实现的。过去,Spring 允许开发人员使用基于 XML 的配置,通过利用应用程序上下文 XML 文件来管理 bean 依赖性。此文件处于应用程序的外部,包含 bean 及其与该应用程序的依赖项的定义。尽管使用 XML 配置较为简单和便捷,但仍有另外一种方法可定义 bean 及其依赖项。这种方法也称为基于 Java 的配置。不同于 XML,基于 Java 的配置使您能够以编程方式管理 bean。这可通过运用多种注释来实现。 这篇文章将演示 Java 配置示例,并将其与传统 XML 配置方法相对比。

一、理解 @Configuration 和 @Bean 注释

1.XML 中的 bean 定义
<beans>
    <bean id="course" class="demo.Course">
        <property name="module" ref="module"/>
    </bean>
     
    <bean id="module" class="demo.Module">
        <property name="assignment" ref="assignment"/>
    </bean>
     
    <bean id="assignment" class="demo.Assignment" />
    </bean>
</beans>

以上 XML 就是在使用 Spring 配置 bean 时通常会编写的代码。这段 XML 代码定义了 Course bean,它引用 Module bean。Module bean 有一个 Assignment bean 的引用。我们现在将 XML 替换为 Java 类,这个 Java 类现在将用作 bean 配置的平台。我们将这个类命名为 AppContext.java。以下代码展示了 AppContext 类。

2.Java包含 bean 定义的 AppContext 配置类
@Configuration
public class AppContext {
    @Bean
    public Course course() {
        Course course = new Course();
        course.setModule(module());
        return course;
    }
 
    @Bean
    public Module module() {
        Module module = new Module();
        module.setAssignment(assignment());
        return module;
    }
 
    @Bean
    public Assignment assignment() {
        return new Assignment();
    }
}

现在可以以编程的方式将 bean 定义为基于 Java 的配置的一部分。AppContext 类现在就像 XML 一样表示配置类。这是通过利用 @Configuration 注释实现的。@Configuration 注释位于类的顶端。它告知 Spring 容器这个类是一个拥有 bean 定义和依赖项的配置类。@Bean 注释用于定义 bean。上述注释位于实例化 bean 并设置依赖项的方法上方。方法名称与 bean id 或默认名称相同。该方法的返回类型是向 Spring 应用程序上下文注册的 bean。您可使用 bean 的 setter 方法来设置依赖项,容器将调用它们来连接相关项。基于 Java 的配置也被视为基于注释的配置。

二、使用 AnnotationConfigApplicationContext 注册配置类

在传统 XML 方法中,您可使用 ClassPathXmlApplicationContext 类来加载外部 XML 上下文文件。但在使用基于 Java 的配置时,有一个 AnnotationConfigApplicationContext 类。AnnotationConfigApplicationContext 类是 ApplicationContext 接口的一个实现,能够注册所注释的配置类。此处的配置类是使用 @Configuration 注释声明的 AppContext。在注册了所述类之后,@Bean 注释的方法返回的所有 bean 类型也会得到注册(bean的id默认为方法名的首字母小写)。
以下代码演示了 AnnotationConfigApplicationContext 类的使用:

1.使用AnnotationConfigApplicationContext实例化Spring容器。
public static void main(String[] args) {
  ApplicationContext ctx = new AnnotationConfigApplicationContext(AppContext.class);
  Course course = ctx.getBean(Course.class);
  course.getName();
}

正如以上代码所示,AppContext 配置类的注册方式是将其传递给 AnnotationConfigApplicationContext 构造函数。此外,您还可以使用所述上下文类的 register 方法来注册配置类。以下代码展示了另外一种方法。

2.使用AnnotationConfigApplicationContext的无参数构造函数实例化,然后使用register()方法进行配置。
public static void main(String[] args) {
  ApplicationContext ctx = new AnnotationConfigApplicationContext();
  ctx.register(AppContext.class)
}

注册配置类将自动注册 @Bean 注释的方法名称,因而其对应的 bean 就是 Course、Module 和 Assignment。随后您可以使用 getBean 方法来获取相关的 bean,并调用其业务方法。

三、配置 Web 应用程序

过去,通常要利用 XmlWebApplicationContext 上下文来配置 Spring Web 应用程序,即在 Web 部署描述符文件 web.xml 中指定外部 XML 上下文文件的路径。XMLWebApplicationContext 是 Web 应用程序使用的默认上下文类。以下代码描述了 web.xml 中指向将由 ContextLoaderListener 监听器类载入的外部 XML 上下文文件的元素。

1.使用外部 XML 上下文文件的 web.xml
<web-app>
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/applicationContext.xml</param-value>
    </context-param>
    <listener>
        <listener-class>
            org.springframework.web.context.ContextLoaderListener
        </listener-class>
    </listener>
    <servlet>
    <servlet-name>sampleServlet</servlet-name>
    <servlet-class>
        org.springframework.web.servlet.DispatcherServlet
    </servlet-class>
    </servlet>
 
...
</web-app>

现在,要将 web.xml 中的上述代码更改为使用 AnnotationConfigApplicationContext 类。切记,XmlWebApplicationContext 是 Spring 为 Web 应用程序使用的默认上下文实现,因此永远不必在您的 web.xml 文件中显式指定这个上下文类。现在,您将使用基于 Java 的配置,因此在配置 Web 应用程序时,需要在 web.xml 文件中指定 AnnotationConfigApplicationContext 类。上述代码将修改如下:

2. 修改后的使用 AnnotationConfigApplicationContext 的 web.xml
<web-app>
    <context-param>
        <param-name>contextClass</param-name>
        <param-value>
            org.springframework.web.context.
            support.AnnotationConfigWebApplicationContext
        </param-value>
    </context-param>
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>
            demo.AppContext
        </param-value>
    </context-param>
    <listener>
        <listener-class>
            org.springframework.web.context.ContextLoaderListener
        </listener-class>
    </listener>
    <servlet>
    <servlet-name>sampleServlet</servlet-name>
    <servlet-class>
        org.springframework.web.servlet.DispatcherServlet
    </servlet-class>
    <init-param>
        <param-name>contextClass</param-name>
        <param-value>
            org.springframework.web.context.
            support.AnnotationConfigWebApplicationContext
        </param-value>
    </init-param>
    </servlet>
 
...
</web-app>

以上修改后的 web.xml 现在定义了 AnnotationConfigWebApplicationContext 上下文类,并将其作为上下文参数和 servlet 元素的一部分。上下文配置位置现在指向 AppContext 配置类。这非常简单。下一节将演示 bean 的生命周期回调和范围的实现。

四、实现 bean 生命周期回调和范围

1.生命周期回调

可以使用基于 Java 的配置来管理 bean 的生命周期。@Bean 支持两种属性,即 initMethod 和 destroyMethod,这些属性可用于定义生命周期方法。在实例化 bean 或即将销毁它时,容器便可调用生命周期方法。生命周期方法也称为回调方法,因为它将由容器调用。使用 @Bean 注释注册的 bean 也支持 JSR-250 规定的标准 @PostConstruct 和 @PreDestroy 注释。如果您正在使用 XML 方法来定义 bean,那么就应该使用 bean 元素来定义生命周期回调方法。
以下代码显示了在 XML 配置中通常使用 bean 元素定义回调的方法。

<bean id="course" class="demo.Course" init-method="setup" destroy-method="cleanup" >
    <property name="module" ref="module"/>
</bean>

以下代码演示了使用 Java 配置的生命周期方法

@Configuration
public class AppContext {
    @Bean(initMethod = "setup", destroyMethod = "cleanup")
    public Course course() {
        Course course = new Course();
        course.setModule(module());
        return course;
    }
 
    @Bean(initMethod = "setup", destroyMethod = "cleanup")
    public Module module() {
        Module module = new Module();
        module.setAssignment(assignment());
        return module;
    }
     
    ...
}       
public class Course {
 
    private Module module;
    private String name;
     
    public Course() {
    }
     
    public void setup() {
        this.name = "M100 Pythagoras Theorems"
    }
     
    public void setModule(Module module) {
        this.module = module;
    }
     
    public void cleanup() {
        module = null;
    }
}

上面的代码重新访问了 AppContext 配置类。@Bean 注释现在有两个额外的属性,即 initMethod 和 destroyMethod。它们定义了生命周期方法的设置和清除。这些方法是在已经注册的 bean 中实现的,最终由容器在 bean 初始化及其销毁之前调用它。这里以 Course bean 为例,提供了生命周期方法实现。所实现的方法是 setup 和 cleanup。同样,您还可以在 Module 和 Assignment bean 中实现这些方法。

2.Bean 范围

bean 的方法是使用 @Scope 注释定义的。XML 中实现这一目标的方法是指定 bean 元素中的 scope 属性。

<bean id="course" class="demo.Course" scope="prototype" >
    <property name="module" ref="module"/>
</bean>

以下代码展示了使用 Java 配置的 bean 范围定义:

@Configuration
public class AppContext {
    @Bean(initMethod = "setup", destroyMethod = "cleanup")
    @Scope("prototype")
    public Course course() {
        Course course = new Course();
        course.setModule(module());
        return course;
    }
    ...
}

在 Java 配置类中定义 bean 的范围非常简单。上面的 AppContext 配置类使用 @Scope 注释为 Course bean 定义了一个 prototype 范围。默认范围是 singleton。

利用 Java 配置可以做许多事情。本文只触及了一些基础内容。使用 Java 配置并无显著优势,它仅仅是 Spring 提供的 XML 配置的替代方法。对于不喜欢在框架中使用 XML 的人来说,这是实现配置的一种出色方法。但缺点也是显而易见的,如果您对 Java 类中的配置做出了任何更改,就必须重新编译应用程序。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,529评论 5 475
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,015评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,409评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,385评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,387评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,466评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,880评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,528评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,727评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,528评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,602评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,302评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,873评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,890评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,132评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,777评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,310评论 2 342

推荐阅读更多精彩内容