Spring之模式注解

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.配置文件清晰(没有业务逻辑)

扫描二维码关注公众号,回复: 11305074 查看本文章

配置分类

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厉害的地方。

猜你喜欢

转载自blog.csdn.net/xfx_1994/article/details/103870541