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

来源:互联网 发布:魔兽世界数据库1.11 编辑:程序博客网 时间:2024/06/06 14:36

之前用到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@ResponseBodypublic @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@Componentpublic @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@Componentpublic @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@Componentpublic @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)@Documentedpublic @interface Autowired {/** * Declares whether the annotated dependency is required. * <p>Defaults to {@code true}. */boolean required() default true;}
从中可以看出,这个注解几乎可以使用在任何地方,但是使用最多的应该是字段和方法上(纯属个人观点),它还有一个属性required,这是说明在使用这个注解之后是否一定要提供相应的实例化对象,默认=为true,说明一定要提供,如果没有则会报错,看下面实例:
UserService类有一个组件UserRepository需要自动装配。
@Servicepublic class UserService {@Autowiredprivate UserRepository repository;public void execute(){System.out.println(repository);}}
@Repositorypublic 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几乎和上面的一样,只是加了一个注解:
@Servicepublic class UserService {@Autowired@Qualifier("user2Repository")private UserRepository repository;public void execute(){System.out.println(repository);}}
将UserRepository改成接口,并提供两个实现类,如下:
public interface UserRepository {}//...@Repositorypublic class User1Repository implements UserRepository {}//...@Repositorypublic class User2Repository implements UserRepository {}
如果没有使用@Qualifier,则会出现和使用默认识别行为一样的错误,另外该注解还可以和在组件扫面中说的四个注解中的value属性配合使用,现在输出如下:

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