欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 文旅 > 美景 > Java Spring的高级装配

Java Spring的高级装配

2024/10/24 16:45:24 来源:https://blog.csdn.net/weixin_42581560/article/details/143087117  浏览:    关键词:Java Spring的高级装配

1.profile与bean

1.1 profile

如果我们在配置类中装配一个bean,但是这个bean与环境相关怎么办?

比如有一个类,它在开发环境采取一种模式,但是到了生产环境,有需要使用另一种环境。

当然,你可能会说,写两个类。但是实际情况可能不允许。这个时候,需要根据环境的不同生产不同的bean。它使用的方法是@Profile

举一个例子:

@Configuration

@Profile("dev")

public class DevProfileConfig{

        @Bean

        public A a{...}

        @Bean

        public B b{...}

}

只有在dev环境中,这个配置类下面的bean才能生成

@Profile不仅仅可以用在类上,也可用在方法中,代表这个类只有满足环境要求才能生产bean

@Configuration

public class DevProfileConfig{

        @Bean

        @Profile("dev1")

        public A a{...}

        @Bean

        @Profile("dev2")

        public B b{...}

        

        

        @Bean

        public B b{...}

        }

我觉得不需要我解释上面的含义了。

但是需要注意。其他的bean指的是同一个类,如果其他bean没用指定相同的profile环境,那么这样的bean依然会生成。

同样,xml文件也能实现,这里就略过xml文件的讲解吧,都是一个逻辑。

1.2 激活profile

上面讲了profile掌握bean的生产条件,它与profile的环境相关。只要profile被激活,比如成为dev环境,那么相应的bean就会生效。如何让它激活?

它依赖于两个独立的属性:

(1).java.profile.active

(2).java.profile.default

它先查看active的值,如果处于激活状态,则取它的值;反之则取default,但是如果两个都没有激活,则profile控制的bean都无法生成。

那么如何设置这两个属性呢?

有很多办法。

其中常用的有

作为DispatcherServlet的初始化参数

作为web应用的上下文

作为环境变量

作为JVM的系统属性

在集成测试类上使用@ActiveProfile注解设置

还要其他很多,根据情况设置即可

2.条件化的bean

2.1 @Conditional

@Conditional

当满足某个条件的时候,该bean才会被创建。这种条件可能是某个库是否存在,其他某个bean是否被创建,某个特定的环境变量是否为特定的值。。。。。。

@Bean
@Conditional(MagicExistsCondition.class)
public MagicBean magicBean(){return new MagicBean();
}

上面的代码片段应该写在某个配置类中。它通过检查MagicExistsCondition这个类是否存在来决定要不要创建MagicBean。

它是通过Conditional这个接口实现来判断的,接口为:

public interface Conditional{boolean matches(Conditional ctxt, AnnotatedTypeMetadata metadata);    
}

显式这个接口的类只需要实现它的matches方法即可,如果返回true,则代表条件满足,下面创建这个类来实现这个接口,以供条件判断

package com.example.demo.conditional;import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotatedTypeMetadata;public class MagicExistsConditional implements Condition {@Overridepublic boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {Environment env = context.getEnvironment();return env.containsProperty("magic");}
}

通过ConditionContext类获取环境类,然后判断环境中有没有这个magic属性。

如果这么写肯定是返回false的,因为环境变量里面确实没有这个属性。

了解一下传入的两个参数很有必要

2.2 ConditionContext类和AnnotatedTypeMetadata类

ConditionContext类定义如下,它是一个接口,我们可以叫他条件上下文。
public interface ConditionContext {BeanDefinitionRegistry getRegistry();@NullableConfigurableListableBeanFactory getBeanFactory();Environment getEnvironment();ResourceLoader getResourceLoader();@NullableClassLoader getClassLoader();
}

 

getRegistry()返回的BeanDefinitionRegistry可以检查bean的定义。
getBeanFactory()返回的ConfigurableListableBeanFactory可以检查bean是否存在。
getEnvironment()返回的Environment可以检查环境变量属性以及值。
getResourceLoader()返回ResourceLoader可以读取加载的资源。
getClassLoader()返回ClassLoader可以判断类是否存在。
AnnotatedTypeMetadata接口如下
public interface AnnotatedTypeMetadata {MergedAnnotations getAnnotations();default boolean isAnnotated(String annotationName) {return this.getAnnotations().isPresent(annotationName);}@Nullabledefault Map<String, Object> getAnnotationAttributes(String annotationName) {return this.getAnnotationAttributes(annotationName, false);}@Nullabledefault Map<String, Object> getAnnotationAttributes(String annotationName, boolean classValuesAsString) {MergedAnnotation<Annotation> annotation = this.getAnnotations().get(annotationName, (Predicate)null, MergedAnnotationSelectors.firstDirectlyDeclared());return !annotation.isPresent() ? null : annotation.asAnnotationAttributes(Adapt.values(classValuesAsString, true));}@Nullabledefault MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationName) {return this.getAllAnnotationAttributes(annotationName, false);}@Nullabledefault MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationName, boolean classValuesAsString) {MergedAnnotation.Adapt[] adaptations = Adapt.values(classValuesAsString, true);return (MultiValueMap)this.getAnnotations().stream(annotationName).filter(MergedAnnotationPredicates.unique(MergedAnnotation::getMetaTypes)).map(MergedAnnotation::withNonMergedAttributes).collect(MergedAnnotationCollectors.toMultiValueMap((map) -> {return map.isEmpty() ? null : map;}, adaptations));}
}

这个接口显然都是检查注解的属性。就不详细解说了。

3. 处理自动装配的歧义性

3.1 @Qualifier

如果很多类都继承了同一个接口且都完成了bean的定义,那么在自动装配的时候,应该选哪个bean?比如下面这段代码:

//公共接口
public interface BaseClass{}
//下面三个类全都实现了公告接口@Component
public class OneClass implements BaseClass{}@Component
public class TwoClass implements BaseClass{}@Component
public class ThreeClass implements BaseClass{}//自动装配选择哪一个?
public class Test{@Autowiredprivate BaseClass b;
}

解决这个问题可以用以下几个方法

(1)@Primary

在@Bean下面注解,表示出现歧义的时候,优先使用这个bean。但是,同类型的只能在其中一个上使用。

(2)@Qualifier

它可以指定某一个bean

@Autowired

@Qualifier("id")

public void setData(BaseClass data){......}

这就相当于指定一个bean进行导入,这个bean的id是id

如果没有显式写id,一般bean的id就是类名的首字母小写,比如Oneclass的id就是oneClass

值得一提的是,id和类名紧耦合在一起可不是什么好事,因为变更以下名字,代码就需要修改了,不如直接给它限定一个id

比如

@Component

@Qualifier("one")

public class OneClass{......}

这样,OneClass的bean id就一直都是one了,通过这个名字可以索引到bean

如果相要写一个单元测试,就需要添加一个配置类,作为管理这三个bean的类。

3.2 自定义限定符

比如出现了一个大类都是数字类,但是下面又有了具体的One,Two,Three这样的小类,而我们想要有一个层次的限定符呢?

首先所有的数字类都可以使用@Qualifier("number")进行限定;

其次,为了对number下的各个小类进行限定

@Target({ElementType.CONSTRUCTOR, ElementType.FIFLD, ElementType.METHOD, ElementType.TYPE})

@Retention(RetentionPollicy.RUNTIME)

@Qualifier

publlic @interface Number()

 上面的代码表示新建了一个Number注解,下面就可以使用@Number注解了。

然后Number注解可以缩小注解范围

@Number

@Qualifier("oneClass")

这代表限制对象的了两层限制。

4.bean的作用域

bean一般情况下是单例模式,但是它可以选择其他模型,比如:

(1)单例模式:每次都导入的都是同一个bean

(2)原型模式:每次创建的都是不同的bean

(3)会话模式:为每个会话创建一个bean

(4)请求模式:为每个请求创建一个bean

想要修改bean的模式,可以使用@Scope注解,比如修改bean的模式是原型模式

@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)

xml文件也差不多这样

这里不详细解释,只是简单说一下。

比如在购物时,每一个人都会有一个购物服务,每一个服务都需要一个购物车,那么我们希望服务每次都是新的,但是购物车都是同一个,不然每一次访问服务都创建一个新购物车,我们岂不是要到处翻上一次添加的购物记录吗?所有才会有会话的作用域。

5. 运行时值注入

我们希望在bean能够在运行时给自己的成员赋值,该如何做?

虽然之前提到在类里面配置,或者在xml种配置,但是这些值其实都是固定的,没办法更改。我们怎么在代码运行时让它自己去设置呢?

5.1 注入外部的值

在spring中,最简单的方法就是使用Environment来检索属性

比如在app.properties写入下面的代码

data.title=title
data.artist= artist

有一个类

public class BlackDisc implements CompactDisc{private String title;private String artist;public BlackDisc(String title, String artist) {this.title = title;this.artist = artist;}public void setTitle(String title) {this.title = title;}public void setArtist(String artist) {this.artist = artist;}@Overridepublic void play() {System.out.println("Playing " + title + " by " + artist);}}

通过配置类读取app.properties

import com.example.demo.project.BlackDisc;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.Environment;@Configuration
@PropertySource("classpath:app.properties")
public class Load {@AutowiredEnvironment env;@Beanpublic BlackDisc blackDisc(){return new BlackDisc(env.getProperty("data.title"),env.getProperty("data.artist"));}
}

测试输出

package com.example.demo.environment;import com.example.demo.autoSelectBean.BaseClass;
import com.example.demo.project.BlackDisc;
import com.example.demo.project.CDConfig;
import com.example.demo.project.CDPlayerConfig;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes= {Load.class})
public class Test3 {@Autowiredprivate BlackDisc blackDisc;@Testpublic void test() {blackDisc.play();}
}

 总结:app.properties的信息会被Environment读取,我们可以使用getProperty来获取参数值

getProperty有多个重载

@Nullable
String getProperty(String key);//返回字符串String getProperty(String key, String defaultValue);//返回字符串,但是如果key不存在,则返回默认值@Nullable
<T> T getProperty(String key, Class<T> targetType);//可以返回指定的类型<T> T getProperty(String key, Class<T> targetType, T defaultValue);//可以返回指定的类型,如果key不存在,则返回默认值

 因为我们的配置文件不一定都是String,也有可能是其他的类型。

Environment类还提供了其他的方法

boolean containsProperty(String key);//判断是否包含key
String getRequiredProperty(String key) throws IllegalStateException;//直接获取key对应的值,返回类型为String,但是如果key对应的值不存在,则抛出错误<T> T getRequiredProperty(String key, Class<T> targetType) throws IllegalStateException;//返回为指定类型

5.2 通过占位符获取属性值

也就是用${}

@Configuration
@PropertySource("classpath:app.properties")
public class Load {@AutowiredEnvironment env;@Value("${data.title}")String title;@Value("${data.artist}")String artist;@Beanpublic BlackDisc blackDisc(){return new BlackDisc(title , artist );}
}

比如上面的代码,就是从app文本中导入数据

5.3 使用Spring表达式语言进行装配

这里不做详细介绍,因为没有必要。在做web开发时,这东西到处都是。只需要看原理和几个案例。

Spring 表达式有很多特性,它可以通过下面的方法导入

(1)使用bean的id引用bean

(2)调用方法和访问对象属性

(3)对值进行运算

(4)正则表达式匹配

(5)集合操作

Spring表达式需要使用#{},也就是表达式要放在里面

比如通过某个类的方法

@Value("#{bean.getNumber()}")

Integer number;

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com