1、背景介绍
随着现在SpringBoot的越来越流行,Spring的注解开发就应该得到重视。因为SpringBoot中很多的注解,对于之前进行xml配置进行开发的人来说,会显得比较陌生。而SpringBoot其实就是更好的封装了Spring,是基于Spring的。所以了解Spring的注解开发,对于学习SpringBoot有很好的帮助,并且Spring的注解开发可以提高开发效率,可以免除繁杂的xml配置的烦恼。
2、注解开发实现IOC和DI
2.1 创建Maven工程
1、创建maven工程
File——》Project——》选择Maven:
下一步如下:
点击finish即可。
2、添加对应的依赖
在pom.xml文件中,添加Spring对应的坐标,以及junit坐标:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.spring.annotation</groupId>
<artifactId>spring-annotation</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.2.4.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
2.2 IOC
以前我们的使用xml配置文件applicatonContext.xml来管理Spring的Bean,现在我们不再需要xml的配置文件,通过Spring的相关注解就能实现。
2.2.1 IOC的入门案例
1、创建pojo
创建一个Bean,该Bean需要交给Spring进行管理。
public class User {
private String name;
private int age;
//get/set方法,有参无参构造,以及toString方法
}
2、创建配置类
创建一个Spring的配置类,这个配置类使用注解:@Configuration 修饰,表明该类是Spring的配置类,相当于是一个applicationContext.xml配置文件,里面可以定义Spring管理的Bean,定义Bean的时候要使用注解:@Bean
package com.spring.annotation.config;
import com.spring.annotation.bean.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @Configuration:表示这个类是配置类,相当于配置文件开发的时候的applicationContext.xml文件
*/
@Configuration
public class SpringConfig {
/**
* @Bean:给容器中注册一个Bean;类型为返回值的类型,id默认是用:方法名作为id
* 也可以使用value属性来为它指定一个 id
*/
@Bean(value = "user")
public User user(){
return new User("张三",23);
}
}
3、测试类,从Spring的配置类中获取实例对象
package com.spring.annotation.test;
import com.spring.annotation.bean.User;
import com.spring.annotation.config.SpringConfig;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class SpringConfigTest {
public static void main(String[] args) {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
User user = applicationContext.getBean(User.class);
System.out.println(user);
String[] names = applicationContext.getBeanNamesForType(User.class);
for(String name : names){
System.out.println(name);
}
}
}
注意:我们使用的是 AnnotationConfigApplicationContext 对象来获取 ApplicationContext (IOC容器对象),xml配置开发的时候,使用的是:ClassPathXmlApplicationContext。
打印结果:
User{name='张三', age=23}
user
以上通过简单的几步,就实现了把Bean交给Spring管理,IOC的功能就已经得到了实现,只是使用了几个简单的注解,根本就不需要复杂的xml配置,这样很明显能提高我们的开发效率。
2.2.2 IOC相关的其他注解
1、@ComponentScans和@ComponentScan
@ComponentScans 注解相当于xml配置文件中的标签:<context:component-scan base-package="com.springmvc,com.test" />,在标签中可以配置扫描多个包,包名之间用逗号分隔,同理,在@ComponentScans中可以配置多个@ComponentScan(在JDK8以后,可以在配置类上配置同级的多个@ComponentScan 也是同样的效果)。
useDefaultFilters=false:不使用默认的扫描规则(默认是扫描所有@Component和@Controller @Service、@Repository注解)
includeFilters:配置只扫描哪些
excludeFilters:配置不扫描哪些
a、在SpringConfig类上添加@ComponentScans和@ComponentScan注解:
package com.spring.annotation.config;
import com.spring.annotation.bean.User;
import org.springframework.context.annotation.*;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Service;
/**
* @Configuration:表示这个类是配置类,相当于配置文件开发的时候的applicationContext.xml文件
*/
@Configuration
/**
* @ComponentScans 相当于标签:<context:component-scan base-package="com.springmvc,com.test" />
* useDefaultFilters=false:不使用默认的扫描规则(默认是扫描所有@Component和@Controller
* @Service、@Repository注解)
* includeFilters:配置只扫描哪些
* excludeFilters:配置不扫描哪些
*/
@ComponentScans(value = {
@ComponentScan(value = "com.spring.annotation",
includeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {Controller.class, Service.class})},
useDefaultFilters = false),
@ComponentScan(value = "com.spring.test",
excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {Controller.class, Service.class})}
)
}
)
public class SpringConfig {
/**
* @Bean:给容器中注册一个Bean;类型为返回值的类型,id默认是用:方法名作为id
* 也可以使用value属性来为它指定一个 id
*/
@Bean(value = "user")
public User user(){
return new User("张三",23);
}
}
b、创建UserController,类上添加@Controller注解,创建UserService,类上添加@Service注解,创建UserDao,类上添加@Repository注解。
c、在src/test下面创建测试类,测试Spring扫描了哪些类:
package com.spring.annotation.test;
import com.spring.annotation.config.SpringConfig;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class IOCTest {
@Test
public void test(){
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
for(String beanName : beanDefinitionNames){
System.out.println(beanName);
}
}
}
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalRequiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
springConfig
userController
userService
user
并没有扫描道UserDao,因为我们指定了只扫描@Controller和@Service。
FilterType.ANNOTATION:按照注解
FilterType.ASSIGNABLE_TYPE:按照给定的类型;
@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE,classes = UserDao.class)
FilterType.ASPECTJ:使用ASPECTJ表达式
FilterType.REGEX:使用正则指定
FilterType.CUSTOM:使用自定义规则
2、@Scope注解
可以在创建Bean实例的方法上使用@Scope,指定作用域,默认是单实例的:singleton:
@Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)
@Bean(value = "user")
public User user(){
return new User("张三",23);
}
可取值以及解释:
@see ConfigurableBeanFactory#COPE_PROTOTYPE
@see ConfigurableBeanFactory#SCOPE_SINGLETON
@see org.springframework.web.context.WebApplicationContext#SCOPE_REQUEST request
@see org.springframework.web.context.WebApplicationContext#SCOPE_SESSION sesssio
prototype:多实例的:ioc容器启动并不会去调用方法创建对象放在容器中。每次获取的时候才会调用方法创建对象;
singleton:单实例的(默认值):ioc容器启动会调用方法创建对象放到ioc容器中。以后每次获取就是直接从容器(map.get())中拿,
request:同一次请求创建一个实例
session:同一个session创建一个实例
3、@Lazy注解
@Lazy注解可以实现懒加载;
懒加载只针对单实例bean(Scope作用域是singleton的):默认在容器启动的时候创建对象;
懒加载:容器启动不创建对象。第一次使用(获取)Bean创建对象,并初始化;第二次获取的也是同一个对象,因为是单实例的。这其实就是单例模式的懒汉式和饿汉式
4、@Conditional注解
@Conditional注解在SpringBoot中被大量的使用,条件化注册Bean实例。
@Conditional({Condition}) : 该注解接受一个Condition(是一个接口)的数组,按照一定的条件进行判断,满足条件才会给容器中注册bean。如果不满足Condition条件,便不会创建@Conditional注解修饰的Bean。
需求:当前环境是Linux系统,就创建Linux之父的User对象linus,如果是windows环境,就创建windows之父的User对象Bill Gates。
步骤:
第一步:创建两个Bean,分别返回linus的User对象和Bill Gates的User对象,但是创建Bean的方法上要加上@Conditional注解,注解里面分别添加自定义的的Condition接口的实现类:LinuxConditiion和WindowsCondition。
@Conditional(LinuxCondition.class)
@Bean
public User linus(){
return new User("linus",50);
}
@Conditional(WindowsCondition.class)
@Bean
public User bill(){
return new User("bill gates",65);
}
第二步:定义Condition接口的实现类:LinuxConditiion和WindowsCondition,通过Condition接口的方法来获取当前系统的环境名称,然后返回true;
package com.spring.annotation.condition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotatedTypeMetadata;
//判断是否linux系统
public class LinuxCondition implements Condition {
/**
* ConditionContext:判断条件能使用的上下文(环境)
* AnnotatedTypeMetadata:注释信息
*/
public boolean matches(ConditionContext context, AnnotatedTypeMetadata annotatedTypeMetadata) {
//1、能获取到ioc使用的beanfactory,BeanFactory的有创建和注入Bean的信息
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
//2、获取类加载器
ClassLoader classLoader = context.getClassLoader();
//3、获取当前环境信息
Environment environment = context.getEnvironment();
//4、获取到bean定义的注册类
BeanDefinitionRegistry registry = context.getRegistry();
//获取当前环境的操作系统的名称
String osName = environment.getProperty("os.name");
//可以判断容器中的bean注册情况,也可以给容器中注册bean
boolean definition = registry.containsBeanDefinition("person");
if(osName.contains("linux")){
return true;
}
return false;
}
}
package com.spring.annotation.condition;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotatedTypeMetadata;
//判断是否是windows操作系统
public class WindowsCondition implements Condition {
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
Environment environment = context.getEnvironment();
String property = environment.getProperty("os.name");
if(property.contains("Windows")){
return true;
}
return false;
}
}
第三步:测试
@Test
public void test2(){
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
ConfigurableEnvironment environment = (ConfigurableEnvironment) applicationContext.getEnvironment();
//动态获取环境变量的值;Windows 10
String property = environment.getProperty("os.name");
System.out.println(property);
String[] namesForType = applicationContext.getBeanNamesForType(User.class);
for (String name : namesForType) {
System.out.println(name);
}
Map<String, User> persons = applicationContext.getBeansOfType(User.class);
System.out.println(persons);
}
在windows环境下,执行测试方法,打印:
Windows 10
user
bill
{user=User{name='张三', age=23}, bill=User{name='bill gates', age=65}}
IDEA修改VM参数,模拟Linux环境:Edit Configuration,添加:-Dos.name=linux
再次执行,结果打印如下:
linux
user
linus
{user=User{name='张三', age=23}, linus=User{name='linus', age=50}}
注意:@Conditional({WindowsCondition.class}) 也可以放到类上面,类中组件统一设置。满足当前条件,这个类中配置的所有bean注册才能生效;
5、@Import注解
之前我们已经学过两种把Bean注册到IOC容器的方法:
方式一:包扫描+组件标注注解(@Controller/@Service/@Repository/@Component)[自己写的类],但是这样只能注册自己写的类,如果是第三方jar包里面的类,我们就无法添加注解了,就需要使用方式二。
方式二:@Bean[导入的第三方包里面的组件],比如我们需要导入JdbcTemplate,就可以使用@Bean注解进行注册。
其实@Import也是向IOC容器注册组件的,它可以快速给容器中导入一个组件,它的使用方式有如下三种:
5.1 @Import(要导入到容器中的组件的Class对象)
在配置类(比如之前的SpringConfig)上面添加@Import注解,容器中就会自动注册这个组件,id默认是全类名。
定义个新的pojo:
public class Car {
}
在SpringConfig配置类上添加@Import注解:
@Import({Car.class})
public class SpringConfig {
//。。。省略了其他注册Bean的方法
}
测试方法,打印IOC容器中所有的Bean:
@Test
public void testImport(){
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
//获取IOC容器中所有注册好的Bean
String[] definitionNames = applicationContext.getBeanDefinitionNames();
for (String name : definitionNames) {
System.out.println(name);
}
}
5.2 ImportSelector
可以定义一个实现接口ImportSelector接口的实现类,自定义导入注解的规则,然后把该实现类的Class对象也放到@Import注解中。ImportSelector:返回需要导入的组件的全类名数组
第一步,创建两个类Audi和Jili(在包com.spring.annotation.bean下面创建)
public class Car {
}
public class Jili {
}
第二步,自定义MyImportSelector
package com.spring.annotation.condition;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;
//自定义逻辑返回需要导入的组件
public class MyImportSelector implements ImportSelector {
//返回值,就是到导入到容器中的组件全类名
//AnnotationMetadata:当前标注了@Import注解的类(比如SpringConfig)的所有注解信息
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
//importingClassMetadata
//方法不要返回null值,返回null会直接报空指针异常
return new String[]{"com.spring.annotation.bean.Audi","com.spring.annotation.bean.Jili"};
}
}
第三步,把MyImportSelector的Class对象添加到SpringConfig配置类的@Import注解上:
@Import({Car.class, MyImportSelector.class})
public class SpringConfig {
}
第四步,执行上面的测试方法,查看结果:
ImportSelector 的这种方式,在SpringBoot中使用的比较多。
5.3 ImportBeanDefinitionRegistrar
ImportBeanDefinitionRegistrar可以手动注册bean到容器中:
第一步,创建类Dazhong
package com.spring.annotation.bean;
public class Dazhong {
}
第二步,自定义MyImportBeanDefinitionRegistrar,实现ImportBeanDefinitionRegistrar接口
package com.spring.annotation.condition;
import com.spring.annotation.bean.Dazhong;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
/**
* AnnotationMetadata:当前类的注解信息
* BeanDefinitionRegistry: BeanDefinition注册类;
* 把所有需要添加到容器中的bean;调用
* BeanDefinitionRegistry.registerBeanDefinition手工注册进来
*/
public void registerBeanDefinitions(AnnotationMetadata annotationMetadata,
BeanDefinitionRegistry beanDefinitionRegistry) {
boolean definition = beanDefinitionRegistry.containsBeanDefinition("com.spring.annotation.bean.Audi");
boolean definition2 = beanDefinitionRegistry.containsBeanDefinition("com.spring.annotation.bean.Jili");
if(definition && definition2){
//指定Bean定义信息;(Bean的类型,Bean。。。)
RootBeanDefinition beanDefinition = new RootBeanDefinition(Dazhong.class);
//注册一个Bean,指定bean名
beanDefinitionRegistry.registerBeanDefinition("dazhong", beanDefinition);
}
}
}
第三步,在SpringConfig配置类的@Import注解上加上MyImportBeanDefinitionRegistrar.class
@Import({Car.class, MyImportSelector.class, MyImportBeanDefinitionRegistrar.class})
public class SpringConfig {
}
第四步测试:
6、通过FactoryBean注册组件
之前注册Bean实例到IOC容器的方式,注册的都是普通Bean,普通Bean得到的就是Bean本身,而FactoryBean注册的getObject方法返回值的对象。
6.1 自定义CarFactoryBean
package com.spring.annotation.bean;
import org.springframework.beans.factory.FactoryBean;
public class CarFactoryBean implements FactoryBean {
//返回一个Car对象,这个对象会添加到IOC容器中
public Object getObject() throws Exception {
return new Car();
}
public Class<?> getObjectType() {
return Car.class;
}
//true:这个bean是单实例,在容器中保存一份
//false:多实例,每次获取都会创建一个新的bean;
public boolean isSingleton() {
return true;
}
}
6.2 在SpringConfig配置类中注册CarFacotryBean
@Bean
public CarFactoryBean carFactoryBean(){
return new CarFactoryBean();
}
6.3 测试
/**
* 4)、使用Spring提供的 FactoryBean(工厂Bean);
* 1)、默认获取到的是工厂bean调用getObject创建的对象
* 2)、要获取工厂Bean本身,我们需要给id前面加一个&&colorFactoryBean
*/
@Test
public void testFactoryBean(){
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
//工厂Bean获取的是调用getObject创建的对象
Object bean2 = applicationContext.getBean("carFactoryBean");
Object bean3 = applicationContext.getBean("carFactoryBean");
System.out.println("bean的类型:"+bean2.getClass());
System.out.println(bean2 == bean3);
//如果要获取到真正的FactoryBean对象,要使用 &
Object bean4 = applicationContext.getBean("&carFactoryBean");
System.out.println(bean4.getClass());
}
2.2.3 Spring中Bean的生命周期
Spring管理的Bean的生命周期,就是Bean从创建,初始化再到销毁的过程。我们可以自定义初始化和销毁方法;容器在bean进行到当前生命周期的时候来调用我们自定义的初始化和销毁方法。可以有如下3种方式来为我们的Bean指定初始化和销毁方法:
方式一:在@Bean注解上执行initMethod和destoryMethod
1、在Car类中定义初始化和销毁方法
package com.spring.annotation.bean;
public class Car {
public Car() {
System.out.println("Car对象创建了");
}
//初始化方法,该方法不能有参数
public void init(){
System.out.println("init method...");
}
//初始化方法,该方法不能有参数
public void destroy(){
System.out.println("destroy method...");
}
}
2、创建新的配置类,在配置类中注册Car
package com.spring.annotation.config;
import com.spring.annotation.bean.Car;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class SpringLifeConfig {
@Bean(initMethod="init",destroyMethod="destroy")
public Car car(){
return new Car();
}
}
3、测试
package com.spring.annotation.test;
import com.spring.annotation.config.SpringLifeConfig;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class SpringLifeTest {
@Test
public void testLife(){
AnnotationConfigApplicationContext applicationContext =
new AnnotationConfigApplicationContext(SpringLifeConfig.class);
System.out.println("IOC容器创建完成...");
//关闭容器
applicationContext.close();
}
}
注意:
构造(对象创建)
单实例:在容器启动的时候创建对象
多实例:在每次获取的时候创建对象
初始化:
对象创建完成,并赋值好,调用初始化方法。。。
销毁:
单实例:容器关闭的时候
多实例:容器不会管理这个bean;容器不会调用销毁方法;可以手动调用销毁方法
方式二:让自定义的Bean实现InitializingBean(定义初始化逻辑),DisposableBean(定义销毁逻辑)
package com.spring.annotation.bean;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
@Component
public class Person implements InitializingBean, DisposableBean {
public void destroy() throws Exception {
System.out.println("DisposableBean --- 销毁方法");
}
public void afterPropertiesSet() throws Exception {
System.out.println("InitializingBean --- 初始化方法");
}
public Person() {
System.out.println("Person构造方法执行,创建完成");
}
}
类上添加了注解:@Component,交给Spring管理。
在SpringLifeConfig配置类上加上:@ComponentScan("com.spring.annotation.bean")//配置包扫描,会自动扫描到下面的所有的类,并注册到IOC容器中,测试效果如下:
方式三:可以使用JSR250的两个注解
@PostConstruct:在bean创建完成并且属性赋值完成;来执行初始化方法
@PreDestroy:在容器销毁bean之前通知我们进行清理工作
package com.spring.annotation.bean;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
@Component
public class Dog {
public Dog() {
System.out.println("Dog构造完成");
}
@PostConstruct
public void postCOnstruct(){
System.out.println("在对象创建完成后会调用该方法。。。");
}
@PreDestroy
public void preDestory(){
System.out.println("Dog的销毁方法");
}
}
2.2.4 BeanPostProcessor【interface】:bean的后置处理器
在bean初始化前后进行一些处理工作,针对所有的IOC容器中的Bean:
postProcessBeforeInitialization:在初始化之前工作
postProcessAfterInitialization:在初始化之后工作
1、BeanPostProcessor的使用
自定义后置处理器:添加@Component注解,交给Spring管理
package com.spring.annotation.bean;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;
@Component//交给Spring管理
public class MyBeanPostProcessor implements BeanPostProcessor {
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("postProcessBeforeInitialization..."+beanName+"=>"+bean);
return bean;
}
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("postProcessAfterInitialization..."+beanName+"=>"+bean);
return bean;
}
}
此时再执行上面的测试方法,查看控制台的打印:
2、BeanPostProcessor的原理
源码中:populateBean(beanName, mbd, instanceWrapper);给bean进行属性赋值
initializeBean()方法中:
{
applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
invokeInitMethods(beanName, wrappedBean, mbd);执行自定义初始化
applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
}
Spring会遍历得到IOC容器中所有的BeanPostProcessor;挨个执行beforeInitialization,一但返回null,跳出for循环,不会执行后面的BeanPostProcessor.postProcessorsBeforeInitialization
Spring底层对 BeanPostProcessor 的使用;bean赋值,注入其他组件,@AutowiredAnnotationBeanPostProcessor,生命周期注解功能,@AsyncAnnotationBeanPostProcessor;
2.2.5 与属性相关注解
1、@Value
@Value注解的作用就相当于xml配置文件中,配置<bean>标签里面的 value属性。@Value注解的value属性可以有以下三种方式赋值:
@Value(vaue="张三") 直接写字面(数字,字符串等)值
@Value(value="#{20+2}") 可以使用SpEL表达式
@Value(value="${key}") 可以从外部配置文件中获取值
a、创建Employee
package com.spring.annotation.bean;
import org.springframework.beans.factory.annotation.Value;
public class Employee {
@Value("张三")
private String name;
@Value("#{6000+3000}")
private int salary;
@Value("${employee.addr}")
private String addr;
//get/set以及无参有参构造和toString方法
}
b、创建配置类
package com.spring.annotation.config;
import com.spring.annotation.bean.Employee;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
@Configuration
//使用@PropertySource 引入类路下面的 employee.properties 文件
@PropertySource(value = {"classpath:employee.properties"})
public class SpringPropertyConfig {
@Bean
public Employee employee(){
return new Employee();
}
}
使用@PropertySource 引入类路下面的 employee.properties 文件
@PropertySource(value = {"classpath:employee.properties"})
c、在类路径下创建employee.properties
employee.addr=湖南长沙
d、测试类
package com.spring.annotation.test;
import com.spring.annotation.bean.Employee;
import com.spring.annotation.config.SpringLifeConfig;
import com.spring.annotation.config.SpringPropertyConfig;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.core.env.ConfigurableEnvironment;
public class SpringPropertyTest {
@Test
public void testProperty(){
AnnotationConfigApplicationContext applicationContext =
new AnnotationConfigApplicationContext(SpringPropertyConfig.class);
Employee employee = (Employee) applicationContext.getBean("employee");
System.out.println(employee);
ConfigurableEnvironment environment = applicationContext.getEnvironment();
String property = environment.getProperty("employee.addr");
System.out.println(property);
}
}
通过这两句:
ConfigurableEnvironment environment = applicationContext.getEnvironment();
String property = environment.getProperty("employee.addr");
也可以获取配置文件中的值。
2.3 DI相关注解
Spring利用依赖注入(DI),完成对IOC容器中中各个组件的依赖关系赋值;完成自动装配。
2.3.1 @Autowired
默认优先按照类型去容器中找对应的组件:applicationContext.getBean(BookDao.class);找到就赋值;
如果找到多个相同类型(要保证每个组件的名称不一样,否则就报错)的组件,再将属性的名称作为组件的id去容器中查找;
可以配合注解@Qualifier("bookDao")一起使用,可以在注入组件的时候,指定需要装配的组件的id,而不是使用属性名。
默认情况下,使用了@Autowired注解进行自动装配,默认一定要将组件注入进来,没有找到就会报错。可以使用@Autowired(required=false); 指定该组件不是必须要注入的,那就不会报错了;
可以配合注解@Primary:让Spring进行自动装配的时候,默认使用首选的bean(没有明确指定使用哪个Bean的时候);同时,也可以继续使用@Qualifier指定需要装配的bean的名字,他的优先级高于@Primary;
@Autowired:可以在 构造器,参数,方法,属性 上使用该注解;都是从容器中获取参数组件的值。
[标注在方法位置]:@Bean+方法参数;参数从容器中获取,默认不写@Autowired效果是一样的;都能自动装配;
@Autowired
//标注在方法,Spring容器创建当前对象,就会调用方法,完成赋值;
//方法使用的参数,自定义类型的值从ioc容器中获取
public void setCar(Car car) {
this.car = car;
}
[标在构造器上]:如果组件只有一个有参构造器,这个有参构造器的@Autowired可以省略,参数位置的组件还是可以自动从容器中获取;(默认加在ioc容器中的组件,容器启动会调用无参构造器创建对象,再进行初始化赋值等操作)
//构造器要用的组件,都是从容器中获取
//@Autowired可以省略
public Boss(Car car){
this.car = car;
System.out.println("Boss...有参构造器");
}
[放在参数位置]:@Bean注解标注的方法,在创建对象的时候,方法参数的值也是从容器中获取的,在参数前面可以使用@Autowired注解,也可以省略。
/**
* @Bean标注的方法创建对象的时候,方法参数的值从容器中获取
* @param car
* @return
*/
@Bean
public Color color(Car car){
Color color = new Color();
color.setCar(car);
return color;
}
2.3.2 @Resource(JSR250)和@Inject(JSR330)[都java规范的注解]
@Resource:
可以和@Autowired一样实现自动装配功能;默认是按照组件名称进行装配的;
不能支持@Primary功能,不支持@Autowired(reqiured=false);
@Inject:
需要导入javax.inject的包,和Autowired的功能一样。没有required=false的功能;
@Autowired:Spring定义的; @Resource、@Inject都是java规范
2.3.3 自定义组件中注入Spring容器底层的组件
自定义组件想要使用Spring容器底层的一些组件(例如:ApplicationContext,BeanFactory,xxx);只需要让 自定义组件实现xxxAware;在创建对象的时候,会调用接口规定的方法注入相关组件;Aware;
把Spring底层一些组件注入到自定义的Bean中:Bean实现xxxAware:使用xxxProcessor功能;比如,如果在我们自定义的组件中,需要使用ApplicationContext 容器对象,那么就让Bean实现ApplicationContextAware接口,重写接口中的方法,Spring就会通过对应的 ApplicationContextAwareProcessor(后置处理器)把我们需要的组件注入到自定义组件中。自定义的组件就可以使用一个变量来存储并使用他。
package com.atguigu.bean;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.EmbeddedValueResolverAware;
import org.springframework.stereotype.Component;
import org.springframework.util.StringValueResolver;
@Component
public class Red implements ApplicationContextAware,BeanNameAware,EmbeddedValueResolverAware {
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
System.out.println("传入的ioc:"+applicationContext);
this.applicationContext = applicationContext;
}
@Override
public void setBeanName(String name) {
System.out.println("当前bean的名字:"+name);
}
@Override
public void setEmbeddedValueResolver(StringValueResolver resolver) {
String resolveStringValue = resolver.resolveStringValue("你好 ${os.name} 我是 #{20*18}");
System.out.println("解析的字符串:"+resolveStringValue);
}
}
2.4 动态加载环境@Profile
@Profile:是Spring为我们提供的可以根据当前环境,动态的激活和切换一系列组件的功能。
开发,测试和生产环境存在不同,需要程序可以动态的去加载不同的配置,比如数据源的不同,为了解决这个问题,Spring提供了@Profile注解,可以根据当前配置,动态的激活不同的组件。
@Profile:指定组件在哪个环境的情况下才能被注册到容器中,不指定,任何环境下都能注册这个组件
* 1)、加了环境标识的bean,只有这个环境被激活的时候才能注册到容器中。默认是default环境。
* 2)、写在配置类上,只有是指定的环境的时候,整个配置类里面的所有配置才能开始生效
* 3)、没有标注环境标识的bean在,任何环境下都是加载的;
代码演示:
数据资源文件dbconfig.properties:
db.user=root
db.password=123456
db.driverClass=com.mysql.jdbc.Driver
涉及到数据源,需要引入下面的连接池和数据库驱动的jar包:
<!-- https://mvnrepository.com/artifact/c3p0/c3p0 -->
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.1.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.44</version>
</dependency>
配置类如下:
package com.spring.annotation.config;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.EmbeddedValueResolverAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.context.annotation.PropertySource;
import org.springframework.util.StringValueResolver;
import com.mchange.v2.c3p0.ComboPooledDataSource;
/**
* Profile:
* Spring为我们提供的可以根据当前环境,动态的激活和切换一系列组件的功能;
*
* 开发环境、测试环境、生产环境;
* 数据源:(/A)(/B)(/C);
*
*
* @Profile:指定组件在哪个环境的情况下才能被注册到容器中,不指定,任何环境下都能注册这个组件
*
* 1)、加了环境标识的bean,只有这个环境被激活的时候才能注册到容器中。默认是default环境
* 2)、写在配置类上,只有是指定的环境的时候,整个配置类里面的所有配置才能开始生效
* 3)、没有标注环境标识的bean在,任何环境下都是加载的;
*/
//使用@PropertySource引入类路径下的dbconfig.properties资源文件, / 表示从根目录开始
@PropertySource("classpath:/dbconfig.properties")
@Configuration
public class MainConfigOfProfile implements EmbeddedValueResolverAware{
@Value("${db.user}")
private String user;
private StringValueResolver valueResolver;
private String driverClass;
@Bean
public Yellow yellow(){
return new Yellow();
}
@Profile("test")
@Bean("testDataSource")
public DataSource dataSourceTest(@Value("${db.password}")String pwd) throws Exception{
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setUser(user);
dataSource.setPassword(pwd);
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test");
dataSource.setDriverClass(driverClass);
return dataSource;
}
@Profile("dev")
@Bean("devDataSource")
public DataSource dataSourceDev(@Value("${db.password}")String pwd) throws Exception{
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setUser(user);
dataSource.setPassword(pwd);
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/ssm_crud");
dataSource.setDriverClass(driverClass);
return dataSource;
}
@Profile("prod")
@Bean("prodDataSource")
public DataSource dataSourceProd(@Value("${db.password}")String pwd) throws Exception{
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setUser(user);
dataSource.setPassword(pwd);
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/scw_0515");
dataSource.setDriverClass(driverClass);
return dataSource;
}
//实现了EmbeddedValueResolverAware接口,可以通过该方法手动注册
@Override
public void setEmbeddedValueResolver(StringValueResolver resolver) {
this.valueResolver = resolver;
//通过值解析器直接共配置文件中解析得到驱动类
driverClass = valueResolver.resolveStringValue("${db.driverClass}");
}
}
测试类:
public class IOCTest_Profile {
//1、使用命令行动态参数: 在虚拟机参数位置加载 -Dspring.profiles.active=test
//2、代码的方式激活某种环境;
@Test
public void test01(){
AnnotationConfigApplicationContext applicationContext =
new AnnotationConfigApplicationContext();
//1、创建一个applicationContext
//2、设置需要激活的环境
applicationContext.getEnvironment().setActiveProfiles("dev");
//3、注册主配置类
applicationContext.register(MainConfigOfProfile.class);
//4、启动刷新容器
applicationContext.refresh();
String[] namesForType = applicationContext.getBeanNamesForType(DataSource.class);
for (String string : namesForType) {
System.out.println(string);
}
Yellow bean = applicationContext.getBean(Yellow.class);
System.out.println(bean);
applicationContext.close();
}
}