在官方文档注解相关的文档开头,有一段
Are annotations better than XML for configuring Spring?
注解是否比配置spring的XML更好?
基本观点就是 注解 和 xml 各有优缺点,没有什么谁一定比谁好。对于开发者,根据具体的情况,个人爱好,选择合适的配置方式,spring框架都对他们有充分的支持,而且他们两个还可以混合起来用
注意:通过注解的方式注入,执行的比XML注入要早,所以如果两者都配置了,XML配置会覆盖注解的配置
注解的方式在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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<!-- 打开注解 -->
<context:annotation-config/>
</beans>
序号 | 注解 & 描述 |
---|---|
1 | @Required 注解应用于 bean 属性的 setter 方法。 |
2 | @Autowired 注解可以应用到 bean 属性的 setter 方法,非 setter 方法,构造函数和属性。 |
3 | @Qualifier通过指定确切的将被连线的 bean,@Autowired 和 @Qualifier 注解可以用来删除混乱。 |
4 | JSR Annotations Spring 支持 JSR 的基础的注解,其中包括了 @Resource,@PostConstruct 和 @PreDestroy 注解。 |
@Required
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Required
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}
@Required 注解 应用于 bean 属性的 setter 方法,它表明受影响的 bean 属性在配置时必须放在 XML 配置文件中,否则容器就会抛出一个 BeanInitializationException 异常。
意思就是说,movieFinder 必须被初始化(这个方法必须被使用,无论是通过显示的配置还是自动装配的方式)
这个注解在 Framework 5.1开始正式弃用,支持使用构造函数注入所需的设置(或者自定义实现InitializingBean.afterPropertiesSet()以及bean属性setter方法)
@Autowired
这个注解极为强大,可以用于:构造,基本类型属性,引用类型属性,setter方法
-
注释在构造方法上,指定spring容器创建对象时使用这个构造方法。
注意,如果构造方法有多个,那么必须在其中的 一个上 注释 @Autowired ,告诉容器要创建哪一个对象,如果没有声明要使用哪个,那么spring会默认使用类中的第一个构造方法,从4.3开始如果构造方法只有一个,那么非必须注解@Autowired,它可以自己识别
-
如果是在 基本属性类型上注解@Autowired,那么效果类似@Required,要求创建必须被beans.xml配置給值,否则会抛出异常。
还可以单独设置属性,如果注解
@Autowired(required=false)
,这代表这个属性是非必须的,在创建时会尝试给这个属性初始化,如果没有可用的函数,会忽略这个属性。默认的情况是
@Autowired(required=true)
,要求必须初始化成功,否则抛出异常 -
对应引用类型的属性,如果其注解了@Autowired,那么spring会自动的通过byType的方式来初始化该属性
同样,对于
@Autowired(required=false)
的写法,表示这个属性非必须的由于注解了@Autowired的属性,使用率byType的自动装配方式,所以setXxx方法不需要写
-
@Autowired如果注解在setter方法上,spring它会在方法中试图执行 byType 自动连接。
setter 方法可以由多个参数,他们通过类型来区分,如果有两个参数的类型一致,那么得到的对象都是同一个
支持集合类型
接下来是一些示例代码
示例代码一
首先是一些固定不变的代码
在Beans.xml中配置bean,没有显式配置注入
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<!-- 打开注解 -->
<context:annotation-config/>
<bean id="book" class="com.bean.Book"></bean>
<bean id="teacher" class="com.bean.Teacher"></bean>
</beans>
测试类
package com.tutorialspoint;
import com.bean.Book;
public class MainApp {
public static void main(String[] arg) {
ApplicationContext context = new FileSystemXmlApplicationContext("src/Beans.xml");
Book obj1 = (Book)context.getBean("book");
System.out.println(obj1.toString());
}
}
用到的Teacher类,每当创建一个 Teacher对象,就会输出一句话
package com.bean;
public class Teacher {
private String name;
public Teacher() {
System.out.println("new Teacher Object");
}
public void setName(String name) {
this.name = name;
}
}
下面是要改变的代码部分,通过改变他们来 认识 到@Autowired 注解的作用
对于构造函数,下面的类中有两个,通过参数的类型来区别,里面什么注解都没有
package com.bean;
public class Book {
Teacher a;
public Book() {
System.out.println("new Book Object");
}
public Book(Teacher a) {
this.a = a;
System.out.println("new Book Object by 2");
}
@Override
public String toString() {
return "Book [a=" + a + "]";
}
}
//运行测试类,输出结果如下
/*
new Book Object
new Teacher Object
Book [a=null]
*/
可以看到,容器创建了 book,teacher对象。
我们并没有 显式的配置将teacher对象注入到Book中,并且也没有注解,所以Book中的teacher属性引用为null
在这里,我们注意到,有两个Book的构造函数,spring使用了第一个构造方法
接下来,我们对代码进行修改
package com.bean;
import org.springframework.beans.factory.annotation.Autowired;
public class Book {
Teacher a;
public Book() {
System.out.println("new Book Object");
}
@Autowired
public Book(Teacher a) {
this.a = a;
System.out.println("new Book Object by 2");
}
@Override
public String toString() {
return "Book [a=" + a + "]";
}
}
//运行测试类,输出结果如下
/*
new Teacher Object
new Book Object by 2
Book [a=com.bean.Teacher@28ac3dc3]
*/
将@Autowired加到 第二个构造上方,在这种情况下,我们告诉spring指定 了要使用这个构造方法来创建对象,调用 了 有参的构造给 Teacher对象赋值
那么,如果把@Autowired 加到第一个无参的构造方法上呢? 其结果 就是调用第一个 构造方法,和上边什么注解也没加的 输出一致
new Book Object
new Teacher Object
Book [a=null]```
原因在于:如果有多个构造函数,spring会使用第一个出现的构造
那么,两个构造都加@Autowired呢?
Error creating bean with name 'book': Invalid autowire-marked constructor: public com.bean.Book(com.bean.Teacher). Found constructor with 'required' Autowired annotation already: public com.bean.Book()
会出现错误已经指定了Book(),你不能再指定其他的了
对于构造函数的优先级,如果都没有注解@Autowired,那么使用第一个出现的,如果有注解,那么使用注解指定的构造方法,如果出现多个注解指定的,那么抛出异常,无法创建对象
示例代码二
配置bean
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<!-- 打开注解 -->
<context:annotation-config/>
<bean id="student" class="com.bean.Student">
<property name="id" value="001"></property>
<property name="name" value="张三"></property>
</bean>
</beans>
测试类
package com.tutorialspoint;
import com.bean.Student;
public class MainApp {
public static void main(String[] arg) {
ApplicationContext context = new FileSystemXmlApplicationContext("src/Beans.xml");
Student obj1 = (Student) context.getBean("student");
System.out.println(obj1.toString());
}
}
bean
package com.bean;
public class Student {
private String id;
private String name;
public String toString() {
return "Student [id=" + id + ", name=" + name + "]";
}
public void setId(String id) {
this.id = id;
}
public void setName(String name) {
this.name = name;
}
}
//运行测试类,输出结果如下
/*
Student [id=001, name=张三]
*/
上面是一个一切完好的 基于xml 配置的 bean,可以看到,一切正常,得到了一个很好的bean对象student
接下来,我们进行改造
如果我们将Student里面的 setId()方法去掉会怎么样?运行一下试试?
Invalid property 'id' of bean class [com.bean.Student]: Bean property 'id' is not writable or has an invalid setter method. Does the parameter type of the setter match the return type of the getter?
bean类[com.bean.Student]的属性'id'无效:Bean属性'id'不可写或具有无效的setter方法。 setter的参数类型是否与getter的返回类型匹配?
我们在bean中配置了属性,id 的值为 001,但是我却没有给它提供set方法,无法写入这个值,自然要有错误了
如果 我们改变一下setId() -> setId2()这样呢?
Invalid property 'id' of bean class [com.bean.Student]: Bean property 'id' is not writable or has an invalid setter method. Did you mean 'id2'?
bean类[com.bean.Student]的属性'id'无效:Bean属性'id'不可写或具有无效的setter方法。你是说'id2'吗?
报错信息与上边差不多,就是最后一句厉害了“Did you mean id2”,错误信息提示的非常明显
我们按照提示,把bean里的id 改为 id2 ,就可以正常运行了,或者把setId2改回去。
恢复代码原样,我们继续往下看
那,如果我们去掉Beans.xml中的<property name="id" value="001"></property>
呢?
Student [id=null, name=张三]
运行正常,没有错误,结果 id的值为空(这是当然啦,我们那就没有设置嘛)
保持Beans.xml里的代码不变
去Student类里,给 private String id;
上方加上注解 @Autowired
会发生什么呢?
No qualifying bean of type 'java.lang.String' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
没有'java.lang.String'类型的限定bean可用:至少需要有1个bean可以作为autowire候选者。依赖注释:{@ org.springframework.beans.factory.annotation.Autowired(required = true)}
看,没有String类可用,String是java里的字符串类型,spring并不会给我们自动注入
如果我们手动加上这一行,在Beans.xml中
<bean id="string1" class="java.lang.String"></bean>
把String类加上去,我们运行,就会得到结果:Student [id=, name=张三]
,spring使用了无参的构造给了我们一个String对象。
如果id的类型是 int,double,这些基本类型呢?
无论你怎么做,它都不可以运行通过,你必须显式的给他们一个值
Integer、Double等等这些基本类型无法被初始
比如:你配置了 <bean id="int_1" class="java.lang.Integer"></bean>
会有如下错误
Failed to instantiate [java.lang.Integer]: No default constructor found; nested exception is java.lang.NoSuchMethodException: java.lang.Integer.<init>()
对于基本数据类型@Autowired,的作用就是,让他们在创建时必须被初始化(经由setter方法或者是构造方法)
当然我们也可以通过改变@Autowired默认值,使其变为:@Autowired(required=false)
来解除这一限制
@Autowired(required=false)
private String id;
/*运行正常
Student [id=null, name=张三]
*/
示例代码三
这是一段运行正常的,基于xml配置的代码
bean
package com.bean;
import org.springframework.beans.factory.annotation.Autowired;
public class Student {
Book book;
Teacher teacher;
public void setBook(Book book) {
this.book = book;
}
public void setTeacher(Teacher teacher) {
this.teacher = teacher;
}
@Override
public String toString() {
return "Student [book=" + book + ", teacher=" + teacher + "]";
}
}
package com.bean;
public class Book {
public Book() {
System.out.println("new Book Object");
}
}
package com.bean;
public class Teacher {
public Teacher() {
System.out.println("new Teacher Object");
}
}
beans.xml
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<bean id="student" class="com.bean.Student">
<property name="teacher" ref="teacher"></property>
<property name="book" ref="book"></property>
</bean>
<bean id="teacher" class="com.bean.Teacher"></bean>
<bean id="book" class="com.bean.Book"></bean>
</beans>
测试类
package com.tutorialspoint;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;
import com.bean.Student;
public class MainApp {
public static void main(String[] arg) {
ApplicationContext context = new FileSystemXmlApplicationContext("src/Beans.xml");
Student obj1 = (Student) context.getBean("student");
System.out.println(obj1.toString());
}
}
/*运行结果
new Teacher Object
new Book Object
Student [book=com.bean.Book@757942a1, teacher=com.bean.Teacher@4a87761d]
*/
我们按照xml显式配置,正确的注入了Teacher、Book到Student中了
接下来,我们换种写法,使用基于注解的配置方式
首先是对Beans.xml中的变化
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<!-- 打开注解 -->
<context:annotation-config />
<bean id="student" class="com.bean.Student"></bean>
<bean id="teacher" class="com.bean.Teacher"></bean>
<bean id="book" class="com.bean.Book"></bean>
</beans>
删除了student当中显示的配置,打开了注解的方式
其次是Student类
package com.bean;
import org.springframework.beans.factory.annotation.Autowired;
public class Student {
@Autowired
Book book;
@Autowired
Teacher teacher;
@Override
public String toString() {
return "Student [book=" + book + ", teacher=" + teacher + "]";
}
}
移除了setter方法,在book和teacher方添加了@Autowire的注解
然后我们运行,看
new Book Object
new Teacher Object
Student [book=com.bean.Book@4a87761d, teacher=com.bean.Teacher@66d1af89]
结果一模一样,@Autowired注解的作用就是这个
它通过byType的自动注入方式来执行代码,有了它你将不必写setter方法,和在bean里显示配置
注意要和在基本类型上方的情形做区分
示例代码四
对于示例三的代码,你在非xml显式配置的情况下,还可以这么写。
package com.bean;
import org.springframework.beans.factory.annotation.Autowired;
public class Student {
Book book;
Teacher teacher;
@Autowired
public void setAll(Book book,Teacher teacher,int a) {
this.book = book;
this.teacher = teacher;
this.a = a;
}
@Override
public String toString() {
return "Student [book=" + book + ", teacher=" + teacher + "]" ;
}
}
把注解放在setter方法上面,spring会尝试通过byType的方式来自动注入bean 的
集合也可以支持
下面看一个示例代码
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<!-- 打开注解 -->
<context:annotation-config />
<bean id="student" class="com.bean.Student">
</bean>
<bean id="book" class="com.bean.Book"></bean>
<bean id="book2" class="com.bean.Book"></bean>
<bean id="book3" class="com.bean.Book"></bean>
</beans>
bean
package com.bean;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.springframework.beans.factory.annotation.Autowired;
public class Student {
List<Book> bookList;
Set<Book> bookSet;
Map<String,Book> bookMap;
@Autowired
public void setAll(List<Book> bookList,Set<Book> bookSet,Map<String,Book> bookMap) {
this.bookList = bookList;
this.bookSet = bookSet;
this.bookMap = bookMap;
}
@Override
public String toString() {
return "Student [\nbookList=" + bookList + "\nbookSet=" + bookSet + "\nbookMap=" + bookMap + "]";
}
public Map<String, Book> getBookMap() {
return bookMap;
}
}
/*输出
new Book Object
new Book Object
new Book Object
Student [
bookList=[com.bean.Book@18bf3d14, com.bean.Book@4fb64261, com.bean.Book@42607a4f]
bookSet=[com.bean.Book@18bf3d14, com.bean.Book@4fb64261, com.bean.Book@42607a4f]
bookMap={book=com.bean.Book@18bf3d14, book2=com.bean.Book@4fb64261, book3=com.bean.Book@42607a4f}]
*/
对于map,会将 <bean id="book1" class="....."></bean>
中的book1作为key,book对象作为值,来存入map中
@Qualifier
有没有想过,有好几个相同类型的bean,里面的内容不同,我们只需要其中一个合适的对象来注入(还懒,想通过自动注入完成),这该怎么办啊?
这个时候就可以用 @qualifier来完成哦!
像这样
package com.bean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
public class Student {
@Autowired
@Qualifier("teacher1")
Teacher teacher;
@Override
public String toString() {
return "Student [teacher=" + teacher + "]";
}
}
手动指定 ,我要 teacher1
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<!-- 打开注解 -->
<context:annotation-config />
<bean id="student" class="com.bean.Student">
</bean>
<bean id="teacher1" class="com.bean.Teacher"></bean>
<bean id="teacher2" class="com.bean.Teacher"></bean>
<bean id="teacher3" class="com.bean.Teacher"></bean>
</beans>
测试
package com.tutorialspoint;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;
import com.bean.Student;
public class MainApp {
public static void main(String[] arg) {
ApplicationContext context = new FileSystemXmlApplicationContext("src/Beans.xml");
Student obj1 = (Student) context.getBean("student");
System.out.println(obj1.toString());
}
}
/*
new Teacher Object
com.bean.Teacher@3e57cd70
new Teacher Object
com.bean.Teacher@5bb21b69
new Teacher Object
com.bean.Teacher@6b9651f3
Student [teacher=com.bean.Teacher@3e57cd70]
*/
可以看到第一个被创建出来的Teacher对象地址 是“@3e57cd70”,而注入到Student里的是“@3e57cd70”,是一样的哦。
这样就拿到了我们想要的了
JSR注解
我们简单的介绍一下
- @PostConstruct 创建行
@PostConstruct
public void init(){
System.out.println("Bean is going through init.");
}
- @PreDestroy 销毁执行
@PreDestroy
public void destroy(){
System.out.println("Bean will destroy now.");
}
- @Resource 使用一个 ‘name’ 属性,该属性以一个 bean 名称的形式被注入。它遵循 by-name 自动连接语义
@Resource(name= "spellChecker")
public void setSpellChecker( SpellChecker spellChecker ){
this.spellChecker = spellChecker;
}
如果没有明确地指定一个 ‘name’,默认名称源于字段名或者 setter 方法。在字段的情况下,它使用的是字段名;在一个 setter 方法情况下,它使用的是 bean 属性名称。
java注解
- @Configuration 和 @Bean 注解
带有 @Configuration 的注解类表示这个类可以使用 Spring IoC 容器作为 bean 定义的来源。@Bean 注解告诉 Spring,一个带有 @Bean 的注解方法将返回一个对象,该对象应该被注册为在 Spring 应用程序上下文中的 bean。
@Configuration
public class HelloWorldConfig {
@Bean
public HelloWorld helloWorld(){
return new HelloWorld();
}
}
等同于
<beans>
<bean id="helloWorld" class="com.tutorialspoint.HelloWorld" />
</beans>
还可以指定生命周期回调方法
@Configuration
public class AppConfig {
@Bean(initMethod = "init", destroyMethod = "cleanup" )
public Foo foo() {
return new Foo();
}
}
指定范围
@Configuration
public class AppConfig {
@Bean
@Scope("prototype")
public Foo foo() {
return new Foo();
}
}
- @import 注解允许从另一个配置类中加载 @Bean 定义。
@Configuration
@Import(ConfigA.class)
public class ConfigB {
@Bean
public B a() {
return new A();
}
}