Spring-IOC高级

作者: Tian_Peng | 来源:发表于2020-01-16 11:04 被阅读0次

    在Spring-IOC基础中我们学习了核心的bean装配技术,但是bean装配所涉及的领域并不仅仅局限于Spring-IOC基础中我们学习的内容,Spring提供了多种技巧,借助他们可以实现更为高级的bean装配功能

    环境与profile

    Spring提供了profile解决多个环境之间的切换问题

    • JavaConfig中使用profile
      在Java配置中可以使用@Profile注解指定某个bean属于哪个profile,@Profile注解源码如下:
    package org.springframework.context.annotation;
    
    import java.lang.annotation.Documented;
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    import org.springframework.core.env.AbstractEnvironment;
    import org.springframework.core.env.ConfigurableEnvironment;
    import org.springframework.core.env.Profiles;
    
    @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();
    
    }
    

    为了测试效果,我们可以定义一个DataSource类如下:

    package com.tp.datasource;
    
    /**
     * FileName: DataSource
     * Author:   TP
     * Description:
     */
    public class DataSource {
    
        public DataSource() {
            System.out.println("无参构造:DataSource被实例化");
        }
    
        private String userName;
    
        private String passWord;
    
        private String 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;
        }
    
        public String getUrl() {
            return url;
        }
    
        public void setUrl(String url) {
            this.url = url;
        }
    
        @Override
        public String toString() {
            return "DataSource{" +
                    "userName='" + userName + '\'' +
                    ", passWord='" + passWord + '\'' +
                    ", url='" + url + '\'' +
                    '}';
        }
    }
    

    通过JavaConfig声明这个bean:

    @Bean("dataSource")
    @Profile("dev")
    DataSource devDataSource(){
        DataSource dataSource = new DataSource();
        dataSource.setUserName("root");
        dataSource.setUserName("tp123456");
        dataSource.setUrl("jdbc:mysql://127.0.0.1:3306/jdbcStudy_dev");
        return dataSource;
    }
    
    @Bean("dataSource")
    @Profile("test")
    DataSource testDataSource(){
        DataSource dataSource = new DataSource();
        dataSource.setUserName("root");
        dataSource.setUserName("tp123456");
        dataSource.setUrl("jdbc:mysql://127.0.0.1:3306/jdbcStudy_test");
        return dataSource;
    }
    
    @Bean("dataSource")
    @Profile("prod")
    DataSource prodDataSource(){
        DataSource dataSource = new DataSource();
        dataSource.setUserName("root");
        dataSource.setUserName("tp123456");
        dataSource.setUrl("jdbc:mysql://127.0.0.1:3306/jdbcStudy_prod");
        return dataSource;
    }
    

    如上我们分别为dev、test、prod环境设置了对应的数据源配置,测试类如下:

    public class ProfileTestMain {
    
        public static void main(String[] args) {
            ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
            ctx.getEnvironment().setActiveProfiles("dev");
            System.out.println("=============================");
            ctx.refresh();
            Object dataSource = ctx.getBean("dataSource");
            System.out.println("=============================");
            System.out.println(dataSource);
        }
    }
    

    测试结果:

    =============================
    无参构造:DataSource被实例化
    =============================
    DataSource{userName='tp123456', passWord='null', url='jdbc:mysql://127.0.0.1:3306/jdbcStudy_dev'}
    
    Process finished with exit code 0
    

    由此可以看出,dev环境的DataSource被实例化了

    • Spring XML中使用profile
      在Spring xml中使用profile你可以选择为每个环境设置一个单独的xml配置文件,在xml的<beans> 标签下指定profile属性,类似这样:
    <?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:context="http://www.springframework.org/schema/context" 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 http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"
           profile="test">
    
           <!--针对test环境的配置信息-->
            <!--...-->
    </beans>
    

    这样配置下来可能配置文件从命名看来显得更明确,但是引发的问题是重复定义会很多。
    另外一种方式是在<bean></beans>内部再添加<bean profile="dev"></beans>,这样你就可以不必为每个环境单独创建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"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
    
        <bean id="course" class="com.tp.beans.Course">
            <property name="name" value="Java"/>
            <property name="durations" value="24"/>
            <property name="id" value="222"/>
        </bean>
    
        <!--测试多环境-->
        <beans profile="dev">
            <bean id="dataSource"
                  class="com.tp.datasource.DataSource"
                  p:userName="root"
                  p:passWord="tp123456"
                  p:url="jdbc:mysql://127.0.0.1:3306/jdbcStudy_dev"/>
        </beans>
    
        <beans profile="test">
            <bean id="dataSource"
                  class="com.tp.datasource.DataSource"
                  p:userName="root"
                  p:passWord="tp123456"
                  p:url="jdbc:mysql://127.0.0.1:3306/jdbcStudy_test"/>
        </beans>
    
        <beans profile="prod">
            <bean id="dataSource"
                  class="com.tp.datasource.DataSource"
                  p:userName="root"
                  p:passWord="tp123456"
                  p:url="jdbc:mysql://127.0.0.1:3306/jdbcStudy_prod"/>
        </beans>
    </beans>
    

    再次运行上面的测试类,效果是一致的。

    条件化的Bean

    假设你希望一个bean或者多个bean在某种特定条件下才被创建,例如应用的类路径下包含特定的库才创建,或者某个bean在另一个bean已经存在时才被创建,在Spring4之前很难实现这种级别的条件化配置,Spring4之后引入了@Conditional注解,它可以用到带有@Bean注解的方法上,如果给定的条件计算为true,那么Spring就会为我们创建这个bean,否则这个bean会被忽略。

    @Conditional可以给定一个任意实现了org.springframework.context.annotation.Condition的类,Condition的源码如下

    @FunctionalInterface
    public interface Condition {
    
        /**
         * Determine if the condition matches.
         * @param context the condition context
         * @param metadata metadata of the {@link org.springframework.core.type.AnnotationMetadata class}
         * or {@link org.springframework.core.type.MethodMetadata method} being checked
         * @return {@code true} if the condition matches and the component can be registered,
         * or {@code false} to veto the annotated component's registration
         */
        boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
    
    }
    

    这个接口只有一个matches()方法,返回一个boolean类型表示是否满足条件。
    为了测试条件化的bean,我们定义3个类实现Condition接口,用来判断当前系统属于哪种操作系统,并为不同的操作系统创建不同的bean:

    package com.tp.conditon;
    
    import org.springframework.context.annotation.Condition;
    import org.springframework.context.annotation.ConditionContext;
    import org.springframework.core.type.AnnotatedTypeMetadata;
    
    /**
     * FileName: LinuxCondition
     * Author:   TP
     * Description:
     */
    public class LinuxCondition  implements Condition {
        @Override
        public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
            return context.getEnvironment().getProperty("os.name").toLowerCase().contains("linux");
        }
    }
    
    package com.tp.conditon;
    
    import org.springframework.context.annotation.Condition;
    import org.springframework.context.annotation.ConditionContext;
    import org.springframework.core.type.AnnotatedTypeMetadata;
    
    /**
     * FileName: MacCondition
     * Author:   TP
     * Description:
     */
    public class MacCondition implements Condition {
        @Override
        public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
            return context.getEnvironment().getProperty("os.name").toLowerCase().contains("mac os x");
        }
    }
    
    package com.tp.conditon;
    
    import org.springframework.context.annotation.Condition;
    import org.springframework.context.annotation.ConditionContext;
    import org.springframework.core.type.AnnotatedTypeMetadata;
    
    /**
     * FileName: WindowsCondition
     * Author:   TP
     * Description:
     */
    public class WindowsCondition implements Condition {
        @Override
        public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
            return context.getEnvironment().getProperty("os.name").toLowerCase().contains("windows");
        }
    }
    

    我们有个类Cat如下:

    package com.tp.beans.condition;
    
    /**
     * FileName: Cat
     * Author:   TP
     * Description:用于条件注解的bean
     */
    
    public class Cat {
    
        private String name;
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        @Override
        public String toString() {
            return "Cat{" +
                    "name='" + name + '\'' +
                    '}';
        }
    }
    

    在JavaConfig声明bean如下:

    @Bean("cat")
    @Conditional(MacCondition.class)
    Cat macCat(){
        Cat cat = new Cat();
        cat.setName("Mac Cat");
        return cat;
    }
    
    @Bean("cat")
    @Conditional(WindowsCondition.class)
    Cat windowsCat(){
        Cat cat = new Cat();
        cat.setName("Windows Cat");
        return cat;
    }
    
    @Bean("cat")
    @Conditional(LinuxCondition.class)
    Cat linuxCat(){
        Cat cat = new Cat();
        cat.setName("Linux Cat");
        return cat;
    }
    

    测试:

    package com.tp.test;
    
    import com.tp.beans.condition.Cat;
    import org.junit.Test;
    
    /**
     * FileName: OnConditionalBeanStatement
     * Author:   TP
     * Description:基于特定条件的JavaBean声明测试
     */
    public class OnConditionalBeanStatementTest extends BaseTestCase{
    
        @Test
        public void conditionalBeanTest(){
            Cat cat = (Cat) applicationContext.getBean("cat");
            System.out.println(">>>基于JavaConfig的JavaBean声明:" + cat);
        }
    }
    

    运行结果:


    由于本人的操作系统是Mac,所以从控制台可以看出为我们创建类Mac对应的bean。
    Condition接口的matches()方法会得到ConditionContext和
    AnnotatedTypeMetadata这两个对象,我们可以通过这两个对象用来做决策,其中ConditionContext是一个接口,源码如下:

    public interface ConditionContext {
    
        /**
         * Return the {@link BeanDefinitionRegistry} that will hold the bean definition
         * should the condition match.
         * @throws IllegalStateException if no registry is available (which is unusual:
         * only the case with a plain {@link ClassPathScanningCandidateComponentProvider})
         */
        BeanDefinitionRegistry getRegistry();
    
        /**
         * Return the {@link ConfigurableListableBeanFactory} that will hold the bean
         * definition should the condition match, or {@code null} if the bean factory is
         * not available (or not downcastable to {@code ConfigurableListableBeanFactory}).
         */
        @Nullable
        ConfigurableListableBeanFactory getBeanFactory();
    
        /**
         * Return the {@link Environment} for which the current application is running.
         */
        Environment getEnvironment();
    
        /**
         * Return the {@link ResourceLoader} currently being used.
         */
        ResourceLoader getResourceLoader();
    
        /**
         * Return the {@link ClassLoader} that should be used to load additional classes
         * (only {@code null} if even the system ClassLoader isn't accessible).
         * @see org.springframework.util.ClassUtils#forName(String, ClassLoader)
         */
        @Nullable
        ClassLoader getClassLoader();
    
    }
    

    通过ConditionContext,我们可以做到如下几点:

    • 借助getRegistry()方法返回的BeanDefinitionRegistry检查bean定义
    • 借助getBeanFactory()方法返回的ConfigurableListableBeanFactory检查bean是否存在,甚至探查bean的属性
    • 借助getEnvironment()方法返回的Environment检查当前应用所在的运行环境信息检查环境变量是否存在
    • 读取并探查getResourceLoader()返回的ResourceLoader所加载的资源
    • 借助getClassLoader()返回的ClassLoader加载并检查类是否存在

    AnnotatedTypeMetadata则能够让我们检查带有@Bean注解的方法上个还有什么其他的注解,像AnnotatedTypeMetadata一样AnnotatedTypeMetadata也是一个接口:

    public interface AnnotatedTypeMetadata {
        boolean isAnnotated(String var1);
    
        @Nullable
        Map<String, Object> getAnnotationAttributes(String var1);
    
        @Nullable
        Map<String, Object> getAnnotationAttributes(String var1, boolean var2);
    
        @Nullable
        MultiValueMap<String, Object> getAllAnnotationAttributes(String var1);
    
        @Nullable
        MultiValueMap<String, Object> getAllAnnotationAttributes(String var1, boolean var2);
    }
    
    • 借助isAnnotated()方法我们可以判断带有@Bean注解的方法是不是还有其他特定的注解
    • 借助其他的那些方法我们能够检查@Bean注解的方法上其他注解的属性。

    其实前面介绍的@Profile也是基于@Conditional实现的

    处理自动装配的歧义性

    在IOC基础的学习中我们知道Spring的自动装配能够为我们减少显式配置的数量,不过仅有一个bean匹配所需的结果时,自动装配才是有效的,如果有不止一个bean能够匹配结果的话,这种歧义性会阻碍Sprig自动装配属性、构造器参数、方法参数。
    歧义性例子:
    为了演示这种歧义性,我们新建一个类AnimalServiceImpl2,同AnimalServiceImpl一样,它也实现了上节课的AnimalService接口
    具体如下:

    @Service
    public class AnimalServiceImpl2 implements AnimalService {
    
        @Override
        public void eat() {
            System.out.println("AnimalServiceImpl2:动物在吃饭....");
        }
    }
    

    我们的User类中引入了AnimalService接口:

    @Component("componentUser")
    public class User {
    
        public User() {
            System.out.println("无参构造:User被实例化");
        }
    
        // 构造器注入
        // @Autowired
        public User(Book book) {
            System.out.println("带参构造:User被实例化");
            this.book = book;
        }
    
        public User(String name, String[] favorites) {
            System.out.println("带参构造:User被实例化");
            this.name = name;
            this.favorites = favorites;
        }
    
        private Integer id;
        private String name;
        private Integer age;
        private String[] favorites;
        private Book book;
    
        // 接口注入
        @Autowired
        private AnimalService animalService;
    
        public Integer getId() {
            return id;
        }
    
        public void setId(Integer id) {
            this.id = id;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public Integer getAge() {
            return age;
        }
    
        public void setAge(Integer age) {
            this.age = age;
        }
    
        public String[] getFavorites() {
            return favorites;
        }
    
        public void setFavorites(String[] favorites) {
            this.favorites = favorites;
        }
    
        public Book getBook() {
            return book;
        }
    
        // Setter注入
        // @Autowired
        public void setBook(Book book) {
            this.book = book;
        }
    
        // 非Setter注入
        // @Autowired
        public void injectBook(Book book) {
            this.book = book;
        }
    
        public AnimalService getAnimalService() {
            return animalService;
        }
    
        @Override
        public String toString() {
            return "User{" +
                    "id=" + id +
                    ", name='" + name + '\'' +
                    ", age=" + age +
                    ", favorites=" + Arrays.toString(favorites) +
                    ", book=" + book +
                    '}';
        }
    }
    

    这个时候我们再运行一次之前正常的测试类:

    /**
     * 打开User成员变量上的@Autowired注解,演示属性注入
     */
    @Test
    public void simpleComponentScanInterfaceInject() {
        User user = (User) applicationContext.getBean("componentUser");
        System.out.println(">>>基于注解扫描的JavaBean声明:" + user);
        System.out.println(">>>基于注解扫描的JavaBean声明,注入的book:" + user.getBook());
        System.out.println(user.getAnimalService());
        if(null != user.getAnimalService()){
            user.getAnimalService().eat();
        }
    }
    

    发现报错 :

    Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.tp.service.AnimalService' available: expected single matching bean but found 2: animalServiceImpl,animalServiceImpl2
        at org.springframework.beans.factory.config.DependencyDescriptor.resolveNotUnique(DependencyDescriptor.java:221)
        at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1229)
        at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1171)
        at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:593)
        ... 42 more
    

    大概意思是期望只有一个匹配的bean,但是Spring在容器中找到了2个符合条件的bean,因此报错。
    发生这个问题的原因是,@Autowired注解是按照类型注入,因为AnimalServiceImpl和AnimalServiceImpl2都实现了AnimalService这个类,所以Spring不知道用哪个好,解决的办法有3种:

    设置首选Bean(不推荐)

    我们可以利用@Primary注解将其中一个bean设置为首选的bean:

    @Service
    @Primary
    public class AnimalServiceImpl2 implements AnimalService {
    
        @Override
        public void eat() {
            System.out.println("AnimalServiceImpl2:动物在吃饭....");
        }
    }
    

    这个时候再次运行测试类,效果如下:

    但是这种方式是不推荐的,@Primary只能设置优先,但是无法保证唯一,因为可能多个bean中有不止一个bean设置了@Primary,那样的话程序还是会发生错误

    使用@Qualifier配合@Autowired

    @Autowired
    @Qualifier("animalServiceImpl2")
    private AnimalService animalService;
    

    @Qualifier注解是使用限定符的主要方式,它可以与@Autowired和@Inject注解协同使用,@Qualifier注解的参数就是想要注入的bean的ID
    在JavaConfig中,@Qualifier也可以与@Bean一起使用
    再次运行测试类:

    使用@Resource注解

    @Resource有2个参数:name、type,它默认按照名称进行装配
    @Resource装配顺序

    • 如果同时指定了name和type,则从Spring上下文中找到唯一匹配的bean进行装配,找不到则抛出异常
    • 如果指定了name,则从上下文中查找名称(id)匹配的bean进行装配,找不到则抛出异常
    • 如果指定了type,则从上下文中找到类型匹配的唯一bean进行装配,找不到或者找到多个,都会抛出异常
    • 如果既没有指定name,又没有指定type,则自动按照byName方式进行装配;如果没有匹配,则回退为一个原始类型进行匹配,如果匹配则自动装配

    修改User.java:

    @Resource(name = "animalServiceImpl2")
    private AnimalService animalService;
    

    运行测试类:

    bean的作用域

    默认情况下,Spring上下文中的beaen都是以单例(singleton)的形式创建的,也就是说不管给定的一个bean被注入到其他bean多少次,每次注入的都是同一个实例。
    但是在某些情况下你使用的类是易变的,他们会保持一些状态,因此重用是不安全的,这种情况下将class声明为单例bean就不是什么好主意了,因为对象会被污染,稍后重用的时候会出现意想不到的问题,Spring针对了多种作用域,可以基于这些作用域创建bean,包括:

    • 单例(Singleton):在整个应用中,只创建bean的一个实例,Spring默认的作用域
    • 原型(Prototype):每次注入或者通过Spring应用上下文获取的时候,都会创建一个新的bean实例
    • 会话(Session):在Web应用中,为每个会话创建一个bean实例
    • 请求(Request):在Web应用中,为每个请求创建一个bean实例
    如何设置bean的作用域?
    • 组件扫描声明bean
      如果我们使用组件扫描声明的bean,那么我们可以使用注解@Scope("xxx")来指定bean的作用域
      @Scope的值我们可以用ConfigurableBeanFactory提供的常量,当然也可以自己手写
      例如声明为原型:
    @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    @Component("componentUser")
    public class User {
    
    }
    
    • xml声明bean
      在XMl中声明bean的时候,我们可以使用<bean>元素的scope属性来设置:
    <!--构造器注入:构造器参数为bean情形-->
    <bean id="xmlUser0" class="com.tp.beans.componentscan.User" scope="prototype">
        <constructor-arg name="book" ref="javaConfigBook"/>
    </bean>
    
    • JavaConfig声明bean
    @Bean("javaConfigBook")
    @Scope("prototype")
    public Book book() {
        Book book = new Book();
        book.setId(222);
        book.setPrice(33.88);
        book.setName("简书");
        return book;
    }
    

    运行时值注入

    Spring提供了2种在运行时求值的方式:

    • 属性占位符(Property placeholder)
    • Spring表达式(SpEL)

    1.注入外部的值

    在Spring中处理外部值最简单的方式是声明属性源并通过Spring的Environment来检索属性
    声明属性源可以使用@PropertySource注解,其值为属性文件路径信息,例如:

    @Configuration
    @PropertySource("classpath:config/app.properties")
    public class ValueInjectConfig {
    
        @Autowired
        Environment environment;
    
        @Bean
        public Jaguar jaguar(){
            Jaguar jaguar = new Jaguar();
            jaguar.setName(environment.getProperty("jaguar.name"));
            jaguar.setColor(environment.getProperty("jaguar.color"));
            return jaguar;
        }
    }
    

    Jaguar.java:

    package com.tp.beans.valueinject;
    
    /**
     * FileName: Jaguar
     * Author:   TP
     * Description:
     */
    public class Jaguar {
    
        private String name;
    
        private String 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 "Jaguar{" +
                    "name='" + name + '\'' +
                    ", color='" + color + '\'' +
                    '}';
        }
    }
    

    在上述代码中,我们使用@PropertySource注解并指定类属性源为类路径下的config/app.properties文件,文件内容如下:

    jaguar.name=捷豹XEL
    jaguar.color=white
    civic.name=思域
    civic.color=yellow
    

    这个文件会被加载到Spring的Environment中,我们就可以借助Environment的getPropery()方法获取出对应的值,Environment的getPropery()方法有4种:

    • String getPropery(String key);
    • String getPropery(String key, String defaultValue);
    • T String getPropery(String key, Class<T> type);
    • T String getPropery(String key, Class<T> type, T defaultValue);
      运行测试类:
    public class ValueInjectTest extends BaseTestCase {
    
        /**
         * "@Properties"注解测试
         */
        @Test
        public void propertyResourceInjectTest() {
            Jaguar jaguar = (Jaguar) applicationContext.getBean("jaguar");
            System.out.println(">>>>@PropertyResource:" + jaguar);
        }
    }
    

    测试结果:

    2.解析属性占位符

    直接从Environment中获取属性是比较方便的,特别是在Java配置中装配bean的时候,但是Spring也为我们提供了占位符装配bean属性的方法,这些占位符的值来源于一个属性源,为了使属性占位符生效,我们需要配置一个PropertySourcesPlaceholderConfigurer,它能够基于Spring Environment及其属性源来解析占位符,我们可以在Spring的配置文件中配置PropertySourcesPlaceholderConfigurer:

    <context:property-placeholder location="classpath*:/config/*.properties" 
                                  file-encoding="utf-8"/>
    

    准备就绪后,我们分别以注解扫描和xml形式声明一个bean

    • xml声明bean使用属性占位符:
    <bean id="xmlPlaceholderJaguar" class="com.tp.beans.valueinject.Jaguar">
        <property name="name" value="${jaguar.name}"/>
        <property name="color" value="${jaguar.color}"/>
    </bean>
    

    测试类:

    /**
     * 属性占位符xml形式测试
     */
    @Test
    public void xmlPlaceholderInjectTest() {
        Jaguar jaguar = (Jaguar) applicationContext.getBean("xmlPlaceholderJaguar");
        System.out.println(">>>>@PropertyPlaceholder of xml:" + jaguar);
    }
    

    结果:

    • 组件扫描声明bean使用占位符
    package com.tp.beans.valueinject;
    
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.stereotype.Component;
    
    /**
     * FileName: Honda
     * Author:   TP
     * Description:
     */
    @Component
    public class Civic {
    
        @Value("${civic.name}")
        private String name;
    
        @Value("${civic.color}")
        private String 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 "Civic{" +
                    "name='" + name + '\'' +
                    ", color='" + color + '\'' +
                    '}';
        }
    }
    

    测试类:

    @Test
    public void scanPlaceholderInjectTest() {
        Civic civic = (Civic) applicationContext.getBean("civic");
        System.out.println(">>>>@PropertyPlaceholder of componentScan:" + civic);
    }
    

    运行结果:

    SpEl

    Spring 3引入了Spring表达式(Spring Expression Language),它能够以一种强大和简洁的方式将值装配到bean的属性和构造器参数中,在这个过程中所使用的表达式会在运行时计算到值。
    SpEl拥有很多特性,包括:

    • 使用bean的ID来引用bean
    • 调用方法和访问对象的属性
    • 对值进行算术、关系和逻辑运算
    • 正则表达式匹配
    • 集合操作

    SpEL表达式需要放在:#{}之中,形为#{xxx},这与属性占位符类似,属性占位符为${...}之中。

    SpEl用法示例

    • 表示字面值
      #{3.1415926}表示一个浮点值
      #{'Hello'}表示一个String类型的字面值
      #{false}表示一个boolean类型的值
    • 引用bean属性、方法
      SpEl能够通过ID获取到这个bean,我们就可以使用SpEL对bean的属性或者方法实现引用
      例如:
      1.获取属性
      #{jaguar.name}:这个表达式会获取ID为jaguar的这个bean的name属性
      2.方法引用
      #{jaguar.getSlogan()}:这个表达式会获取ID为jaguar的这个bean,并返回其getSlogan()方法的返回值
      对于方法的返回值我们还可以继续调用返回值的函数,例如:#{jaguar.getSlogan().toUpperCase()},当然方法的返回值可能为null,这样就有可能引发空指针问题,为了避免空指针问题,我们可以使用类型安全的运算符:#{jaguar.getSlogan()?.toUpperCase()},这里我们使用了"?",这个运算符能够在访问它右边的内容之前确保左侧内容不为null,如果为null则不会执行后面的内容,直接返回null。
    • 在表达式中使用类型
      如果要在SpEL中访问类作用域的方法和常量的话,要依赖T()这个关键的运算符,例如:为了在SpEL中使用Java的Math类,需要按照如下的方式使用T()运算符:T(java.lang.Math),此时T()运算符会得到一个Class对象,代表了java.lang.Math,假如你想将PI值装配到bean的属性中,可以这样写:
      ${T(java.lang.Math).PI},当然我们不仅可以获取访问目标的常量,我们还可以调用其方法:${T(java.lang.Math).random()*10}
    • SpEL运算符
      SpEL提供了多种运算符,这些运算符可以用在SpEL表达式上:
    运算符类型 运算符
    算术运算 +、-、*、/、%、^
    比较运算符 <、>、==、<=、>=、lt、gt、eq、le、ge
    逻辑运算符 and、or、not、|
    条件运算符 三元运算符
    正则表达式 matches
    • 计算集合
      SpEL还可以操作集合和数组
      例如:#{jukebox.songs[4].tile},这个表达式会计算jukebox这个bean的songs属性(是个集合,集合装的Song这个javabean)中第五个元素Song的title属性,[]用来从集合或者数组中按照索引获取数据

    抛出几个简单的测试类,其他用法测试读者自行测试吧:

    package com.tp.beans.valueinject;
    
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.stereotype.Component;
    
    /**
     * FileName: Benz
     * Author:   TP
     * Description:用于测试SpEl的实体
     */
    @Component
    public class Benz {
    
        //SpEL引用bean属性
        @Value("#{xmlSpelBenz.name}")
        private String name;
    
        //SpEL引用bean方法
        @Value("#{xmlSpelBenz.getColor()}")
        private String color;
    
        //SpEL表达式中使用类型
        @Value("#{T(java.lang.Math).random()*10}")
        private int carAge;
    
        //字面常量
        @Value("#{'买奔驰吗?这次不漏油哦~'}")
        private String slogan;
    
        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;
        }
    
        public int getCarAge() {
            return carAge;
        }
    
        public void setCarAge(int carAge) {
            this.carAge = carAge;
        }
    
        public String getSlogan() {
            return slogan;
        }
    
        public void setSlogan(String slogan) {
            this.slogan = slogan;
        }
    
        @Override
        public String toString() {
            return "Benz{" +
                    "name='" + name + '\'' +
                    ", color='" + color + '\'' +
                    ", carAge=" + carAge +
                    ", slogan='" + slogan + '\'' +
                    '}';
        }
    }
    

    app.properties:

    jaguar.name=捷豹XEL
    jaguar.color=white
    civic.name=思域
    civic.color=yellow
    benz.slogan=买奔驰吗?漏油的那种!!!
    

    applicationContext.xml:

    <!--############################SpEL############################-->
    <bean id="xmlSpelBenz" class="com.tp.beans.valueinject.Benz">
        <property name="name" value="C260L"/>
        <property name="color" value="富士白"/>
        <property name="carAge" value="#{T(Math).random()*10}"/>
        <property name="slogan" value="${benz.slogan}"/>
    </bean>
    

    测试类:

    //=========================SpEl=========================
    @Test
    public void xmlSpelInjectTest() {
        Benz benz = (Benz) applicationContext.getBean("xmlSpelBenz");
        System.out.println(">>>>SpEL of xml:" + benz);
    }
    
    
    @Test
    public void componentScanSpelInjectTest() {
        Benz benz = (Benz) applicationContext.getBean("benz");
        System.out.println(">>>>SpEL of componentScan:" + benz);
    }
    

    测试结果:

    相关文章

      网友评论

        本文标题:Spring-IOC高级

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