说明:由于能更好的说明IOC,本文章基于Spring 4.2.4版本。
此篇为基于注解的IOC的下篇,有关于上篇请看Spring入门到精通之基于XML的IOC(上篇)。
从上篇可以看到基于XML的方式还是比较繁琐的,感觉就是在面向XML编程……因此Spring框架的大佬也考虑到这一点,因此开发出基于注解的方式来配置,接下来看下注解是怎么定义的吧!
一、IOC基于注解
// value参数可以省略,默认是类的短名称,开头字母小写。
@Component(value = "customerService")
public class CustomerServiceImpl implements CustomerService {
//
}
上述代码等同于XML中的定义
<bean id="customerService" class="com.youngledo.service.impl.CustomerServiceImpl"></bean>
由代码可知就是增加了一个叫@Component
的注解,这个注解就相当于在商品中配置的<bean id="customerService" class="com.youngledo.service.impl.CustomerServiceImpl"></bean>
,这样是不是简单多了?但从@Component
单词翻译过来就是组件的意思,为了更好的区分不同层次的业务,因此Spring框架又定义了这些注解:
@Controller
:代表控制层@Service
:代表业务层@Reponsitory
:代表数据层
而@Component
你可以用在其它层面。
接下来获取以下对象看看:
// 1. 实例化Spring上下文对象,并使用上述的bean.xml文件来加载。加载的同时会使用文件中定义好的bean来创建对象
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
// 2. 根据bean的id获取对象
CustomerService customerService = (CustomerService)ac.getBean("customerService");
运行之后,结果Spring抛出了NoSuchBeanDefinitionException
异常,原因是因为Spring框架找不到这个被注解标注的Bean对象。这又是怎么回事,难道配置有误?其实不是,原因是因为这样只是声明了该类是一个可以被Spring框架认识的Bean类,但要想被Spring框架加载,还得配置一下Spring能找到的路径,在XML配置文件如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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">
<!--
告知Spring框架在创建容器(Bean容器)时要扫描的包名,配置完之后Spring框架就会去找该包下被上述注解标记的类,然后完成创建。
注意:该标签需要导入context相关的命名空间
-->
<context:component-scan base-package="com.youngledo"></context:component-scan>
</beans>
配置完后,重新运行即可。
从上述可以看出,最后在加载的时候还是少不了需要在XML中配置,并且这还只是声明了一个@Component,如果还需要配置其它的功能(比如事务、数据源等等)还得在这里面配置,这样看来是不是觉得还是抛弃不了XML,还是很繁琐?是的,没错,所以Spring框架大佬也考虑到了这一点,又新增了一个新的注解来替代context:component-scan
标签即@ComponentScan
。但如果想彻底或者说更方便的使用Spring框架,可以使用Spring大佬新起的神器,那就是——Spring Boot。不过Spring Boot也是基于Spring的,因此掌握好Spring原始的配置,非常利于掌握、理解Spring Boot,包括后续的Spring Cloud。
二、基于注解的注入
关于XML的注入方式,请看上篇的基于XML的注入
,接下来看下使用注解是怎么做的。
2.1. @Autowired
自动按照数据类型注入。
@Service
public class CustomerServiceImpl implements CustomerService {
@Autowired
private CustomerDao customerDao;
}
@Reponsitory("customerDao")
public class CustomerDaoImpl implements CustomerDao {
// 第一个
}
注意点如下:
- 只要Spring容器中有唯一类型匹配的就能注入成功;
- 如果注入的Bean在容器中类型不唯一,则会把变量名称作为bean的id,在容器中查找,找到即注入成功。
比如上述代码中如果CustomerDao接口有多个实现类CustomerDaoImpl
和CustomerDaoImpl2
,并且给它们定义了不同的id名,因此CustomerServiceImpl一样可以把CustomerDaoImpl注入成功。@Reponsitory("customerDao") public class CustomerDaoImpl implements CustomerDao { // 第一个 } @Reponsitory("customerDao2") public class CustomerDaoImpl2 implements CustomerDao { // 第二个 }
只要不能满足上述两点,则代表注入失败,Spring框架抛出异常。
2.2. @Qualifier
由上可知,@Autowired有个弊端就是无法直接指定id的名称,那么有没有一种注解可以指定id名称呢?它就是@Qualifier
!但该注解在给类成员
注入时不能单独使用,如下:
@Qualifier("customerDao2")
@Autowired
private CustomerDao customerDao;
不过在给方法的形参
注入时则可以独立使用:
public void setCustomerDao(@Qualifier("customerDao2") CustomerDao customerDao) {
this.customerDao = customerDao;
}
2.3. @Resource
介绍了上面两种注解可以互相搭配使用,但是是不是觉得如果在需要指定id名称注入时显得很啰嗦?那么Java原生自带的注解就可以使用了:
@Resource(name = "customerDao2")
private CustomerDao customerDao;
方便快捷,需要注意的是使用name
属性来定义的。
2.4. @Value
用于注入基本数据类型
和String类型
的数据。类似的注解还有@ConfigurationProperties
、@PropertySource
,因介绍的内容不一样,具体可参考https://blog.csdn.net/wangmx1993328/article/details/81005170。
@Value(name = "养乐多")
private String name;
@Value注解还可以使用spEL表达式读取properties或yaml文件中的配置。
server.port=2020
# 或者yaml格式
server:
port: 1990
@Value(name = "${server.port}")
private Integer port;
总结:以上3个注解都是用于注入其它Bean(即被@Component、@Controller、@Service、@Repository这4个注解标记的类,以及在XML中配置的标签的类。而基本类型和String类型的注入则只能使用@Value注解来做,至于类似集合这种复杂类型Spring并没有提供相应的注解。
2.5. @Scope
用于改变Bean的作用范围,取值与XML中的scope属性一致,默认值为singleton
。
三、彻底摆脱XML
不管从上篇还是本篇的前两节来看,始终摆脱不了XML的配置,那么有没有一种方式完全不使用XML呢,全部使用注解的方式呢?答案是有的。不过在使用注解之前,我们先思考一个问题:假如我们要使用第三方定义的类,或者我自定义的类的参数需要用到第三方的类,那该如何配置呢?为了更好的描述问题,还是以上篇的XML中定义的示例来说明:
<bean id="customerService" class="com.youngledo.service.impl.CustomerServiceImpl">
<constructor-arg name="host" value="localhost"></constructor-arg>
<constructor-arg name="port" value="8080"></constructor-arg>
<!-- 使用下面定义的Bean对象,通过ref来赋值 -->
<constructor-arg name="date" ref="now"></constructor-arg>
</bean>
<!-- 配置Date类的实例,会去调用Date类的默认构造方法 -->
<bean id="now" class="java.util.Date"></bean>
该实例中host和port分别是字符串和整型,这个都好说能很方便的注入,但是now却是java.util.Date
的实例,上述可以使用Spring提供的bean标签来配置,并且调用java.util.Date
默认构造方法来获取实例的,如果是用注解呢?
3.1. @Bean
该注解等同于XML中的bean
标签,并且该注解的属性name
可以指定bean的id,不指定默认是方法名首字母小写。
@Configuration
public class BeanConfiguration {
@Bean
public Date now() {
return new Date();
}
}
对比上述的<bean id="now" class="java.util.Date"></bean>
,区别就在于定义方式不一样。
这种还是最简单的方式,如果该bean还需要依赖其它的bean又该如何定义呢?
/**
* 自定义一个配置类,并非Spring官方配置
*/
@Configuration
public class JdbcConfiguration {
/**
* QueryRunner需要依赖DataSource实例,DataSource实例也需要进行注入。
*/
@Bean(name = "runner"
public QueryRunner queryRunner(DataSource dataSource) {
return new QueryRunner(dataSource);
}
@Bean
public DataSource dataSource() {
ComboPooledDateSource dataSource = new ComboPooledDateSource();
// 省略中间的参数设置和异常处理
return dataSource;
}
}
由此可见,如果有依赖关系,都可以使用这种方式来定义。
3.2. @Configuration
但需要注意的是使用使用@Bean
注解的前提是该类必须被@Configuration
标注(当然这个类还可以与ClassPathXmlApplicationContext配合使用,来指定配置类),表示当前类是Spring框架的配置类,也相当于上篇的XML的bean.xml
的配置。而要想完全不使用xml的配置则需要使用AnnotationConfigApplicationContext
来加载Spring容器。
接下来看下如果使用注解标注的对象:
// 注意使用的是AnnotationConfigApplicationContext类
ApplicationContext ac = new AnnotationConfigApplicationContext(BeanConfiguration.class);
QueryRunner runner = (QueryRunner)ac.getBean("runner");
3.3. @Import
在讲解该注解之前,我们先要弄清楚一个事情,那就是如果我们有很多类似的@Configuration
配置类,但这些类并非Spring官方的配置,只不过是我们为了业务的需要定义的配置类,但我并不想或者完全没必要让该类成为Spring容器的实例,只想让该配置类中被@Bean
标注的对象成为Spring容器的实例。或者说该配置类仅仅提供了Spring容器加载的Bean对象,而没有其它普通方法时,我们没必要在类上使用类似@Configuration
或者@Component
的注解。那该如何做呢?
/**
* 自定义一个配置类,注意没有使用@Configuration来标注
*/
public class JdbcConfiguration {
/**
* QueryRunner需要依赖DataSource实例,DataSource实例也需要进行注入。
*/
@Bean(name = "runner"
public QueryRunner queryRunner(DataSource dataSource) {
return new QueryRunner(dataSource);
}
@Bean
public DataSource dataSource() {
ComboPooledDateSource dataSource = new ComboPooledDateSource();
// 省略中间的参数设置和异常处理
return dataSource;
}
}
再来定义一个统一的配置类
/**
* 注意我们在这个类中使用@Import注解来统一导入其它配置类,
*/
@ComponentScan({"com.youngledo"})
@Import({JdbcConfiguration.class, RedisConfiguration.class})
@Configuration
public class GlobalConfiguration {
}
说明,但一般来说使用该注解有个麻烦的事就是只要新增了一个配置类,那么就需要在上述代码中增加一个,显得有些麻烦,所以大家一般直接使用@Configuration
注解了事了。
四、总结
由此可见@Component
以及@Bean
这两个注解都可以让类称为Spring容器中的实例,那么它们到底有何区别呢,或者说在什么时候用呢?
- @Component只能作用在类上,而@Bean只能作用在方法和注解上。
- @Component一般来说用在我们自定义的类上,而像有的第三方类并没有使用@Component来标注,但你也想把它放在Spring容器中方便后续的使用,那么就可以使用@Bean来标注。
当然要想彻彻底底的完全抛弃XML的配置方式,说白了就是完全使用注解的方式来做了,因此随着Spring的发展,Spring Boot应运而生了,因此在学习Spring Boot之前还是很有必要学习Spring相关的知识的,否则你就是基于应用层开发不知背后的原理。