一、课前说明
说明:
- 使用Spring、SpringMvc、MyBatis整合开发项目需要
写很多配置比较麻烦
,在SpringBoot,SpingCloud兴起之后,可以通过注解代替这些配置文件
。 - SpringBoot、SpringCloud
作为Spring之上的框架
,他们大量使用到了
Spring的一些底层注解、原理,比如@Conditional、@Import、@EnableXXX等。 - 学习后面的课程SpringBoot,SpingCloud等框架技术,
在这些陌生的注解上、以及对Spring原理的理解上,浪费了很多时间
,所以在这里学习一些常用的注解以及一些注解的原理,之后在学习后面的课程就变的很轻松了。 - 总结:
SpringBoot、SpringCloud这些框架用到了很多Spring的注解和原理,所以在这里提前学下,这样之后在学习SpringBoot、SpringCloud的时候就比较轻松了。
1 课程知识流程图
二、IOC容器
1 准备环境
1.1 创建工程
1.2 添加依赖
<dependencies>
<!--核心容器所依赖的环境-->
<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.12.RELEASE</version>
</dependency>
</dependencies>
2 组件注册/创建对象(使用注解)
说明:
- 使用
纯注解
的方式完成,容器的组件注册管理以及依赖注入功能。 - 所谓的
注册一个组件
指的就是,之前Spring如何创建一个对象
。
2.1 方式一:使用@Configuration+@Bean
2.1.1 回顾:xml方式
需求:Spring
使用xml方式中的bean标签
,来创建对象、添加属性。
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.xsd">
<bean id="person" class="com.atguigu.bean.Person" scope="prototype" >
<property name="age" value="18"></property>
<property name="name" value="zhangsan"></property>
</bean>
</beans>
pojo类:
package com.atguigu.bean;
public class Person {
private String name;
private Integer age;
public Person() {
}
public Person(String name, Integer age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
测试类:
package com.atguigu;
import com.atguigu.bean.Person;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MainTest {
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
Person bean = (Person) applicationContext.getBean("person");
System.out.println(bean);
}
}
2.1.2 现在:配置类+@Bean
配置类:
package com.atguigu.config;
import com.atguigu.bean.Person;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
//配置类==配置文件
@Configuration //告诉Spring这是一个配置类
public class MainConfig {
//给容器中注册一个Bean,方法的返回值为spring容器所管理的一个Bean对象,
// 对象的类型为返回值的类型,id默认是用方法名作为id(对象名)
//修改创建的对象名字:可以直接修改方法名,也可以在注解上指定value属性="对象名",只有一个值value可以省略
//在配置类的方法上加上此注解,ioc容器启动时就会自动调用这个方法,将方法的返回值
// 放到ioc容器中,将方法名作为组件的id(即对象名)
@Bean("person")
public Person person01(){
return new Person("lisi", 20);
}
}
测试类:
package com.atguigu;
import com.atguigu.bean.Person;
import com.atguigu.config.MainConfig;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class MainTest {
public static void main(String[] args) {
//xml的方式
// ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
// Person bean = (Person) applicationContext.getBean("person");
// System.out.println(bean);
//配置类的方式来代替xml的方式
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig.class);
Person bean = applicationContext.getBean(Person.class);
System.out.println(bean);
//查看在容器中创建的组件/对象名
String[] namesForType = applicationContext.getBeanNamesForType(Person.class);
for (String name : namesForType) {
System.out.println(name);
}
}
}
2.2 包扫描
2.2.1 回顾:xml方式
说明:之前Spring使用注解(@Controller...)
方式创建对象,需要在xml配置文件中,开启包扫描
。
<!-- 包扫描、只要标注了@Controller、@Service、@Repository,@Component -->
<context:component-scan base-package="com.atguigu" use-default-filters="false"></context:component-scan>
2.2.2 现在:配置类+@ComponentScan(不提示警告)
说明:以前的注解开发需要在配置文件中配置包扫描,现在只需要在配置类中使用注解配置
即可。
controller,srvice,dao层类:
package com.atguigu.controller;
import org.springframework.stereotype.Controller;
@Controller
public class BookController {
}
-------------------------------
package com.atguigu.service;
import org.springframework.stereotype.Service;
@Service
public class BookService {
}
-----------------------------
package com.atguigu.dao;
import org.springframework.stereotype.Repository;
@Repository
public class BookDao {
}
配置类:
package com.atguigu.boot.config;
import com.atguigu.bean.Person;
import com.atguigu.service.BookService;
import org.springframework.context.annotation.*;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Service;
//配置类==配置文件
@Configuration //告诉Spring这是一个配置类,配置类本身也是一个组件对象
/**
* @ComponentScan :
* value:指定要扫描的包:
* excludeFilters = Filter[] :指定扫描的时候按照什么规则排除那些组件
* includeFilters = Filter[] :指定扫描的时候只需要包含哪些组件
* FilterType.ANNOTATION:按照注解
* FilterType.ASSIGNABLE_TYPE:按照给定的类型;
* FilterType.ASPECTJ:使用ASPECTJ表达式
* FilterType.REGEX:使用正则指定
* FilterType.CUSTOM:使用自定义规则 :需要写一个类继承对应的接口之后重写方法,在方法里面指定过滤规则。
*
* value使用举例:value是一个数组类型,注解只有一个属性value可以省略。
* 扫描一个包的范围:@ComponentScan(value = "com.itheima.controller")
* 扫描多个包的范围:@ComponentScan({"com.itheima.controller","com.itheima.config"})
*
* excludeFilters使用举例:
* @ComponentScan(value = "com.atguigu",
* excludeFilters = {@Filter(type= FilterType.ANNOTATION, classes={Controller.class, Service.class})}
* )
* includeFilters: 之前需要在xml中配置禁用默认的过滤规则,默认的扫描规则就是所有的。
* 现在只需要在此注解添加上useDefaultFilters = false即可。
*/
@ComponentScan(value = "com.atguigu",
includeFilters = {
@Filter(type= FilterType.ANNOTATION,classes={Controller.class, Service.class}),
@Filter(type= FilterType.ASSIGNABLE_TYPE,classes={BookService.class}),
@Filter(type=FilterType.CUSTOM,classes={MyTypeFilter.class})
},
useDefaultFilters = false
)
/* jdk8写重复注解的写法:可以写直接写多个@ComponentScan,来指定不同的扫描策略
jdk8之前重复注解的写法:
@ComponentScans(value = {
@ComponentScan(value = "com.atguigu",
includeFilters = {@Filter(type= FilterType.ANNOTATION,classes={Controller.class, Service.class})},
useDefaultFilters = false
)
} ) */
public class MainConfig {
//给容器中注册一个Bean;类型为返回值的类型,id默认是用方法名作为id
//修改创建的对象名字:可以直接修改方法名,也可以在注解上指定value属性="对象名",只有一个值value可以省略
@Bean("person")
public Person person01(){
return new Person("lisi", 20);
}
}
自定义过滤规则的类:
package com.atguigu.config;
import org.springframework.core.io.Resource;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.ClassMetadata;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.filter.TypeFilter;
import java.io.IOException;
public class MyTypeFilter implements TypeFilter {
/**
* metadataReader:读取到的当前正在扫描的类的信息
* metadataReaderFactory:可以获取到其他任何类信息的
*/
@Override
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
throws IOException {
// TODO Auto-generated method stub
//获取当前类注解的信息
AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
//获取当前正在扫描的类的类信息
ClassMetadata classMetadata = metadataReader.getClassMetadata();
//获取当前类资源(类的路径)
Resource resource = metadataReader.getResource();
String className = classMetadata.getClassName();
System.out.println("--->"+className);
if(className.contains("er")){//全类名中,包含er,就返回为true,匹配成功。
return true;
}
return false;//一个都不匹配
}
}
测试类:
package com.atguigu.test;
import com.atguigu.config.MainConfig;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class IOCTest {
// @SuppressWarnings("resource")不提示警告
@Test
public void test01(){
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig.class);
//查看容器中对象的名字
String[] definitionNames = applicationContext.getBeanDefinitionNames();
for (String name : definitionNames) {
System.out.println(name);
}
}
}
2.3 @Scope+@Lazy(作用域 懒加载)
配置类:
package com.atguigu.config;
import com.atguigu.bean.Person;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
@Configuration
public class MainConfig2 {
/**
* Scope的取值源码说明:
* @see ConfigurableBeanFactory#SCOPE_PROTOTYPE
* @see ConfigurableBeanFactory#SCOPE_SINGLETON
* @see org.springframework.web.context.WebApplicationContext#SCOPE_REQUEST request
* @see org.springframework.web.context.WebApplicationContext#SCOPE_SESSION sesssion
* @return\
*
*
* @Scope:可以调整作用域
* singleton(单实例):Spring容器创建的类默认是单实例的
* ioc容器启动会调用方法创建对象放到ioc容器中,
* 以后每次获取就是直接从容器(map.get())中拿。
*
* prototype(多实例):ioc容器启动并不会去调用方法创建对象放在容器中。
* 每次获取的时候才会调用方法创建对象;
*
* request:同一次请求创建一个实例 不常用 (需要在web环境)
*
* session:同一个session创建一个实例 不常用 (需要在web环境)
*
* @Lazy:懒加载,即容器启动不创建对象。第一次使用(获取)Bean创建对象,并初始化。(针对单实例)
* 单实例bean:默认在容器启动的时候创建对象,所以想要实现懒加载需要加上@Lazy注解
* 多实例:默认就是懒加载,不需要添加注解。
*/
@Bean("person")
//@Scope("prototype")
//@Scope:默认是单实例, 连@Scope注解也不写是单实例,只写了@Scope没有指定值里面的值 那么值默认就是singleton单实例。
@Lazy
public Person person(){
System.out.println("给容器中添加Person....");
return new Person("张三", 25);
}
}
测试类:
@Test
public void test02(){
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig2.class);
String[] definitionNames = applicationContext.getBeanDefinitionNames();
for (String name : definitionNames) {
System.out.println(name);
}
System.out.println("ioc容器创建完成....");
// Object bean = applicationContext.getBean("person");
// Object bean2 = applicationContext.getBean("person");
// System.out.println(bean == bean2);
}
2.4 按照条件注册(@Conditional)
配置类:
package com.atguigu.config;
import com.atguigu.bean.Person;
import com.atguigu.condition.LinuxCondition;
import com.atguigu.condition.WindowsCondition;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
//类中组件统一设置,满足当前条件,这个类中配置的所有bean注册才能生效;
@Conditional({WindowsCondition.class})
@Configuration
public class MainConfig2 {
/**
* @Conditional({Condition}) : 按照一定的条件进行判断,满足条件给容器中注册bean 可以用在类上,也可以用在方法上。
* 业务:创建2个对象,bill和linus,默认都可以注册bean,
* 现在要求:
* 如果系统是windows,给容器中注册("bill")
* 如果是linux系统,给容器中注册("linus")
* 实现步骤: 需要写一个类,继承对应的接口,重写里面的方法,在方法中指定注入规则,
* 之后使用注解@Conditional标识需要注入的Bean,如果重写规则是true就注入。
*/
@Bean("bill")
public Person person01(){
return new Person("Bill Gates",62);
}
@Conditional(LinuxCondition.class)
@Bean("linus")
public Person person02(){
return new Person("linus", 48);
}
}
重写的规则类1:
package com.atguigu.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 {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
Environment environment = context.getEnvironment();//获取运行环境
String property = environment.getProperty("os.name");//获取操作系统名
if(property.contains("Windows")){
return true;//如果是true匹配成功,如果是false匹配失败
}
return false;
}
}
重写的规则类2:
package com.atguigu.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:注释信息
*/
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// TODO是否linux系统
//1、能获取到ioc使用的beanfactory
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
//2、获取类加载器
ClassLoader classLoader = context.getClassLoader();
//3、获取当前环境信息
Environment environment = context.getEnvironment();
//4、获取到bean定义的注册类
BeanDefinitionRegistry registry = context.getRegistry();
String property = environment.getProperty("os.name");
//可以判断容器中的bean注册情况,也可以给容器中注册bean
boolean definition = registry.containsBeanDefinition("person");
if(property.contains("linux")){
return true;
}
return false;
}
}
测试类:
因为现在是使用的windows系统,不可能在切换到Linux系统中测试。可以添加运行时变量进行测试。
@Test
public void test03(){
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig2.class);
String[] namesForType = applicationContext.getBeanNamesForType(Person.class);
ConfigurableEnvironment environment = applicationContext.getEnvironment();
//动态获取环境变量的值;Windows 10
String property = environment.getProperty("os.name");
System.out.println(property);
for (String name : namesForType) {
System.out.println(name);
}
Map<String, Person> persons = applicationContext.getBeansOfType(Person.class);
System.out.println(persons);
}
2.5 方式二:@Import(导入一个组件)
2.5.1 实现方式1:@Import(组件名)
说明:
- @Import(要导入到容器中的组件),容器中就会自动注册这个组件,id默认是全类名。
- 里面的值是数组类型,可以导入一个也可以一次性导入多个。
配置类:
模拟需要导入的第三方组件:Color类,Red类:
package com.atguigu.bean;
public class Color {
}
-------------------------
package com.atguigu.bean;
public class Red {
}
测试类:
@Test
public void testImport04(){
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig2.class);
//查看容器中对象的名字
String[] definitionNames = applicationContext.getBeanDefinitionNames();
for (String name : definitionNames) {
System.out.println(name);
}
}
2.5.2 实现方式2:@Import+ImportSelector接口
说明:
- ImportSelector接口:返回需要导入的组件的全类名数组
- 实现方式1,是直接在@Import添加需要导入的组件反射名,来注册组件。
- 实现方式2,在@Import中添加添加的也是类的反射名,只不过这个类实现ImportSelector接口后,注册的组件就变为类中重写方法的返回值。
配置类:
指定需要导入组件的类:
package com.atguigu.condition;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;
//自定义逻辑返回需要导入的组件
public class MyImportSelector implements ImportSelector {
//返回值,就是到导入到容器中的组件全类名
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
//AnnotationMetadata importingClassMetadata:可以获取当前标注@Import注解的类的所有注解信息
//方法不要返回null值,可以返回空数组
return new String[]{"com.atguigu.bean.Blue","com.atguigu.bean.Yellow"};
}
}
测试类:
2.5.3 实现方式3:@Import+ImportBeanDefinitionRegistrar
说明:
- ImportBeanDefinitionRegistrar:手动注册bean到容器中;
- 编写一个类实现ImportBeanDefinitionRegistrar接口,重写里面的方法,在方法中可以指定需要注册的组件。
指定需要导入组件的类:
package com.atguigu.condition;
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;
import com.atguigu.bean.RainBow;
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
/**
* AnnotationMetadata:当前类的注解信息
* BeanDefinitionRegistry,即BeanDefinition注册类:
* 把所有需要添加到容器中的bean,通过调用BeanDefinitionRegistry.registerBeanDefinition方法,手工注册进来
*。
*/
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
boolean definition = registry.containsBeanDefinition("com.atguigu.bean.Red");
boolean definition2 = registry.containsBeanDefinition("com.atguigu.bean.Blue");
//逻辑:如果容器中有组件Red和Blue,就注册rainBow
if(definition && definition2){
//指定Bean定义信息;(Bean的类型,Bean。。。)
RootBeanDefinition beanDefinition = new RootBeanDefinition(RainBow.class);
//注册一个Bean,指定bean名
registry.registerBeanDefinition("rainBow", beanDefinition);
}
}
}
配置类:
测试类:
2.6 方式三:FactoryBean注册组件
说明:
- 使用
Spring提供
的FactoryBean注册组件。 - 方式一,方式二注入的都是普通Bean,导入到容器中会调用构造方法来创建对象。现在需要注册的是工厂Bean,它调用的是getObject()方法、getObjectType()、isSingleton(),返回的对象放在容器中。
- 工厂Bean:定义一个类实现FactoryBean接口,重写里面的方法。
Color:
package com.atguigu.bean;
public class Color {
private Car car;
public Car getCar() {
return car;
}
public void setCar(Car car) {
this.car = car;
}
@Override
public String toString() {
return "Color [car=" + car + "]";
}
}
定义一个工厂Bean:
package com.atguigu.bean;
import org.springframework.beans.factory.FactoryBean;
//创建一个Spring定义的FactoryBean
//泛型指定需要创建什么类型的对象
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:这个bean是单实例,在容器中保存一份
//false:多实例,每次获取都会创建一个新的bean;
@Override
public boolean isSingleton() {
return false;
}
}
把工厂bean加入到容器中:
/*使用@Bean注解,之前返回的对象是这个方法名colorFactoryBean,
现在获取的是工厂Bean调用getObject创建的对象*/
@Bean
public ColorFactoryBean colorFactoryBean(){
return new ColorFactoryBean();
}
测试类:
@Test
public void testImport04(){
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig2.class);
//查看容器中对象的名字
String[] definitionNames = applicationContext.getBeanDefinitionNames();
for (String name : definitionNames) {
System.out.println(name);
}
//工厂Bean获取的是调用getObject创建的对象
Object bean2 = applicationContext.getBean("colorFactoryBean");
Object bean3 = applicationContext.getBean("colorFactoryBean");
System.out.println("bean的类型:"+bean2.getClass());
System.out.println(bean2 == bean3);
//加上&前缀获取的是工厂Bean本身,即@Bean注解标识的方法返回值。
Object bean4 = applicationContext.getBean("&colorFactoryBean");
System.out.println(bean4.getClass());//class com.atguigu.bean.ColorFactoryBean
}
2.7、总结:给容器注册组件的注解方式(4种)
注解方式注册组件:
- 包扫描+组件标注注解(@Controller/@Service/@Repository/@Component)[
自己写的类,普通Bean
] - @Bean[
导入的第三方包里面的组件,自己写的类。普通Bean
] - @Import[
快速给容器中导入一个组件:第三方包里面的组件,自己写的类。普通Bean
]- 3.1 @Import(要导入到容器中的组件):容器中就会自动注册这个组件,id默认是全类名
- 3.2 ImportSelector:返回需要导入的组件的全类名数组;
- 3.3 ImportBeanDefinitionRegistrar:手动注册bean到容器中
- 使用Spring提供的 FactoryBean(
第三方包里面的组件。工厂Bean
)- 4.1 默认获取到的是工厂bean调用getObject创建的对象
- 4.2 要获取工厂Bean本身,我们需要给id前面加一个
&
,如:&colorFactoryBean
3 Bean生命周期
3.1 方式一:@Bean指定初始化和销毁方法
3.1.1 单实例Bean
配置类:
/**
* 一、bean的生命周期:
* bean创建---初始化----销毁的过程
* 容器管理bean的生命周期:
* 我们可以自定义初始化和销毁方法:容器在bean进行到当前生命周期的时候来调用我们自定义的初始化和销毁方法
*
* 1)构造(对象创建)
* 单实例:在容器启动的时候创建对象
* 多实例:在每次获取的时候创建对象
* BeanPostProcessor.postProcessBeforeInitialization (方式4的:在初始化方法前调用)
* 2)初始化:
* 对象创建完成,并赋值好,调用初始化方法
* BeanPostProcessor.postProcessAfterInitialization (方式4的:在初始化方法后调用)
* 3)销毁:
* 单实例:容器关闭的时候
* 多实例:容器不会管理这个bean;容器不会调用销毁方法,想要销毁可以手动调用销毁方法;
*
*
* 二、指定Bean的初始化和销毁有4种方式:
* 方式1)、指定初始化和销毁方法;
* 通过@Bean指定init-method和destroy-method; (解释:在@Bean注解上指定属性来代替原先的xml方式)
* 方式2)、通过让Bean实现InitializingBean(定义初始化逻辑),
* DisposableBean(定义销毁逻辑); (解释:让实体类实现对应的初始化和销毁的接口)
* 方式3)、可以使用JSR250;
* @PostConstruct:在bean创建完成并且属性赋值完成;来执行初始化方法
* @PreDestroy:在容器销毁bean之前通知我们进行清理工作 (解释:使用2个注解)
* 方式4)、BeanPostProcessor【interface】:bean的后置处理器,在bean初始化前后进行一些处理工作;
* postProcessBeforeInitialization:在初始化之前工作
* postProcessAfterInitialization:在初始化之后工作 (解释:使用后置处理器)
*
*
* 三、BeanPostProcessor原理
* 遍历得到容器中所有的BeanPostProcessor;挨个执行beforeInitialization,
* 一但返回null,跳出for循环,不会执行后面的BeanPostProcessor.postProcessorsBeforeInitialization
*
* populateBean(beanName, mbd, instanceWrapper);给bean进行属性赋值
* initializeBean
* {
* applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
* invokeInitMethods(beanName, wrappedBean, mbd);执行自定义初始化
* applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
* }
*
*
* 四、Spring底层对 BeanPostProcessor 的使用;
* bean赋值,注入其他组件,@Autowired,生命周期注解功能,@Async,xxx BeanPostProcessor;
*
* @author lfy
*
*/
@Configuration
public class MainConfigOfLifeCycle {
//方式一:使用@Bean的方式指定初始化和销毁方法
@Bean(initMethod="init",destroyMethod="detory")
public Car car(){
return new Car();
}
}
实体类:
package com.atguigu.bean;
public class Car {
public Car(){
System.out.println("car constructor...");
}
public void init(){
System.out.println("car ... init...");
}
public void detory(){
System.out.println("car ... detory...");
}
}
测试类:
package com.atguigu.test;
import com.atguigu.config.MainConfigOfLifeCycle;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class IOCTest_LifeCycle {
@Test
public void test01(){
//1、创建ioc容器
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfigOfLifeCycle.class);
System.out.println("容器创建完成...");
//关闭容器
applicationContext.close();
}
}
3.1.2 多实例Bean
修改配置类:
@Configuration
public class MainConfigOfLifeCycle {
@Scope("prototype")
//方式一:使用@Bean的方式指定初始化和销毁方法
@Bean(initMethod="init",destroyMethod="detory")
public Car car(){
return new Car();
}
修改测试类:
public class IOCTest_LifeCycle {
@Test
public void test01(){
//1、创建ioc容器
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfigOfLifeCycle.class);
System.out.println("容器创建完成...");
applicationContext.getBean("car");//多实例bean只有在获取时,才会创建对象并初始化
//关闭容器
applicationContext.close();//多实例bean不会自动销毁,想要销毁可以手动调用销毁方法。
}
}
3.2 方式二:实现InitializingBean和DisposableBean接口
配置类:
package com.atguigu.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
/**
* 一、bean的生命周期:
* bean创建---初始化----销毁的过程
* 容器管理bean的生命周期:
* 我们可以自定义初始化和销毁方法;容器在bean进行到当前生命周期的时候来调用我们自定义的初始化和销毁方法
*
* 1)构造(对象创建)
* 单实例:在容器启动的时候创建对象
* 多实例:在每次获取的时候创建对象
* BeanPostProcessor.postProcessBeforeInitialization (方式4的:在初始化方法前调用)
* 2)初始化:
* 对象创建完成,并赋值好,调用初始化方法
* BeanPostProcessor.postProcessAfterInitialization (方式4的:在初始化方法后调用)
* 3)销毁:
* 单实例:容器关闭的时候
* 多实例:容器不会管理这个bean;容器不会调用销毁方法,想要销毁可以手动调用销毁方法;
*
* 二、指定Bean的初始化和销毁有4种方式:
* 方式1)、指定初始化和销毁方法;
* 通过@Bean指定init-method和destroy-method; (解释:在@Bean注解上指定属性来代替原先的xml方式)
* 方式2)、通过让Bean实现InitializingBean(定义初始化逻辑),
* DisposableBean(定义销毁逻辑); (解释:让实体类实现对应的初始化和销毁的接口)
* 方式3)、可以使用JSR250;
* @PostConstruct:在bean创建完成并且属性赋值完成;来执行初始化方法
* @PreDestroy:在容器销毁bean之前通知我们进行清理工作 (解释:使用2个注解)
* 方式4)、BeanPostProcessor【interface】:bean的后置处理器,在bean初始化前后进行一些处理工作;
* postProcessBeforeInitialization:在初始化之前工作
* postProcessAfterInitialization:在初始化之后工作 (解释:使用后置处理器)
*
* 三、BeanPostProcessor原理
* 遍历得到容器中所有的BeanPostProcessor;挨个执行beforeInitialization,
* 一但返回null,跳出for循环,不会执行后面的BeanPostProcessor.postProcessorsBeforeInitialization
*
* populateBean(beanName, mbd, instanceWrapper);给bean进行属性赋值
* initializeBean
* {
* applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
* invokeInitMethods(beanName, wrappedBean, mbd);执行自定义初始化
* applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
* }
*
* 四、Spring底层对 BeanPostProcessor 的使用;
* bean赋值,注入其他组件,@Autowired,生命周期注解功能,@Async,xxx BeanPostProcessor;
*
*/
@Configuration
@ComponentScan("com.atguigu.bean")//使用包扫描+注解标识(@compotent)
public class MainConfigOfLifeCycle {
}
实体类:
package com.atguigu.bean;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;
@Component//使用注解+包扫描的方式注册bean,当然也可以在配置类中使用@Bean的方式
public class Cat implements InitializingBean,DisposableBean {
public Cat(){
System.out.println("cat constructor...");
}
//销毁方法
@Override
public void destroy() throws Exception {
System.out.println("cat...destroy...");
}
//初始化方法
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("cat...afterPropertiesSet...");
}
}
测试类:
package com.atguigu.test;
import com.atguigu.config.MainConfigOfLifeCycle;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class IOCTest_LifeCycle {
@Test
public void test01(){
//1、创建ioc容器
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfigOfLifeCycle.class);
System.out.println("容器创建完成...");
//关闭容器
applicationContext.close();
}
}
3.3 方式三:使用注解@PostConstruct,@PreDestroy
说明:可以使用JSR250,java规范提供的注解。
配置类:
package com.atguigu.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
/**
* 一、bean的生命周期:
* bean创建---初始化----销毁的过程
* 容器管理bean的生命周期:
* 我们可以自定义初始化和销毁方法;容器在bean进行到当前生命周期的时候来调用我们自定义的初始化和销毁方法
*
* 1)构造(对象创建)
* 单实例:在容器启动的时候创建对象
* 多实例:在每次获取的时候创建对象
* BeanPostProcessor.postProcessBeforeInitialization (方式4的:在初始化方法前调用)
* 2)初始化:
* 对象创建完成,并赋值好,调用初始化方法
* BeanPostProcessor.postProcessAfterInitialization (方式4的:在初始化方法后调用)
* 3)销毁:
* 单实例:容器关闭的时候
* 多实例:容器不会管理这个bean;容器不会调用销毁方法,想要销毁可以手动调用销毁方法;
*
* 二、指定Bean的初始化和销毁有4种方式:
* 方式1)、指定初始化和销毁方法;
* 通过@Bean指定init-method和destroy-method; (解释:在@Bean注解上指定属性来代替原先的xml方式)
* 方式2)、通过让Bean实现InitializingBean(定义初始化逻辑),
* DisposableBean(定义销毁逻辑); (解释:让实体类实现对应的初始化和销毁的接口)
* 方式3)、可以使用JSR250;
* @PostConstruct:在bean创建完成并且属性赋值完成;来执行初始化方法
* @PreDestroy:在容器销毁bean之前通知我们进行清理工作
* (解释:使用2个注解)
* 方式4)、BeanPostProcessor【interface】:bean的后置处理器,在bean初始化前后进行一些处理工作;
* postProcessBeforeInitialization:在初始化之前工作
* postProcessAfterInitialization:在初始化之后工作
* (解释:使用后置处理器)
*
* 三、BeanPostProcessor原理
* 遍历得到容器中所有的BeanPostProcessor;挨个执行beforeInitialization,
* 一但返回null,跳出for循环,不会执行后面的BeanPostProcessor.postProcessorsBeforeInitialization
*
* populateBean(beanName, mbd, instanceWrapper);给bean进行属性赋值
* initializeBean
* {
* applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
* invokeInitMethods(beanName, wrappedBean, mbd);执行自定义初始化
* applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
* }
*
* 四、Spring底层对 BeanPostProcessor 的使用;
* bean赋值,注入其他组件,@Autowired,生命周期注解功能,@Async,xxx BeanPostProcessor;
*
*/
@Configuration
@ComponentScan("com.atguigu.bean")//使用包扫描+注解标识(@compotent)
public class MainConfigOfLifeCycle {
}
实体类:
package com.atguigu.bean;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
@Component
public class Dog {
public Dog(){
System.out.println("dog constructor...");
}
//对象创建并赋值之后调用
@PostConstruct
public void init(){
System.out.println("Dog....@PostConstruct...");
}
//容器移除对象之前
@PreDestroy
public void detory(){
System.out.println("Dog....@PreDestroy...");
}
}
测试类:
package com.atguigu.test;
import com.atguigu.config.MainConfigOfLifeCycle;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class IOCTest_LifeCycle {
@Test
public void test01(){
//1、创建ioc容器
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfigOfLifeCycle.class);
System.out.println("容器创建完成...");
//关闭容器
applicationContext.close();
}
}
3.4 方式四:使用后置处理器
配置类:
package com.atguigu.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
/**
* 一、bean的生命周期:
* bean创建---初始化----销毁的过程
* 容器管理bean的生命周期:
* 我们可以自定义初始化和销毁方法;容器在bean进行到当前生命周期的时候来调用我们自定义的初始化和销毁方法
*
* 1)构造(对象创建)
* 单实例:在容器启动的时候创建对象
* 多实例:在每次获取的时候创建对象
* BeanPostProcessor.postProcessBeforeInitialization (方式4的:在初始化方法前调用)
* 2)初始化:
* 对象创建完成,并赋值好,调用初始化方法
* BeanPostProcessor.postProcessAfterInitialization (方式4的:在初始化方法后调用)
* 3)销毁:
* 单实例:容器关闭的时候
* 多实例:容器不会管理这个bean;容器不会调用销毁方法,想要销毁可以手动调用销毁方法;
*
* 二、指定Bean的初始化和销毁有4种方式:
* 方式1)、指定初始化和销毁方法;
* 通过@Bean指定init-method和destroy-method; (解释:在@Bean注解上指定属性来代替原先的xml方式)
* 方式2)、通过让Bean实现InitializingBean(定义初始化逻辑),
* DisposableBean(定义销毁逻辑); (解释:让实体类实现对应的初始化和销毁的接口)
* 方式3)、可以使用JSR250;
* @PostConstruct:在bean创建完成并且属性赋值完成;来执行初始化方法
* @PreDestroy:在容器销毁bean之前通知我们进行清理工作 (解释:使用2个注解)
* 方式4)、BeanPostProcessor【interface】:bean的后置处理器,在bean初始化前后进行一些处理工作;
* postProcessBeforeInitialization:在初始化之前工作
* postProcessAfterInitialization:在初始化之后工作 (解释:使用后置处理器)
*
* 三、BeanPostProcessor原理
* 遍历得到容器中所有的BeanPostProcessor;挨个执行beforeInitialization,
* 一但返回null,跳出for循环,不会执行后面的BeanPostProcessor.postProcessorsBeforeInitialization
*
* populateBean(beanName, mbd, instanceWrapper);给bean进行属性赋值
* initializeBean
* {
* applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
* invokeInitMethods(beanName, wrappedBean, mbd);执行自定义初始化
* applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
* }
*
* 四、Spring底层对 BeanPostProcessor 的使用;
* bean赋值,注入其他组件,@Autowired,生命周期注解功能,@Async,xxx BeanPostProcessor;
*
*/
@Configuration
@ComponentScan("com.atguigu.bean")//使用包扫描+注解标识(@compotent)
public class MainConfigOfLifeCycle {
}
创建后置处理器:
package com.atguigu.bean;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;
/**
* BeanPostProcessor:后置处理器的接口
* 后置处理器:初始化前后进行处理工作
*/
@Component //将后置处理器加入到容器中
public class MyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
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;
}
}
测试方法:
可以看出,分别在前几种方式的初始化方法前后执行这2个方法。
package com.atguigu.test;
import com.atguigu.config.MainConfigOfLifeCycle;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class IOCTest_LifeCycle {
@Test
public void test01(){
//1、创建ioc容器
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfigOfLifeCycle.class);
System.out.println("容器创建完成...");
//关闭容器
applicationContext.close();
}
}
4 属性赋值:注入普通属性
4.1 @Value+@PropertySource
配置文件:
#这是因为中文乱码问题,可以在@PropertySource注解上设置编码
person.nickName=\u5C0F\u674E\u56DB
实体类:
package com.atguigu.bean;
import org.springframework.beans.factory.annotation.Value;
public class Person {
/**
* 使用@Value赋值:
* 写法1:基本数值
* 写法2:可以写SpEL; #{} Spring提供的EL表达式
* 写法3:可以写${};取出配置文件【properties】中的值(在运行环境变量里面的值),
* 需要在配置类中使用注解@PropertySource 引入外部属性文件
* 如果获取不到还可以使用设置的默认值:
* @Value("${person.nickName:小明}")
* private String nickName;
* 如果person.nickName获取不到值就使用这个默认值 小明
*/
@Value("张三")
private String name;
@Value("#{20-2}")
private Integer age;
@Value("${person.nickName}")
private String nickName;
public String getNickName() {
return nickName;
}
public void setNickName(String nickName) {
this.nickName = nickName;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Person(String name, Integer age) {
super();
this.name = name;
this.age = age;
}
public Person() {
super();
// TODO Auto-generated constructor stub
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + ", nickName=" + nickName + "]";
}
}
配置类:
package com.atguigu.boot.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import com.atguigu.bean.Person;
/**
* 使用@PropertySource读取外部配置文件中的k/v保存到运行的环境变量中,
* 加载完外部的配置文件以后使用${}取出配置文件的值
* 路径方式:
* 1.文件路径:"file/path/to/file"
* 2.类路径:"classpath:/com/my/person.properties" 从resources目录下开始
*/
@PropertySource(value={"classpath:/person.properties"},encoding = "utf-8")
@Configuration
public class MainConfigOfPropertyValues {
@Bean
public Person person(){
return new Person();
}
}
测试类:
package com.atguigu.test;
import com.atguigu.bean.Person;
import com.atguigu.boot.config.MainConfigOfPropertyValues;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.core.env.ConfigurableEnvironment;
public class IOCTest_PropertyValue {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfigOfPropertyValues.class);
@Test
public void test01(){
printBeans(applicationContext);
System.out.println("=============");
Person person = (Person) applicationContext.getBean("person");
System.out.println(person);
ConfigurableEnvironment environment = applicationContext.getEnvironment();
String property = environment.getProperty("person.nickName");
System.out.println(property);//配置文件中的值都加载到了环境变量中,所以还可以通过环境变量取值。
applicationContext.close();
}
private void printBeans(AnnotationConfigApplicationContext applicationContext){
String[] definitionNames = applicationContext.getBeanDefinitionNames();
for (String name : definitionNames) {
System.out.println(name);
}
}
}
5 自动装配:注入对象属性
5.1 方式一:@Autowired(属性上)
说明:这是Spring
提供提供的注解。
5.1.1 默认优先按照类型注入(情况1)
配置类:
package com.atguigu.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
/**
* 自动装配;
* Spring利用依赖注入(DI),完成对IOC容器中中各个组件的依赖关系赋值;
*
* 1)、@Autowired:自动注入: [Spring提供]
* 1)、默认优先按照类型去容器中找对应的组件:applicationContext.getBean(BookDao.class);找到就赋值
* 2)、如果找到多个相同类型的组件,再将属性的名称作为组件的id去容器中查找
* applicationContext.getBean("bookDao")
* 3)、@Qualifier("bookDao"):使用@Qualifier指定需要装配的组件的id,而不是使用属性名
* 4)、自动装配默认一定要将属性赋值好,没有就会报错;
* 可以使用@Autowired(required=false);即:正常情况下默认为true:属性注册好后一定要赋好值,否则运行时报错。
* 修改为false:能找到就找到,找不到就拉到,属性注册好后即便没有赋值,运行时也不会报错。
* 5)、@Primary:让Spring进行自动装配的时候,默认使用首选的bean;
* 也可以继续使用@Qualifier指定需要装配的bean的名字
* BookService{
* @Autowired
* BookDao bookDao;
* }
*
* 2)、Spring还支持使用@Resource(JSR250)和@Inject(JSR330)[java规范的注解]
* @Resource:
* 可以和@Autowired一样实现自动装配功能;默认是按照组件名称进行装配的;
* 没有能支持@Primary功能没有支持@Autowired(reqiured=false);
* @Inject:
* 需要导入javax.inject的包,和Autowired的功能一样。没有required=false的功能;
* @Autowired:Spring定义的; @Resource、@Inject都是java规范
*
* AutowiredAnnotationBeanPostProcessor:解析完成自动装配功能;
*
* 3)、 @Autowired:构造器,方法参数,方法,属性;都是从容器中获取参数组件的值
* 1)、[标注在方法上]:(参数从容器中获取;)
* 2)、[标在构造器上]:如果组件只有一个有参构造器,这个有参构造器的@Autowired可以省略,参数位置的组件还是(可以自动从容器中获取)
* 3)、放在方法参数位置:普通方法,构造方法,set get, (参数从容器中获取);
* 4)、使用@Bean方式注册组件,用在@Bean标注的方法参数位置。默认不写@Autowired效果是一样的;都能自动装配 (参数从容器中获取);
*
* 4)、自定义组件想要使用Spring容器底层的一些组件(ApplicationContext,BeanFactory,xxx),需要实现一些接口。
* 总接口为Aware,它有很多子接口,我们自定义组件只需要实现实现xxxAware子接口。在创建对象的时候,
* 会调用接口规定的方法注入相关组件,把Spring底层一些组件注入到自定义的Bean中。
* xxxAware:功能使用xxxProcessor;
* ApplicationContextAware==》ApplicationContextAwareProcessor;
*
*
* @author lfy
*
*/
@Configuration
@ComponentScan({"com.atguigu.service","com.atguigu.dao","com.atguigu.controller"})
public class MainConifgOfAutowired {
}
控制层:
package com.atguigu.controller;
import com.atguigu.service.BookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
@Controller
public class BookController {
@Autowired
private BookService bookService;
}
业务层:
package com.atguigu.service;
import com.atguigu.dao.BookDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class BookService {
@Autowired
private BookDao bookDao;
public void print(){
System.out.println(bookDao);
}
@Override
public String toString() {
return "BookService [bookDao=" + bookDao + "]";
}
}
数据层:
package com.atguigu.dao;
import org.springframework.stereotype.Repository;
//名字默认是类名首字母小写
@Repository
public class BookDao {
}
测试类:
package com.atguigu.test;
import com.atguigu.config.MainConifgOfAutowired;
import com.atguigu.dao.BookDao;
import com.atguigu.service.BookService;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class IOCTest_Autowired {
@Test
public void test01(){
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConifgOfAutowired.class);
//获取业务层注入dao层的Bean
BookService bookService = applicationContext.getBean(BookService.class);
System.out.println(bookService);//输出对象的引用,相当于调用toStrin方法
//获取dao层的Bean
BookDao bean = applicationContext.getBean(BookDao.class);
System.out.println(bean);
applicationContext.close();
}
}
5.1.2 类型相同,按照属性名注入(情况2)
修改配置类:
@Configuration
@ComponentScan({"com.atguigu.service","com.atguigu.dao","com.atguigu.controller"})
public class MainConifgOfAutowired {
//再添加一个相同类型的BookDao,测试相同类型的属性如何赋值
// BookDao bookDao2
@Bean("bookDao2")
public BookDao bookDao(){
BookDao bookDao = new BookDao();
bookDao.setLable("2");
return bookDao;
}
}
修改数据层:为了方便测试。
package com.atguigu.dao;
import org.springframework.stereotype.Repository;
//名字默认是类名首字母小写
@Repository //BookDao bookDao
public class BookDao {
private String lable = "1";
public String getLable() {
return lable;
}
public void setLable(String lable) {
this.lable = lable;
}
@Override
public String toString() {
return "BookDao [lable=" + lable + "]";
}
}
测试类:
说明:
- 现在有2个对象BookDao bookDao2、BookDao bookDao,类型相同名字不同。
业务层
写的是BookDao bookDao,所以注入的是bookDao。
业务层
修改为BookDao bookDao2,所以注入的是bookDao2。
5.1.3 类型相同,@Qualifier指定属性名(情况3)
说明:
- 现在有2个对象BookDao bookDao2、BookDao bookDao,类型相同名字不同。
- 业务层写的是BookDao bookDao2,本来是类型相同根据名字注入booDao2,但现在使用了@Qualifier注解,在类型相同的情况下注入的是注解中标识的对象名。
修改业务层:
测试类:
5.1.4 @Autowired(required=false)(情况4)
说明:
- 自动装配默认一定要将属性赋值好,没有就会报错,可以修改为@Autowired(required=false),能找到就找到,找不到就拉到,属性注册好后即便没有赋值,运行时也不会报错
- 即:使用@Autowired注解注入属性时,一定要注入成功,否则运行时会报错。
可以修改为@Autowired(required=false),这样即便没有注入成功,运行时也不会报错。默认为true。
修改数据层,配置类:把组件(对象)注释掉
默认情况下:@Autowired(required=true),属性注册好后一定要赋好值,否则运行时报错
修改为:@Autowired(required=false),能找到就找到,找不到就拉到,属性注册好后即便没有赋值,运行时也不会报错。
5.1.5 类型相同,@Primary指定首选Bean(情况5)
说明:
组件相同的类型有多个时
,使用@Qualifier指定太麻烦
,此时就可以使用@Primary让Spring进行自动装配的时候,默认使用首选的bean
。前提是
不能使用@Qualifier,使用@Qualifier明确指定后即便加的有@Primary注解也不生效。
修改配置类:
修改业务层:把@Qualifier注释掉
此时有2个对象:BookDao bookDao2、BookDao bookDao,优先使用注解标识的bookDao2。
前提是不能使用@Qualifier,使用@Qualifier明确指定后即便加的有@Primary注解也不生效。
5.2 方式二:@Resource和@Inject
说明:
- Spring还支持使用
@Resource(JSR250)
和@Inject(JSR330)
java规范提供的注解
5.2.1 @Resource (JSR250)
说明:
- 可以和@Autowired一样实现自动装配功能,默认是按照组件名称进行装配的;
- 没有能支持@Primary功能没有支持@Autowired(reqiured=false);
- 即:不能和@Primary连用,也不支持false属性。
修改业务层:
package com.atguigu.service;
import com.atguigu.dao.BookDao;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@Service
public class BookService {
//@Qualifier("bookDao")
//@Autowired(required = false)
//@Resource 默认是按照组件名称进行装配的
@Resource(name="bookDao2") //也可以自己指定组件名
private BookDao bookDao;
public void print(){
System.out.println(bookDao);
}
@Override
public String toString() {
return "BookService [bookDao=" + bookDao + "]";
}
}
测试:
5.2.2 @Inject (JSR330)
说明:需要导入javax.inject
的包,和Autowired的功能一样
。没有required=false
的功能;
添加依赖:
<!-- https://mvnrepository.com/artifact/javax.inject/javax.inject -->
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<version>1</version>
</dependency>
修改业务层:
测试:
5.3 方式一:@Autowired(其它位置上)
说明:之前方式一@Autowired注解使用在属性上,此外还能放在构造器,参数,方法上使用。
@Autowired:构造器,参数,方法,属性;都是从容器中获取参数组件的值:
[标注在方法上]
:参数从容器中获取。[标在构造器上]
:如果组件只有一个有参构造器,这个有参构造器的@Autowired可以省略,参数位置的组件还是可以自动从容器中获取放在参数位置
:构造方法,set方法都可以- 用在配置类@Bean的方式注册组件的方法参数上,默认@Autowired可以省略。
5.3.1 情况1:标注在普通方法上
配置类:添加包扫描
实体类:
package com.atguigu.bean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
//默认加在ioc容器中的组件,容器启动会调用无参构造器创建对象,再进行初始化赋值等操作
@Component
public class Boss {
private Car car;
public Car getCar() {
return car;
}
@Autowired
//标注在方法,Spring容器创建当前对象,就会调用方法,完成赋值;
//方法使用的参数,自定义类型的值从ioc容器中获取
public void setCar(Car car) {//写在普通方法上也一样
this.car = car;
}
@Override
public String toString() {
return "Boss [car=" + car + "]";
}
}
package com.atguigu.bean;
import org.springframework.stereotype.Component;
@Component
public class Car {
public Car(){
System.out.println("car constructor...");
}
public void init(){
System.out.println("car ... init...");
}
public void detory(){
System.out.println("car ... detory...");
}
}
测试类:
package com.atguigu.test;
import com.atguigu.bean.Boss;
import com.atguigu.bean.Car;
import com.atguigu.bean.Color;
import com.atguigu.config.MainConifgOfAutowired;
import com.atguigu.service.BookService;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class IOCTest_Autowired {
@Test
public void test01(){
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConifgOfAutowired.class);
BookService bookService = applicationContext.getBean(BookService.class);
System.out.println(bookService);
// BookDao bean = applicationContext.getBean(BookDao.class);
// System.out.println(bean);
Boss boss = applicationContext.getBean(Boss.class);
System.out.println(boss);//获取BOSS类中通过方法注入的car
Car car = applicationContext.getBean(Car.class);
System.out.println(car);//获取容器中的组件car,发现是同一个
applicationContext.close();
}
}
5.3.2 情况2:标注在构造方法上
修改实体类:添加有参构造,并把情况一注释掉。
package com.atguigu.bean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
//默认加在ioc容器中的组件,容器启动会调用无参构造器创建对象,再进行初始化赋值等操作
@Component
public class Boss {
private Car car;
//构造器要用的组件,都是从容器中获取
@Autowired //如果组件只有一个有参构造器,这个有参构造器的@Autowired可以省略,参数位置的组件还是可以自动从容器中获取
public Boss(Car car){
this.car = car;
System.out.println("Boss...有参构造器");
}
public Car getCar() {
return car;
}
//@Autowired
//标注在方法,Spring容器创建当前对象,就会调用方法,完成赋值;
//方法使用的参数,自定义类型的值从ioc容器中获取
public void setCar(Car car) {
this.car = car;
}
@Override
public String toString() {
return "Boss [car=" + car + "]";
}
}
测试1:
测试2:
- 如果组件只有一个有参构造器,这个有参构造器的@Autowired可以省略,参数位置的组件还是可以自动从容器中获取。
- 构造方法上,构造方法的参数上,标注的@Autowired都可以省。
5.3.3 情况3:标注在方法的参数位置
说明:构造方法,普通方法,set get方法都可以标注到参数位置。
修改实体类:
package com.atguigu.bean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
//默认加在ioc容器中的组件,容器启动会调用无参构造器创建对象,再进行初始化赋值等操作
@Component
public class Boss {
private Car car;
//构造器要用的组件,都是从容器中获取
//如果组件只有一个有参构造器,这个有参构造器的@Autowired可以省略,参数位置的组件还是可以自动从容器中获取
// 这个省略指的是构造方法上的和参数上的都可以省。
public Boss(@Autowired Car car){
this.car = car;
System.out.println("Boss...有参构造器");
}
public Car getCar() {
return car;
}
//@Autowired
//标注在方法,Spring容器创建当前对象,就会调用方法,完成赋值;
//方法使用的参数,自定义类型的值从ioc容器中获取
public void setCar( Car car) {
this.car = car;
}
@Override
public String toString() {
return "Boss [car=" + car + "]";
}
}
测试类:
5.3.4 情况4:@Bean标注的方法参数位置
添加实体类:
package com.atguigu.bean;
public class Color {
private Car car;
public Car getCar() {
return car;
}
public void setCar(Car car) {
this.car = car;
}
@Override
public String toString() {
return "Color [car=" + car + "]";
}
}
修改配置类:
/**
* @Bean标注的方法创建对象的时候,方法参数的值从容器中获取
* @param car
* @return
*/
@Bean
public Color color(@Autowired Car car){ //@Autowired可以省略
Color color = new Color();
color.setCar(car);
return color;
}
测试类:
package com.atguigu.test;
import com.atguigu.bean.Boss;
import com.atguigu.bean.Car;
import com.atguigu.bean.Color;
import com.atguigu.config.MainConifgOfAutowired;
import com.atguigu.service.BookService;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class IOCTest_Autowired {
@Test
public void test01(){
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConifgOfAutowired.class);
BookService bookService = applicationContext.getBean(BookService.class);
System.out.println(bookService);
// BookDao bean = applicationContext.getBean(BookDao.class);
// System.out.println(bean);
Boss boss = applicationContext.getBean(Boss.class);
System.out.println(boss);
Car car = applicationContext.getBean(Car.class);
System.out.println(car);
Color color = applicationContext.getBean(Color.class);
System.out.println(color);
System.out.println(applicationContext);
applicationContext.close();
}
}
5.4 方式三:Aware注入Spring容器底层组件
说明:
- 自定义组件想要使用Spring容器底层的一些组件
(ApplicationContext,BeanFactory,xxx)
,需要实现对应的接口,总接口为Aware,它有很多子接口
。 - 我们自定义组件只需要实现实现xxxAware子接口。在创建对象的时候,
会调用接口规定的方法注入相关组件,把Spring底层一些组件注入到自定义的Bean中。
编写实体类:
eg:
- ApplicationContextAware:帮我们自动注入IOC容器
- BeanNameAware:组件名字
- EmbeddedValueResolverAware:值解析器
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 {
// TODO Auto-generated method stub
System.out.println("传入的ioc:"+applicationContext);
this.applicationContext = applicationContext;
}
@Override
public void setBeanName(String name) {
// TODO Auto-generated method stub
System.out.println("当前bean的名字:"+name);
}
@Override
public void setEmbeddedValueResolver(StringValueResolver resolver) {
// TODO Auto-generated method stub
String resolveStringValue = resolver.resolveStringValue("你好 ${os.name} 我是 #{20*18}");
System.out.println("解析的字符串:"+resolveStringValue);
}
}
测试类:
package com.atguigu.test;
import com.atguigu.bean.Boss;
import com.atguigu.bean.Car;
import com.atguigu.bean.Color;
import com.atguigu.config.MainConifgOfAutowired;
import com.atguigu.service.BookService;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class IOCTest_Autowired {
@Test
public void test01(){
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConifgOfAutowired.class);
BookService bookService = applicationContext.getBean(BookService.class);
System.out.println(bookService);
//BookDao bean = applicationContext.getBean(BookDao.class);
//System.out.println(bean);
Boss boss = applicationContext.getBean(Boss.class);
System.out.println(boss);
Car car = applicationContext.getBean(Car.class);
System.out.println(car);
Color color = applicationContext.getBean(Color.class);
System.out.println(color);
System.out.println(applicationContext);
applicationContext.close();
}
}
5.5 @Profil根据环境注入bean
5.5.1 @Profil环境搭建(没有加环境标识)
添加依赖:
<!-- 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>
编写配置类:没有用环境标识@Profile的bean
package com.atguigu.config;
import com.atguigu.bean.Yellow;
import com.mchange.v2.c3p0.ComboPooledDataSource;
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 javax.sql.DataSource;
/**
* Profile:
* Spring为我们提供的可以根据当前环境,动态的激活和切换一系列组件的功能;
*
* 开发环境、测试环境、生产环境;
* 数据源:(/A)(/B)(/C);
*
*
* @Profile:指定组件在哪个环境的情况下才能被注册到容器中,不指定,任何环境下都能注册这个组件
*
* 1)、加了环境标识的bean,只有这个环境被激活的时候才能注册到容器中。默认是default环境@Profile("default")=@Profile()
* 2)、写在配置类上,只有是指定的环境的时候,整个配置类里面的所有配置才能开始生效
* 3)、没有标注环境标识的bean,在任何环境下都是加载的;
*/
@PropertySource("classpath:/dbconfig.properties")
@Configuration
public class MainConfigOfProfile implements EmbeddedValueResolverAware{
@Value("${db.user}") //获取配置文件的值,方式一:直接加在属性上
private String user;
private StringValueResolver valueResolver;
//获取配置文件的值,方式三:继承值解析器的接口EmbeddedValueResolverAware来获取
private String driverClass;
@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;
}
@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;
}
@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;
}
//获取配置文件的值,方式三:继承值解析器的接口来获取
@Override
public void setEmbeddedValueResolver(StringValueResolver resolver) {
// TODO Auto-generated method stub
this.valueResolver = resolver;
driverClass = valueResolver.resolveStringValue("${db.driverClass}");
}
}
配置文件:
db.user=root
db.password=root
db.driverClass=com.mysql.jdbc.Driver
测试类:没有使用@Profile注解进行环境标识Bean,此时有三个数据源都可以加载。
package com.atguigu.test;
import com.atguigu.config.MainConfigOfProfile;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import javax.sql.DataSource;
public class IOCTest_Profile {
@Test
public void test01(){
AnnotationConfigApplicationContext applicationContext =
new AnnotationConfigApplicationContext(MainConfigOfProfile.class);
String[] namesForType = applicationContext.getBeanNamesForType(DataSource.class);
for (String string : namesForType) {
System.out.println(string);
}
applicationContext.close();
}
}
5.5.2 测试:加了环境标识Bean的情况(@value)
说明:
- @Profil:Spring为我们提供的可以根据当前环境,动态的激活和切换一系列组件的功能;
- 没有标注环境标识的bean在,任何环境下都是加载的;
5.5.2.1 加环境标识bean,没有激活
说明
:加了环境标识的bean,只有这个环境被激活的时候才能注册到容器中,如果没有激活,那么一个Bean也不会被加载。
package com.atguigu.config;
import com.atguigu.bean.Yellow;
import com.mchange.v2.c3p0.ComboPooledDataSource;
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 javax.sql.DataSource;
/**
* Profile:
* Spring为我们提供的可以根据当前环境,动态的激活和切换一系列组件的功能;
*
* 开发环境、测试环境、生产环境;
* 数据源:(/A)(/B)(/C);
*
*
* @Profile:指定组件在哪个环境的情况下才能被注册到容器中,不指定,任何环境下都能注册这个组件
*
* 1)、加了环境标识的bean,只有这个环境被激活的时候才能注册到容器中。默认是default环境@Profile("default")=@Profile()
* 2)、写在配置类上,只有是指定的环境的时候,整个配置类里面的所有配置才能开始生效
* 3)、没有标注环境标识的bean在,任何环境下都是加载的;
*/
@PropertySource("classpath:/dbconfig.properties")
@Configuration
public class MainConfigOfProfile implements EmbeddedValueResolverAware{
@Value("${db.user}") //获取配置文件的值,方式一:直接加在属性上
private String user;
private StringValueResolver valueResolver;
//获取配置文件的值,方式三:继承值解析器的接口EmbeddedValueResolverAware来获取
private String driverClass;
//数据源1:测试
@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;
}
//数据源2:开发
@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;
}
//数据源3:生产
@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;
}
//获取配置文件的值,方式三:继承值解析器的接口来获取
@Override
public void setEmbeddedValueResolver(StringValueResolver resolver) {
// TODO Auto-generated method stub
this.valueResolver = resolver;
driverClass = valueResolver.resolveStringValue("${db.driverClass}");
}
}
此时没有激活,可以看出一个数据源也没有。
5.5.2.2 使用默认环境标识,默认激活
说明:啥也不写,或指定@Profile(“default”),表示使用默认的环境。
5.5.2.3 使用命令行参数激活环境
说明:使用命令行动态参数: 在虚拟机参数位置加载 -Dspring.profiles.active=test
添加组件:
//此时就有了2个test环境的组件。
@Profile("test")
@Bean
public Yellow yellow(){
return new Yellow();
}
测试方法打印的只是数据源的名字,所以yellow不显示。
5.5.2.4 代码的方式激活某种环境
说明:使用的是无参构造器,如果使用有参构造器会加载配置类启动环境,而配置类的环境还没设置好。
package com.atguigu.test;
import com.atguigu.config.MainConfigOfProfile;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import javax.sql.DataSource;
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);
}
applicationContext.close();
}
}
5.5.2.5 @Profile写在配置类上
说明:还可以写在配置类上,只有是指定的环境的时候,整个配置类里面的所有配置
才能开始生效
类上使用test环境,激活的是dev,所以类里面的配置都不生效。
切换为test环境,添加输出yellow Bean的代码。
Yellow yellow = applicationContext.getBean(Yellow.class);//获取yellow的bean
System.out.println(yellow);
5.6 总结:注解注入属性的方式(4种)
- @Value:(注入普通属性)
- 写法1:基本数值
- 写法2:可以写SpEL; #{} Spring提供的EL表达式
- 写法3:可以写${};取出配置文件【properties】中的值(在运行环境变量里面的值),需要在配置类中使用注解@PropertySource 引入外部属性文件
- @Autowired:默认按照类型注入,类型相同按照名称注入。(注入对象属性)
- 用于:属性,方法,构造方法,参数,和@Qualifier注解搭配使用
- @Qualifier:和@Autowired搭配使用,如果类型相同,可以使用此注解指定属性名称注入。
- 有false功能,能和@Primary连用。
- Spring提供的注解。
- @Resource (JSR250)+ @Inject (JSR330):(注入对象属性)
- @Resource (JSR250):
- 用在:属性上、setter方法上
- 默认按照属性名注入,属性名相同按照类型注入。
- 可以直接在注解中指定名称,按照指定的名称注入
- 没有false功能,不能和@Primary连用。
- @Inject (JSR330):
- 用法和 @Autowired相同,只不过没有false功能,可以和@Primary连用。
- 这2个注解都是 java提供的规范
- @Resource (JSR250):
- 实现Aware的子接口,可以注入Spring容器底层组件。(注入对象属性)
三、Aop功能测试
https://www.bilibili.com/video/BV1gW411W7wy?p=27