[华为云在线课程][Spring入门][IoC][一][上][学习笔记]
1.Spring简单介绍
1.1.Spring介绍
Spring框架是一个开放源代码的J2EE应用程序框架,由Rod Johnson发起,是针对bean的生命周期进行管理的轻量级容器(lightweight container)。 Spring解决了开发者在J2EE开发中遇到的许多常见的问题,提供了功能强大IOC、AOP及Web MVC等功能。
Spring框架主要由七部分组成,分别是 Spring Core、 Spring AOP、 Spring ORM、 Spring DAO、Spring Context、 Spring Web和 Spring Web MVC。
1.2.Spring下载
日常开发我们只需要在pom.xml
中添加Spring的Maven依赖就可以使用Spring了。
想阅读源码的话可以单独下载,https://repo.spring.io/artifactory/libs-release/org/springframework/
。
本笔记是采用spring-5.3.20
版本,将下载好的压缩包解压后进入spring-framework-5.3.20\libs
目录,可以看到Spring的各个组件。
2.IoC容器
2.1.IoC简介及IoC初体验
IoC(Inversion of Control),控制反转。其意思就是对象控制权的反转。为了更好理解其含义,在IDE中新建一个Maven工程来实际演示。
- 在pom.xml导入spring-context依赖
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.20</version>
</dependency>
</dependencies>
- 在resources目录下创建spring的配置文件
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
</beans>
- 创建一个User类
package org.example.ioc.model;
public class User {
private String username;
private String address;
private Integer id;
public User() {
System.out.println("----------INIT NULL CONSTRUCTOR----------");
}
public User(String username, String address, Integer id) {
System.out.println("----------INIT CONSTRUCTOR----------");
this.username = username;
this.address = address;
this.id = id;
}
@Override
public String toString() {
return "User{" +
"username='" + username + '\'' +
", address='" + address + '\'' +
", id=" + id +
'}';
}
}
- 如果想打印User的结果,按照传统方法的话就要在Main方法里面进行实例化并打印输出,这就表示User的控制权在Main方法里面。
package org.example.ioc;
import org.example.ioc.model.User;
public class Main {
public static void main(String[] args) {
User user = new User();
System.out.println("user = " + user);
}
}
- 如果想将User的控制权交给Spring去管理,就要在Spring配置文件里配置User类,这就是控制反转。好处是可以实现资源集中管理,降低耦合度。
<!-- class属性是需要配置的bean的全路径,id表示bean的唯一标记,也可以用name属性作为标记 -->
<bean class="org.example.ioc.model.User" id="user"/>
- 在项目启动时加载Spring配置文件
public class Main {
public static void main(String[] args) {
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationcontext.xml");
}
}
- 在User中的无参构造函数中打印内容,看看Main方法中的配置是否生效
public User() {
System.out.println("----------INIT NULL CONSTRUCTOR----------");
}
- 在项目启动后,可以打印了内容,证明User已经在Spring加载配置时进行了初始化。这样User管理就交给了Spring容器。
----------INIT NULL CONSTRUCTOR----------
- 最后通过getBean方法,从容器获取对象
public class Main {
public static void main(String[] args) {
/*
* 除了ClassPathXmlApplicationContext加载方式之外(在classpath下查找配置文件),也可以使用FileSystemXmlApplicationContext,会从系统路径下去寻找配置文件
*/
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationcontext.xml");
User user = (User) applicationContext.getBean("user");
System.out.println(user);
}
}
2.1.1.Bean的获取
除了通过applicationContext.getBean()来从Spring容器中获取Bean,传入的参数是Bean的name或者id属性之外,也可以直接通过Class去获取一个Bean。
public class Main {
public static void main(String[] args) {
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationcontext.xml");
User user = applicationContext.getBean(User.class);
System.out.println(user);
}
}
但是这个方法存在一个弊端,那就是如果存在多个实例就不可用了。例如xml文件中存在两个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" xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean class="org.example.ioc.model.User" id="user"/>
<bean class="org.example.ioc.model.User" id="user1"/>
</beans>
通过Class去查找Bean,就会报错:
----------INIT CONSTRUCTOR----------
----------INIT NULL CONSTRUCTOR----------
----------INIT NULL CONSTRUCTOR----------
Exception in thread "main" org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'org.example.ioc.model.User' available: expected single matching bean but found 2: user,user1
所以一般建议使用name或id获取Bean实例。
2.2.基本属性注入
2.2.1.构造方法注入
通过Bean的构造方法给Bean属性注入值
- 给User添加构造方法
public User(String username, String address, Integer id) {
System.out.println("----------INIT CONSTRUCTOR----------");
this.username = username;
this.address = address;
this.id = id;
}
- 在Spring配置文件中进行注入
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean class="org.example.ioc.model.User" id="user">
<constructor-arg index="0" value="hello"/>
<constructor-arg index="1" value="china"/>
<constructor-arg index="2" value="1"/>
</bean>
</beans>
注意,其中index要和User构造方法参数对应,写的顺序可以颠倒,但index和value要对应。另一种方法是指定参数名注入:
<bean class="org.example.ioc.model.User" id="user">
<constructor-arg name="username" value="hello"/>
<constructor-arg name="address" value="china"/>
<constructor-arg name="id" value="1"/>
</bean>
- 运行结果
User{username='hello', address='china', id=1}
2.2.2.set方法注入
<bean class="org.example.ioc.model.User" id="user1">
<property name="username" value="hello1"/>
<property name="address" value="china"/>
<property name="id" value="1"/>
</bean>
set方法注入有一个很关键的问题就是属性名。很多人觉得这个属性名就是在类中定义的属性名,其实不是的。属性名是通过Java中的内省机制分出出来的,简单来说就是根据get/set方法分析出来的。如果没有声明get/set方法,那么就会报错。
Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'user1' defined in class path resource [applicationcontext.xml]: Error setting property values; nested exception is org.springframework.beans.NotWritablePropertyException: Invalid property 'username' of bean class [org.example.ioc.model.User]: Bean property 'username' is not writable or has an invalid setter method. Does the parameter type of the setter match the return type of the getter?
2.2.3.p名称空间注入
p名称空间注入,使用的比较少,本质上也是调用了set方法。
<bean class="org.example.ioc.model.User" id="user2" p:username="hello2" p:address="china" p:id="2"/>
输出结果:
----------INIT CONSTRUCTOR----------
----------INIT NULL CONSTRUCTOR----------
----------INIT NULL CONSTRUCTOR----------
User{username='hello2', address='china', id=2}
2.2.4.外部Bean注入
有时需要使用外部Bean,可能会遇到这些Bean没有构造方法的情况,它们是通过Builder构造的,这时候就无法使用上面的方式进行值的注入了。
例如OkHttp网络请求中的原生写法如下:
import okhttp3.*;
import java.io.IOException;
public class OkHttpMain {
public static void main(String[] args) {
OkHttpClient okHttpClient = new OkHttpClient.Builder().build();
Request request = new Request.Builder()
.get()
.url("https://www.baidu.com")
.build();
Call call = okHttpClient.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
System.out.println("e.getMessage() = " + e.getMessage());
}
@Override
public void onResponse(Call call, Response response) throws IOException {
assert response.body() != null;
System.out.println("response.body().string() = " + response.body().string());
}
});
}
}
可以看到OkHttpClient和Request两个实例都不是直接new出来的,在调用Builder方法的过程中,都会给它配置一些默认的参数。这时候我们可以使用静态工厂注入或者实例工厂注入来给OkHttpClient提供一个实例,以便交给Spring容器管理。
2.3.工厂方法注入
2.3.1.静态工厂注入
声明一个OkHttpClient的静态工厂
import okhttp3.OkHttpClient;
public class OkHttpClientStaticFactory {
private static OkHttpClient okHttpClient;
public static OkHttpClient getInstance() {
if (okHttpClient == null) {
okHttpClient = new OkHttpClient.Builder().build();
}
return okHttpClient;
}
}
然后在Spring配置文件中配置静态工厂
<bean class="org.example.OkHttpClientStaticFactory" factory-method="getInstance" id="okHttpClient"/>
getInstance表示在静态工厂类中我们需要的实例,实例名字为okHttpClient。这样我们就能够通过Spring容器来调用了。
import okhttp3.*;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.io.IOException;
public class OkHttpMain {
public static void main(String[] args) {
// OkHttpClient okHttpClient = new OkHttpClient.Builder().build();
ClassPathXmlApplicationContext cxt = new ClassPathXmlApplicationContext("applicationcontext.xml");
OkHttpClient okHttpClient = cxt.getBean("okHttpClient", OkHttpClient.class);
Request request = new Request.Builder()
.get()
.url("https://www.baidu.com")
.build();
Call call = okHttpClient.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
System.out.println("e.getMessage() = " + e.getMessage());
}
@Override
public void onResponse(Call call, Response response) throws IOException {
assert response.body() != null;
System.out.println("response.body().string() = " + response.body().string());
}
});
}
}
2.3.2.实例工厂注入
实例工厂就是工厂方法是一个实例方法,工厂类必须实例化后才能调用工厂方法。
import okhttp3.OkHttpClient;
public class OkHttpInstanceFactory {
private OkHttpClient okHttpClient;
public OkHttpClient getInstance() {
if (okHttpClient == null) {
okHttpClient = new OkHttpClient.Builder().build();
}
return okHttpClient;
}
}
在Spring配置文件中,需要首先提供工厂方法的实例才能调用工厂方法。
<bean class="org.example.nobean.OkHttpInstanceFactory" id="okHttpClient"/>
<bean class="okhttp3.OkHttpClient" factory-bean="okHttpClient" factory-method="getInstance" id="okHttpInstanceFactory"/>
自己写的Bean一般不会使用这两种方式注入,但如果引入外部jar,外部jar的类的初始化,就需要使用这两种方式。
2.4.复杂属性注入
2.4.1.对象注入
首先,新建一个Cat类,并定义相关属性。
package org.example.ioc.model;
public class Cat {
private String name;
private String color;
public Cat() {
}
public Cat(String name, String color) {
this.name = name;
this.color = color;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
@Override
public String toString() {
return "Cat{" +
"name='" + name + '\'' +
", color='" + color + '\'' +
'}';
}
}
定义好后在User类中引入,并生成get/set和toString方法。
private Cat cat;
然后在Spring配置文件中进行对象注入,通过ref来引用
<bean class="org.example.ioc.model.User" id="user">
<property name="username" value="hellocat"/>
<property name="address" value="china"/>
<property name="id" value="1"/>
<property name="cat" ref="cat"/>
</bean>
<bean class="org.example.ioc.model.Cat" id="cat">
<property name="name" value="小白"/>
<property name="color" value="白色"/>
</bean>
2.4.2.数组注入
数组注入和集合注入在xml中的配置是一样的。
在User类中增加cats和favorite数组,增加get/set和toString方法。
private Cat[] cats;
private List<String> favorite;
在Spring配置文件中进行配置
<bean class="org.example.ioc.model.User" id="user">
<property name="cats">
<array>
<bean class="org.example.ioc.model.Cat" id="cat1">
<property name="name" value="小白"/>
<property name="color" value="白色"/>
</bean>
<bean class="org.example.ioc.model.Cat" id="cat2">
<property name="name" value="小黑"/>
<property name="color" value="黑色"/>
</bean>
</array>
</property>
</bean>
输出结果:
User{username='null', address='null', id=null, cat=null, cats=[Cat{name='小白', color='白色'}, Cat{name='小黑', color='黑色'}]}
array也可以用list代替,当然,array或者list节点中也可以是对象
<bean class="org.example.ioc.model.User" id="user">
<property name="favorite">
<list>
<value>吃饭</value>
<value>睡觉</value>
</list>
</property>
<property name="cats">
<array>
<ref bean="cat"/>
</array>
</property>
</bean>
<bean class="org.example.ioc.model.Cat" id="cat">
<property name="name" value="猫"/>
<property name="color" value="白色"/>
</bean>
输出结果:
----------INIT NULL CONSTRUCTOR----------
User{username='null', address='null', id=null, cat=null, cats=[Cat{name='猫', color='白色'}], favorite=[吃饭, 睡觉]}
注意,既可以通过ref使用外部定好的Bean,也可以直接在list或者array节点中定义Bean。
2.4.3.Map注入
在User类中增加Map,并设置get/set和toString方法
private Map<String,Object> map;
<bean class="org.example.ioc.model.User" id="user">
<property name="map">
<map>
<entry key="name" value="helloworld"/>
<entry key="age" value="18"/>
</map>
</property>
</bean>
输出结果:
----------INIT NULL CONSTRUCTOR----------
User{username='null', address='null', id=null, cat=null, cats=null, favorite=null, map={name=helloworld, age=18}}
2.4.4.Properties注入
在User类中增加Properties字段,并设置get/set和toString方法
private Properties info;
<bean class="org.example.ioc.model.User" id="user">
<property name="info">
<props>
<prop key="name">worldhello</prop>
<prop key="age">18</prop>
</props>
</property>
</bean>
输出结果:
----------INIT NULL CONSTRUCTOR----------
User{username='null', address='null', id=null, cat=null, cats=null, favorite=null, map=null, info={name=worldhello, age=18}}
2.5.Java配置
在Spring中,将Bean注册到Spring容器中有三种方式:1,XML注入;2,Java配置(通过Java代码将Bean注册到Spring容器中);3,自动化扫描。
Java配置在Spring Boot出现之前很少使用,自从有了Spring Boot,Java配置开发被广泛使用,因为在Spring Boot中,不使用一行XML配置。
package org.example.javaconfig;
// 这是一个Bean
public class SayHello {
public String sayHello(String name) {
return "hello" + name;
}
}
package org.example.javaconfig;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
// 这是Java配置类,相当于applicationContext.xml
@Configuration
public class JavaConfig {
@Bean
SayHello sayHello() {
return new SayHello();
}
}
配置类中的@Configuration注解表示这个类不是一个普通类,而是一个配置类,作用相当于applicationContext.xml。然后定义方法,方法返回对象,方法上添加@Bean注解,表示将这个方法返回值注入Spring容器中。@Bean对应的方法,相当于applicationContext.xml中的bean节点。最后就是在启动类中加入即可。
public class Main {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(JavaConfig.class);
SayHello hello = context.getBean(SayHello.class);
System.out.println(hello.sayHello("你好啊"));
}
}
这里Bean的名字默认就是方法名(sayHello),如果想自定义名字的话,直接在@Bean注解中配置。
@Configuration
public class JavaConfig {
@Bean("hello")
SayHello sayHello() {
return new SayHello();
}
}
2.6.自动扫描注入
自动扫描注入可以通过Java配置实现,也可以通过xml配置实现。
2.6.1.准备工作
假如有一个Service类,想在自动扫描时将这个类自动注入到Spring容器中,那么可以在该类添加一个@Service作为标记。和@Service注解功能类似的一共有4个:
- @Component
- @Repository
- @Service
- @Controller
另外三个都是基于@Component做出来的,功能是一样的,主要是为了在不同类上面添加时比较方便。
- Service层使用@Service
- Dao层使用@Repository
- Controller层使用@Controller
- 其他组件使用@Component
package org.example.autoconfig;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
@Service
public class UserService {
public List<String> getAllUsers() {
List<String> users = new ArrayList<>();
for (int i = 0; i < 10; i++) {
users.add("user: " + i);
}
return users;
}
}
完成类的添加后,接下来分别演示两种自动扫描方式,Java代码配置和XML文件配置。
2.6.2.Java配置自动扫描
新建一个JavaConfig,并加上@Configuration注解,表示这个是配置类。添加@ComponentScan注解指定要扫描的包(如果不指定,默认情况下扫描的是配置类所在包下的Bean以及配置类所在包下的子包的类),然后就可以获取UserService的实例了。
package org.example.autoconfig;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan(basePackages = "org.example.autoconfig")
public class JavaConfig {
}
package org.example.autoconfig;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Main {
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(JavaConfig.class);
UserService userService = applicationContext.getBean(UserService.class);
System.out.println(userService.getAllUsers());
}
}
输出结果:
[user: 0, user: 1, user: 2, user: 3, user: 4, user: 5, user: 6, user: 7, user: 8, user: 9]
问题1:Bean的名字是什么?Bean名字是类名首字母小写。例如上面的UserService,实例名默认是userService。如果想自定义名字,直接在@Service注解中添加即可。
@Service("us")
UserService us = (UserService) applicationContext.getBean("us");
问题2:有几种扫描方式?可以按照包的位置扫描。Bean必须放在指定的扫描位置,否则使用@Service注解也没用。另一种方法就是根据注解来扫描,例如以下配置:
@Configuration
@ComponentScan(basePackages = "org.example.autoconfig",useDefaultFilters = true,
excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,classes = Controller.class)})
public class JavaConfig {
}
表示扫描org.example.autoconfig下的所有Bean,除了Controller。
2.6.3.XML配置自动扫描
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="org.example.autoconfig"/>
</beans>
上面配置表示扫描org.example.autoconfig下的所有Bean。也可以按照类来扫描。
public class XMLMain {
public static void main(String[] args) {
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationcontext.xml");
UserService userService = applicationContext.getBean(UserService.class);
List<String> list = userService.getAllUsers();
System.out.println(list);
}
}
输出结果:
[user: 0, user: 1, user: 2, user: 3, user: 4, user: 5, user: 6, user: 7, user: 8, user: 9]
当然,也可以在XML配置中按照注解的类型扫描:
<context:component-scan base-package="org.example.autoconfig" use-default-filters="true">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
2.7.对象注入问题
自动扫描的对象注入有三种方式:
- @Autowired
- @Resources
- @Injected
@Autowired根据类型查找并赋值,但只可以有一个对象。@Resources根据名称查找,默认为定义的变量名,也可以手动指定新的变量名。如果一个类存在多个实例使用@Resources。一定要使用@Autowired的话需要配合@Qualifier使用,在@Qualifier指定变量名,两个一起使用可以实现通过变量名查找变量。
@Repository
public class UserDao {
public String hello() {
return "UserDao + hello()";
}
}
@Service
public class UserService {
@Autowired
UserDao userDao;
public String hello() {
return userDao.hello();
}
}
public class Main {
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(JavaConfig.class);
UserService userService = applicationContext.getBean(UserService.class);
System.out.println(userService.hello());
}
}
2.8.条件注解
Windows查看文件夹目录命令是dir,Linux查看文件夹目录命令是ls,如果想让程序运行在Windows时,打印出Windows的查看目录命令,运行在Linux时打印Linux的查看目录命令。可以使用条件注解来实现。
首先定义一个显示文件夹目录的接口:
package org.example.conditionanno;
public interface ShowCmd {
String showCmd();
}
然后实现两个实例:
package org.example.conditionanno;
public class WindowsCmd implements ShowCmd{
@Override
public String showCmd() {
return "dir";
}
}
package org.example.conditionanno;
public class LinuxCmd implements ShowCmd{
@Override
public String showCmd() {
return "ls";
}
}
定义两个条件,一个Windows,一个Linux。
package org.example.conditionanno;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
public class WindowsCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return context.getEnvironment().getProperty("os.name").toLowerCase().contains("windows");
}
}
package org.example.conditionanno;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
public class LinuxCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return context.getEnvironment().getProperty("os.name").toLowerCase().contains("linux");
}
}
定义Bean时就可以配置条件注解
package org.example.conditionanno;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
@Configuration
public class JavaConfig {
@Bean("showCmd")
@Conditional(WindowsCondition.class)
ShowCmd winCmd() {
return new WindowsCmd();
}
@Bean("showCmd")
@Conditional(LinuxCondition.class)
ShowCmd linuxCmd() {
return new LinuxCmd();
}
}
注意:一定要给两个Bean相同的名字,这样调用时才能自动匹配。然后给Bean加条件注解,当返回true时Bean生效。
package org.example.conditionanno;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Main {
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(JavaConfig.class);
ShowCmd showCmd = (ShowCmd) applicationContext.getBean("showCmd");
System.out.println(showCmd.showCmd());
}
}
2.9.Profile环境切换
条件注解的经典场景是多环境切换。在日常开发中,我们经常需要在开发/生产/测试环境之间来回切换,Spring提供了Profile来解决在这些环境中快速切换的问题。Profile的底层就是条件注解。这个可以从Profile.java
和ProfileCondition.java
中看出。
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(ProfileCondition.class)
public @interface Profile {
/**
* The set of profiles for which the annotated component should be registered.
*/
String[] value();
}
class ProfileCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());
if (attrs != null) {
for (Object value : attrs.get("value")) {
if (context.getEnvironment().acceptsProfiles(Profiles.of((String[]) value))) {
return true;
}
}
return false;
}
return true;
}
}
以一个模拟数据库类来演示@Profile类切换环境。
首先定义一个DataSource类。
package org.example.conditionanno;
public class DataSource {
private String url;
private String username;
private String password;
public DataSource() {
}
public DataSource(String url, String username, String password) {
this.url = url;
this.username = username;
this.password = password;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "DataSource{" +
"url='" + url + '\'' +
", username='" + username + '\'' +
", password='" + password + '\'' +
'}';
}
}
配置Bean,并通过@Profile注解指定不同的环境
package org.example.conditionanno;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
@Configuration
public class JavaConfig {
@Bean("ds")
@Profile("dev")
DataSource devDataSource() {
DataSource dataSource = new DataSource();
dataSource.setUrl("jdbc:mysql:///dev");
dataSource.setUsername("root");
dataSource.setPassword("123");
return dataSource;
}
@Bean("ds")
@Profile("prod")
DataSource prodSource() {
DataSource dataSource = new DataSource();
dataSource.setUrl("jdbc:mysql:///prod");
dataSource.setUsername("abcd");
dataSource.setPassword("abcd");
return dataSource;
}
}
最后,再启动方法中先设置环境,再加载配置类
public class DataSourceMain {
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
applicationContext.getEnvironment().setActiveProfiles("dev");
applicationContext.register(JavaConfig.class);
applicationContext.refresh();
DataSource ds = (DataSource) applicationContext.getBean("ds");
System.out.println(ds); //DataSource{url='jdbc:mysql:///dev', username='root', password='123'}
}
}
以上是在Java代码中配置的。也可以在Spring配置文件中进行配置。
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<beans profile="dev">
<bean class="org.example.conditionanno.DataSource" id="devDs">
<property name="url" value="jdbc:mysql:///devdb"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</bean>
</beans>
<beans profile="prod">
<bean class="org.example.conditionanno.DataSource" id="prodDs">
<property name="url" value="jdbc:mysql:///proddb"/>
<property name="username" value="rootroot"/>
<property name="password" value="rootroot"/>
</bean>
</beans>
</beans>
在启动类中设置环境并加载配置
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext();
applicationContext.getEnvironment().setActiveProfiles("prod");
applicationContext.setConfigLocation("applicationcontext.xml");
applicationContext.refresh();
DataSource dataSource = (DataSource) applicationContext.getBean("dataSource");
System.out.println(dataSource); //DataSource{url='jdbc:mysql:///proddb', username='rootroot', password='rootroot'}
2.10.Bean的作用域
从Spring容器中多次获取同一个Bean,默认情况下是同一个。
public class Main {
public static void main(String[] args) {
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationcontext.xml");
User user = applicationContext.getBean("user", User.class);
User user1 = applicationContext.getBean("user", User.class);
System.out.println(user==user1); //true
}
}
也可以进行手动配置。
<bean class="org.example.beanscope.User" id="user" scope="prototype"/>
scope的值默认为singleton,表示以单例形式存在,如果scope为prototype,表示不是单例,多次获取将会是不同的实例。除了singleton和prototype之外还有两个request和session。这两个在web环境下有效。
Java配置scope
@Configuration
public class JavaConfig {
@Bean
@Scope("prototype")
SayHello sayHello() {
return new SayHello();
}
}
自动扫描配置
@Repository
@Scope("prototype")
public class UserDao {
public String hello() {
return "UserDao + hello()";
}
}
- 点赞
- 收藏
- 关注作者
评论(0)