Spring 基于注解装配Bean
前言
在 Spring 中,尽管可以使用 XML 配置文件实现 Bean 的装配工作,但如果应用中 Bean 的数量较多,会导致 XML 配置文件过于臃肿,从而给维护和升级带来一定的困难。
Java 从 JDK 5.0 以后,提供了 Annotation(注解)功能,Spring 2.5 版本开始也提供了对 Annotation 技术的全面支持,我们可以使用注解来配置依赖注入。
Spring 默认不使用注解装配 Bean,因此需要在配置文件中添加 <context:annotation-config/>
,启用注解。
- 注解:就是一个类,使用@注解名称
- 开发中:使用注解 取代 xml配置文件。
什么是注解
Annontation是Java5开始引入的新特征,中文名称叫注解。
它提供了一种安全的类似注释的机制,用来将任何的信息或元数据(metadata)与程序元素(类、方法、成员变量等)进行关联。为程序的元素(类、方法、成员变量)加上更直观更明了的说明,这些说明信息是与程序的业务逻辑无关,并且供指定的工具或框架使用。Annontation像一种修饰符一样,应用于包、类型、构造方法、方法、成员变量、参数及本地变量的声明语句中。
Java注解是附加在代码中的一些元信息,用于一些工具在编译、运行时进行解析和使用,起到说明、配置的功能。注解不会也不能影响代码的实际逻辑,仅仅起到辅助性的作用。包含在 java.lang.annotation 包中。
注解的用处:
1、生成文档。这是最常见的,也是java 最早提供的注解。常用的有@param @return 等
2、跟踪代码依赖性,实现替代配置文件功能。比如Dagger 2依赖注入,未来java开发,将大量注解配置,具有很大用处;
3、在编译时进行格式检查。如@override 放在方法前,如果你这个方法并不是覆盖了超类方法,则编译时就能检查出。
更多注解信息点击这里-JAVA 注解的基本原理 - Single_Yam - 博客园 (cnblogs.com)
为什么要使用注解装配Bean
传统的Spring做法是使用.xml文件来对bean进行注入或者是配置aop、事物,这么做有两个缺点:
- 如果所有的内容都配置在.xml文件中,那么.xml文件将会十分庞大;如果按需求分开.xml文件,那么.xml文件又会非常多,总之这将导致配置文件的可读性与可维护性变得很低。
- 在开发中在.java文件和.xml文件之间不断切换,是一件麻烦的事,同时这种思维上的不连贯也会降低开发的效率。
为了解决这两个问题,Spring引入了注解,通过"@XXX"的方式,让注解与Java Bean紧密结合,大大减少了配置文件的体积,又增加了Java Bean的可读性与内聚性。
使用Spring注解
0.启用组件扫描
1)使用XML进行扫描
<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.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 组件扫描,扫描含有注解的类 -->
<context:component-scan base-package="com.spring.learn2" />
</beans>
配置属性<context:component-scan base-package="com.spring.learn2" />
其中`base-package
2) @ComponentScan注解启用了组件扫描
package com.spring.learn2.config;
import org.springframework.context.annotation.ComponentScan;
@ComponentScan
public class BeanConfig {}
package com;
import com.spring.learn2.config.BeanConfig;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class MainApp {
public static void main(String[] args) {
ApplicationContext applicationContext =
new AnnotationConfigApplicationContext(BeanConfig.class);
String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
for (String beanName : beanDefinitionNames) {
System.out.println("beanName: " + beanName);
}
}
}
运行效果
beanName: org.springframework.context.annotation.internalConfigurationAnnotationProcessor
beanName: org.springframework.context.annotation.internalAutowiredAnnotationProcessor
beanName: org.springframework.context.annotation.internalCommonAnnotationProcessor
beanName: org.springframework.context.event.internalEventListenerProcessor
beanName: org.springframework.context.event.internalEventListenerFactory
beanName: beanConfig
除了 spring 本身注册的一些 bean 之外,可以看到最后一行,已经将 BeanConfig 这个类注册进容器中了。
创建一个配置类,在配置类上添加 @ComponentScan 注解。该注解默认会扫描该类所在的包下所有的配置类,相当于之前的 <context:component-scan>
。
类BeanConfig通过Java代码定义了Spring的装配规则。观察可知,BeanConfig类并没有显式地声明任何bean,只不过它使用了@ComponentScan注解,这个注解能够在Spring中启用组件扫描
如果没有其他配置的话,@ComponentScan默认会扫描与配置类相同的包。因为BeanConfig类位于com.spring.learn2.config包中,因此Spring将会扫描这个包以及这个包下的所有子包,查找带有@Component注解的类。这样的话,就能发现使用了注解的类,并且会在Spring中自动为其创建一个Bean
3)指定要扫描的包
(使用@ComponentScan 的 valule 属性来配置)
@ComponentScan(value = "com.spring.learn2")
public class BeanConfig {
}
excludeFilters 和 includeFilters 的使用
使用 excludeFilters 来按照规则排除某些包的扫描。
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.FilterType;
import org.springframework.stereotype.Controller;
@ComponentScan(value = "com.spring.learn2",
excludeFilters = {@Filter(type = FilterType.ANNOTATION,
value = {Controller.class})})
public class BeanConfig {
}
excludeFilters
的参数是一个 Filter[]
数组,然后指定 FilterType
的类型为 ANNOTATION
,也就是通过注解来过滤,最后的 value
则是Controller
注解类。
配置之后,在 spring 扫描的时候,就会跳过 com.spring.learn2 包下,所有被 @Controller 注解标注的类。
使用 includeFilters 来按照规则只包含某些包的扫描。
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.FilterType;
import org.springframework.stereotype.Controller;
@ComponentScan(value = "com.spring.learn2", includeFilters = {@Filter(type = FilterType.ANNOTATION, classes = {Controller.class})})
public class BeanConfig {
}
spring 默认会自动发现被 @Component、@Repository、@Service 和 @Controller 标注的类,并注册进容器中。要达到只包含某些包的扫描效果,就必须将这个默认行为给禁用掉(在 @ComponentScan 中将 useDefaultFilters 设为 false 即可)。
@ComponentScan 的 useDefaultFilters 属性,该属性默认值为 true。
1. 定义Bean@Component
需要在类上使用注解@Component,该注解的value属性用于指定该bean的id值。
@Component(value = "role")
public class Role {
private Long id;
private String roleName;
private String note;
}
2. Bean的作用域@Scope
需要在类上使用注解@Scope,其value属性用于指定作用域。默认为singleton。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
@Scope("prototype")
@Component("RoleService")
public class RoleServiceImpl implements RoleService {
...
}
3. 基本类型属性注入@Value
需要在属性上使用注解@Value,该注解的value属性用于指定要注入的值。
使用该注解完成属性注入时,类中无需setter。当然,若属性有setter,则也可将其加到setter上。
package com.spring.learn2;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component(value = "role")
public class Role {
@Value("1")
private Long id;
@Value("role_name_1")
private String roleName;
@Value("role_note_1")
private String note;
}
4.按类型注入域属性@Autowired
@Autowired是spring的注解
需要在域属性上使用注解@Autowired,该注解默认使用按类型自动装配Bean的方式。
使用该注解完成属性注入时,类中无需setter。当然,若属性有setter,则也可将其加到setter上。
package com.spring.learn2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
@Scope("prototype")
@Component("RoleService")
public class RoleServiceImpl implements RoleService {
@Autowired
private Role role;
/**** setter and getter ****/
public Role getRole() {
return role;
}
public void setRole(Role role) {
this.role = role;
}
}
5. 按名称注入域属性@Autowired与@Qualifier
需要在域属性上联合使用注解@Autowired与@Qualifier。
@Qualifier的value属性用于指定要匹配的Bean的id值。同样类中无需setter,也可加到setter上。
package com.spring.learn2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
@Scope("prototype")
@Component("RoleService")
public class RoleServiceImpl implements RoleService {
@Autowired(required =false)
@Qualifier("role")
private Role role;
/**** setter and getter ****/
public Role getRole() {
return role;
}
public void setRole(Role role) {
this.role = role;
}
}
@Autowired还有一个属性required,默认值为true,表示当匹配失败后,会终止程序运行。若将其值设置为false,则匹配失败,将被忽略,未匹配的属性值为null。
6. 域属性注解@Resource
@Resource是java自带的注解
Spring提供了对JSR-250规范中定义@Resource标准注解的支持。@Resource注解既可以按名称匹配Bean,也可以按类型匹配Bean。使用该注解,要求JDK必须是6及以上版本。
1. 按类型注入域属性
@Resource注解若不带任何参数,则会按照类型进行Bean的匹配注入。
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
@Component("RoleService")
public class RoleServiceImpl implements RoleService {
@Resource
private Role role;
/**** setter and getter ****/
public Role getRole() {
return role;
}
public void setRole(Role role) {
this.role = role;
}
}
2. 按名称注入域属性
@Resource注解指定其name属性,则name的值即为按照名称进行匹配的Bean的id。
package com.spring.learn2;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
@Component("RoleService")
public class RoleServiceImpl implements RoleService {
@Resource(name="role")
private Role role;
/**** setter and getter ****/
public Role getRole() {
return role;
}
public void setRole(Role role) {
this.role = role;
}
}
7. Bean的生命始末@PostConstruct与@PreDestroy
在方法上使用@PostConstruct,与原来的init-method等效。在方法上使用@PreDestroy,与destroy-method等效。
package com.spring.learn2;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.annotation.Resource;
@Component("RoleService")
public class RoleServiceImpl implements RoleService {
@Resource(name="role")
private Role role;
@PostConstruct
public void init(){
System.out.println("Bean初始化后执行");
}
@PreDestroy
public void destroy(){
System.out.println("Bean销毁前执行");
}
/**** setter and getter ****/
public Role getRole() {
return role;
}
public void setRole(Role role) {
this.role = role;
}
}
Spring 中常用的注解
@Component
取代<bean class="">
。
@Component("id")
取代 <bean id="" class="">
web开发,提供3个@Component注解衍生注解(功能一样)取代<bean class="">
@Repository
:dao层 。
@Service
:service层 。
@Controller
:web层。
@Component
可以使用此注解描述 Spring 中的 Bean,但它是一个泛化的概念,仅仅表示一个组件(Bean),并且可以作用在任何层次。使用时只需将该注解标注在相应类上即可。
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component(value = "role")
public class Role {
@Value("1")
private Long id;
@Value("role_name_1")
private String roleName;
@Value("role_note_1")
private String note;
}
- 注解@Component代表Spring IoC会把这个类扫描生成Bean实例,而其中的value属性代表这个类在Spring中的id,这就相当于XML方式定义的Bean的id,也可以简写成@Component("role"),甚至直接写成@Component,对于不写的,Spring IoC容器就默认类名,但是以首字母小写的形式作为id,为其生成对象,配置到容器中。
- 注解@Value代表的是值的注入,这里只是简单注入一些值,其中id是一个long型,注入的时候Spring会为其转化类型。
@Repository
用于将数据访问层(DAO层)的类标识为 Spring 中的 Bean,其功能与 @Component 相同。
@Service
通常作用在业务层(Service 层),用于将业务层的类标识为 Spring 中的 Bean,其功能与 @Component 相同。
@Controller
通常作用在控制层(如 Struts2 的 Action、SpringMVC 的 Controller),用于将控制层的类标识为 Spring 中的 Bean,其功能与 @Component 相同。
@Autowired
自动装配。
通过学习Spring IoC容器,我们知道Spring是先完成Bean的定义和生成,然后寻找需要注入的资源。也就是当Spring生成所有的Bean后,如果发现这个注解,他就会在Bean中查找,然后找到对应的类型,将其注入进来,这样就完成依赖注入了。
所谓自动装配技术是一种由Spring自己发现对应的Bean,自动完成装配工作的方式,它会应用到一个十分常用的注解@Autowired上,这个时候Spring会根据类型去寻找定义的Bean然后将其注入,这里需要留意按类型(Role)的方式。
可以应用到 Bean 的属性变量、属性的 setter 方法、非 setter 方法及构造函数等,配合对应的注解处理器完成 Bean 的自动配置工作。默认按照 Bean 的类型进行装配。
-
示例
role
package com.spring.learn2; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; @Component(value = "role") public class Role { @Value("1") private Long id; @Value("role_name_1") private String roleName; @Value("role_note_1") private String note; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getRoleName() { return roleName; } public void setRoleName(String roleName) { this.roleName = roleName; } public String getNote() { return note; } public void setNote(String note) { this.note = note; } }
RoleService
package com.spring.learn2; public interface RoleService { public void printRoleInfo(); }
package com.spring.learn2; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @Component("RoleService") public class RoleServiceImpl implements RoleService { @Autowired private Role role; @Override public void printRoleInfo() { System.out.println("id =" + role.getId()); System.out.println("roleName =" + role.getRoleName()); System.out.println("note =" + role.getNote()); } /**** setter and getter ****/ public Role getRole() { return role; } public void setRole(Role role) { this.role = role; } }
MainApp
package com; import com.spring.learn2.RoleService; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class MainApp { public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("Beans.xml"); RoleService uc = (RoleService) ctx.getBean("RoleService"); uc.printRoleInfo(); } }
Beans.xml
扫描含有注解类
<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.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- 组件扫描,扫描含有注解的类 --> <context:component-scan base-package="com.spring.learn2" /> </beans>
这里的@Autowired注解,表示在Spring IoC定位所有的Bean后,这个字段需要按类型注入,这样IoC容器就会寻找资源,然后将其注入。。比如代码Role和代码RoleServiceImpl的两个Bean,假设将其定义,那么Spring IoC容器会为它们先生成对应的实例,然后依据@Autowired注解,按照类型找到定义的实例,将其注入。
IoC容器有时候会寻找失败,在默认的情况下寻找失败它就会抛出异常,也就是说默认情况下,Spring IoC容器会认为一定要找到对应的Bean来注入这个字段,有些时候这并不是一个真实的需要,比如日志,有时候我们会觉得这是可有可无的,这个时候可以通过@Autowired的配置项required来改变它,比如@Autowired(required=false)。
正如之前所谈到的在默认情况下是必须注入成功的,所以这里的required的默认值为true。当把配置修改为了false时,就告诉Spring IoC容器,假如在已经定义好的Bean中找不到对应的类型,允许不注入,这样也就没有了异常抛出,只是这样这个字段可能为空,读者要自行校验,以避免发生空指针异常。在大部分的情况下,都不需要这样修改。
@Autowired除可以配置在属性之外,还允许方法配置,常见的Bean的setter方法也可以使用它来完成注入。
@Resource
作用与 Autowired 相同,区别在于 @Autowired 默认按照 Bean 类型装配,而 @Resource 默认按照 Bean 实例名称进行装配。
@Resource 中有两个重要属性:name 和 type。
Spring 将 name 属性解析为 Bean 的实例名称,type 属性解析为 Bean 的实例类型。如果指定 name 属性,则按实例名称进行装配;如果指定 type 属性,则按 Bean 类型进行装配。如果都不指定,则先按 Bean 实例名称装配,如果不能匹配,则再按照 Bean 类型进行装配;如果都无法匹配,则抛出 NoSuchBeanDefinitionException 异常。
另外要注意,@Resource是java自带的注解,不是Spring中的注解。@Resource注解完整的包路径为
import javax.annotation.Resource;
示例
@Resource(name = "userServiceImpl")
private UserService userService;
@Qualifier
与 @Autowired 注解配合使用,会将默认的按 Bean 类型装配修改为按 Bean 的实例名称装配,Bean 的实例名称由 @Qualifier 注解的参数指定。
@Autowired
@Qualifier("userServiceImp")
private UserSerevice userService;
@Service
,@Controller
这些注解要放在接口的实现类上,而不是接口上面。
@Autowired
和@Resource
是用来修饰字段,构造函数,或者设置方法,并做注入的。
而@Service
,@Controller
,@Repository
,@Component
则是用来修饰类,标记这些类要生成bean。
注解使用前提,添加命名空间,让spring扫描含有注解类
示例
Beans.xml
<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.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 组件扫描,扫描含有注解的类 -->
<context:component-scan base-package="com.spring.learn1" />
</beans>
UserDao
package com.spring.learn1;
public interface UserDao {
/**
* 输出方法
*/
public void outContent();
}
UserDaoImpl
package com.spring.learn1;
import org.springframework.stereotype.Repository;
@Repository("userDao")
public class UserDaoImpl implements UserDao {
@Override
public void outContent() {
System.out.println("dao方法");
}
}
package com.spring.learn1;
public interface UserService {
/**
* 输出方法
*/
public void outContent();
}
UserServiceImpl
package com.spring.learn1;
import javax.annotation.Resource;
import org.springframework.stereotype.Service;
@Service("userService")
public class UserServiceImpl implements UserService{
@Resource(name="userDao")
private UserDao userDao;
public UserDao getUserDao() {
return userDao;
}
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
@Override
public void outContent() {
userDao.outContent();
System.out.println("service");
}
}
UserController
package com.spring.learn1;
import javax.annotation.Resource;
import org.springframework.stereotype.Controller;
@Controller("userController")
public class UserController {
@Resource(name = "userService")
private UserService userService;
public UserService getUserService() {
return userService;
}
public void setUserService(UserService userService) {
this.userService = userService;
}
public void outContent() {
userService.outContent();
System.out.println("content");
}
}
MainApp
package com;
import com.spring.learn1.UserController;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MainApp {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("Beans.xml");
UserController uc = (UserController) ctx.getBean("userController");
uc.outContent();
}
}
运行时以下错误
org.springframework.context.support.AbstractApplicationContext refresh
警告: Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'userController' defined in file [。。。]: Post-processing of merged bean definition failed; nested exception is java.lang.NoSuchMethodError: javax.annotation.Resource.lookup()Ljava/lang/String;
Exception in thread "main" org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'userController' defined in file [。。。]: Post-processing of merged bean definition failed; nested exception is java.lang.NoSuchMethodError: javax.annotation.Resource.lookup()Ljava/lang/String;
。。。。
Caused by: java.lang.NoSuchMethodError: javax.annotation.Resource.lookup()Ljava/lang/String;
检查发现不存在javax.annotation这个包
在pom.xml文件中引入
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.3.1</version>
</dependency>
运行如下
dao方法
service
content
@Resource vs @Autowired
@Resource的装配顺序如下:
- @Resource后面没有任何内容,默认通过name属性去匹配bean,找不到再按type去匹配;
- 指定了name或者type则根据指定的类型去匹配bean;
- 指定了name和type则根据指定的name和type去匹配bean,任何一个不匹配都将报错。
然后,区分一下@Autowired和@Resource两个注解的区别:
- @Autowired默认按照byType方式进行bean匹配,@Resource默认按照byName方式进行bean匹配
- @Autowired是Spring的注解,@Resource是J2EE的注解,这个看一下导入注解的时候这两个注解的包名就一清二楚了
Spring属于第三方的,J2EE是Java自己的东西,因此,建议使用@Resource注解,以减少代码和Spring之间的耦合。
总结一下:
@Resource根据name和type,是先Name后Type,@Autowired是Type,一般情况下我们最好使用@Resource。
文中详细讲解了@Service、@Autowired、@Resource和@Qualifier的用法,其中重点讲述了@Autowired、@Resource的区别,那么对于@Component、@Repository、@Controller这3个注解,文中也就开头提到,这3个注解其实和@Service一个含义,只是我们在写代码时,会进行分层,比如DAO层、Service层、Action层,分别可以用@Repository、@Service、@Controller表示,其实也就字面含义不一样,效果其实是一样的,然后@Component可以作用在任何层次。所以看起来有7个注解,其实你可以理解只有4个。