Spring框架学习笔记(1.Spring IOC)

1. 基础概念

IOC:即控制反转,是一种解耦类与类之间关系的一种设计模式;例如当A类中需要B类的时候,并不是在A类中直接new一个B类,而是通过IOC容器统一创建A类和B类,并通过DI(依赖注入),将B类注入到A类中!

DI:即依赖注入,是IOC模式的一种实现方式;包括设值注入和构造注入

SpringBean:与JavaBean不同,所有被Spring IOC容器管理的实例都可以称作SpringBean,下文简称Bean

 

2. SpringBean的装配

SpringBean的装配有三种配置方式:xml文件配置方式、JavaConfig配置类配置方式、注解实现自动装配的配置方式

2.1 xml文件配置:

Bean类代码:

package springtest;

public class TestBean {
    private String str;

    private DIBean di;

    public TestBean() {
        System.out.println("TestBean init");
    }

    /*通过构造方法注入直接量*/
    public TestBean(String s) {
        this.str = s;
        System.out.println("TestBean init s is " + s);
    }

    /*通过构造方法注入Bean*/
    public TestBean(DIBean di) {
        this.di = di;
        System.out.println("TestBean init di ");
    }

    /*通过设值方法注入直接量*/
    public void setStr(String s) {
        this.str = s;
        System.out.println("Set str s is " + s);
    }

    /*通过设值方法注入Bean*/
    public void setDi(DIBean di) {
        this.di = di;
        System.out.println("Set di");
    }

    public void fun() {
        System.out.println("TestBean fun");
    }
}

class DIBean {
    /*Bean的构造方法为private还是public属性,并不影响装配*/
    private DIBean () {
        System.out.println("DIBean init");
    }

}


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:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="
       http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd">

    <beans>
    <!-->不设置id的Bean配置<-->
    <bean class="springtest.TestBean" />

    <!-->设置id的Bean配置<-->
    <bean id="TBean1" class="springtest.TestBean" />

    <!-->通过构造器注入常量<-->
    <bean id="TBean2" class="springtest.TestBean">
        <constructor-arg value = "Hello"/>
    </bean>

    <!-->通过设值方法注入常量<-->
    <bean id="TBean3" class="springtest.TestBean">
        <property name="str" value = "Hello"/>
    </bean>

    <bean id="DIBean" class="springtest.DIBean" />
    <!-->通过构造器注入Bean<-->
    <bean id="TBean4" class="springtest.TestBean">
        <constructor-arg ref = "DIBean"/>
    </bean>

    <!-->通过设值方法注入Bean<-->
    <bean id="TBean5" class="springtest.TestBean">
        <property name="di" ref = "DIBean"/>
    </bean>

    </beans>

</beans>

Bean获取代码:

package springtest;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SpringTest {
    public static void main(String[] args) {
        /*通过xml配置文件,生成Spring IOC容器*/
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("config.xml");

        /*通过包名.类名#序号为id的格式,获取无显示id的Bean*/
        TestBean t = (TestBean)context.getBean("springtest.TestBean#0");
        t.fun();

        /*通过id获取Bean*/
        TestBean t1 = (TestBean)context.getBean("TBean1");
        t1.fun();
    }
}

Ps:

  • xml中配置一个Bean的时候最好指定id,如果不指定id会被默认用包名.类名#序号的格式赋予一个默认的id
  • 获取Bean的时候,如果该类型的Bean只有一个实例,可以通过Bean的类类型对象来获取;如果存在多个实例的时候,则必须使用id来获取!
  • 可以使用util命名空间的标签来实现容器类型的注入(包括构造注入和设值注入)

2.2 JavaConfig配置类配置:

Bean类代码(与上述代码相似,此处只展示差异代码):

/*Java Bean示例代码与上面代码,仅仅修改了DIBean类的构造方法为public属性*/
class DIBean {
    /*JavaConfig类方式配置Bean,构造方法不能是private属性*/
    public DIBean () {
        System.out.println("DIBean init");
}

JavaConfig配置:

package springtest;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class Config {
    //不设置id的Bean配置
    @Bean
    public TestBean TBean() {
        return new TestBean();
    }

    //设置id的Bean配置
    @Bean(name = "TBean1")
    public TestBean TBean1() {
        return new TestBean();
    }

    //通过构造器注入常量
    @Bean(name = "TBean2")
    public TestBean TBean2() {
        return new TestBean("Hello");
    }

    //通过设值方法注入常量
    @Bean(name = "TBean3")
    public TestBean TBean3() {
        TestBean t =  new TestBean();
        t.setStr("Hello");
        return t;
    }

    @Bean(name = "DIBean")
    public DIBean DIBeanInit() {
        return new DIBean();
    }

    //通过构造器注入Bean
    @Bean(name = "TBean4")
    public TestBean TBean4() {
        /*通过DIBeanInit()方法,获取id为"DIBean"的Bean实例,
        该方法由于有@Bean注解的封装,并不是真正的执行方法调用,只是获取Bean实例而已*/
        return new TestBean(DIBeanInit());
    }

    //通过设值方法注入Bean
    @Bean(name = "TBean5")
    public TestBean TBean5() {
        TestBean t = new TestBean();
        t.setDi(DIBeanInit());
        return t;
    }
}

Bean获取代码:

package springtest;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class SpringTest {
    public static void main(String[] args) {
        /*通过Java Config类,生成Spring IOC容器*/
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Config.class);

        /*通过Bean id获取Bean实例*/
        TestBean t = (TestBean)context.getBean("TBean1");
        t.fun();

    }
}

Ps:

  • JavaConfig类实现Bean装配的配置,可以自己实现具体Bean装配时的初始化逻辑,对应Bean的初始化是通过调用被@Bean注解修饰的方法实现的!
  • JavaConfig类中被@Bean注解修饰的方法,装配的Bean始终是一个单例;该方法的调用并不会像普通调用方法一样,每次都重复执行方法体,而仅仅是通过该方法获取Bean实例而已!

2.3 通过注解实现自动装配:

Bean类代码:

package springtest;
import javax.inject.Named;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/*通过@Component注解,配置当前类为一个JavaBean*/
@Component("TBean1")
//@Named("TBean1")
public class TestBean {
    //@Autowired
    public DIBean di;

    /*通过@Value注解设值注入直接量*/
    @Value("Hello")
    public String s;

    /*默认构造方法*/
    public TestBean (){
        System.out.println("TestBean init");
    }

    /*通过@Value注解构造注入直接量*/
    public TestBean(@Value("Hello") String s) {
        System.out.println("Init TestBean S is" + s);
    }

    /*通过构造方法注入Bean*/
    /*通过@Autowired注解,注入一个JavaBean*/
    @Autowired
    public TestBean(DIBean di) {
        this.di = di;
        System.out.println("TestBean init di ");
    }

    /*通过设值方法注入Bean*/
    //@Autowired
    public void setDi(DIBean di) {
        this.di = di;
        System.out.println("Set di");
    }

    public void fun() {
        System.out.println("TestBean fun");
    }
}

@Component
class DIBean {
    public int a;
    private DIBean () {
        this.a = 100;
        System.out.println("DIBean init");
    }
}


JavaConfig类配置自动扫描:

package springtest;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan(basePackages = {"springtest"})
public class Config {

}

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:context="http://www.springframework.org/schema/context"
       xmlns:util="http://www.springframework.org/schema/util"
       xsi:schemaLocation="
       http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="springtest"/>

</beans>

Bean获取代码:

package springtest;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class SpringTest {
    public static void main(String[] args) {
        /*通过Java Config类,生成Spring IOC容器*/
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Config.class);

        /*通过Bean id获取Bean实例*/
        TestBean t = (TestBean)context.getBean("TBean1");
        t.fun();
        System.out.println(t.di.a);
    }
}

Ps:

  • 依赖注入分为两种:设值注入和构造注入,两种方式分别可以注入直接量和Bean引用切记通过设值注入的时候,被注入的属性必须要有一个public属性的setter方法!
  • Bean的自动装配,需要配置自动扫描包路径,可以使用xml配置,也可以使用JavaConfig类配置;Java Config配置类使用@ComponentScan注解的时候,可以传入一个包名数组给basePackages属性,也可以传入一个类名数组给basePackageClasses属性;用来指示扫描自动装配Bean的范围!当不设置参数的时候,默认扫描当前Java Config类的同包范围!
  • 通过@Component注解,实现Bean的配置;可以通过参数指定Bean的id
  • 通过@Autowired注解,实现Bean的依赖注入,但是@Autowired只能注入Bean,不能注入直接量;可以通过@Autowired注解修饰构造方法、setter方法、成员变量来实现Bean的注入
  • 通过@Bean注解,实现直接量的注入;可以通过@Bean注解修饰成员变量、方法入参来实现直接量的注入
  • 当通过@Autowired注解,自动注入Bean失败的时候,Spring会抛出异常,可以通过使用@Autowired注解的required属性设置为false来屏蔽这个异常的抛出
  • Spring支持JSR330的相关注解,可以使用@Named注解替换@Component注解;使用@Inject注解替换@Autowired注解
  • Spring支持JSR250的相关注解,可以使用@Resource注解替换@Autowired注解,@Resource注解可以明确通过Bean id注入;支持@PostConstruct注解(创造资源之后的回调处理),支持@PreDestroy注解(销毁资源之前的回调处理) 

2.4 混合配置(即上述三种方式是可以混合使用的):

2.4.1 xml配置中引入JavaConfig配置类:

    <!-- xml配置文件中引入其他xml配置文件 -->
    <import resource="配置文件.xml" /> 
    <!-- xml配置文件中引入JavaConfig配置类 -->
    <bean class="包路径.JavaConfig配置类名" />

2.4.2 JavaConfig配置类中引入xml配置:

@Configuration
/*JavaConfig配置类中引入其他配置类*/
@Import({Config.class})
/*JavaConfig配置类中引入xml配置文件*/
@ImportResource({"config.xml"})
public class Config {

}

比较:

  • xml配置和JavaConfig类配置属于显示装配Bean和显示注入依赖,注解配置属于根据扫描范围和IOC容器实现自动装配及自动注入依赖!
  • xml配置和注解自动装配的Bean的构造方法可以为private属性,JavaConfig类配置的Bean的构造方法必须要是public属性
  • xml文件相比JavaConfig配置类,依赖注入配置比较麻烦;而且JavaConfig配置类是基于Java代码实现的比起xml配置可以更好的利用Java的语言特性,并且在Bean的初始配置的时候可以增加自己的逻辑!
  • 注解自动装配在很多场景下使用都比较简单,但是在针对一个类进行多个Bean装配的时候和多个同类型的Bean注入的时候,就显的比较复杂了(即由于同一个类不能有两个@Component注解,@Autowired注解不可以明确指定注入的Bean id)!!!

 

3. Profile配置

Spring可以通过Profile的配置,来控制不同Bean在不同的Profile属性中差异化装配

3.1 Profile属性的配置:

3.1.1 JavaConfig配置类配置:

可以通过@Profile注解整体标记一个JavaConfig配置类,也可以通过@Profile标记一个Bean的配置方法

/*标记一个JavaConfig类*/
@Configuration
@Profile("profile1")
public class JavaConfig {

}
/*标记一个Bean*/
@Configuration
public class JavaConfig {
    @Bean
    @Profile("profile1")
    public TestBean TBean() {
        return new TestBean();
    }
}

3.1.2 xml文件的配置:

可以通过<beans>标签的profile属性,标记一个或者多个Bean

    <beans profile="profile1">
        <bean class="A1"/>
        <bean class="A2"/>
    </beans>
    <beans profile="profile2">
        <bean class="B1"/>
    </beans>

3.2 Profile属性的激活:

Profile属性默认情况下是不激活的,即所有被标记为Profile的Bean都是默认不装配的;只有当对应的Profile属性值被激活后,对应的Profile标记的Bean才会进行装配!

3.2.1 属性值:

spring.profiles.default:profile的默认值,优先active;active没设置,再找defualt

spring.profiles.active: profile的属性值

3.2.2 激活属性值:

  • DispatcherServlet的初始参数
  • web应用上下文参数
  • 环境变量
  • JVM系统属性

可以通过上面描述的参数设置方式,激活Profile属性值!

 

4. 条件化创建Bean

Spring4引入了@Conditional注解,可以在@Component或@Bean注解后面使用@Conditional注解,并给出Condition函数式接口的实现类,通过返回值确定Bean是否可以创建;返回true,则创建;返回false,则不创建

Ps:Spring4后的版本,@Profile注解也是依赖@Conditional注解实现的

 

5. @Autowired和@Inject注解注入的歧义性

由于@Autowired和@Inject注解都是依靠类类型进行注入的,所以当存在多个同类型Bean的情况下,依赖注入就会发生歧义性!针对这样的歧义性,Spring给出两种解决方式!

5.1 Primary标签:

可以在@Component注解和@Bean注解后面增加@Primary注解,用来指示当前Bean类型被注入的时候进行首选注入!也可以在xml配置文件<bean>标签中增加Primary属性!

/*JavaConfig配置类中,使用@Primary注解*/
@Configuration
public class JavaConfig {
    @Bean
    @Primary
    public TestBean TBean() {
        return new TestBean();
    }
}


/*通过注解自动装配Bean使用@Primary注解*/
@Component("TBean")
@Primary
public class TestBean {

}


<bean class="Bean" primary="true"/>

5.2 @Qualifier注解:

可以在@Component注解后面使用@Qualifier注解设置限定符,在@Autowired注解后面使用相同的@Qualifier限定符就可以把指定的Bean注入进来!但是Spring并没有约束@Component后面@Qualifier注解限定符必须是唯一的,所以依然有可能存在多对一的歧义场景!

@Component("DIBean")
@Qualifier("DI")
public class DIBean {

}

@Component("TBean")
public class TestBean {
    @Autowired
    @Qualifier("DI")
    DIBean di;
}

5.3 @Autowired、@Inject和@Resource区别:

  • @Resource是JSR250规范中描述的注解、@Inject是JSR330规范中描述的注解、@Autowired是Spring框架的注解;
  • 使用上@Autowired除了比@Inject多一个required参数,并无其他区别
  • @Resource相比@Autowired和@Inject,可以指定Bean id或者Bean类型进行注入

6. SpringBean的作用域

SpringBean有四种作用域:单例作用域、原型作用域、会话作用域、请求作用域

单例作用域:

整个应用中,只有一个Bean实例,每次依赖注入都是相同的一个Bean实例

/*可以修饰JavaConfig类中的@Bean配置*/
/*可以修饰注解配置的@Component*/
@Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)
<bean class="Bean1" scope="singleton"/>

原型作用域:

每次注入或者通过Spring应用上下文获取Bean的时候,都会创建一个新的Bean实例

@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
<bean class="Bean2" scope="prototype"/>

会话作用域:

在web应用中,每次会话重新创建一个新的Bean实例

@Scope(value=WebApplicationContext.SCOPE_SESSION)
<bean class="Bean3" scope="session"/>

请求作用域:

在web应用中,每次请求重新创建一个新的Bean实例

@Scope(value=WebApplicationContext.SCOPE_REQUEST)
<bean class="Bean3" scope="rquest"/>

会话作用域和请求作用域的代理:

当会话作用域或者请求作用域的Bean注入的时候,由于Spring初始上下文的时候并不存在会话和请求,需要注入一个代理类,通过代理类进行不同会话作用域或者请求作用域Bean的委托!

  • 当注入类型为接口的时候,使用ScopedProxyMode.INTERFACES代理模式
  • 当注入类型为实现类的时候,使用ScopedProxyMode.TARGET_CLASS代理模式
@Scope(proxyMode = ScopedProxyMode.INTERFACES)
@Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)
<bean class="Bean4" scope="session">
    <aop:scoped-proxy proxy-target-class="false"/>
<bean/>

 

7 Spring获取资源文件

7.1 加载资源文件:

7.1.1 注解方式:

在JavaConfig配置类上,@Configuration注解后面使用@PropertySource注解指定资源文件路径,实现资源文件的加载!

@Configuration
@PropertySource("classpath:app.properties")
public class JavaConfig {
    @Bean
    public PropertySourcesPlaceholderConfigurer placeholderConfigurer() {
        return new PropertySourcesPlaceholderConfigurer();
    }
}

7.1.2 xml配置文件方式:

xml文件配置同样可以实现资源文件的加载,可以使用context:property-placeholder命名空间和装配PropertySourcesPlaceholderConfigurer类两种方式

context:property-placeholder命名空间:

<context:property-placeholder location="classpath:app.properties" file-encoding="UTF-8"/>

 装配PropertySourcesPlaceholderConfigurer类:

<bean class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
    <property name="location">
        <value>app.properties</value>
    </property>
</bean>

7.2 资源信息获取:

资源文件

#app.properties文件
t1=test1
t2=test2

7.2.1 Environment接口实现(通过getProperty方法获取参数):

@Component("TBean")
public class TestBean {
    @Autowired
    public Environment env;
}

public class SpringTest {
    public static void main(String[] args) {
        /*通过Java Config类,生成Spring IOC容器*/
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(JavaConfig.class);

        /*通过Bean id获取Bean实例*/
        TestBean t = (TestBean)context.getBean("TBean");
        System.out.println(t.env.getProperty("t1"));
    }
}

7.2.2 占位符方式:

@Component("TBean")
public class TestBean {
    @Value("${t1}")
    public String s;
}

7.2.3 SpEL表达式方式:

@Component("TBean")
public class TestBean {
    @Value("#{'${t1}'}")
    public String s;
}

Ps:

  • 当使用$占位符的时候,一定要装配PropertySourcesPlaceholderConfigurer类,因为PropertySourcesPlaceholderConfigurer是占位符实现类,否则报错!JavaConfig配置类需要显示配置、xml配置文件也可以显示配置,同样也可以使用context:property-placeholder命名空间,Spring会自动完成装配!
  • SpEL表达式比较复杂,这里就先不展开描述了!

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章