[华为云在线课程][Spring入门][IoC][一][上][学习笔记]

举报
John2021 发表于 2022/06/24 05:29:38 2022/06/24
【摘要】 1.Spring简单介绍 1.1.Spring介绍Spring框架是一个开放源代码的J2EE应用程序框架,由Rod Johnson发起,是针对bean的生命周期进行管理的轻量级容器(lightweight container)。 Spring解决了开发者在J2EE开发中遇到的许多常见的问题,提供了功能强大IOC、AOP及Web MVC等功能。Spring框架主要由七部分组成,分别是 Spr...

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工程来实际演示。

  1. 在pom.xml导入spring-context依赖
<dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.3.20</version>
        </dependency>
    </dependencies>
  1. 在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>
  1. 创建一个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 +
                '}';
    }
}
  1. 如果想打印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);
    }
}
  1. 如果想将User的控制权交给Spring去管理,就要在Spring配置文件里配置User类,这就是控制反转。好处是可以实现资源集中管理,降低耦合度。
<!-- class属性是需要配置的bean的全路径,id表示bean的唯一标记,也可以用name属性作为标记 -->
<bean class="org.example.ioc.model.User" id="user"/>
  1. 在项目启动时加载Spring配置文件
public class Main {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationcontext.xml");
    }
}
  1. 在User中的无参构造函数中打印内容,看看Main方法中的配置是否生效
public User() {
   System.out.println("----------INIT NULL CONSTRUCTOR----------");
}
  1. 在项目启动后,可以打印了内容,证明User已经在Spring加载配置时进行了初始化。这样User管理就交给了Spring容器。
----------INIT NULL CONSTRUCTOR----------
  1. 最后通过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属性注入值

  1. 给User添加构造方法
public User(String username, String address, Integer id) {
    System.out.println("----------INIT CONSTRUCTOR----------");
    this.username = username;
    this.address = address;
    this.id = id;
}
  1. 在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>
  1. 运行结果
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个:

  1. @Component
  2. @Repository
  3. @Service
  4. @Controller

另外三个都是基于@Component做出来的,功能是一样的,主要是为了在不同类上面添加时比较方便。

  1. Service层使用@Service
  2. Dao层使用@Repository
  3. Controller层使用@Controller
  4. 其他组件使用@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.对象注入问题

自动扫描的对象注入有三种方式:

  1. @Autowired
  2. @Resources
  3. @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.javaProfileCondition.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()";
    }
}
【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。