- 引入依赖
- 创建类和方法
- 编写配置文件
4.测试
Spring是如何创建对象的呢?原理是什么?
// dom4j解析beans.xml文件,从中获取class属性值,类的全类名
// 通过反射机制调用无参数构造方法创建对象
Class clazz = Class.forName("com.atguigu.spring6.bean.HelloWorld");
//Object obj = clazz.newInstance();
Object object = clazz.getDeclaredConstructor().newInstance();
把创建好的对象存储到一个什么样的数据结构当中了呢?
bean对象最终存储在spring容器中,在spring源码底层就是一个map集合,存储bean的map在DefaultListableBeanFactory类中:
private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);
Spring容器加载到Bean类时 , 会把这个类的描述信息, 以包名加类名的方式存到beanDefinitionMap 中,
Map<String,BeanDefinition> , 其中 String是Key , 默认是类名首字母小写 , BeanDefinition , 存的是类的定义(描述信息) , 我们通常叫BeanDefinition接口为 : bean的定义对象。
IOC原理
- xml解析、工厂模式、反射
- 画图
耦合度高
耦合度有待降低
IOC过程
- 第一步xml配置文件
<bean id="dao" class="com.happywei.UserDao"></bean>
- 有service类和dao类,创建工厂对象
class UserFactory{
public static UserDao getDao(){
//1. 读取配置文件,获取类的全类名
String classValue = class属性值;
Class clazz = Class.forName(classValue);//2. 反射创建对象
return (UserDao)clazz.newInstance();
}
}
IOC(接口)
- IOC思想基于 IOC容器完成,IOC容器底层就是对象工厂
- Spring 提供 IOC 容器实现两种方式::(两个接口)
- BeanFactory:IOC容器基本实现,是 Spring 内部的使用接口,不提供开发人员进行使用、加载配置文件时候不会创建对象,在获取对象(使用)才去创建对象
- ApplicationContext:BeanFactor,接口的子接口,提供更多更强大的功能,一般由开发人员进行使用,加载配置文件时候就会把在配置文件对象进行创建
Bean管理
什么是 Bean 管理
Bean 管理指的是两个操作:1.Spring 创建对象;2. spirng 注入属性
Bean 管理操作有两种方式
- 基于 xml 配置文件方式实现
- 基于注解方式实现
基于xml方式注入属性
(1)DI:依赖注入,就是注入属性
注入属性的几种方式
- 通过set方法
- 通过有参构造
- 通过xml
set方式注入
import com.atguigu.spring5
class Book{
private String bname;
private String bauthor;
}
有参构造注入
import com.atguigu.spring5
class Orders{
private String oname;
private String address;
}
特殊类型注入
对象类型注入
比如员工和部门的关系,一个部门有多个员工,部门应是员工的一个属性。
员工类有部门、姓名、年龄等属性;部门有名字等属性。
引用外部bean
注意value变成了ref
引用内部bean
数组类型注入
List集合注入
<bean id="clazzTwo" class="com.atguigu.spring6.bean.Clazz">
<property name="clazzId" value="4444"></property>
<property name="clazzName" value="Javaee0222"></property>
<property name="students">
<list>
<ref bean="studentOne"></ref>
<ref bean="studentTwo"></ref>
<ref bean="studentThree"></ref>
</list>
</property>
</bean>
Bean生命周期
基于XML管理Bean-自动装配
在MVC中,Control类包含Service属性,Service类包含Dao属性,想实现注入得创建set方法。然后去xml文件配置
<bean id="userController" class="com.happywei.controller.UserController"
autowire="byType">
</bean>
<bean id="userService" class="com.happywei.service.UserServiceImpl"
autowire="byType">
</bean>
<bean id="userDao" class="com.happywei.dao.UserDaoImpl"
autowire="byType">
</bean>
<!--注意,如果是通过名字注入,id则需要和类里定义的名字一致;
比如修改“userDao“为“userDaoImpl”,在userService那里的注入就会报错-->
基于注解管理Bean
Spring实现注解的自动装配如下
- 引入依赖
- 开启组件扫描
<context:component-scan base-package="com.happywei"></context:component-scan>
- 使用注解定义Bean
- 依赖注入
开启组件扫描后,去对应的包下面的类加上注解@Component
,在属性上面加注解@Autowried
实现注入。
@Autowired注入
- 通过属性注入
在Controller、Service、Dao层注入属性。创建对应的接口和对象,在属性上面加@Autowired
- set方法注入
在set方法上加@Autowired
- 构造方法注入
声明属性,再写一个包含这个属性的有参构造,在构造方法上面加@Autowired
- 形参注入
和第3点一样,但@Autowired
写在参数前面而不是方法名上面 - 只有一个有参树构造函数,无注解
只能有一个构造函数才能这样写 - @Autowired和@Qualifier联合
注入的时候是类型注入,写的是接口(UserDao、UserService,而非UserDaoImpl),但如果接口UserDao有两个实现类,那么@Autowired
类型注入就会报错。此时只能用名称注入。
假设UserDao有两个实现类,一个UserDaoImpl、一个UserRedisDaoImpl。可以在声明属性UserDao上面加@Autowired
和@Qualifier(value = "userRedisDaoImpl")//默认实现类名首字母小写
@Resource注入
- @Resource注解默认根据名称装配byName,未指定name时,使用属性名作为name。通过name找不到的话会自动启动通过类型byType装配。
- @Autowired注解默认根据类型装配byType,如果想根据名称装配,需要配合@Qualifier注解一起用。
- @Resource注解用在属性上、setter方法上。
- @Autowired注解用在属性上、setter方法上、构造方法上、构造方法参数上。
1、根据name注入
Controller注入Service,在Service实现类上加@Service(value = "myUserService")
,在Controller类里面声明service属性,在属性上面加@Resource(name = "myUserService")
2、不指定名字,根据属性名称注入
例如在Service里注入Dao,@Resource
后面不加name,但是默认值是属性的名称。即myUserDao,它回根据myUserDao去找
3、既没有指定名字,也没有找到匹配
按类型注入
全注解开发
写一个配置类,在配置类上加@Configuration
和@ComponentScan("com.happywei.spring6")
这样就代替了xml.所以在测试类里面不用读取xml文件。而是读取配置类通过
ApplicationContext context = new AnnotationConfigApplicationContext(Spring6Config.class);
手写Ioc
实现过程
创建测试类
我们可以通过在实现类添加注解@Service和@Repository,在属性添加@Autowired实现对象的创建和注入。
但现在我们要自己写Bean对象的创建和注入的注解。
创建注解
创建bean容器接口
在Ioc里面用到对象BeanFactory,是基于工厂模式+反射创建对象;而我们用到的是它的子接口;ApplicationContext,这个子接口有更多的实现类,针对不同场景做不同功能。我们也效仿这些功能。(即BeanFactory和它的子接口)
BeanFactory用的是工厂,工厂返回对象,所以接口应该有返回对象的方法。
public interface ApplicationContext {
Object getBean(Class clazz);
}
public class AnnotationApplicationContext implements ApplicationContext{
@Override
public Object getBean(Class clazz) {
return null;
}
}
spring里面Ioc创建对象后会放入map集合,我们也创建一个。
在实际使用中,我们会先创建ApplicationContext对象,传入需要扫描的包路径。所以AnnotationApplicationContext 的有参构造接收的参数是String类型。此时拿到路径后,要做的是把路径下包含@Bean注解的类全部实例化,并且存入map容器中。再实现注入。即完成两件事,实例化和注入;
实例化通过拿到类的全类名用反射创建。再存入map中,key是类,value是object.
属性注入:拿到map集合里的所有对象,通过获得对象的class对象拿到属性数组,遍历这个数组,如果有@Di这个注解就注入。
遍历map用entryset,对象就在Object obj = entry.getValue()里面。获取class对象并得到属性数组Fild[]
field.set(obj,beanFactory.get(field.getType()))
field.set表示向里面设置值,第一个参数obj表示当前对象设置值,设置什么呢,设置这个属性,field.getType()
是属性的类型,可以是service可以是dao。也就是把beanFactory中的Bean对象通过反射设置给(注入)对象中的属性。