這篇文章主要用於說明在日常開發中經常使用到的註解。
1. @Configuration
該註解用於類上,表明這是一個配置類。在Spring MVC中,項目的配置通常放在 xml文件中, 爲此需要先寫一個配置文件,例如在xml中使用bean標籤來註冊一些組件。如下:
<bean id="person" class="com.zhao.springboot.bean.Person">
<property name="age" value="22"></property>
<property name="name" value="zhangsan"></property>
</bean>
在註解式開發中 就用該註解 來替代 xml配置文件,在配置文件中註冊組件,就被替換成在配置類中添加組件。如下所示:
// 配置類==配置文件
@Configuration //告訴SpringBoot 這是一個配置類
public class MainConfig {
@Bean // 給容器中注入一個Bean ,類型爲返回值的類型,id默認是用方法名作爲id
public Person person() {
return new Person("lisi", 25);
}
}
測試代碼如下:
ApplicationContext annoContext = new AnnotationConfigApplicationContext(MainConfig.class);
Person person = annoContext.getBean(Person.class);
System.out.println(person);
輸出結果如下:
注意: @Bean 向容器中注入Bean ,id默認爲方法的名稱,可以通過name改變 ,@Bean(name="person1")
2. @ComponentScan
該註解用於指定包掃描,用於類上。 Spring MVC中通常用 <context:component-scan /> 來指定掃描範圍,只要標註了@Controller、@Service、@Repository、@Component就會被掃描到。註解式開發中使用 @ComponentScan 來替代。
在剛纔的配置類上使用該註解進行標註: value指定掃描的位置
測試如下:
分別建立 controller、service、dao類。並用相關注解進行標識。
@Repository
public class BookDao {
}
.....
@Service
public class BookService {
}
.....
@Controller
public class BookController {
}
編寫測試代碼:
@Test
public void test01() {
ApplicationContext context = new AnnotationConfigApplicationContext(MainConfig.class);
String[] names = context.getBeanDefinitionNames();
for (String name : names) {
System.out.println(name);
}
}
輸出如下:
說明: 配置類(MainConfig)自身也會被註冊到bean容器中(@Configuration中包含了一個@Component註解) ,此外@ComponentScan註解中還包含有其他可以用的功能,如掃描時只包含哪些(includeFilters)、只排除哪些(excludeFilters)。
2.1 excludeFilters
eg : 排除 所有Controller和Service
@ComponentScan(value = "com.zhao.springboot",excludeFilters = {
@Filter(type = FilterType.ANNOTATION,classes = {Controller.class, Service.class})
})
測試結果:
2.2 includeFilters
eg: 只包含 Controller和Service
@ComponentScan(value = "com.zhao.springboot",includeFilters = {
//type 指定根據什麼規則排除
@Filter(type = FilterType.ANNOTATION,classes = {Controller.class, Service.class})
},useDefaultFilters = false)
測試結果:
注意: 使用 includeFilters 時需要禁用掉默認的過濾規則,即需要設置useDefaultFilters =false(默認爲true)
2.3 @ComponentScans
@ComponentScan 還是一個重複註解(@Repeatable(ComponentScans.class)),在java8中,可以多次使用該註解來指定不同的掃描策略:
假如不是java8 ,也可以通過 @ComponentScans來達到想要的效果。
2.4 FilterType :
@Filter中的type指定 根據某種規則過濾,
有以下幾種規則:
public enum FilterType {
ANNOTATION, // 按照註解
ASSIGNABLE_TYPE, //按照給定的類型
ASPECTJ, // 使用ASPECTJ表達式,不是很常用
REGEX, // 使用正則表達式
CUSTOM; // 自定義規則
private FilterType() {
}
}
上面已經使用了 按照註解 FilterType.ANNOTATION 類型, 這裏重點說明 FilterType.ASSIGNABLE_TYPE 和FilterType.CUSTOM。
2.4.1 給定類型 FilterType.ASSIGNABLE_TYPE:使用如下
@ComponentScan(value = "com.zhao.springboot",includeFilters = {
//type 指定根據什麼規則排除
@Filter(type = FilterType.ANNOTATION,classes = {Controller.class}),
@Filter(type = FilterType.ASSIGNABLE_TYPE,classes = {BookService.class})
},useDefaultFilters = false)
測試結果:
2.4.2 自定義 類型
自定義過濾類型,需要實現TypeFilter (org.springframework.core.type.filter.TypeFilter)
用法如下:
public class MyTypeFilter implements TypeFilter {
/**
* @param metadataReader 讀取到的當前正在掃描的類的信息
* @param metadataReaderFactory 可以獲取到其他任何類的信息
* @return
* @throws IOException
*/
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
// 獲取當前類的註解信息
AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
// 獲取當前正在掃描的類的信息 ,類型,接口是什麼......
ClassMetadata classMetadata = metadataReader.getClassMetadata();
// 獲取當前類資源(類的路徑)
Resource resource = metadataReader.getResource();
String className = classMetadata.getClassName();
String superClassName = classMetadata.getSuperClassName();
System.out.println("當前掃描類的類名---> " +className);
// System.out.println("當前掃描類的父類---> " +superClassName);
if (className.contains("er")) { // 類名中包含er,就包含進去
return true;
}
return false;
}
}
測試結果:
3. @Scope
在IOC容器中,我們在IOC容器中添加的實例默認都是單實例的 。也可以通過 @Scope 來指定它的作用範圍。
@Scope 取值有 以下幾種:
prototype 多實例
singleton 單實例 (默認)
request :同一次請求創建一個實例
session: 同一個session創建一個請求
後兩種並不常用,所以這裏僅僅說明前兩種。
3.1. singleton
默認爲單實例的(@Scope("singleton")),測試如下:
@Bean("person")
@Scope
public Person person() {
System.out.println("給容器中添加Person");
return new Person("張三", 25);
}
@Test
public void test02() {
ApplicationContext context = new AnnotationConfigApplicationContext(MainConfig2.class);
System.out.println("IOC容器創建完成......");
Object bean1 =context.getBean("person");
Object bean2 =context.getBean("person");
System.out.println(bean1 ==bean2);
}
運行結果如下:
另外,從測試結果還能看出 在單實例情況下,當IOC容器創建時,同時也會調用方法創建對象並放到IOC容器中,以後每次獲取,就是直接從容器中拿。
3.1.1 懶加載 @Lazy
由於單實例bean默認在容器啓動時創建組件實例,可以設置懶加載,使容器啓動時不創建實例,而在第一次使用(獲取)實例時創建,並初始化,注意:懶加載僅僅適用於單實例。
測試如下:
@Configuration
public class MainConfig3 {
@Bean("person")
@Lazy
public Person person() {
System.out.println("給容器中添加Person");
return new Person("張三", 25);
}
}
@Test
public void test03() {
ApplicationContext context = new AnnotationConfigApplicationContext(MainConfig3.class);
System.out.println("IOC容器創建完成......");
// Object bean1 =context.getBean("person");
// Object bean2 =context.getBean("person");
// System.out.println(bean1 ==bean2);
}
測試結果:
在測試如下:
@Test
public void test03() {
ApplicationContext context = new AnnotationConfigApplicationContext(MainConfig3.class);
System.out.println("IOC容器創建完成......");
Object bean1 =context.getBean("person");
Object bean2 =context.getBean("person");
System.out.println(bean1 ==bean2);
}
測試結果如下:
3.2 prototype
當@Scope的值設置爲 prototype 時,則添加的組件在容器中是多實例的,IOC容器創建時,並不會調用方法創建該組件的實例,而是每當使用到該組件時,就去創建一個實例。
測試如下:
@Bean("person")
@Scope("prototype")
public Person person() {
System.out.println("給容器中添加Person");
return new Person("張三", 25);
}
@Test
public void test02() {
ApplicationContext context = new AnnotationConfigApplicationContext(MainConfig2.class);
// System.out.println("IOC容器創建完成......");
//
// Object bean1 =context.getBean("person");
// Object bean2 =context.getBean("person");
// System.out.println(bean1 ==bean2);
}
執行測試方法,會發現控制檯並沒有輸出 創建Person的實例的語句(給容器中添加Person)。
在進行測試如下:
@Test
public void test02() {
ApplicationContext context = new AnnotationConfigApplicationContext(MainConfig2.class);
System.out.println("IOC容器創建完成......");
Object bean1 =context.getBean("person");
Object bean2 =context.getBean("person");
System.out.println(bean1 ==bean2);
}
輸出如下:
4. @Conditional
該條件用於 按照一定的條件進行判斷,滿足條件進行判斷,滿足條件給容器中註冊bean
其值是一個 Condition數組, Condition是一個包含一個方法的接口:
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
context : 判斷條件能使用到的上下文(環境) , metadata 註釋信息。
使用如下:
@Configuration
public class MainConfig4 {
@Bean("Windows")
public Person person01() {
return new Person("Windows", 50);
}
@Bean("Linux")
public Person person02() {
return new Person("Linux", 40);
}
}
假如在配置類中向容器中注入兩個Person實例,分別爲 Windows和Linux ,想要實現根據操作系統(Windows或Linux)不同而分別注入不同的實例。那麼,可以這樣做:
首先編寫兩個類實現Condition類,並實現 matches 方法。WindowsCondition和 LinuxCondition(與WindowsCondition代碼基本一致,不再重複貼)
public class WindowsCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// 1. 能獲取ioc 使用的beanFactory ,裝配的工廠。
ConfigurableListableBeanFactory factory = context.getBeanFactory();
// 2. 獲取類加載器
ClassLoader loader = context.getClassLoader();
//3. 獲取當前環境信息,包括環境變量、虛擬機變量等
Environment environment = context.getEnvironment();
//4. 獲取到bean定義的註冊類
BeanDefinitionRegistry registry = context.getRegistry();
String operationName = environment.getProperty("os.name");
// System.out.println(operationName);
if (operationName.contains("Windows")) {
return true;
}
return false;
}
然後在配置類中使用 @Conditional 註解引入。
@Conditional({WindowsCondition.class})
@Bean("Windows")
public Person person01() {
return new Person("Windows", 50);
}
@Conditional({LinuxCondition.class})
@Bean("Linux")
public Person person02() {
return new Person("Linux", 40);
}
編寫測試類如下:
@Test
public void test04() {
ApplicationContext context = new AnnotationConfigApplicationContext(MainConfig4.class);
System.out.println("IOC容器創建完成......");
Environment environment = context.getEnvironment();
String operationName =environment.getProperty("os.name");
System.out.println("操作系統名稱:"+operationName);
Map<String, Person> beans = context.getBeansOfType(Person.class);
System.out.println(beans);
}
輸出結果如下:
另外 @Conditional 也可以標註在類上 ,那麼此時的含義爲滿足當前條件,該類中配置的所有bean註冊纔會生效。
@Configuration
@Conditional({LinuxCondition.class})
public class MainConfig2 {
@Bean("person")
public Person person() {
System.out.println("給容器中添加Person");
return new Person("張三", 25);
}
}
編寫測試方法:
@Test
public void test04() {
ApplicationContext context = new AnnotationConfigApplicationContext(MainConfig2.class);
System.out.println("IOC容器創建完成......");
Environment environment = context.getEnvironment();
String operationName =environment.getProperty("os.name");
System.out.println("操作系統名稱:"+operationName);
Map<String, Person> beans = context.getBeansOfType(Person.class);
System.out.println(beans);
}
測試結果:
5. 向容器中註冊組件相關注解
給容器註冊組件除了 上面的
包掃描+組件標註註解(@Controller /@Service...) ,
@Bean[導入的第三方包裏面的組件]
也可以通過 @Import 快速給容器中導入一個組件 ,下面說明@Import用法。
5.1 @Import (要導入到容器中的組件)
該方式會自動向容器中註冊組件,id默認爲全類名
@Configuration
public class MainConfig5 {
}
....
public class Color {
}
.....
public class Red {
}
編寫測試代碼:
private void printBeans(ApplicationContext context) {
String[] beanDefinitionNames = context.getBeanDefinitionNames();
for (String name:beanDefinitionNames) {
System.out.println(name);
}
}
@Test
public void test05(){
ApplicationContext context = new AnnotationConfigApplicationContext(MainConfig5.class);
printBeans(context);
}
輸出結果如下:
由於此時 配置類中並沒有註冊 Red 和Color ,所以並沒有打印出其名稱。修改配置類MainConfig5 ,加上@Import註解:
@Configuration
@Import({Color.class, Red.class})
public class MainConfig5 {
}
測試結果:
5.2 ImportSelector
ImportSelector 導入選擇器, 是一個接口,裏面包含一個 selectImports 方法, 通過實現該方法能實現返回 需要導入類的全類名,進而向容器註冊組件。SpringBoot中該方式用的也比較多。
用法: 編寫類實現 ImportSelector接口,實現裏面的 selectImports:
public class MyImportSelector implements ImportSelector {
// 返回值,就是導入到容器中的組件全類名
// AnnotationMetadata : 當前標註 @Import註解的類的所有註解信息
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{"com.zhao.springboot.bean.Blue","com.zhao.springboot.bean.Yellow"};
//方法不要返回null
}
}
測試:
@Configuration
@Import({Color.class, Red.class, MyImportSelector.class})
public class MainConfig5 {
}
測試結果如下:
注意:MyImportSelector 並不會被註冊到容器中,該類實現了 ImportSelector,僅僅是把方法返回值中的全類名註冊到容器中。
5.3 ImportBeanDefinitionRegistrar
也可以手動註冊組件,通過實現 ImportBeanDefinitionRegistrar中的 registerBeanDefinitions 方法來註冊 ,用法如下:
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
/**
*
* @param importingClassMetadata :當前類的註解信息
* @param registry : BeanDefinition註冊類,把所有需要添加到容器中的bean
*/
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
boolean redDefinition = registry.containsBeanDefinition("com.zhao.springboot.bean.Blue");
boolean blueDefinition = registry.containsBeanDefinition("com.zhao.springboot.bean.Yellow");
if (redDefinition && blueDefinition){
// 指定bean定義信息,(Bean 類型..等)
BeanDefinition definition = new RootBeanDefinition(RainBow.class);
// definition.setScope();
// 指定bean名稱
// String beanName, BeanDefinition beanDefinition
// 註冊一個bean
registry.registerBeanDefinition("rainBow",definition);
}
}
}
測試如下:
5.4 使用Spring 提供的FactoryBean (工廠bean)註冊
使用方式如下:
public class ColorFactoryBean implements FactoryBean<Color> {
// 返回一個Color對象,這個對象將會被添加到容器中
@Override
public Color getObject() throws Exception {
System.out.println("ColorFactoryBean........getObject....");
return new Color();
}
@Override
public Class<?> getObjectType() {
return Color.class;
}
/*
是否是單例? true:單例,在容器中保存一份,
false: 多實例, 每次獲取都會創建一個新的bean
*/
@Override
public boolean isSingleton() {
return true;
}
}
將 ColorFactoryBean 註冊到容器中:
@Bean
public FactoryBean colorFactoryBean() {
return new ColorFactoryBean();
}
測試:
@Test
public void test06(){
ApplicationContext context = new AnnotationConfigApplicationContext(MainConfig5.class);
// 工廠bean 獲取的是 調用 getObject創建的對象
Object colorFactoryBean = context.getBean("colorFactoryBean");
Object colorFactoryBean1 = context.getBean("colorFactoryBean");
System.out.println(colorFactoryBean.getClass().getName());
System.out.println(colorFactoryBean == colorFactoryBean1);
// 如果想獲取 工廠bean本身 ,可以使用 &
Object colorFactoryBean3 = context.getBean("&colorFactoryBean");
System.out.println(colorFactoryBean3.getClass().getName());
}
測試結果:
6. Bean生命週期相關注解
容器管理bean的生命週期,我們可以自定義初始化和銷燬方法,容器在bean進行到當前生命週期的時候來調用我們自定義的初始化和銷燬方法。
1) 指定初始化和銷燬方法;
通過@Bean指定init-method和destory-method 。具體用法如下:
public class Car {
public Car() {
System.out.println("Car...constructor.....");
}
public void init(){
System.out.println("Car.....init.....");
}
public void destroy() {
System.out.println("Car......destroy....");
}
}
.....
@Configuration
public class MainConfigOfLifeCycle {
@Bean(initMethod ="init",destroyMethod ="destroy")
public Car car() {
return new Car();
}
}
編寫測試方法:
注意: 單實例的銷燬發生在容器關閉的時候,而當設置爲多實例時,容器不會管理這個bean,容器不會調用銷燬方法。
@Bean(initMethod ="init",destroyMethod ="destroy")
@Scope("prototype")
public Car car() {
return new Car();
}
......
@Test
public void test01() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfigOfLifeCycle.class);
System.out.println("容器創建完成.......");
context.getBean("car");
context.close();
}
測試結果:
2) 通過讓Bean 實現 InitializingBean(定義初始化邏輯), DisposableBean(定義銷燬邏輯)
public class Cat implements InitializingBean, DisposableBean {
public Cat() {System.out.println("Cat...constructor.....");}
@Override
public void destroy() {
System.out.println("Cat......destroy....");
}
@Override
public void afterPropertiesSet() {
System.out.println("Cat.....afterPropertiesSet.....");
}
}
......
@Bean
public Cat cat(){
return new Cat();
}
測試結果:
3) 可以使用 JSR250
@PostConstruct :在bean創建完成並屬性賦值完成,來執行初始化方法。
@PreDestroy:在容器銷燬bean之前,通知我們進行銷燬工作。
使用如下:
public class Dog {
public Dog() {
System.out.println("Dog...constructor");
}
@PreDestroy
public void destroy() {
System.out.println("Dog......@PreDestroy....");
}
@PostConstruct
public void init() {
System.out.println("Dog.....@PostConstruct.....");
}
}
.....
@Bean
public Dog dog() {
return new Dog();
}
測試結果如下:
4) 使用 BeanPostProcessor【Interface】
bean的後置處理器。在bean初始化前後進行一些處理工作。即在afterPropertiesSet 或init-method 前後進行一系列的操作。
有以下兩個api :
// 在初始化之前工作
Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;
//在初始化之後工作
Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;
使用如下:
@Component
public class MyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
// bean : the new bean instance
System.out.println("postProcessBeforeInitialization...." + beanName+">= " +bean);
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("postProcessAfterInitialization......" + beanName+">= " +bean);
return bean;
}
}
注意:要將實現類加入到bean容器。測試結果如下:
debug查看該執行流程,發現 執行初始化方法的時候(invokeInitMethods) ,先調用 applyBeanPostProcessorsBeforeInitialization,執行完初始化方法後又執行了 applyBeanPostProcessorsAfterInitialization:
執行流程:
doCreateBean --> createBeanInstance--> initializeBean--> applyBeanPostProcessorsBeforeInitialization -->invokeInitMethods--> applyBeanPostProcessorsAfterInitialization ...(AbstractAutowireCapableBeanFactory)
7. @Value
@Value 爲屬性賦值 , 支持 : 1) 基本數值 2) 可以寫 SpEL ;#{ } 3) 可以寫${} ;取出配置文件的值(在環境變量裏面的值)
使用如下:
public class Student {
@Value("李四")
private String name;
@Value("#{25-5}")
private Integer age;
// ... 省略 getter 、setter、constructor 、toString
}
打印出該容器中所有bean ,結果如下:
若需要使用 ${}取出配置文件中的值,需要使用到另一個註解: @PropertySource ,該註解用於指定配置文件的位置,從而可以使用${} 取出配置文件中的值。下面說明用法:
給Student類中添加一個nickName的字段, 此時由於沒有賦值,重新測試,打印出容器中的bean ,顯示該字段爲null:
寫一個配置文件(Student.properties):
student.nickName=小灰灰
在配置類上進行修改:
@Configuration
@PropertySource(value={"classpath:/student.properties"}) // 指定配置文件的位置
public class MainConfigOfPropertyValues {
@Bean
public Student student() {
return new Student();
}
}
測試:
ConfigurableEnvironment environment = context.getEnvironment();
String property = environment.getProperty("student.nickName");
8. 自動裝配的相關注解
8.1 @Autowired :自動注入
用法如下:
@Service
public class BookService {
@Autowired
private BookDao bookDao;
// toString 省略
}
.....
@Repository
public class BookDao {
private String label = "1";
// getter、setter、toString省略
}
.....
@Configuration
@ComponentScan({"com.zhao.springboot.controller", "com.zhao.springboot.service", "com.zhao.springboot.dao"})
public class MainConfigOfAutowired {
}
測試代碼:
@Test
public void test01() {
BookService bookService = context.getBean(BookService.class);
System.out.println(bookService);
}
測試結果:
注意:
(1) 默認優先按照類型去容器中找對應的組件:applicationContext.getBean(BookDao.class);找到就賦值
(2)如果找到了多個相同類型的組件,在將屬性的名稱作爲組件的id去容器中查找
applicationContext.getBean("bookDao");
(3) 使用 @Qualifier("bookDao"):使用@Qualifier指定需要裝配的組件的id,而不是使用屬性名
(4) 自動裝配默認一定要將屬性賦值好,沒有就會報錯,也可以使用 @Autowired(required =false),設置不必須裝配
(5) @Primary:讓spring 進行自動裝配的時候,默認使用首選的bean,也可以繼續使用@Qualifier指定需要裝配的bean的名字
測試如下:
1)容器中存在兩個bean
@Configuration
@ComponentScan({"com.zhao.springboot.controller", "com.zhao.springboot.service", "com.zhao.springboot.dao"})
public class MainConfigOfAutowired {
@Bean("bookDao2")
public BookDao bookDao() {
BookDao dao = new BookDao();
dao.setLabel("2");
return dao;
}
}
運行測試方法進行測試:
2) 使用@Primary指定默認裝配的bean
@Primary
@Bean("bookDao2")
public BookDao bookDao() {
BookDao dao = new BookDao();
dao.setLabel("2");
return dao;
}
測試結果:
3)@Qualifier指定 要裝配的組件的id
在 2)的基礎上進入修改:
@Service
public class BookService {
@Autowired
@Qualifier("bookDao")
private BookDao bookDao;
// toString 省略
}
測試結果:
8.2 Spring 還支持使用 @Resource (JSR250) 和@Inject(JSR330)[JAVA規範的註解]
@Resource可以和 @Autowired 一樣實現自動裝配功能,默認是安裝組件名稱進行裝配的。但是沒有能支持@Primary,也沒有支持@Autowired(required=false)的功能。其使用如下:
@Service
public class BookService {
// @Autowired
// @Qualifier("bookDao")
@Resource(name ="bookDao2")
private BookDao bookDao;
// toString 省略
}
測試結果:
如果想要使用@Inject, 還需要引入一個依賴:
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<version>1</version>
</dependency>
使用如下:
@Service
public class BookService {
// @Autowired
// @Qualifier("bookDao")
// @Resource(name ="bookDao2")
@Inject
private BookDao bookDao;
// toString 省略
}
測試如下:
引入了 配置類中使用@Bean注入的實例,是因爲 該實例使用了@Primary註解,也即 @Inject 和@Autowired功能一樣,但是@Inject沒有 @Autowired的required=false的功能。
8.3 @Autowired 標註的位置
@Autowired 可以標註在: 構造器、參數、方法、 屬性 ,這些都是從容器中獲取組件參數的值。
1) 標註在方法位置:用的最多的就是 @Bean +方法參數,參數從容器中自動獲取,默認不寫@Autowired
2) 標註在構造器上: 如果組件只有一個有參構造器,那麼這個有參構造器的@AutoWired可以省略,參數位置的組件還是可以從容器中自動獲取
3) 標註在方法中的參數上
@Component
public class Boss {
// @Autowired
private Car car;
// @Autowired // 可以直接標註在方法上
public void setCar(Car car) {
this.car = car;
}
// 省略 getter 和 toString方法
// @Autowired // 可以標註在構造器上
//public Boss(@Autowired Car car) { // 如果組件只有一個有參構造器,那麼這個有參構造器的@Autowired可以省略,參數位置的組件還是可以從容器中自動獲取
public Boss( Car car){
this.car = car;
}
}
@Bean
// public Boss boss(@Autowired Car car) { //Car只有一個有參構造器,所以可以省略 @Autowired
public Boss boss( Car car) {
return new Boss(car);
}
8.4 自定義組件想要使用Spring 容器底層的一些組件(ApplicationContext,BeanFactory,xxxx)
如果想要使用Spring容器底層組件,只需要讓自定義組件 實現xxxAware即可。在創建對象的時候,會調用接口規定的方法注入相關組件。頂層 接口爲 Aware(org.springframework.beans.factory)
使用如下:
public class Cup implements ApplicationContextAware, BeanNameAware, EmbeddedValueResolverAware {
private ApplicationContext applicationContext;
@Override
public void setBeanName(String name) {
System.out.println("當前bean的名稱: " + name);
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
System.out.println("傳入的IOC 容器: " + applicationContext);
this.applicationContext = applicationContext;
}
@Override
public void setEmbeddedValueResolver(StringValueResolver resolver) { //解析字符串
String resolveStringValue = resolver.resolveStringValue("你好,${os.name} 我是 #{20*18}");
System.out.println("解析的字符串:" + resolveStringValue);
}
}
.......
@Bean
public Cup cup() { // 向容器中注入 bean
return new Cup();
}
編寫測試代碼:
@Test
public void test02(){
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfigOfAutowired2.class);
System.out.println(context);
}
測試結果如下:
9. @Profile
@Profile 是Spring爲我們提供的,具有可以根據當前環境,動態的激活和切換一系列組件的功能。該註解指定組件在哪個環境的情況下才能被註冊到容器中,若不指定,任何環境下都能註冊這個組件。
1) 加了環境標識的bean,只有這個環境被激活的時候才能註冊到容器中,默認是default環境
2) 標註在 配置類上,只有在指定的環境的時候,整個配置類裏面的所有配置才能開始生效
使用如下:(以多數據源爲例。引入c3p0依賴)
@Configuration
@PropertySource("classpath:/dbConfig.properties")
public class MainConfigOfProfile implements EmbeddedValueResolverAware {
@Value("${db.user}")
private String user;
private String driverClass;
private StringValueResolver resolver;
@Profile("default")
// @Profile("test")
@Bean("testDataSource")
public DataSource dataSourceTest(@Value("db.password") String password) throws Exception {
return getDataSource(password,"jdbc:mysql://localhost:3306/test");
}
//@Profile("dev")
@Bean("devDataSource")
public DataSource dataSourceDev(@Value("db.password") String password) throws Exception {
return getDataSource(password,"jdbc:mysql://localhost:3306/dev");
}
@Profile("prod")
@Bean("prodDataSource")
public DataSource dataSourceProd(@Value("db.password") String password) throws Exception {
return getDataSource( password ,"jdbc:mysql://localhost:3306/prod");
}
private DataSource getDataSource(String password, String url) throws Exception {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setJdbcUrl(url);
dataSource.setUser(user);
dataSource.setPassword(password);
dataSource.setDriverClass(driverClass);
return dataSource;
}
@Override
public void setEmbeddedValueResolver(StringValueResolver resolver) {
this.resolver = resolver;
this.driverClass = resolver.resolveStringValue("${db.driverClass}");
}
}
該配置類寫的有點複雜,也是爲了練習前面的註解 ,測試代碼如下:
@Test
public void test01() {
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(MainConfigOfProfile.class);
String[] names = context.getBeanNamesForType(DataSource.class);
for (String name : names) {
System.out.println(name);
}
}
測試結果如下:
激活prod環境測試如下:
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext();
context.getEnvironment().setActiveProfiles("prod"); //允許設置多個活動的環境
context.register(MainConfigOfProfile.class);
context.refresh();
String[] names = context.getBeanNamesForType(DataSource.class);
for (String name : names) {
System.out.println(name);
}
.....
測試如下:
注意:出現devDataSource 數據源是因爲,配置類上該數據源並沒有指定環境,所以默認任何環境下都註冊了。
也可以通過 指定虛擬機參數的方法激活環境 , 加上 -Dspring.profiles.active=prod
10. 切面相關注解
前置通知(@Before) :在目標方法執行前執行
後置通知(@After): 目標方法運行結束後執行(無論正常還是異常結束)
返回通知(@AfterReturning) : 在目標方法正常返回後執行
異常通知(@AfterThrowing): 在目標方法出現異常後執行
環繞通知(@Around):動態代理,手動推進目標方法運行(joinPoint.procced())
@Aspect :告訴spring這是一個切面類
@EnableAspectJAutoProxy :開啓基於註解的aop模式
使用方法步驟如下:
假如有需求如下: 做除法運算,並返回結果。 並且 在業務邏輯執行的時候將日誌進行打印(方法運行前、運行結束、出現異常)
1)首先定義一個業務類(默認已經導入了aop相關依賴):
public class MathCalculator {
public int div(int i, int j) {
System.out.println("MathCalculator..div...");
return i / j;
}
}
2)定義日誌切面
@Aspect//指明這個一個切面類
public class LogAspects {
// @Before 在目標方法之前切入:切入點表達式(指定在哪個方法切入)
// @Before("com.zhao.springboot.aop.MathCalculator.div(int ,int )")
// @Before("com.zhao.springboot.aop.MathCalculator.*(..)")
@Before("pointCut()")
public void logStart(JoinPoint joinPoint) { // 注意:當方法有多個參數時,JoinPoint必須放置第一位置
Object[] args = joinPoint.getArgs();
System.out.println(joinPoint.getSignature().getName() + " 除法運行...參數列表: {" + Arrays.asList(args) + "}");
}
@After("com.zhao.springboot.aop.LogAspects.pointCut()")
public void logEnd(JoinPoint joinPoint) {
System.out.println(joinPoint.getSignature().getName() + " 除法結束");
}
@AfterReturning(value = "pointCut()", returning = "result")
public void logReturn(Object result) {
System.out.println("除法正常返回。。。運行結果: {" + result + "}");
}
@AfterThrowing(value = "pointCut()", throwing = "ex")
public void logException(Exception ex) {
System.out.println("除法異常。。。異常信息: {" + ex + "}");
}
// 抽取公共的切入點表達式
// 1. 本類引用 pointCut()
//2. 其他的切面引用 com.zhao.springboot.aop.LogAspects.pointCut()
@Pointcut("execution(public int com.zhao.springboot.aop.MathCalculator.*(..))")
public void pointCut() {
}
}
3) 將切面類和業務邏輯類加入到容器中,並開啓基於註解的aop模式
@EnableAspectJAutoProxy // 開啓註解
@Configuration
public class MainConfigOfAop {
@Bean
public MathCalculator mathCalculator() {
return new MathCalculator();
}
@Bean
public LogAspects logAspects() {
return new LogAspects();
}
}
測試代碼:
@Test
public void test01() {
// 1. 不要自己創建對象 ,需要使用spring創建的實例,才能使用到spring的切面
// MathCalculator calculator =new MathCalculator();
// calculator.div(1,1);
MathCalculator calculator = context.getBean(MathCalculator.class);
// calculator.div(1, 1);
calculator.div(1, 1);
}
測試結果如下:
注意:切面類上必須加上 @Aspect 表名該類爲一個切面類,並且 配置類上也需要使用 @EnableAspectJAutoProxy
開啓基於註解的aop模式,此時切面纔會生效。 除外次,要使用Spring容器創建的bean實例,纔會觸發切面。
11 與事務相關
@Transactional //標註在方法上,表示當前方法是一個事務方法
@EnableTransactionManagement // 開啓事務管理功能
使用步驟如下:
1. 配置數據源、JdbcTemplate(Spring 提供的簡化數據庫操作的工具)操作數據
2. 給方法上標註@Transactional 表示當前方法是一個事務方法
3. 開啓事務管理功能。
4. 往容器中 配置一個事務管理器,並管理 dataSource
代碼使用如下:
@Configuration
@PropertySource("classpath:/dbConfig.properties")
@ComponentScan("com.zhao.springboot.config.tx")
@EnableTransactionManagement //開啓事務管理功能
public class TxConfig {
@Value("${db.user}")
private String user;
@Value("${db.password}")
private String password;
@Value("${db.driverClass}")
private String driverClass;
@Bean
public DataSource comboPooledDataSource() throws Exception {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test");
dataSource.setUser(user);
dataSource.setPassword(password);
dataSource.setDriverClass(driverClass);
return dataSource;
}
// 配置JdbcTemplate
@Bean
public JdbcTemplate jdbcTemplate() throws Exception {
JdbcTemplate jdbcTemplate = new JdbcTemplate();
jdbcTemplate.setDataSource(comboPooledDataSource());
return jdbcTemplate;
}
// 註冊事務管理器
@Bean // PlatformTransactionManager 爲事務管理器的頂層接口
public PlatformTransactionManager transactionManager() throws Exception {
return new DataSourceTransactionManager(comboPooledDataSource());
}
}
.... dao層
@Repository
public class UserDao {
@Autowired
private JdbcTemplate jdbcTemplate;
public void insert() {
String sql = "INSERT INTO `tbl_user` ( `username`, `age`) VALUES (?, ?);";
String username = UUID.randomUUID().toString().substring(0, 5);
jdbcTemplate.update(sql, username, 19);
}
}
.... service層
@Service
public class UserService {
@Autowired
private UserDao userDao;
@Transactional // 標註 這個一個事務方法
public void addUser(){
userDao.insert();
System.out.println("插入成功");
int i=1/0;
}
}
測試代碼:
@Test
public void test01() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(TxConfig.class);
UserService userService = context.getBean(UserService.class);
userService.addUser();
}
測試前數據庫數據:
測試後:
數據庫數據: