zoukankan      html  css  js  c++  java
  • 你真的了解Spring吗?

    Spring解决了什么问题

    控制反转与依赖注入

    IOC 是 Inversion of Control 的缩写,通常被翻译为“控制反转”。
    为了解决对象之间的耦合度过高的问题,软件专家 Michael Mattson 提出了 IOC 理论,用来实现对象之间的解耦。
    2004年,Martin Fowler探讨了同一个问题,既然IOC是控制反转,那么到底是“哪些方面的控制被反转了呢?”,经过详细地分析和论证后,他得出了答案:“获得依赖对象的过程被反转了”。控制被反转之后,获得依赖对象的过程由自身管理变为了由 IOC 容器主动注入。于是,他给“控制反转”取了一个更合适的名字叫做“依赖注入(Dependency Injection)”。他的这个答案,实际上给出了实现 IOC 的方法:注入。所谓依赖注入,就是由IOC容器在运行期间,动态地将某种依赖关系注入到对象之中。
    依赖注入(DI)和控制反转(IOC)是从不同的角度的描述的同一件事情,就是指通过引入 IOC 容器,利用依赖关系注入的方式,实现对象之间的解耦。

    为什么需要依赖注入

    现实开发中,许多系统服务类是不需要创建多个实例的,该实例被其它服务依赖,而使用单例模式仍然需要调用getInst()静态方法。
    如果这些实例在运行时自行创建,在需要时自动注入(字段注入、入参注入),那该多好?

    @see https://docs.spring.io/spring-framework/docs/current/reference/html/core.html

    此外,控制反转还有一个重要作用:面向接口编程。
    在一个类中提出接口的注入请求,Spring会将符合的接口实现在运行时注入该类。表现出的是完全面向接口。

    依赖注入

    使用应用上下文

    Spring自带了多种类型的应用上下文。下面罗列的几个是你最有可能遇到的。

    AnnotationConfigApplicationContext:从一个或多个基于Java的配置类中加载Spring应用上下文。
    AnnotationConfigWebApplicationContext:从一个或多个基于Java的配置类中加载Spring Web应用上下文。
    ClassPathXmlApplicationContext:从类路径下的一个或多个XML配置文件中加载上下文定义,把应用上下文的定义文件作为类资源。
    FileSystemXmlapplicationcontext:从文件系统下的一个或多个XML配置文件中加载上下文定义。
    XmlWebApplicationContext:从Web应用下的一个或多个XML配置文件中加载上下文定义。
    

    创建Spring应用程序上下文(注解方式)

    import org.springframework.context.ApplicationContext
    import org.springframework.context.annotation.AnnotationConfigApplicationContext
    
    lateinit var ctx: ApplicationContext
    
    fun main() {
        ctx = AnnotationConfigApplicationContext(App::class.java)   <-- 启动类
        val app = ctx.getBean(App::class.java)
        app.run()
    }
    

    Bean的生命周期

    上面这些接口其实都是钩子函数,要使用该生命周期钩子函数,继承对应的Interface就可以了。

    钩子:BeanNameAware

    使用接口BeanNameAware即可知道实例的name:

    /**
     * 意图在Bean工厂中知道其Bean名称的Bean将实现的接口。
     * 注意,通常不建议对象依赖于它的bean名称,因为这表示对外部配置的潜在脆弱依赖,以及对Spring API的不必要依赖。
     */
    public interface BeanNameAware extends Aware {
    	/**
    	 * 在创建此bean的bean工厂中设置的bean的名称。
    	 * 在填充常规bean属性之后但在初始化回调(例如InitializingBean.afterPropertiesSet()或自定义init-method)之前调用。
    	 */
    	void setBeanName(String name);
    }
    

    用例:

    @Service
    @ComponentScan(basePackageClasses = [App::class])
    class App : BeanNameAware {
    
        override fun setBeanName(name: String) {
            println("实例化$name") ---> name equals "app"
        }
    }
    

    在配置类中使用@Bean注解配置Bean

    import org.springframework.context.annotation.Bean
    import org.springframework.context.annotation.Configuration
    
    @Configuration
    class JavaConfig {
      @Bean(initMethod = "init", destroyMethod = "close")    <----自定义的初始化、销毁方法
      fun configXXX() = XXX().apply {
        this.xxx()
      }
    }
    

    Profile

    作用域

    @Profile可以作用在@Configuation(或者@Component、@Service)注解的类上,也可以作用在@Bean方法上。

    在Spring 3.1中,只能在类级别上使用@Profile注解。不过,从Spring 3.2开始,你也可以在方法级别上使用@Profile注解,与@Bean注解一同使用。

    运行时激活Profile

    Spring在确定哪个profile处于激活状态时,需要依赖两个独立的属性:spring.profiles.activespring.profiles.default
    如果设置了spring.profiles.active属性的话,那么它的值就会用来确定哪个profile是激活的。
    但如果没有设置spring.profiles.active属性的话,那Spring将会查找spring.profiles.default的值。
    如果spring.profiles.active和spring.profiles.default均没有设置的话,那就没有激活的profile,因此只会创建那些没有定义在profile中的bean。
    有多种方式来设置这两个属性:

    • 作为DispatcherServlet的初始化参数;作为Web应用的上下文参数;
    • 作为JNDI条目;
    • 作为环境变量;
    • 作为JVM的系统属性;
    • 在集成测试类上,使用@ActiveProfiles注解设置。
    System.setProperty("spring.profiles.active", "dev")
    

    e.g.

    package profile
    
    import org.junit.Assert.assertEquals
    import org.junit.Assert.assertNotNull
    import org.junit.Test
    import org.junit.runner.RunWith
    import org.springframework.beans.factory.annotation.Autowired
    import org.springframework.context.ApplicationContext
    import org.springframework.context.annotation.ComponentScan
    import org.springframework.context.annotation.Configuration
    import org.springframework.context.annotation.Profile
    import org.springframework.core.env.get
    import org.springframework.stereotype.Component
    import org.springframework.test.context.junit.jupiter.SpringJUnitConfig
    import org.springframework.test.context.junit4.SpringJUnit4ClassRunner
    
    @Profile("dev")
    @Component
    open class A {
        companion object {
            init {
                println("A class loaded")
            }
        }
        init {
            println("a init")
        }
    }
    
    @Configuration
    @ComponentScan
    open class JavaConfig
    
    @RunWith(SpringJUnit4ClassRunner::class)
    @SpringJUnitConfig(classes = [JavaConfig::class])
    class Test {
        @Autowired lateinit var ctx: ApplicationContext
    
        init {
            System.setProperty("spring.profiles.active", "dev, another")
        }
    
        @Test fun `profile 'dev' should be active`() {
            val profiles = ctx.environment["spring.profiles.active"]?.split(Regex(""", *"""))
            assertNotNull(profiles)
            val profile = profiles?.find {
                it == "dev"
            }
            assertEquals("dev", profile)
        }
    
        @Test fun `autowired profile bean`() {
            val bean = ctx.getBean(A::class.java)
            assertNotNull(bean)
        }
    
        @Test fun springStartup() {
            assertNotNull(ctx)
        }
    }
    

    Bean 消歧义

    @Primary 标记首选Bean

    @Component
    @Primary
    class Implement : Interface
    

    @Qualifier 限定符

    限定id

    如果Bean定义时没有指定限定符,可以使用Bean ID作为限定符。即一个Bean至少有一个限定符,那就是ID,默认ID为类名首字母小写。

    @Component class BeanA
    ...
    @Autowired
    @Qualifier("beanA")
    lateinit var bean: BeanA
    

    自定义限定符号

    @Qualifier("wft")
    @Component class BeanA
    ...
    @Autowired
    @Qualifier("wft")
    lateinit var bean: BeanA
    

    使用自定义的限定符注解

    由于注解不能重复,所以无法使用字符串多次限定,这时可以创建接口。

    @Target(AnnotationTarget.CLASS, AnnotationTarget.FIELD)
    @Retention(AnnotationRetention.RUNTIME)
    @Qualifier
    annotation class MyQualifierA
    

    e.g.

    package profile
    
    import org.junit.jupiter.api.Assertions.*
    import org.junit.jupiter.api.Test
    import org.junit.jupiter.api.extension.ExtendWith
    import org.springframework.beans.factory.NoUniqueBeanDefinitionException
    import org.springframework.beans.factory.annotation.Autowired
    import org.springframework.beans.factory.annotation.Qualifier
    import org.springframework.context.ApplicationContext
    import org.springframework.context.annotation.ComponentScan
    import org.springframework.context.annotation.Configuration
    import org.springframework.stereotype.Component
    import org.springframework.test.context.junit.jupiter.SpringExtension
    import org.springframework.test.context.junit.jupiter.SpringJUnitConfig
    
    @Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION, AnnotationTarget.FIELD)
    @Retention(AnnotationRetention.RUNTIME)
    @Qualifier
    annotation class MyQualifier
    
    interface Interface // 定义需要注入的接口
    
    // 构建两个Bean实例,实现同一个接口
    @Component
    @MyQualifier
    class A : Interface
    
    @Component
    class B : Interface
    
    @Configuration
    @ComponentScan
    open class JavaConfig
    
    @ExtendWith(SpringExtension::class)
    @SpringJUnitConfig(classes = [JavaConfig::class])
    class CustomQualifier {
        @Autowired
        lateinit var ctx: ApplicationContext
        @Test fun `ctx not null`() {
            assertNotNull(ctx)
        }
    
        @Test
        fun `should be throw a exception`() {
            assertThrows(NoUniqueBeanDefinitionException::class.java) {
                ctx.getBean(Interface::class.java)
            }
        }
    
        @Autowired
        @MyQualifier
        lateinit var bean: Interface
        @Test fun `custom @Qualifier`() {
            assertNotNull(bean)
        }
    }
    

    END

  • 相关阅读:
    Hadoop学习笔记——配置文件
    Hive学习笔记——SerDe
    MapReduce中的OutputFormat
    Nginx 转发时的一个坑,运维居然让我背锅!!
    教你用 Netty 实现一个简单的 RPC!
    完整的支付系统整体架构!
    String 拼接一定会走 StringBuilder?
    腾讯牛逼!终于开源了自家的 Tencent JDK——Kona!!
    Java 数组转 List 的 3 种方式,哪种性能最牛?
    Spring Boot 2.4.0 发布,配置文件重大调整,不要乱升级!!
  • 原文地址:https://www.cnblogs.com/develon/p/14469839.html
Copyright © 2011-2022 走看看