我们书写的程序中,各个类之间有依赖的,需要手动实例化依赖类再赋给它。既然我们都通过IoC容器自动管理Bean了,每次使用都需要自己管理这样的依赖关系过于繁琐。
于是就有了通过配置文件的方式,使其自动注入依赖的bean。
spring提供3种装配的方式:xml装配,java装配,自动装配。
相比于
xml装配,推荐的是使用java装配。
通常使用自动装配,减少配置文件。
spring从2个角度实现自动装配:
组件扫描:spring会自动发现应用上下文中所创建的bean。自动装配:spring自动将满足的bean装配。.
├── build.gradle
└── src/
├── main/
│?? ├── java/
│?? │?? └── com/
│?? │?? └── yww/
│?? │?? ├── Main.java
│?? │?? ├── Message.java
│?? │?? ├── ReaderConfig.java
│?? │?? └── Reader.java
│?? └── resources/
│?? └── beans.xml
└── test/
├── java/
│?? └── com/
│?? └── yww/
│?? └── MainTest.java
└── resources/
build.gradle项目构建配置。
注释的部分是使用插件并直接
gradle run命令运行项目。jar{}部分的配置,可以打包成一个用java命令运行的jar包。(注意需要指定好主类的全名)
plugins {
id 'java'
// id 'application'
}
// mainClassName = 'com.yww.Main'
group 'com.yww'
version '1.0-SNAPSHOT'
sourceCompatibility = 1.8
repositories {
mavenCentral()
}
ext{
springVersion = '5.2.0.RELEASE'
}
dependencies {
compile "org.springframework:spring-core:$springVersion"
compile "org.springframework:spring-context:$springVersion"
compile "org.springframework:spring-beans:$springVersion"
compile "org.springframework:spring-expression:$springVersion"
compile "org.springframework:spring-aop:$springVersion"
compile "org.springframework:spring-aspects:$springVersion"
testCompile "junit:junit:4.12"
testCompile "org.springframework:spring-test:$springVersion"
}
jar {
from {
configurations.runtime.collect{zipTree(it)}
}
manifest {
attributes 'Main-Class': 'com.yww.Main'
}
}
Message.java通过使用@Component注解,在组件扫描时,注册bean。
@Value注解简单直接赋值。不同于xml配置文件配置bean这么繁琐。
package com.yww;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class Message {
@Value("---hello world---")
private String msg;
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = "this mssage is : " + msg;
}
}
Reader.java依赖于Message.java,使用@Autowired注入Message这个bean到Reader中。
此处可以将
@Autowired写在成员变量上,也可写在构造函数上,二者选其一皆可。(构造函数自动连线可以做一些其它操作而已)
package com.yww;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class Reader {
// @Autowired
private Message msg;
@Autowired
public Reader(Message msg){
this.msg = msg;
}
public void print(){
System.out.println(msg.getMsg());
}
}
ReaderConfig.java配置文件,主要时用于在获取应用上下文时,设置此类可以完成开启其中的配置。其类名任意。这里主要是开启组件扫描,将默认同包名下带有@Component等注解的类注册为bean。
习惯上,我们可以在此处做一些配置工作,比如设置初始值,或者如何装配。为了方便,初始值在前面已使用
@Value设置。
package com.yww;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan
public class ReaderConfig {
// 可以在配置文件中设置初始值
// 为了方便直接,就在Message的成员上使用@Value设置了值。
// @Bean
// public Message message(){
// Message message = new Message();
// message.setMsg("---hi---");
// return message;
// }
}
Main.java主函数,用于启动应用。这里展示如何获取到有依赖关系,并且按照自动装配好的bean。
不同于web应用,app需要有个主函数启动应用,常规的web应用会打包成war放入tomcat加载,不过
spring boot的web应用也可以有个主函数启动web应用。
代码里注释的部分,是使用
xml配置,演示如何获取bean。
package com.yww;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Main {
public static void main(String[] args){
// xml配置(beans.xml)
// ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
// Message msg = (Message) context.getBean("msg1");
// System.out.println(msg.getMsg());
// java配置(ReaderConfig.java) + 获取组件扫描到的bean
// 测试文件AppTest.java中,可以通过注解@ContextConfiguration加载配置文件上下文,方便使用@Autowired注解获取Bean。
ApplicationContext context = new AnnotationConfigApplicationContext(com.yww.ReaderConfig.class);
Reader reader = (Reader) context.getBean(Reader.class);
reader.print();
}
}
最后剩下测试文件MainTest.java,这里不多过问,只是可以使用@ContextConfiguration注解获取上下文的bean,在使用@Autowired自动注入bean,方便了测试。
因为为了简单,项目是非web项目,很少有帖子讲解对此的打包和运行。(web项目就很容易通过添加
war插件和gradle build打包)
运行的方法尝试出了几种:
方法1:使用idea直接运行main类即可,也可运行测试类MainTest.java。
build.gradle文件中,添加插件application,并设置好主函数的名称mainClassName。最后在项目根目录下(build.gradle同级目录)执行命令:gradle run
在
非web应用中,会发现一个问题,无法通过@Autowired获取到bean,这是由于非web应用无法知道bean,也没有提供相应的注解去处理,只能通过ApplicationContext应用上下文获取bean。而bean之间是可以通过@Autowired获取到的。
很多时候,通过组件扫描和自动装配实现自动化配置都是推荐的方式。但如果要将第三方库装配到自己的应用中,就无法使用@Component注解实现自动化装配。
要想显式配置bean,可以在配置文件中使用@Bean获取。
// src/main/java/com/yww/ReaderConfig.java
// ...
@Bean
public Message message(){
Message message = new Message();
message.setMsg("---hi---");
return message;
}
// ...
使用自动装配,不能创建多个同类型的bean。
默认bean的ID与@Bean注解的方法名一样的。
可提供name参数设置其它名称。
@Bean(name="msgX")
如果不使用组件扫描,那么一个配置如何找到另一个配置,或者bean?
此时,可以使用@Import导入那个类。
package com.yww;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
@Configuration
@Import({MessageConfig.class})
public class ReaderConfig {
// 可以在配置文件中设置初始值
// 为了方便直接,就在Message的成员上使用@Value设置了值。
// @Bean
// @Bean(name="msgX")
// public Message message(){
// Message message = new Message();
// message.setMsg("---hi---");
// return message;
// }
// 这个bean传入的名称可以任意.
// 但如果在同一个类里声明了名称,像上面那样指定了名词,这里的参数名也必须相同。
@Bean
public Reader reader(Message msg){
System.out.println(msg.getMsg());
msg.setMsg("---hello---");
return new Reader(msg);
}
}
其中,导入的另一个bean的java配置文件如下。
// src/main/java/com/yww/MessageConfig.java
package com.yww;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MessageConfig {
@Bean
public Message message(){
return new Message();
}
}
xml配置放在src/main/resources/目录下。xml配置放在web/WEB-INF/目录下,需要在web.xml中配置<context-param>。配置形如下面这个。
<?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/
<bean id="msg1" class="com.yww.Message">
<property name="msg" value="hello world"/>
</bean>
</beans>
如果不给id,将会通过全限定类名来命名,此处的bean将会被命名为com.yww.Message#0,#0是计数同类型的其它bean。
由于不推荐使用xml配置,故只是简单在此记下有哪些配置。
很多配置有
c-命名空间,p-命名空间去替代繁杂的标签。
<beans>的profile属性。<jdbc>,<jee:jndi-lookup>。<beans>嵌套<beans>。提供更多配置,实现更多装配的控制。
通常我们需要配置不同的环境,如:测试环境使用嵌入数据库h2,生产环境使用jndi获取数据库等。
(从spring4开始)
@Profile也是基于@Conditional实现。
可以使用@Profile注解在类或方法上,去生成Bean。(此处使用JavaConfig配置,也可以使用xml配置)
package com.yww;
import org.springframework.context.annotation.*;
@Configuration
@ComponentScan
@PropertySource("application.properties")
public class ReaderConfig {
@Bean
@Profile("dev")
public Message message1(){
Message message = new Message();
message.setMsg("---hi---1");
return message;
}
@Bean
@Profile("prod")
public Message message2(){
Message message = new Message();
message.setMsg("---hi---2");
return message;
}
}
@PropertySource导入配置文件。
通过两个属性确定哪个profile激活,spring.profiles.active和spring.profiles.default。
如果设置了active,就用此值确定哪个profile激活。如果没设置active,就用default确定。如果都没设置,只会创建没有定义profile的bean。
#src/main/resources/application.properties
spring.profiles.default=dev
spring.profiles.active=prod
在测试类中,可以使用
@ActiveProfiles("dev")便捷的激活配置。
有多种方式设置属性:
例,web.xml的配置。
<!-- ... -->
<context-param>
<param-name>spring.profiles.default</param-name>
<param-value>dev</param-value>
</context-param>
<!-- ... -->
程序如何找到这个.properties文件?
在app项目中,我们通过@PropertySource注解到JavaConfig类上,设置.properties配置文件的路径。
在gradle项目中,配置文件放在
src/main/resources/路径下,还可以放在这个目录下的文件夹。如:src/main/resources/demo/app.properties的设置@PropertySource("demo/app.properties")。
在web项目中,spring web已经将配置文件设置好了,不需要@PropertySource配置。
如果希望实现一个bean只有在应用的类路径下包含特定的库时才创建,或者希望某个bean只有当在另一个特定bean声明之后才会创建,或者在设置了特定的环境变量后才会创建某个bean。
通过@Conditional注解,它可以用于带有@Bean注解的方法上。如果条件计算为true就会创建bean,否则这个bean就会被忽略。
条件化bean是
spring boot自动装配的实现原理。
当配置文件中含有"reader"值时,创建bean。
目录结构
.
├── build.gradle
└── src/
├── main/
│?? ├── java/
│?? │?? └── com/
│?? │?? └── yww/
│?? │?? ├── Main.java
│?? │?? ├── Message.java
│?? │?? ├── ReaderConfig.java
│?? │?? ├── ReaderExistsCondition.java
│?? │?? └── Reader.java
│?? └── resources/
│?? ├── application.properties
└── test/
├── java/
│?? └── com/
│?? └── yww/
│?? └── MainTest.java
└── resources/
关键代码
先取消了Reader.java类的@Component注解,方便此处使用条件化方式创建Reader的bean。
设置的条件为ReaderExistsCondition.java,这个类继承接口Condition。
package com.yww;
import org.springframework.context.annotation.*;
@Configuration
@ComponentScan
@PropertySource("application.properties")
public class ReaderConfig {
@Bean
public Message message1(){
Message message = new Message();
message.setMsg("---hi---");
return message;
}
@Bean
@Conditional(ReaderExistsCondition.class)
public Reader reader(Message message){
return new Reader(message);
}
}
设置的条件为,从配置文件中寻找是否存在名为reader的配置属性,如果方法matches返回true,即会创建被设置条件的bean。
package com.yww;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotatedTypeMetadata;
public class ReaderExistsCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
Environment env = context.getEnvironment();
return env.containsProperty("reader");
}
}
Condition接口源码如下,只包含一个matches方法,如果方法返回true即会满足条件创建bean,false即不会创建。
@FunctionalInterface
public interface Condition {
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}
ConditionContext接口可以检查Bean,环境变量,资源,类。
AnnotatedTypeMetadata接口能检查带有@Bean注解的方法上还有什么其它注解。
当同一个类型有多个bean被创建,组件扫描无法选取来装配。
spring会抛出
NoUniqueBeanDefinitionException。
有2种处理方法:
@Component或@Bean上,使用@Primary注解,在遇到歧义时,选择首选的bean。@Autowired或@Inject上,使用@Qualifier限定要注入的bean的ID。当注解到@Component或@Bean时,则是创建自己的限定符(类似于更名bean的ID)。
@Qualifier限定名类似于设置标签给bean,spring会装载满足标签的bean。
为了设置多个标签,可以自己定义注解了
@Qualifier的接口,用自定义注解作为标签限定。
一般情况下,虽然创建的bean和注入的参数名不一样(此处的bean名message1和参数名message),但spring会以byType的方式找到类型相同的bean装配好。
package com.yww;
import org.springframework.context.annotation.*;
@Configuration
@ComponentScan
public class ReaderConfig {
@Bean
public Message message1(){
Message message = new Message();
message.setMsg("---hi---1");
return message;
}
@Bean
public Reader reader(Message message){
return new Reader(message);
}
}
但有多个同类型的bean时,就无法依赖byType找到对应的bean,可以通过改变传入的参数名(此处改用参数名message2),采用byName指定bean。
package com.yww;
import org.springframework.context.annotation.*;
@Configuration
@ComponentScan
public class ReaderConfig {
@Bean
public Message message1(){
Message message = new Message();
message.setMsg("---hi---1");
return message;
}
@Bean
public Message message2(){
Message message = new Message();
message.setMsg("---hi---2");
return message;
}
@Bean
public Reader reader(Message message2){
return new Reader(message2);
}
}
可以通过3种方式获取环境值:
Environment。SpEL。#src/main/resources/application.properties
info.message=this is a message.
info.counter=10
Environment:
import org.springframework.context.annotation.*;
import org.springframework.core.env.Environment;
@Component
@PropertySource("application.properties")
public class Message {
@Autowired
Environment env;
public void print(){
String msgStr = env.getProperty("info.message", "this is a msg");
int msgInt = env.getProperty("info.counter", Integer.class, 30);
}
}
属性占位符:
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;
@Component
@PropertySource("application.properties")
public class Message {
@Value("${info.message}")
private String msg;
}
Spring EL:
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;
@Component
@PropertySource("application.properties")
public class Message {
@Value("#{systemProperties['info.message']}")
private String msg;
}原文:https://www.cnblogs.com/maplesnow/p/11628393.html