Spring基于注解的配置
前言
之前我们对Spring的容器以及依赖注入进行了学习了解,我们知道Spring可以通过XML文件来进行依赖注入,但是我们有时会感觉xml进行注入的方法虽然可以将对象解耦,但是在有些开发中也会感到比较麻烦,所以开发者也对依赖注入的方式进行了很多改变。
在接触注解配置之前,我们先补充一个依赖注入的方式:
使用Spring表达式
Spring表达式是Spring引用的类似EL表达式的一个读取方法:
<bean id="msg" class="...">
<property name="name" value="张三"/>
</bean>
<!-- 读取.properties文件的bean -->
<util:properties id="config" location="classpath:config.properties"/>
<bean id="..." class="...">
<!-- 为节省时间接下来的栗子和前一节注入集合部分的xml内容相关 -->
<!-- 这里的msg是其他bean,底层调用msg的对象的get方法 -->
<property name="name" value="#{msg.name}"/>
<!-- 对数组内的值进行获取 -->
<property name="city" value="#{vb1.list1[1]}"/>
<!-- 对Map进行获取(后面是建) -->
<property name="score" value="#{vb1.mapa.english}"/>
<!-- 如果map的键是中文可以用另一种方法(这种方法通用)-->
<property name="score" value="#{vb1.mapa['历史']}"/>
<!-- 读取文件中的属性 -->
<property name="pageSize" value="#{config.propertiessize}"/>
</bean>
什么是注解?
可以参考上一篇文章:再有人问注解就把这篇文章丢给他!,Java注解基本原理
好了,现在我们就开始学习Spring是如何使用注解来简化配置了!
正文
基于Xml的配置
从 Spring 2.5 开始就可以使用注解来配置依赖注入。而不是采用 XML 来描述一个 bean 连线,你可以使用相关类,方法或字段声明的注解,将 bean 配置移动到组件类本身。
在 XML 注入之前进行注解注入,因此后者的配置将通过两种方式的属性连线被前者重写。
注解连线在默认情况下在 Spring 容器中不打开。因此,在可以使用基于注解的连线之前,我们将需要在我们的 Spring 配置文件中启用它。所以如果你想在 Spring 应用程序中使用的任何注解,可以考虑到下面的配置文件。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<context:annotation-config/>
<!-- bean definitions go here -->
</beans>
一旦 被配置后,你就可以开始注解你的代码,表明 Spring 应该自动连接值到属性,方法和构造函数。让我们来看看几个重要的注解,并且了解它们是如何工作的:
注解 | 描述 |
---|---|
@Required | @Required 注解应用于 bean 属性的 setter 方法 |
@Autowired | @Autowired 注解可以应用到 bean 属性的 setter 方法,非 setter 方法,构造函数和属性 |
@Qualifier | @Qualifier 通过指定确切的将被连线的 bean,@Autowired 和 @Qualifier 注解可以用来删除混乱。 |
more | Spring 支持 JSR-250 的基础的注解,其中包括了 @Resource,@PostConstruct 和 @PreDestroy 注解 |
组件扫描
举个例子:
package annotation;
import org.springframework.stereotype.Component;
@Component("sb1")
//相当于配置文件中有id等于sb1的Bean
public class SomeBean {
//组件
public SomeBean() {
System.out.println("SomeBean");
}
}
<context:component-scan base-package="annotation"></context:component-scan>
base-package:spring容器回扫描该包和其子包的所有类如果该类前有特定的注解(如@Component),则Spring容器会将其纳入容器进行管理。相当于配置Bean元素
而这有一些等价的可替换使用的注解(只是增加代码的可读性):
注解 | 描述 |
---|---|
@Component | 通用注解 |
@Named | 通用注解 |
@Repository | 持久层组建注解(Dao) |
@Service | 业务层注解 |
@Controller | 控制层组件注解 |
指定作用域 @Scope
package annotation;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Component;
@Scope("prototype")
//使用注解制定作用域为非单例
@Component("sb1")
//相当于配置文件中有id等于sb1的Bean
public class SomeBean {
//组件
public SomeBean() {
System.out.println("SomeBean");
}
}
Spring 生命周期
package annotation;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
@Scope("prototype")
//使用注解制定作用域为非单例
@Component("sb1")
//相当于配置文件中有id等于sb1的Bean
public class SomeBean {
//把该方法注解为初始化方法(对象创建好之后)
@PostConstruct
public void init() {
System.out.println("init");
}
//销毁方法
@PreDestroy
public void destory() {
}
public SomeBean() {
System.out.println("SomeBean");
}
}
这个注解来自于Sun公司,Tomcat中有这个注解,所以光有Spring还不行哦
延迟加载 @Lazy
import org.springframework.context.annotation.Lazy;
@Lazy(true)
public class SomeBean {
//@Lazy(true)表示延迟加载
public SomeBean() {
System.out.println("SomeBean");
}
}
}
Spring关于注入的注解
Spring @Required注解
@Required 注释应用于 bean 属性的 setter 方法,它表明受影响的 bean 属性在配置时必须放在 XML 配置文件中,否则容器就会抛出一个 BeanInitializationException 异常。下面显示的是一个使用 @Required 注释的示例。
import org.springframework.beans.factory.annotation.Required;
public class Student {
private Integer age;
private String name;
@Required
public void setAge(Integer age) {
this.age = age;
}
public Integer getAge() {
return age;
}
@Required
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
意思就是,在执行容器时,若age和name没有注入值,则报错。
自动装配/依赖注入 @Autowired注解
因为不经过xml,所以注解定义自动装配来进行依赖注入
Spring @Autowired 注释
@Autowired 注释对在哪里和如何完成自动连接提供了更多的细微的控制。
@Autowired 注释可以在 setter 方法中被用于自动连接 bean,就像 @Autowired 注释,容器,一个属性或者任意命名的可能带有多个参数的方法。
Setter 方法中的 @Autowired
你可以在 XML 文件中的 setter 方法中使用 @Autowired 注释来除去 元素。当 Spring遇到一个在 setter 方法中使用的 @Autowired 注释,它会在方法中视图执行 byType 自动连接。
举例:
Setter方法上的@Autowired
import org.springframework.beans.factory.annotation.Autowired;
public class TextEditor {
private SpellChecker spellChecker;
@Autowired
public void setSpellChecker( SpellChecker spellChecker ){
this.spellChecker = spellChecker;
}
public SpellChecker getSpellChecker( ) {
return spellChecker;
}
public void spellCheck() {
spellChecker.checkSpelling();
}
}
public class SpellChecker {
public SpellChecker(){
System.out.println("Inside SpellChecker constructor." );
}
public void checkSpelling(){
System.out.println("Inside checkSpelling." );
}
}
此时,@Autowired会对Setter方法进行自动装配(试图使用byType)
属性中的@Autowired
import org.springframework.beans.factory.annotation.Autowired;
public class TextEditor {
@Autowired
private SpellChecker spellChecker;
public TextEditor() {
System.out.println("Inside TextEditor constructor." );
}
public SpellChecker getSpellChecker( ){
return spellChecker;
}
public void spellCheck(){
spellChecker.checkSpelling();
}
}
此时,将不需要Setter方法,就可以将被注解的属性在容器中查找到相应的引用进行装配
构造函数中的 @Autowired
import org.springframework.beans.factory.annotation.Autowired;
public class TextEditor {
private SpellChecker spellChecker;
@Autowired
public TextEditor(SpellChecker spellChecker){
System.out.println("Inside TextEditor constructor." );
this.spellChecker = spellChecker;
}
public void spellCheck(){
spellChecker.checkSpelling();
}
}
此时会根据构造器进行自动装配。
@Autowired 的(required=false)选项
默认情况下,@Autowired 注释意味着依赖是必须的,它类似于 @Required 注释,然而,你可以使用 @Autowired 的 (required=false) 选项关闭默认行为。
即使你不为 age 属性传递任何参数,下面的示例也会成功运行,但是对于 name 属性则需要一个参数。你可以自己尝试一下这个示例,因为除了只有 Student.java 文件被修改以外,它和 @Required 注释示例是相似的。
import org.springframework.beans.factory.annotation.Autowired;
public class Student {
private Integer age;
private String name;
@Autowired(required=false)
public void setAge(Integer age) {
this.age = age;
}
public Integer getAge() {
return age;
}
@Autowired
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
@Qualifier注释消除混乱
可能会有这样一种情况,当你创建多个具有相同类型的 bean 时,并且想要用一个属性只为它们其中的一个进行装配,在这种情况下,你可以使用 @Qualifier 注释和 @Autowired 注释通过指定哪一个真正的 bean 将会被装配来消除混乱。下面显示的是使用 @Qualifier 注释的一个示例
package com.tutorialspoint;
public class Student {
private Integer age;
private String name;
public void setAge(Integer age) {
this.age = age;
}
public Integer getAge() {
return age;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
package com.tutorialspoint;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
public class Profile {
@Autowired
@Qualifier("student1")
private Student student;
public Profile(){
System.out.println("Inside Profile constructor." );
}
public void printAge() {
System.out.println("Age : " + student.getAge() );
}
public void printName() {
System.out.println("Name : " + student.getName() );
}
}
<!-- Definition for profile bean -->
<bean id="profile" class="com.tutorialspoint.Profile">
</bean>
<!-- Definition for student1 bean -->
<bean id="student1" class="com.tutorialspoint.Student">
<property name="name" value="Zara" />
<property name="age" value="11"/>
</bean>
<!-- Definition for student2 bean -->
<bean id="student2" class="com.tutorialspoint.Student">
<property name="name" value="Nuha" />
<property name="age" value="2"/>
</bean>
@Resource 注解
与生命周期的注解同属于JSR-250注解
@Resource 注释是根据byName规则进行依赖注入的
import javax.annotation.Resource;
public class TextEditor {
private SpellChecker spellChecker;
@Resource(name= "spellChecker")
public void setSpellChecker( SpellChecker spellChecker ){
this.spellChecker = spellChecker;
}
public SpellChecker getSpellChecker(){
return spellChecker;
}
public void spellCheck(){
spellChecker.checkSpelling();
}
}
基于Java的配置
到目前为止,你已经看到如何使用 XML 配置文件来配置 Spring bean。如果你熟悉使用 XML 配置,那么我会说,不需要再学习如何进行基于 Java 的配置是,因为你要达到相同的结果,可以使用其他可用的配置。
基于 Java 的配置选项,可以使你在不用配置 XML 的情况下编写大多数的 Spring,但是一些有帮助的基于 Java 的注解,解释如下:
@Configuration 和 @Bean 注解
带有 @Configuration 的注解类表示这个类可以使用 Spring IoC 容器作为 bean 定义的来源。@Bean 注解告诉 Spring,一个带有 @Bean 的注解方法将返回一个对象,该对象应该被注册为在 Spring 应用程序上下文中的 bean。最简单可行的 @Configuration 类如下所示:
import org.springframework.context.annotation.*;
@Configuration
public class HelloWorldConfig {
@Bean
public HelloWorld helloWorld(){
return new HelloWorld();
}
}
等同于:
<beans>
<bean id="helloWorld" class="com.tutorialspoint.HelloWorld" />
</beans>
在这里,带有 @Bean 注解的方法名称作为 bean 的 ID,它创建并返回实际的 bean。你的配置类可以声明多个 @Bean。一旦定义了配置类,你就可以使用 AnnotationConfigApplicationContext 来加载并把他们提供给 Spring 容器,如下所示:
public static void main(String[] args) {
ApplicationContext ctx =
new AnnotationConfigApplicationContext(HelloWorldConfig.class);
HelloWorld helloWorld = ctx.getBean(HelloWorld.class);
helloWorld.setMessage("Hello World!");
helloWorld.getMessage();
}
注入 Bean 的依赖性
import org.springframework.context.annotation.*;
@Configuration
public class AppConfig {
@Bean
public Foo foo() {
return new Foo(bar());
}
@Bean
public Bar bar() {
return new Bar();
}
}
@Import 注解
@import 注解允许从另一个配置类中加载 @Bean 定义。考虑 ConfigA 类,如下所示:
@Configuration
public class ConfigA {
@Bean
public A a() {
return new A();
}
}
@Configuration
@Import(ConfigA.class)
public class ConfigB {
@Bean
public B a() {
return new A();
}
}
生命周期回调
@Bean 注解支持指定任意的初始化和销毁的回调方法,就像在 bean 元素中 Spring 的 XML 的初始化方法和销毁方法的属性:
public class Foo {
public void init() {
// initialization logic
}
public void cleanup() {
// destruction logic
}
}
@Configuration
public class AppConfig {
@Bean(initMethod = "init", destroyMethod = "cleanup" )
public Foo foo() {
return new Foo();
}
}
指定 Bean 的作用域
默认范围是单实例,但是你可以重写带有 @Scope 注解的该方法
@Configuration
public class AppConfig {
@Bean
@Scope("prototype")
public Foo foo() {
return new Foo();
}
}
Spring 事件处理
你已经看到了在所有章节中 Spring 的核心是 ApplicationContext,它负责管理 beans 的完整生命周期。当加载 beans 时,ApplicationContext 发布某些类型的事件。例如,当上下文启动时,ContextStartedEvent 发布,当上下文停止时,ContextStoppedEvent 发布。
通过 ApplicationEvent 类和 ApplicationListener 接口来提供在 ApplicationContext 中处理事件。如果一个 bean 实现 ApplicationListener,那么每次 ApplicationEvent 被发布到 ApplicationContext 上,那个 bean 会被通知。
Spring 提供了以下的标准事件:
序号 | Spring 内置事件 & 描述 |
---|---|
1 | ContextRefreshedEvent:ApplicationContext 被初始化或刷新时,该事件被发布。这也可以在 ConfigurableApplicationContext 接口中使用 refresh() 方法来发生。 |
2 | ContextStartedEvent:当使用 ConfigurableApplicationContext 接口中的 start() 方法启动 ApplicationContext 时,该事件被发布。你可以调查你的数据库,或者你可以在接受到这个事件后重启任何停止的应用程序。 |
3 | ContextStoppedEvent:当使用 ConfigurableApplicationContext 接口中的 stop() 方法停止 ApplicationContext 时,发布这个事件。你可以在接受到这个事件后做必要的清理的工作。 |
4 | ContextClosedEvent:当使用 ConfigurableApplicationContext 接口中的 close() 方法关闭 ApplicationContext 时,该事件被发布。一个已关闭的上下文到达生命周期末端;它不能被刷新或重启。 |
5 | RequestHandledEvent:这是一个 web-specific 事件,告诉所有 bean HTTP 请求已经被服务。 |
举个例子
package com.tutorialspoint;
public class HelloWorld {
private String message;
public void setMessage(String message){
this.message = message;
}
public void getMessage(){
System.out.println("Your Message : " + message);
}
}
package com.tutorialspoint;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextStartedEvent;
public class CStartEventHandler
implements ApplicationListener<ContextStartedEvent>{
public void onApplicationEvent(ContextStartedEvent event) {
System.out.println("ContextStartedEvent Received");
}
}
package com.tutorialspoint;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextStoppedEvent;
public class CStopEventHandler
implements ApplicationListener<ContextStoppedEvent>{
public void onApplicationEvent(ContextStoppedEvent event) {
System.out.println("ContextStoppedEvent Received");
}
}
<bean id="helloWorld" class="com.tutorialspoint.HelloWorld">
<property name="message" value="Hello World!"/>
</bean>
<bean id="cStartEventHandler"
class="com.tutorialspoint.CStartEventHandler"/>
<bean id="cStopEventHandler"
class="com.tutorialspoint.CStopEventHandler"/>
扩展:Spring 中自定义事件
##本文供自身学习分享,多有参考,不足之处请指出