spring5入门与实践第三讲Spring的其他特性
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编程讲解。