作者即将开启三期关于Spring开发的学习,为了深入理解spring的开发流程。
前言:
1.为什么需要学习Spring的开发呢?有什么主要的作用?
- 是Spring在市场的占有率与使用率高
- Spring可以简化开发,降低企业级开发的复杂性,使开发变得更简单快捷
- Spring可以框架整合,高效整合其他技术,提高企业级应用开发与运行效率,例如Mybatis框架的整合
2.对于Spring的学习应该去学什么?
- Spring的IOC/DI
- Spring的AOP
- AOP的具体应用,事务管理
- IOC/DI的具体应用,整合Mybatis
一:如何用Spring解决项目中耦合度高的问题
业务层需要调用数据层的方法,就需要在业务层new数据层的对象,如果数据层的实现类发生变化,那么业务层的代码也需要跟着改变,发生变更后,都需要进行编译打包和重部署,所以,现在代码在编写的过程中存在的问题是:耦合度偏高
如图所示我们应该如何去解决实现下方创建对象问题?————使用对象时,在程序中不要主动使用new产生对象,转换为由外部提供对象,这种实现思就是Spring的一个核心概念
二:IOC反转(控制反转)
如上面的案例:使用对象时,由主动new产生对象转换为由外部提供对象,此过程中对象创建控制权由程序转移到外部,此思想称为控制反转(IOC)。
问题是Spring是如何实现控制反转的呢?————Spring提供了一个容器,称为IOC容器,用来充当思想中的"外部",负责对象的创建、初始化等一系列工作,其中包含了数据层和业务层的类对象被创建或被管理的对象在IOC容器中统称为Bean,IOC容器中放的就是一个个的Bean对象
上述案例中,当IOC容器中创建好service和dao对象后,程序能正确执行么?————不行,因为service运行需要依赖dao对象,IOC容器中虽然有service和dao对象,但是service对象和dao对象没有任何关系,需要把dao对象交给service,也就是说要绑定service和dao对象之间的关系
如何建立关系呢?(在其他类中新建一个类,如何建立两者关系?)————DI依赖注入(在容器中建立bean与bean之间的依赖关系的整个过程)上述例子如图下建立依赖关系:
三:案例详细介绍使用Spring的IOC过程
1.首先增添spring的jar包(依赖包)pom.xml文件中
<dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.10.RELEASE</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> </dependencies>
2.添加案例中需要的类
3.添加spring配置文件,在配置文件中完成bean的配置
<?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标签标示配置bean id属性标示给bean起名字 class属性表示给bean定义类型 --> <bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl"/> <bean id="bookService" class="com.itheima.service.impl.BookServiceImpl"/> </beans>
注意事项:bean定义时id属性在同一个上下文中(配置文件)不能重复
5.获取IOC容器
使用Spring提供的接口完成IOC容器的创建,创建App类,编写main方法
public class App { public static void main(String[] args) { //获取IOC容器 ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); } }
6.从容器中获取对象进行方法调用
public class App { public static void main(String[] args) { //获取IOC容器 ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); // BookDao bookDao = (BookDao) ctx.getBean("bookDao"); // bookDao.save(); BookService bookService = (BookService) ctx.getBean("bookService"); bookService.save(); } }
总结:不在使用new创建类的对象,直接从Spring管理的IOC容器中获取对象
四:IOC的引入简化代码耦合
在 BookServiceImpl 的类中依然存在 BookDaoImpl 对象的new操作,它们之间的耦合度还是比较高,这块该如何解决,就需要用到下面的 DI:依赖注入 。
1: 去除代码中的new,在BookServiceImpl类中,删除业务层中使用new的方式创建的dao对象
public class BookServiceImpl implements BookService { //删除业务层中使用new的方式创建的dao对象 private BookDao bookDao; public void save() { System.out.println("book service save ..."); bookDao.save(); } }
2:为属性提供setter方法,在BookServiceImpl类中,为BookDao提供setter方法
public class BookServiceImpl implements BookService { //删除业务层中使用new的方式创建的dao对象 private BookDao bookDao; public void save() { System.out.println("book service save ..."); bookDao.save(); } //提供对应的set方法 public void setBookDao(BookDao bookDao) { this.bookDao = bookDao; } }
3:修改配置完成注入,在配置文件中添加依赖注入的配置(建立关系)
<?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标签标示配置bean id属性标示给bean起名字 class属性表示给bean定义类型 --> <bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl"/> <bean id="bookService" class="com.itheima.service.impl.BookServiceImpl"> <!--配置server与dao的关系--> <!--property标签表示配置当前bean的属性 name属性表示配置哪一个具体的属性 ref属性表示参照哪一个bean --> <property name="bookDao" ref="bookDao"/> </bean> </beans>
注意:配置中<property name="bookDao" ref="bookDao"/>的两个bookDao的含义是不一样的
1.name="bookDao"中 bookDao 的作用是让Spring的IOC容器在获取到名称后,将首字母大写,前面加set找对应的 setBookDao() 方法进行对象注入2.ref="bookDao"中 bookDao 的作用是让Spring能在IOC容器中找到id为 bookDao 的Bean对象给bookService 进行注入(如下关系图)
总结:在案例中我们通过set方法再次通过修改xml文件,建立两个Bean之间的关系就是建立了依赖注入,这样我们就可以在其他类中直接创建类对象,在IOC容器中找到这个对象并且使用,而不用去new,极大的降低了代码的耦合性
五:如何配置Bean(建立关系)
前面我们通过DI依赖注入,配置了简单的Bean让两个bean建立了关系方便开发,减低耦合
Bean中的属性介绍:
id属性:就是bean的id,IOC容器可以通过唯一的id来找到这个Bean
class属性:Bean的类型,就是配置Bean的全路径类名
name属性:bean的别名配置,<别名可以有多个,使用逗号,分号,空格进行分隔
根据名称容器中获取bean对象,例如:name="service service4 bookEbi":
//此处根据bean标签的id属性和name属性的任意一个值来获取bean对象 BookService bookService = (BookService) ctx.getBean("service4");
ref属性:bean依赖注入的ref属性指定bean,必须在容器中存在
scope属性:来实现bean的非单例创建(singleton 默认为单例 prototype 为非单例)
六:Bean的实例化方法:
我们已经知道了对象可以由Spring管理的IOC容器创建了,但是我们如何去创建对象呢?
实例化bean的三种方式—— 构造方法 , 静态工厂 和 实例工厂
1.构造方法实例化Bean:
1.创建接口和类:
public interface BookDao { public void save(); } public class BookDaoImpl implements BookDao { public void save() { System.out.println("book dao save ..."); } }
2.配置Spring容器:
<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl"/>
3.编写运行程序:
public class AppForInstanceBook { public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); BookDao bookDao = (BookDao) ctx.getBean("bookDao"); bookDao.save(); } }
4.类中提供构造函数测试:
public class BookDaoImpl implements BookDao { public BookDaoImpl() { System.out.println("book dao constructor is running ...."); } public void save() { System.out.println("book dao save ..."); } }
5:将构造函数改成private测试:
public class BookDaoImpl implements BookDao { private BookDaoImpl() { System.out.println("book dao constructor is running ...."); } public void save() { System.out.println("book dao save ..."); } }
总结:能执行成功,说明内部走的依然是构造函数,能访问到类中的私有构造方法,显而易见
Spring底层用的是反射
2.静态工厂实例化
(1)在spring的配置文件application.properties中添加以下内容:
- class:工厂类的类全名
- factory-mehod:具体工厂类中创建对象的方法名
<bean id="orderDao" class="com.itheima.factory.OrderDaoFactory" factory- method="getOrderDao"/>
(2)在AppForInstanceOrder运行类,使用从IOC容器中获取bean的方法进行运行测试
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); OrderDao orderDao = (OrderDao) ctx.getBean("orderDao"); orderDao.save();
看到这,可能有人会问了,你这种方式在工厂类中不也是直接new对象的,和我自己直接new没什么太大的区别,而且静态工厂的方式反而更复杂,这种方式的意义是什么?————了解为主
3.实例工厂实例化
(1)在spring的配置文件中添加以下内容:
<bean id="userFactory" class="com.itheima.factory.UserDaoFactory"/>
<bean id="userDao" factory-method="getUserDao" factory-bean="userFactory"/>
(1)创建一个工厂类OrderDaoFactory并提供一个普通方法,注意此处和静态工厂的工厂类不一样的地方是方法不是静态方法
public class UserDaoFactory { public UserDao getUserDao(){ return new UserDaoImpl(); } }
(2)编写AppForInstanceUser运行类,在类中通过工厂获取对象
public class AppForInstanceUser { public static void main(String[] args) { //创建实例工厂对象 UserDaoFactory userDaoFactory = new UserDaoFactory(); //通过实例工厂对象创建对象 UserDao userDao = userDaoFactory.getUserDao(); userDao.save(); }
- factory-bean:工厂的实例对象
- factory-method:工厂对象中的具体创建对象的方法名,对应关系如下:
- factory-mehod:具体工厂类中创建对象的方法名
总结:配置的过程还是比较复杂,所以Spring为了简化这种配置方式就提供了一种叫 FactoryBean 的方式来简化开发
简化开发的方式:
FactoryBean的使用
(1)创建一个UserDaoFactoryBean的类,实现FactoryBean接口,重写接口的方法
public class UserDaoFactoryBean implements FactoryBean<UserDao> { //代替原始实例工厂中创建对象的方法 public UserDao getObject() throws Exception { return new UserDaoImpl(); } //返回所创建类的Class对象 public Class<?> getObjectType() { return UserDao.class; } }
(2)在Spring的配置文件中进行配置
<bean id="userDao" class="com.itheima.factory.UserDaoFactoryBean"/>
(3)AppForInstanceUser运行类不用做任何修改,直接运行
总结:Spring的IOC实例化对象的三种方式分别是:
- 构造方法(常用)
- 静态工厂(了解)
- 实例工厂(了解)
- FactoryBean(实用)
这些方式中,重点掌握 构造方法 和 FactoryBean 即可。
需要注意的一点是,构造方法在类中默认会提供,但是如果重写了构造方法,默认的就会消失,在使用的过程中需要注意,如果需要重写构造方法,最好把默认的构造方法也重写下。