Spring(1):控制反转和依赖注入、Spring注解的使用
一、依赖注入DI 与 控制反转IOC
1、实例化对象方面,Spring和传统模式的区别
- 传统的方式: 通过new 关键字主动创建一个对象
- Spring方式:对象的生命周期由Spring来管理,在加载类的时候由Spring容器统一创建类的实例,使用实例的时候,直接从Spring容器中去获取该类的实例对象。IOC是反转控制(Inversion Of Control)的缩写,就像控制权从本来在自己手里,交给了Spring。
2、定义控制反转的Bean类的两种方式
(1)在Bean配置文件中,通过配置<bean>节点来配置
<!-- 在xml文件中,配置bean -->
<bean id="person" class="com.newbie.domain.Person">
<property name="name" value="荆小六"/>
</bean>
public class Person { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } }
(2)通过注解的方式,来配置Bean
步骤一:使用注解的方式,配置bean
<!-- 使用注解的方式,配置bean-->
<!—- 在Bean配置文件中(如:applicationContext.xml),增加自动扫描组件的配置 —>
<context:annotation-config/>
<context:component-scan base-package="com.newbie"/>
步骤二:定义类组件时,添加@Component注解
//使用注解方式进行依赖注入时,Spring容器自动扫描@Component注解的类,进行对象实例化 @Component public class Person { //如需给属性设置初始值,可使用@Value注解方式,给实体类的属性赋值 @Value("荆小六”) private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } }
步骤三:使用Bean实例对象
//需要使用Bean实例对象时,通过ApplicationContext.getBean(“”)进行获取 public class Test { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext(new String[]{"applicationContext.xml"}); Person person =(Person) context.getBean("person"); System.out.println(person); } }
3、依赖注入,使用注解的方式自动装配Bean实例对象
(1) @Autowired注解:自动装配属性。
在Spring中,可以使用 @Autowired 注解通过字段、setter方法或构造方法来自动装配Bean。
实现自动装配的步骤:
步骤一:注册AutowiredAnnotationBeanPostProcessor
要启用@Autowired,必须注册“AutowiredAnnotationBeanPostProcessor', 通过在Bean配置文件中,添加 <context:annotation-config />配置实现。
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.springframework.org/schema/beans" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"> <context:annotation-config/> </beans>
步骤二:定义组件类时,给自动装配的属性添加 @Autowired 注解。它可以在字段、 setter 方法、构造方法中使用。
@Component public class Team { @Autowired private Person leader; @Autowired(required = false) private Dept dept; //Getter、Setter方法 }
(2)required = false|true,指定是否进行依赖检查
默认情况下,@Autowired将执行相关检查,以确保属性已经装配正常。当Spring无法找到匹配的Bean装配,它会抛出异常。要解决这个问题,可以通过 @Autowired 的“required”属性设置为false来禁用此检查功能。
@Autowired(required = false)
4、解决自动装配时的歧义性(@Primary和@Qualifier)
我们使用 @Autowired 注解来自动注入一个 Source 类型的 Bean 资源,但如果我们现在有两个 Srouce 类型的资源,Spring IoC 就会不知所措,不知道究竟该引入哪一个 Bean。我们可以会想到 Spring IoC最底层的容器接口——BeanFactory 的定义,它存在一个按照类型获取 Bean 的方法,显然通过 Source.class 作为参数无法判断使用哪个类实例进行返回,这就是自动装配的歧义性。
为了消除歧义性,Spring 提供了两个注解:
@Primary 注解:代表首要的,当 Spring IoC 检测到有多个相同类型的 Bean 资源的时候,会优先注入使用该注解的类。
- 问题:该注解只是解决了首要的问题,但是并没有选择性的问题
- @Qualifier 注解:上面所谈及的歧义性,一个重要的原因是 Spring 在寻找依赖注入的时候是按照类型注入引起的。除了按类型查找 Bean,Spring IoC 容器最底层的接口 BeanFactory 还提供了按名字查找的方法,如果按照名字来查找和注入不就能消除歧义性了吗?
- 使用方法: 指定注入名称为 "source1" 的 Bean 资源
/* 包名和import */ class JuiceMaker { @Autowired @Qualifier("source1") public void setSource(Source source) { this.source = source; } }
二、使用注解的方式,让Sping容器自动扫描组件
1、在配置文件中,配置由Spring自动扫描组件
通过注解的方式,Spring容器能够自动扫描包中的文件,然后由Spring容器注册Bean实例。这样,可以有效减少在XML文件中繁琐的Bean类声明。
配置方式如下:
(1)首先,在Bean配置文件中,添加<context:component-scan base-pachage=“”>,这意味着,在 Spring 中启用自动扫描功能。其中,base-package 是指明存储组件的包,Spring将扫描所指定的包,并找出注解为@Component的Bean,将其实例注册到 Spring 容器。
<context:component-scan base-package=“Spring要扫描的包(中间以逗号分隔)" />
(2)然后,在定义组件类时,显示地添加@Component注解
@Component class TeamDemo { ……. }
2、自动扫描组件的四种注释类型
在Spring2.5中,有4种类型的组件自动扫描注释类型,如下:
- @Component – 指示自动扫描组件。
- @Repository – 表示在持久层DAO组件。
- @Service – 表示在业务层服务组件。
- @Controller – 表示在表示层控制器组件。
查看源代码会发现, @Repository、@Service 和 @Controller 都被注解为 @Component类型。因此,以上三种注解 和 @Component注解的功能完全相同,Spring会自动扫描所有组件的 @Component 、@Repository、 @Service 和 @Controller注解。为便于阅读,应该始终声明@Repository,@ Service 或 @Controller 在指定的层,使你的代码更易于阅读。
//@Controller 注解的源码 @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Component public @interface Controller { String value() default ""; }
3、自定义自动扫描组件的名称
(1)默认情况下,采用类的首字母小写的形式,作为Bean的名称
@Component class TeamDemo { ……. }
//获取Bean实例的方式 TeamDemo bean = (TeamDemo) context.getBean("teamDemo");
(2)显示地指定Bean的名称
@Component("teamDemoSimple") class TeamDemo { ……. }
//获取Bean实例的方式 TeamDemo bean = (TeamDemo) context.getBean("teamDemoSimple");
4、Spring Bean作用域
在Spring中,bean作用域用于确定哪种类型的 bean 实例应该从Spring容器中返回给调用者。bean支持的5种范围域:
- 单例 - 每个Spring IoC 容器返回一个bean实例
- 原型- 当每次请求时返回一个新的bean实例
- 请求 - 返回每个HTTP请求的一个Bean实例
- 会话 - 返回每个HTTP会话的一个bean实例
- 全局会话- 返回全局HTTP会话的一个bean实例
在大多数情况下,可能只处理了 Spring 的核心作用域 - 单例和原型,默认作用域是单例。
如果 bean 配置文件中没有指定 bean 的范围,默认作用域是单例。在单例中,每个Spring IoC容器只有一个实例,无论多少次调用 getBean()方法获取它,它总是返回同一个实例。
如果要将作用域改为原型模式,可在类定义时,增加@Scope("prototype”) 注解,这样每次调用ApplicationContext.getBean(“customer”)是,Sping容器都会重新new一个新的bean实例,进行返回。
//注解方式,将作用域改为原型 @Component @Scope("prototype”) public class Customer{ ……. }
三、给实体类的属性赋值的方式
1、使用@Value注解的方式,给实体类的属性赋值
利用spring注解,可以为实体类的属性赋值,作用就类似于在配置文件里赋值一样。
赋值时的值类型可以是值内容,也可以进行SpEL表达式,同时也可以引入配置文件的值。
使用@Value赋值时,值类型如下:
- 基本数值: @Value("荆小六”)
- SpEL表达式; @Value("#{24+4}”) , @Value("#{teamDemo.name}”)
- 使用${...}取出配置文件【properties】中的值(在运行环境变量里面的值)。如需配置文件,则需要在配置类上指定文件的路径位置,使用@PropertySource读取外部配置文件中的k/v保存到运行的环境变量中;加载完外部的配置文件以后使用${}取出配置文件的值
@Component @PropertySource(value = {"classpath:/params.properties"}) public class Person { //1、基本数值 @Value("荆小六”) private String name; //2、使用计算结果赋值,age=28 @Value("#{24+4}”) private Integer age; //3、取出配置文件【params.properties】中key/valuez值:"sex=男" @Value("${sex}”) private String sex; }
SpEL表达式的使用介绍:https://www.yiibai.com/spring/spring-el-hello-world-example.html
2、使用PropertyPlaceholderConfigurer类,从外部配置文件中读取参数值
很多时候,大多数Spring开发人员只是把整个部署的详细信息(数据库的详细信息,日志文件的路径)写在XML bean配置文件中,但是,在企业环境中,部署的细节通常只可以由系统管理员或数据库管理员来'触碰',他们可能会拒绝直接访问你的bean的配置文件,它们会要求部署配置一个单独的文件,例如,一个简单的性能(properties)文件,仅具有部署细节。可以使用 PropertyPlaceholderConfigurer 类通过一个特殊的格式在外部部署细节到一个属性(properties )文件,以及访问bean的配置文件 – ${variable}.
步骤一:创建一个属性文件(database.properties),包括数据库的详细信息,把它放到你的项目类路径
jdbc.driverClassName=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/mydb jdbc.username=root jdbc.password=123456
步骤二:在声明bean配置文件中,提供一个PropertyPlaceholderConfigurer映射到 刚才创建的“database.properties”属性文件。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" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd"> <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="location"> <value>database.properties</value> </property> </bean> <bean id="driverManager" class="com.newbie.propertyPlaceholderConfigurer.DriverManager"> <property name="driverClassName" value="${jdbc.driverClassName}"/> <property name="datebaseURL" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </bean> </beans>
步骤三:实体类DriverManager的定义结构
public class DriverManager { private String driverClassName; private String datebaseURL; private String username; private String password; //Getter、Setter方法 }
四、Spring在创建对象实例或销毁实例时,设置默认执行的方法
1、在Bean配置文件中设置:
在bean 配置文件中定义<bean>节点时,使用 init-method 和 destroy-method 属性指定用于在bean初始化和销毁时,执行某些动作的方法
<bean id="teamDemo" class="com.newbie.initMethod.TeamDemo" init-method="afterPropertiesSet" destroy-method="destroy"/>
@Component class TeamDemo { public void afterPropertiesSet() throws Exception { System.out.println(“初始化对象时,执行的方法"); } public void destroy() throws Exception { System.out.println(“销毁对象实例时,执行的方法"); } }
2、使用注解的方式进行设置:
在类定义时,为类初始化和销毁时要执行的方法,添加 @PostConstruct 和 @PreDestroy 注解。其中,@PostConstruct注解 和 init-method功能相同,标识初始化时要执行的方法;@PreDestroy注解 和 destroy-method 功能相同,标识类销毁时要执行的方法。
注:@PostConstruct 和 @PreDestroy 注解不属于 Spring,它包含在J2EE库- common-annotations.jar中。
<!--XML文件中,配置“context:annotation-config”支持-->
<context:annotation-config/>
<context:component-scan base-package="com.newbie.initMethod"/>
@Component class TeamDemo { @PostConstruct public void afterPropertiesSet() throws Exception { System.out.println("初始化对象时,执行的方法"); } @PreDestroy public void destroy() throws Exception { System.out.println("销毁对象实例时,执行的方法"); } }
//测试类 class Test{ public static void main(String[] args) { ConfigurableApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); System.out.println(context.getBean("teamDemo")); //ConfigurableApplicationContext.close()将关闭该应用程序的上下文,释放所有资源,并销毁所有缓存的单例bean。 context.close(); } }
五、使用@Bean 装配外部引用文件中的Bean
- 问题: 以上都是通过 @Component 注解来装配 Bean ,并且只能注解在类上,当你需要引用第三方包的(jar 文件),而且往往并没有这些包的源码,这时候将无法为这些包的类加入 @Component 注解,让它们变成开发环境中的 Bean 资源。
-
解决方案:1.自己创建一个新的类来扩展包里的类,然后再新类上使用 @Component 注解,但这样很 low2.使用 @Bean 注解,注解到方法之上,使其成为 Spring 中返回对象为 Spring 的 Bean 资源。(注意:@Configuration 注解相当于 XML 文件的根元素,必须要,有了它才能解析其中的 @Bean 注解)。
@Bean 的配置项中包含 4 个配置项:
- name: 是一个字符串数组,允许配置多个 BeanName
- autowire: 标志是否是一个引用的 Bean 对象,默认值是 Autowire.NO
- initMethod: 自定义初始化方法
- destroyMethod: 自定义销毁方法
使用 @Bean 注解的好处就是能够动态获取一个 Bean 对象,能够根据环境不同得到不同的 Bean 对象。或者说将 Spring 和其他组件分离(其他组件不依赖 Spring,但是又想 Spring 管理生成的 Bean)
/**
* 在方法上使用@Bean注解,将外部类的实例注册到Spring容器中
*/
@Configuration
class ConfigBeanDemo {
/**
* 由Spring容器,实例化一个java.lang.String对象,实例的名称为"pName"
*/
@Bean(name = "pName")
public String getString() {
return "简三十六";
}
/**
* 由Spring容器,实例化一个java.io.InputStream对象,实例的名称为"pInput"
*/
@Bean(name = "pInput")
public InputStream getInputStream() throws Exception {
return new FileInputStream("/Users/newbie/Documents/project/aaa");
}
}
/**
* @ComponentScan注解:代表进行扫描,默认是扫描当前包的路径,扫描所有带有 @Component 注解的 POJO。
*/
@ComponentScan(basePackages = "com.newbie.domain4")
class ApplicationContextConfig {
}
/**
* 在测试类中编写代码,从 Spring IoC 容器中获取到这个 Bean
*/
public class TestStringBean {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(ApplicationContextConfig.class);
String name = (String) context.getBean("pName");
InputStream input = (InputStream)context.getBean("pInput");
System.out.println(name);
System.out.println(input);
}
}