美文网首页我爱编程
spring5入门与实践第三讲Spring的其他特性

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

作者: 孔浩 | 来源:发表于2018-03-20 08:56 被阅读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编程讲解。

    相关文章

      网友评论

        本文标题:spring5入门与实践第三讲Spring的其他特性

        本文链接:https://www.haomeiwen.com/subject/hsbkqftx.html