Spring IoC学习笔记(1):注解配置Bean

本文详细介绍了Spring框架中Bean的注解配置方式,包括组件扫描机制和组件装配机制。探讨了@Component、@Controller、@Service等注解的使用场景,并深入解析了@Autowired和@Qualifier注解的工作原理。

之前用到Spring AOP,于是就总结了一下,Spring的另一个特点也顺便总结一下(其实是我的强迫症犯了)。

大家最开始学习Spring时,基本上都是先学XML的配置方式,但是在实际的开发中反倒是基于注解的方式使用的更多一些(个人感觉,不喜轻喷)。所以首先总结一下Bean的基于注解的配置方式。

一、 组件扫描机制

Spring能在Classpath下自动扫描被某些注解标识的类,并把它们放到IoC容器中,作用和使用XML配置是一样的,但是效率要高很多。
特定的组件有如下几个(不止):
  • @Component:最普通的组件,说明它能被Spring容器管理,其他的组件都是通过加入相应的具体含义在它的基础上扩展而来;
  • @Controller:标识表现层的组件;
  • @Service:标识服务层的组件;
  • @Repository:标识持久层的组件;

可以使用的组件当然不止以上区区几种,可以说,只要是任何被@Component或者由它标识的注解标识的注解(好绕啊!!)都可以被Spring的容器管理,如使用@RestController,该注解就是被@Controller和@ResponseBody标识。看源码如下:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Controller
@ResponseBody
public @interface RestController {

	/**
	 * The value may indicate a suggestion for a logical component name,
	 * to be turned into a Spring bean in case of an autodetected component.
	 * @return the suggested component name, if any
	 * @since 4.0.1
	 */
	String value() default "";

}
而上面所说的@Controller,@Service,@Repository都是被@Component标识,看源码如下:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Controller {

	/**
	 * The value may indicate a suggestion for a logical component name,
	 * to be turned into a Spring bean in case of an autodetected component.
	 * @return the suggested component name, if any
	 */
	String value() default "";

}
//...
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Service {

	/**
	 * The value may indicate a suggestion for a logical component name,
	 * to be turned into a Spring bean in case of an autodetected component.
	 * @return the suggested component name, if any
	 */
	String value() default "";

}
//...
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Repository {

	/**
	 * The value may indicate a suggestion for a logical component name,
	 * to be turned into a Spring bean in case of an autodetected component.
	 * @return the suggested component name, if any
	 */
	String value() default "";

}

综上所述,我们自己也可以自定义注解被Spring的IoC容器检测(好开心,有没有??),还有一个问题就是其实上面各种注解之间在被Spring扫描识别时,被没有任何区别,也就是可以互换使用,但是不建议这样做(毕竟人家不想那么土嘛),还是建立大家在合适的环境中使用对应的注解,并且Spring已经开始识别这几个注解了,首先就是@Repository在持久层中,已经开始作为自动异常转换的标识了,所以大家小心了。

对于扫描到的组件,我们可以通过该相应的名称在容器中获得该组件,和XML配置中的bean的id相对应,我们有两种手段处理:

  • 通过上面注解源码中value属性标识,该值默认为空字符串;
  • Springl有自己的默认命名策略,使用非限定类名(就是不带包名,说的这么高大上干嘛),第一个字母小写,例如你的类是UserService,所以你可以通过userService这个名称在容器找到对应的组件;

建议还是使用value属性来标识一下吧,毕竟默认策略里面究竟是怎么实现的,我们可能不太清楚(是我们不想清楚好吧。。。),这样百分百不会出错。

加好注解就结束了吗,骚年,还太年轻!学习Spring怎么不知道下面这条呢!

<context:component-scan base-package="..." />

我一直弄不懂这个配置到底做了什么事情,所以查了一下官方文档,发现通过它,可以注册几个Bean,其中还有几个关于它和另外一个配置的区别(你懂得),之后会另写一篇总结一下,这里就先不说了。

在这个配置信息中还可以使用许多的过滤器(自己命名的),base-package用来说明扫描哪些包及其子包,必须使用全限定名,在其中还可以指定多个包,包名之间使用逗号、分号或者空白分隔。

另外该节点下还有如下子节点:

  • <context:include-filter>:表示要包括哪些目标类;
  • <context:exclude-filter>:表示要排除哪些目标类;

这两种子节点可以同时有多个来配置不同的过滤器,这两个节点的属性也是相同的,都有type和expression属性,关于type,Spring提供了几种过滤器的类型,不过常用的只有一两种:

  • annotation:用在目标组件上的注解来进行过滤;
  • regex:用正则表达式匹配全限定类名;
  • 其他:assignable,aspectj,custom,如果想进一步了解,请查官方文档;

下面给出一个官方的示例:

<context:component-scan base-package="org.example">
    <context:include-filter type="regex" expression=".*Stub.*Repository"/>
    <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Repository"/>
</context:component-scan>

注意在使用<context:include-filter>时,如果只想扫描被特定注解指定的类,但是由于默认情况下Spring会扫描对应路径下所有带有上述四个注解的类,所以要改变这种默认行为,通过使用use-default-filters属性来实现,它默认是true,如果有需要可以改为false,例如修改上述例子:

<context:component-scan base-package="org.example" use-default-filters="false">
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Repository"/>
</context:component-scan>
这样上述的配置只会扫描被注解@Repository标识的类,不会扫描其他三个注解标识的类。

二、 组件装配机制

上面我们所说的都是单个Bean自己玩耍的情况,这样好孤独呀,所以我们要说说Bean之间怎么建立联系(有没有很激动, 羡慕),关于建立Bean之间的关系是如此简单,只需要一个注解@Autowired就可以了,别的事情Spring几乎都会帮你做,但是其中还是有一些问题值得我们注意一下。先看一下它的源码如下:
@Target({ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {

	/**
	 * Declares whether the annotated dependency is required.
	 * <p>Defaults to {@code true}.
	 */
	boolean required() default true;

}
从中可以看出,这个注解几乎可以使用在任何地方,但是使用最多的应该是字段和方法上(纯属个人观点),它还有一个属性required,这是说明在使用这个注解之后是否一定要提供相应的实例化对象,默认=为true,说明一定要提供,如果没有则会报错,看下面实例:
UserService类有一个组件UserRepository需要自动装配。
@Service
public class UserService {
	
	@Autowired
	private UserRepository repository;
	
	public void execute(){
		System.out.println(repository);
	}
}
@Repository
public class UserRepository {
	
}
下面是测试代码:
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService service = (UserService) context.getBean("userService");
service.execute();
当没有在IoC容器中提供UserRepository的实例时,报如下的错误:


当提供了required=false 后,就不会报错,但是要小心NullPointerException。输入如下:

另外如果出现这样的情况,声明一个接口,但是这个接口有多个实现类,在进行组件装配的时候会采用哪个实现类的实例,有两种方式来解决这个问题:
  • 默认方式,Spring通过名称自动识别,一般我自己不用,感觉莫名其妙的总会出现一些问题,如果大家有想深入了解的可以看看源码,如果名字识别错误会出现如下错误;
  • 通过使用@Qualifier来标识相关联的Bean的名称,关于这个,如果正确配置就一定不会出问题,下面给出一个例子;
实例如下:
UserService几乎和上面的一样,只是加了一个注解:
@Service
public class UserService {
	
	@Autowired
	@Qualifier("user2Repository")
	private UserRepository repository;
	
	public void execute(){
		System.out.println(repository);
	}
}
将UserRepository改成接口,并提供两个实现类,如下:
public interface UserRepository {
	
}
//...
@Repository
public class User1Repository implements UserRepository {

}
//...
@Repository
public class User2Repository implements UserRepository {

}
如果没有使用@Qualifier,则会出现和使用默认识别行为一样的错误,另外该注解还可以和在组件扫面中说的四个注解中的value属性配合使用,现在输出如下:

顺便说一下,当组件扫描或者组件装配涉及到接口时,只要在该接口的实现类上加入对应的注解就可以。
该注解还可以在集合类上,比如数组、容器,但是我没有用过,所以不敢说会出现什么情况,如果以后用到了再回来补充。
还有两个和@Autowired注解作用相类似的注解,@Resource,@Inject,但是本人好像很少看到,不过以后看到了也好认识。
相关的内容可以看下面的相关文章。
相关文章:
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值