之前用到Spring AOP,于是就总结了一下,Spring的另一个特点也顺便总结一下(其实是我的强迫症犯了)。
大家最开始学习Spring时,基本上都是先学XML的配置方式,但是在实际的开发中反倒是基于注解的方式使用的更多一些(个人感觉,不喜轻喷)。所以首先总结一下Bean的基于注解的配置方式。
一、 组件扫描机制
- @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标识的类,不会扫描其他三个注解标识的类。
二、 组件装配机制

@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,说明一定要提供,如果没有则会报错,看下面实例:
@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的实例时,报如下的错误:
- 默认方式,Spring通过名称自动识别,一般我自己不用,感觉莫名其妙的总会出现一些问题,如果大家有想深入了解的可以看看源码,如果名字识别错误会出现如下错误;
- 通过使用@Qualifier来标识相关联的Bean的名称,关于这个,如果正确配置就一定不会出问题,下面给出一个例子;
@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属性配合使用,现在输出如下:
顺便说一下,当组件扫描或者组件装配涉及到接口时,只要在该接口的实现类上加入对应的注解就可以。
相关的内容可以看下面的相关文章。