我爱编程

spring5入门与实践第三讲Spring的其他特性

2018-03-20  本文已影响0人  孔浩

spring整合测试

spring可以和junit很好的进行整合,首先我们需要添加junit和spring和test的依赖

<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>5.0.2.RELEASE</version>
    <scope>test</scope>
</dependency>

创建一个配置类

@Configuration
public class BaseConfig {
    @Bean
    public String hello() {
        return "hello";
    }
}

创建基于spring 的测试类,首先在测试类上添加@RunWith(SpringJUnit4ClassRunner.class),说明这个类是一个spring的测试类,声明之后该测试类就可以直接注入spring容器中的对象,之后通过@ContextConfiguration(classes=BaseConfig.class)来说明具体的配置配置类是哪个。

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes=BaseConfig.class)
public class BaseTest {
    @Autowired
    private String hello;
    @Test
    public void test01() {
        System.out.println(hello);
    }
}

在BaseConfig中创建了一个String的Bean对象,使用的是hello方法创建的,此时注入对象名称就是hello,所以在BaseTest中直接注入一个hello的对象即可。

spring基于Annotation的配置

spring4之后,spring就支持完全基于java的Annotation的配置,这里需要特别拿出来讲解几个比较常用的方法,首先是如何分割多个配置文件,通过import既可以完成分割,创建两个配置类

@Configuration
public class AConfig {

    @Bean("a")
    public String a() {
        return "a";
    }
}

@Configuration
public class BConfig {
    @Bean
    public String b() {
        return "b";
    }
}

在BaseConfig中通过@Import可以导入这些配置类

@Configuration
@Import({AConfig.class,BConfig.class})
public class BaseConfig {
    @Bean("hello")
    public String hello() {
        return "hello";
    }
}

在测试类中只要引入BaseConfig,其他两个测试类也会被导入

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes=BaseConfig.class)
public class BaseTest {
    @Autowired
    private String hello;

    @Autowired
    private String a;

    @Autowired
    private String b;

    @Test
    public void test01() {
        System.out.println(hello);
        System.out.println(a);
        System.out.println(b);
    }
}

在@Bean中除了默认的value之外,还提供了initMethod和destoryMethod来执行初始化和销毁操作,创建一个BaseObject来运行

public class BaseObject {
    private void init() {
        System.out.println("begin run....");
    }
    private void destory() {
        System.out.println("destory program!!");
    }
}

在Config类中加入该bean

@Bean(initMethod = "init",destroyMethod = "destory")
public BaseObject baseObject() {
    return new BaseObject();
}

此时只要注入这个对象就会首先执行init和destroy方法。

spring的Profile

spring从3之后就加入了Profile的处理,Profile可以分阶段和分用户来设置配置,该功能在多用户管理中特别的好用,我们从java的配置信息和properties的信息读取两方面来进行处理。

假设有这样一种需要,项目中需要设定静态资源文件的路径,首先创建一个SystemPath的接口和两个不同的实现类,一个用来指定开发时的路径,一个用来指定发布后的路径

public interface SystemPath {
    String getRealPath();
}

public class DevPath implements SystemPath {
    public String getRealPath() {
        return "dev:path";
    }
}

public class QaPath implements SystemPath {
    public String getRealPath() {
        return "qa:path";
    }
}

下一步在具体的配置类中创建这两个bean并且指定Profile

@Configuration
@Import({AConfig.class,BConfig.class})
public class BaseConfig {
    ....    
    @Bean("path")
    @Profile("dev")
    public SystemPath devPath() {
        return new DevPath();
    }
    
    @Bean("path")
    @Profile("qa")
    public SystemPath qaPath() {
        return new QaPath();
    }

}

最后就是在使用的时候激活,在基于web的应用的程序中,可以通过web.xml来进行设定,通过spring.profiles.active和spring.profiles.default来配合使用,spring.profiles.active用来指定当前激活的配置,如果没有设置spring.profiles.active这个的值,会自动去找spring.profiles.default,可以在web.xml中通过<context>来进行设定。此处会在web项目时介绍。

如果需要在maven中的使用Profile,可以将这两个参数加在maven的命令之后,如果希望使用在测试类中,使用@ActiveProfiles来进行激活

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = BaseConfig.class)
@ActiveProfiles("dev")
public class PathTest {
    @Autowired
    private SystemPath path;

    @Test
    public void testPath() {
        System.out.println(path.getRealPath());
    }
}

以上就是Profile的思路,在实际应用中,这种需求更多的是运用在properties的环境中,当需要多个用户同时开发时难免文件的路径,数据库的用户名和密码这些有不同,如果同时使用svn或者git这些版本管理工具,只有一个properties文件会很难处理,所以可以根据用户创建多个不同的配置文件,根据不同的需求进行加载,下面将介绍基于不同配置文件的实现方式。

首先创建三个配置文件application.properties,application-kh.properties,application-ls.properties

#### application.properties
profile.name = default.profile
realpath = /project/test
jdbc.username = ok
jdbc.password = 111111

#### application-kh.properties
profile.name = kh.profile
realpath = d:/project/test
jdbc.username = kh
jdbc.password = 123456

#### application-ls.properties
profile.name = ls.profile
realpath = d:/project/test
jdbc.username = ls
jdbc.password = 666666

创建两个类,一个类模拟数据库的配置,一个类模拟环境的配置

@Component
public class DataSourceProperties {
    @Value("${jdbc.username}")
    private String username;
    @Value("${jdbc.password}")
    private String password;
    ..省略getter和setter...
}

@Component
public class EnvProperties {
    @Value("${profile.name}")
    private String profileName;
    @Value("${realpath}")
    private String realpath;

   ..省略getter和setter...
}


这两类都是spring的Component,一个存储了数据库的基本信息,一个存储了环境的信息,都是通过properties来获取这两个值,下面就需要在BaseConfig这个配置类中添加相应的Properties,虽然spring提供了@PropertySource,但是这种方式并不能很好的实现Profile,它只会把每一个properties文件都加载进去,如果有相同的值会用最后一个来替换,这显然无法实现基于Profile的Properties。所以此处需要开发人员根据ActiveProfile手动把相应的配置文件导入,通过创建一个init的方法来执行

@Configuration
@Import({AConfig.class,BConfig.class})
@ComponentScan("org.konghao")
public class BaseConfig {
    @Autowired
    private ConfigurableEnvironment env;

    private String prefix = "application";

    @PostConstruct//该方法在构造函数之后执行
    public void init() {
        try {
            //判断环境参数中是否有ActiveProfiles
            if(env.getActiveProfiles().length>0) {
                //如果加了Profile,就将该Profile的前缀添加到spring的Property资源中
                for(String activeProfile:env.getActiveProfiles()) {
                    //获取前缀的资源文件
                    ClassPathResource cpr = new ClassPathResource(prefix+"-"+activeProfile+".properties");
                    if(cpr.exists()) {
                        //添加到环境的资源文件中
                        env.getPropertySources().addLast(new ResourcePropertySource(cpr));
                    }
                }
            } else {
                //如果没有Profile,就把默认的文件添加进去
                ClassPathResource cpr = new ClassPathResource(prefix+".properties");
                if(cpr.exists()) {
                    env.getPropertySources().addLast(new ResourcePropertySource(cpr));
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

使用这种方式就可以有效的解决properties文件的Profile问题。

条件化Bean

在Spring4之后,spring提供了一种基于条件来创建Bean的方式,这可以用于某种特殊的需求,如只有在某个Bean创建成功了才创建该Bean,或者说只有在配置了某个环境变量之后才创建这个Bean,这些需求通过单纯的配置是不太容易实现的。

通过@Conditional 来设定创建该Bean的条件,@Conditional 中要传入Condition 的对象,所以需要我们手动实现这个对象

public class HelloBeanCheck implements Condition {
    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
        //conditionContext.getBeanFactory()//获取BeanFactory
//        conditionContext.getRegistry()//获取Bean的定义对象
//        conditionContext.getBeanFactory()//获取Bean的工厂
//        conditionContext.getClassLoader()//获取ClassLoader
//        conditionContext.getEnvironment()//获取环境变量
//        conditionContext.getResourceLoader()//获取资源信息
//        annotatedTypeMetadata可以用来检测自己的Annotation信息
        //如果有hello这个bean才会创建HelloBean
        return conditionContext.getRegistry().containsBeanDefinition("hello");
    }
}

Condition中需要实现一个matches方法,该方法非常强大,可以通过该方法两个参数做很多检查操作,大家可以自行测试,最后如果返回true就会创建,否则就不会创建。

动态注入值

在原来的代码中,基本都是以硬编码的方法为Bean注入值的,如果希望动态的注入值也是可以实现的,有两种具体的解决方案,一种是基于配置文件的方式,另外一种就是基于spring的表达式来实现。

基于配置文件的实现方式非常简单,首先通过@PropertySource添加一个资源文件,其次注入一个Environment对象即可,上一小节所使用的ConfigurableEnvironment是Environment的子类。创建一个User对象,并且创建一个base.properties的资源文件

user.username = zs
user.nickname = zhangsan

下面创建User对象

public class User {
    private String username;
    private String nickname;

    public User(){}

    public User(String username,String nickname) {
        this.username = username;
        this.nickname = nickname;
    }
    //........省略getter和setter.....
}

在BaseConfig中注入该对象

@Configuration
@Import({AConfig.class,BConfig.class})
@ComponentScan("org.konghao")
@PropertySource("base.properties")
public class BaseConfig {
    @Autowired
    private ConfigurableEnvironment env;

    //...省略多余代码..
        
    @Bean
    public User user() {
        return new User(env.getProperty("user.username"),
                env.getProperty("user.nickname"));
    }
}

如果不使用env,也可以使用${xxx.xxx}的占位符来指定

public class User {
    @Value("${user.username}")
    private String username;
    @Value("${user.nickname}")
    private String nickname;
}

此时在BaseConfig中直接使用不带参数的构造方法来创建,都会给username和nickname设置到相应的值

 @Bean
    public User user() {
//        return new User(env.getProperty("user.username"),
//                env.getProperty("user.nickname"));
        return new User();
    }

基于配置的方法非常简单,下面看看基于spring表达式SpEL的方式。

spring表达式是使用#{...} 来编写,表达式非常的强大,可以是单个的值,也可以是对象,还能是对象的某个属性,并且这些值还可以进行运算,spring的SpEL是使用@Value来注入,创建三个类来模拟一下SpEL。

@Component
public class Student {

    @Value("#{systemProperties['user.name']}")
    private String name;
    private List<Book> books;

    public Student() {
        books = Arrays.asList(
                new Book("b1",12),
                new Book("b2",33),
                new Book("b3",44));
    }

   //...省略getter和setter....
}

此处就使用了一个SpEL,从系统变量中获取用户名,下面看看Book这个类

@Component
public class Book {
    private String name;
    private double price;

    public Book() {}

    public Book(String name,double price) {
        this.name = name;
        this.price = price;
    }

   //...省略getter和setter...
}

通过一个StudentDto来模拟几种常用的SpringSPEL

@Component
public class StudentDto {
    @Autowired
    private Student student;

    @Value("#{student.name}")
    private String name;

    @Value("#{student.books.![name]}")
    private List<String> books;

    @Value("#{student.books.size()}")
    private int bookCount;

    @Value("#{T(org.konghao.spring.model.BookUtil).calPrice(student.books)}")
    private double bookPrice;

    //...省略来的getter和setter...
}

name这个属性使用的是Student的name,books属性使用的是一组books的name列表,bookCount通过list的size求和,而bookPrice是调用了BookUtil中 方法求所有书的价格,只要是需要使用某个类的静态方法或者引用常量都需要使用T()来表示,下面看看BookUtil,

public class BookUtil {
    public static double calPrice(List<Book> books) {
        return books.stream().mapToDouble(s->s.getPrice()).sum();
    }
}

以上使用了Lamdba表达式来完成求和,强烈建议大家将来使用List都通过Lamdba来处理。这一部分的内容就到此为止了,主要讲解了spring的一些用法,下一部分进入spring的web编程讲解。

上一篇下一篇

猜你喜欢

热点阅读