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();
}
}