自己实现类似于spring IOC的功能(以后再深入单例与多例对象,解耦),通过工厂方式调用资源,容器的概念,以及使用spring管理容器,spring容器管理的细节,创建bean的三种方式,bean对象的生命周期,依赖注入的三种方式
实现类似于spring IOC的功能
- spring IOC:控制反转,实际上就是为了解耦合,
在开发中,通常通过service层调用dao层,业务层调用持久层,并且此时业务层在依赖持久层的接口和实现类。如果此时没有持久层实现类,编译将不能通过。这种编译期依赖关系,应该在我们开发中杜绝。我们需要优化代码解决。
实际上就是减少用new来创建对象 - 当是我们使用jdbc 时,是通过反射来注册驱动的 :
Class.forName(“com.mysql.jdbc.Driver”);//此处只是一个字符串
这时候如果数据库改用Oracle了,只需改掉该字符串, - 同时,也产生了一个新的问题,mysql 驱动的全限定类名字符串是在 java 类中写死的,一旦要改还是要修改源码。
- 解决这个问题也很简单,使用配置文件配置。
在resource包中创建一个文件bean.properties,
accountService=com.service.IAccountService
accountDao=com.dao.IAccountDao
配置文件中存储形式为key,value的形式,前为名称,后为全限定类名
- 在实际开发中我们可以把三层的对象都使用配置文件配置起来,当启动服务器应用加载的时候,让一个类中的方法通过读取配置文件,把这些对象创建出来并存起来。在接下来的使用的时候,直接拿过来用就好了。
这其实就是工厂模式,这个读取配置文件,创建和获取三层对象的类(BeanFactory)就是工厂。
不直接new对象,而是通过工厂提供资源。
public class BeanFactory {
//用于读取配置文件
public static Properties pro;
//定义一个map,用于存放我们要创建的对象,称之为容器
public static Map<String,Object> beans;
static{
try {
pro = new Properties();
//通过类加载器加载配置文件中的流
InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
pro.load(in);
//实例化容器
beans = new HashMap<String, Object>();
//取出配置文件中所有的key
Enumeration keys = pro.keys();
while(keys.hasMoreElements()){
//取出每个key
String key = keys.nextElement().toString();
//根据key获取value
String beanPath = pro.getProperty(key);
//反射创建对象
Object value = Class.forName(beanPath).newInstance();
beans.put(key,value);
}
}catch (Exception e){
throw new ExceptionInInitializerError("初始化bean.properties失败!");
}
}
public static Object getBean(String beanName){
return beans.get(beanName);
}
}
现在通过工厂就可以获得accountDao和accountService对象了。
/**
* 模拟一个表现层,用于调用业务层
*/
public class Client {
public static void main(String[] args) {
//IAccountService as = new AccountServiceImpl();
for(int i=0;i<5;i++) {
IAccountService as = (IAccountService) BeanFactory.getBean("accountService");
System.out.println(as);
as.saveAccount();
}
}
}
所以在测试类中,不用通过new来获得accountService的实现类了。
使用spring管理容器
基于xml的配置
通过maven创建工程,在pom.xml中引入spring依赖
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
</dependencies>
然后创建bean.xml,这次通过spring管理容器
首先引入xsd约束,然后将对象注入容器
<?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="accountService" class="com.service.impl.AccountServiceImpl"></bean>
<bean id="accountDao" class="com.dao.impl.AccountDaoImpl"></bean>
</beans>
public class Client {
/**
* 获取spring容器的ioc容器,并根据id获取对象
* @param args
*/
public static void main(String[] args) {
//获取核心容器对象
ApplicationContext app = new ClassPathXmlApplicationContext("bean.xml");
//根据id获取bean对象
IAccountService ias = (IAccountService)app.getBean("accountService");
IAccountDao iad = app.getBean("accountDao",IAccountDao.class);
System.out.println(ias);
System.out.println(iad);
}
}
此时就可以得到accountDao和accountService对象了,以上使用两种方法获取。
spring对bean的管理细节
创建Bean的三种方式
- 第一种方式:使用默认构造函数创建。
在spring的配置文件中使用bean标签,配以id和class属性之后,且没有其他属性和标签时。
采用的就是默认构造函数创建bean对象,此时如果类中没有默认构造函数,则对象无法创建。
<bean id="accountService" class="com.service.impl.AccountServiceImpl" scope="singleton" init-method="init" destroy-method="destroy"></bean>
其中scope="singleton"指单例对象,多例则指定为prototype,init-method=“init” destroy-method="destroy"则指定从bean.xml获取到的AccountServiceImpl 类中的init()和destory()方法
public class AccountServiceImpl implements IAccountService {
//使用spring的第一种使用默认构造函数构造bean的方法,因为重写了构造函数,所以没有默认构造,报错
// public AccountServiceImpl(String name){Failed to instantiate [com.service.impl.AccountServiceImpl]: No default constructor found;
// public AccountServiceImpl(String name){
// System.out.println("对象创建了");
// }
//默认构造函数
public AccountServiceImpl(){
System.out.println("对象创建了");
}
public void saveAccount(){
System.out.println("service中的saveAccOunt执行了");
}
public void init(){
System.out.println("对象初始化了。。。");
}
public void destroy(){
System.out.println("对象销毁了。。。");
}
}
- 第二种方式: 使用普通工厂中的方法创建对象(使用某个类中的方法创建对象,并存入spring容器)
<bean id="instanceFactory" class="com.factory.InstanceFactory"></bean>
<bean id="accountService" factory-bean="instanceFactory" factory-method="getAccountService"></bean>
/**
* 模拟一个工厂类(该类可能是存在于jar包中的,我们无法通过修改源码的方式来提供默认构造函数)
*/
public class InstanceFactory {
public IAccountService getAccountService(){
return new AccountServiceImpl();
}
}
- 第三种方式:使用工厂中的静态方法创建对象(使用某个类中的静态方法创建对象,并存入spring容器)
<bean id="accountService" class="com.factory.StaticFactory" factory-method="getAccountService"></bean>
/**
* 模拟一个工厂类(该类可能是存在于jar包中的,我们无法通过修改源码的方式来提供默认构造函数)
*/
public class StaticFactory {
public static IAccountService getAccountService(){
return new AccountServiceImpl();
}
}
spring的依赖注入
Dependency Injection
IOC的作用:
降低程序间的耦合(依赖关系)
依赖关系的管理:
以后都交给spring来维护
在当前类需要用到其他类的对象,由spring为我们提供,我们只需要在配置文件中说明
依赖关系的维护:
就称之为依赖注入。
注入的方式:有三种
-
第一种:使用构造函数提供
构造函数注入:
使用的标签:constructor-arg
标签出现的位置:bean标签的内部
标签中的属性
type:用于指定要注入的数据的数据类型,该数据类型也是构造函数中某个或某些参数的类型
index:用于指定要注入的数据给构造函数中指定索引位置的参数赋值。索引的位置是从0开始
name:用于指定给构造函数中指定名称的参数赋值 常用的
以上三个用于指定给构造函数中哪个参数赋值
value:用于提供基本类型和String类型的数据
ref:用于指定其他的bean类型数据。它指的就是在spring的Ioc核心容器中出现过的bean对象优势: 在获取bean对象时,注入数据是必须的操作,否则对象无法创建成功。 弊端: 改变了bean对象的实例化方式,使我们在创建对象时,如果用不到这些数据,也必须提供。
能注入的数据:有三类
基本类型和String
其他bean类型(在配置文件中或者注解配置过的bean)
复杂类型/集合类型
<bean id="accountService" class="com.service.impl.AccountServiceImpl">
<constructor-arg name="age" value="18"></constructor-arg>
<constructor-arg name="name" value="lin"></constructor-arg>
<constructor-arg name="birthday" ref="1997-1-1"></constructor-arg>
</bean>
日期注入就要用在配置文件配置过的bean来注入
<!-- 读取全限定类名,通过反射创建一个对象,并且存入spring容器中,可通过id将其取出 -->
<bean id="now" class="java.util.Date"></bean>
- 第二种:使用set方法提供
涉及的标签:property
出现的位置:bean标签的内部
标签的属性
name:用于指定注入时所调用的set方法名称
value:用于提供基本类型和String类型的数据
ref:用于指定其他的bean类型数据。它指的就是在spring的Ioc核心容器中出现过的bean对象
优势:
创建对象时没有明确的限制,可以直接使用默认构造函数
弊端:
如果有某个成员必须有值,则获取对象是有可能set方法没有执行。
set方法就要在class属性中的类中生成set方法
public class AccountServiceImpl implements IAccountService {
//如果是经常变化的数据,并不适用于注入的方式
private String name;
private Integer age;
private Date birthday;
//set方法注入
public void setName(String name) {
this.name = name;
}
public void setAge(Integer age) {
this.age = age;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
public void saveAccount(){
System.out.println("service中的saveAccount方法执行了。。。"+name+" "+age+" "+birthday);
}
}
<bean id="accountService" class="com.service.impl.AccountServiceImpl">
<property name="age" value="18"></property>
<property name="birthday" ref="now"></property>
<property name="name" value="test"></property>
</bean>
也可以用set方法注入复杂类型(list,map等)
- 复杂类型的注入/集合类型的注入
用于给List结构集合注入的标签:
list array set
用于个Map结构集合注入的标签:
map props
结构相同(指key,value结构),标签可以互换
public class AccountServiceImpl2 implements IAccountService {
private String[] myStrs;
private List<String> myList;
private Set<String> mySet;
private Map<String,String> myMap;
private Properties properties;
public void setMyStrs(String[] myStrs) {
this.myStrs = myStrs;
}
public void setMyList(List<String> myList) {
this.myList = myList;
}
public void setMySet(Set<String> mySet) {
this.mySet = mySet;
}
public void setMyMap(Map<String, String> myMap) {
this.myMap = myMap;
}
public void setProperties(Properties properties) {
this.properties = properties;
}
public void saveAccount(){
System.out.println(Arrays.toString(myStrs));
System.out.println(myList);
System.out.println(myMap);
System.out.println(mySet);
System.out.println(properties);
}
}
<bean id="accountService2" class="com.itheima.service.impl.AccountServiceImpl2">
<property name="myStrs">
<array>
<value>AAA</value>
<value>CCC</value>
<value>BBB</value>
</array>
</property>
<property name="myMap">
<map>
<entry key="test1" value="aaa"></entry>
<entry key="test2" value="bbb"></entry>
</map>
</property>
<property name="myList">
<list>
<value>AAA</value>
<value>CCC</value>
<value>BBB</value>
</list>
</property>
</bean>
- 第三种:使用注解提供
基于注解形式创建
见第二天