Spring框架中有很多可用的注解,模式注解(Stereotype Annotations) 是其中的一类。首先来看一段官方对Stereotype Annotations的定义。
A stereotype annotation is an annotation that is used to declare the role that a component plays within the application. For example, the @Repository annotation in the Spring Framework is a marker for any class that fulfills the role or stereotype of a repository (also known as Data Access Object or DAO).
模式注解是用于声明组件在应用中所扮演角色的注解。例如,Spring中的@Repository标注在任何扮演仓储角色类上。
模式注解包括:@Component, @Service, @Repository, @Controller, @RestController和@Configuration等。
@Component@Service@Repository@Controller
@Service, @Repository, @Controller这三个注解目前在功能上和@Component是完全一样的,只要在相应的类上标注这些注解,就能成为 Spring 中组件(Bean)。那为什么还需要多个不同的注解?
不同的模式注解虽然功能相同,但是代表含义却不同。@Controller通常用于标注控制层,@Service用于标注服务层,@Repository用于标注数据控制层。从基本功能上来讲都是@Component所赋予他们的功能。在 Spring 中任何标注 @Component 的组件都可以成为扫描的候选对象。类似的,任何使用 @Component 标注的注解,如 @Service,当其标注组件时,也会被当做扫描的候选对象。
这几个注解的使用方式很简单,只要标注在类上就可以
@Component
public class Diana implements ISkill {
public Diana() {
System.out.println("Hello Diana");
}
}
@Configuration
@Configuration里面也包含@Component,也可以实现以上几个注解相同的效果,但是在使用方法与以上几个注解不太一样。
使用方法
public class Camille implements ISkill {
private String name;
private Integer age;
public Camille(String name,Integer age) {
this.name = name;
this.age = age;
}
public Camille() {
System.out.println("Hello Camille");
}
public void r(){
System.out.println("Camille R");
}
}
-----------------------------------------------------------------------
@Configuration
public class HeroConfiguration {
@Bean
public ISkill camille(){
return new Camille();
}
}
使用配置类的方式和在类上加@Component可以起到相同的效果
更多详细的使用方法在这里就不做过多赘述,请看另一篇文章,总结的很详细。
使用@Configuration的意义
既然@Configuration可以实现@Component及其衍生注解相同的效果那为什么还需要@Configuration?
@Component实际应用中使用的比较多,但是单纯的使用@Component对于我们处理变化意义不是很大,还需要和其它的注解进一步去搭配组合才能真正解决变化的问题。
对于面向对象编程来说,一个类除了行为之外还有特征。类中的方法体现了类的行为,类中的字段体现了类的特征。而单纯的@Component不能给成员变量赋值。
@Configuration和@Bean的组合就可以实现。除此之外,一个@Configuration配置类下面可以有多个@Bean
@Configuration
public class HeroConfiguration {
@Bean
public ISkill camille(){
return new Camille("Camille",18);
}
@Bean
public ISkill irelia(){
return new Irelia();
}
}
@Configuration相当于以前xml配置文件中的 < beans > ,@Bean就相当于原来的 < bean > 。上面的代码就相当于:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" 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">
<bean id="camille" class="com.lin.sample.hero.camille">
<property name="name" value="Camille"></property>
<property name="age" value="18"></property>
</bean>
<bean id="irelia" class="com.lin.sample.hero.irelia"></bean>
</beans>
配置
要想理解@Configuration标注配置类的意义,还要说说为什么Spring如此的偏爱配置?OCP原则是为了解决变化,变化是不能被消除的只能被隔离,或是反映到配置文件
为什么要把变化隔离到配置文件?
1.配置文件具有集中性
2.配置文件清晰(没有业务逻辑)
配置分类
1.常规配置 key:value
2.xml配置 类/对象
应用举例
public class MySQL implements IConnected {
private String ip = "localhost";
private Integer port = 3306;
public MySQL() {}
public MySQL(String ip, Integer port) {
this.ip = ip;
this.port = port;
}
public void setIp(String ip) {
this.ip = ip;
}
public void setPort(Integer port) {
this.port = port;
}
@Override
public void connect(){
System.out.println(this.ip+":"+this.port);
}
}
@Configuration
public class DatabaseConfiguration {
@Value("${mysql.ip}")
private String ip;
@Value("${mysql.port}")
private Integer port;
@Bean
public IConnected getMySQL(){
return new MySQL(this.ip,this.port);
}
}
我们可能会觉得这种写法比较麻烦,是否可以把配置文件中的这两个值直接绑定到MySQL的两个属性,而不是像现在这样在配置类中读取?可以是可以的。但是这样DatabaseConfiguration
的意义就不大了。配置类实质上起到了两个作用:读取配置文件和把Bean加入到IOC容器。如果我们直接把配置文件的值绑定到MySQL
,然后在MySQL
上加@Component
,这样看似配置类就没有什么用了。我们还是要回到之前提到的两个变化:
1.制定一个interface,然后用多个类实现同一个interface。—— 策略模式
2.一个类,修改属性解决变化。
一个变化是通过更改类的属性,那直接绑定配置文件是可以解决第二种变化的,但是在某些情况下既存在第二种变化又存在第一种变化。上面的例子更改ip和port实质上是属于第二种变化。那第一种变化呢?比如以后不想用MySQL
想要换成Oracle
。通过配置类我们可以有选择的注入某一个Bean,比如跟条件注解组合的方式
1.使用配置类的形式更加灵活,可以通过一些处理有选择的把一些什么样的类注入到容器里。
2.通过一个配置类可以把所需要的一组Bean批量导入到容器中,方便统一管理某一方向的所有配置类
这也就是为什么在有了@Component
的基础上还需要有配置类,Springboot其中很多的内置类都是采用的这种形式。比如org.springframework.boot:spring-boot-autoconfigure
下面的mongo -> MongoProperties
读取配置文件
@ConfigurationProperties(prefix = "spring.data.mongodb")
public class MongoProperties {
MongoAutoConfiguration
配置类
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(MongoClient.class)
@EnableConfigurationProperties(MongoProperties.class)
@ConditionalOnMissingBean(type = "org.springframework.data.mongodb.MongoDbFactory")
public class MongoAutoConfiguration {
@Bean
@ConditionalOnMissingBean(type = { "com.mongodb.MongoClient", "com.mongodb.client.MongoClient" })
public MongoClient mongo(MongoProperties properties, ObjectProvider<MongoClientOptions> options,Environment environment) {
return new MongoClientFactory(properties, environment).createMongoClient(options.getIfAvailable());
}
}
@Configuration是一种编程模式
我们在学习其他框架时都能看到清晰的调用关系,但是在学习Spring的时候却发现很多时候调用关系并不是非常明确。为什么很多时候我们找不到明确的调用关系?是因为很多东西都是从容器中给我们注入进来的。Spring在解决OCP问题的同时也带来了一些困扰,有时我们在阅读源码时不是特别清晰易懂,因为缺少了明确的调用关系。只有非常熟悉Spring的特性才能更容易的看懂源码。我们写的很多对象都是通过标注注解的方式,将其加入到IOC容器,在使用的时候是在内部进行调用,看不出一个很明确的调用关系。
这里举例的@Configuration
和@Bean
只是自动配置最基本的原理,真实情况下还会组合其他注解去应对更加复杂的场景。
大家也不要误认为在一个类上面打上了一个@Configuration这个类下面就必须要有@Bean。例如框架中的MailSenderAutoConfiguration
中并没有@Bean,但是它又导入了另一个类MailSenderJndiConfiguration
,在MailSenderJndiConfiguration
中是有@Bean的
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ MimeMessage.class, MimeType.class, MailSender.class })
@ConditionalOnMissingBean(MailSender.class)
@Conditional(MailSenderCondition.class)
@EnableConfigurationProperties(MailProperties.class)
@Import({ MailSenderJndiConfiguration.class, MailSenderPropertiesConfiguration.class })
public class MailSenderAutoConfiguration {
/**
* Condition to trigger the creation of a {@link MailSender}. This kicks in if either
* the host or jndi name property is set.
*/
static class MailSenderCondition extends AnyNestedCondition {
MailSenderCondition() {
super(ConfigurationPhase.PARSE_CONFIGURATION);
}
@ConditionalOnProperty(prefix = "spring.mail", name = "host")
static class HostProperty {
}
@ConditionalOnProperty(prefix = "spring.mail", name = "jndi-name")
static class JndiNameProperty {
}
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(Session.class)
@ConditionalOnProperty(prefix = "spring.mail", name = "jndi-name")
@ConditionalOnJndi
class MailSenderJndiConfiguration {
private final MailProperties properties;
MailSenderJndiConfiguration(MailProperties properties) {
this.properties = properties;
}
@Bean
JavaMailSenderImpl mailSender(Session session) {
JavaMailSenderImpl sender = new JavaMailSenderImpl();
sender.setDefaultEncoding(this.properties.getDefaultEncoding().name());
sender.setSession(session);
return sender;
}
@Bean
@ConditionalOnMissingBean
Session session() {
String jndiName = this.properties.getJndiName();
try {
return JndiLocatorDelegate.createDefaultResourceRefLocator().lookup(jndiName, Session.class);
}
catch (NamingException ex) {
throw new IllegalStateException(String.format("Unable to find Session in JNDI location %s", jndiName), ex);
}
}
}
Spring通过@Configuration和@Bean的组合给了我们一个通用的编程模式,我们只要采用@Configuration写出的代码都是符合OCP原则的代码。既可以帮我们很方便地把Bean和配置文件绑定在一起同时还可以帮我们把这个Bean加入到IOC容器,这就是@Configuration厉害的地方。