没有Spring的时候我们如何进行开发的?
没有Spring为我们服务的,写代码一定是这样的
调用层:
public static void main(String[] args) {
//创建视图层对象
TestController testController = new TestController();
//调用视图层方法
testController.test();
}
视图层:
//视图层是这么写的
public class TestController {
DataBaseService testService = new DataBaseServiceImpl();
public void test(){
testService.testMethod();
}
}
业务层:
public class DataBaseServiceImpl implements DataBaseService {
DataBaseDao mysqlDao= new MysqlDaoImpl();
@Override
public void testMethod() {
mysqlDao.testMethodDao();
}
}
持久层:
public class MysqlDaoImpl implements DataBaseDao {
@Override
public void testMethodDao() {
System.out.println("mysql方法被调用了");
}
}
这样的流程虽然能够正常调用到方法,如下
但是存在一个问题:
如果现在除了MySQL的数据源,业务上面要扩展一个Oracle库,只能如下操作
在业务层中多一种实现方式,修改原来的代码
public class DataBaseServiceImpl implements DataBaseService {
DataBaseDao mysqlDao = new MysqlDaoImpl();
DataBaseDao oracleDao = new OracleDaoImpl();
@Override
public void testMethod() {
mysqlDao.testMethodDao();
}
@Override
public void testMethod2() {
oracleDao.testMethodDao();
}
}
OCP原则
开闭原则,英文缩写OCP,全称Open Closed Principle。
原始定义:
Software entities (classes, modules, functions) should be open for extension but closed for modification。
字面翻译:
软件实体(包括类、模块、功能等)应该对扩展开放,但是对修改关闭。
为什么要“开”和“闭”
一般情况,我们接到需求变更的通知,通常方式可能就是修改模块的源代码,然而修改已经存在的源代码是存在很大风险的,尤其是项目上线运行一段时间后,开发人员发生变化,这种风险可能就更大。所以,为了避免这种风险,在面对需求变更时,我们一般不修改源代码,即所谓的对修改关闭。不允许修改源代码,我们如何应对需求变更呢?答案就是我们下面要说的对扩展开放。
通过扩展去应对需求变化,就要求我们必须要面向接口编程,或者说面向抽象编程。所有参数类型、引用传递的对象必须使用抽象(接口或者抽象类)的方式定义,不能使用实现类的方式定义;通过抽象去界定扩展,比如我们定义了一个接口A的参数,那么我们的扩展只能是接口A的实现类。总的来说,开闭原则提高系统的可维护性和代码的重用性。
明显,上述代码就不符合开闭原则。
DIP原则
依赖倒置原则(Dependence Inversion Principle),简称DIP。
高层模块不应该依赖于低层模块,两者都应该依赖其抽象。
抽象不应该依赖细节。细节应该依赖抽象。
这里的低层模块就是不可分割的原子逻辑,原子逻辑再组装就成了高级模块。
抽象是指接口或者抽象类,两者都是不能直接被实例化;
细节就是实现类,实现接口或继承抽象类而产生的类,可以直接被直接实例化。
所谓的能否直接实例化也就在于是否可以通过关键字new产生的一个对象。
简单来说就是模块之间的依赖都是通过抽象发生,实现类之间并不直接产生依赖关系,其依赖关系都是通过接口或者抽象类产生的。
实现类依赖接口或抽象类,但接口或抽象类不依赖实现类。这也正好对应面向接口编程。
为了符合DIP原则,我们刚才的代码就应该这么写,不能引入Service的实现类了,要引入service的接口:
public class DataBaseController {
//但是这时候有另一个问题,就是testService现在是null
//因为还没有用Spring框架进行对象管理,它替我们管理以后就不会是null了
DataBaseService testService;
public void test(){
testService.testMethod();
testService.testMethod2();
}
}
其实,这种写法就是控制反转的思想。
控制反转
控制反转: IoC (Inversion of Control)
反转是什么呢?
反转的是两件事:
第一件事:
我不在程序中采用硬编码的方式来new对象了。
(new对象我不管了,new对象的权利交出去了。)
第二件事:
我不在程序中采用硬编码的方式来维护对象的关系了。
(对象之间关系的维护权,我也不管了,交出去了。)
控制反转:是一种编程思想。或者叫做一种新型的设计模式。
由于出现的比较新,没有被纳入GOF23种设计模式范围内。
Spring中的IoC
- Spring框架实现了控制反转IOC这种思想
Spring框架可以帮你new对象。
Spring框架可以帮你维护对象和对象之间的关系。
*Spring是一个实现了IoC思想的容器。
*控制反转的实现方式有多种,
其中比较重要的叫做: 依赖注入(Dependency Injection,简称DI).
依赖注入DI,又包括常见的两种方式:
第一种: set注入(执行set方法给属性赋值)
第二种:构造方法注入(执行构造方法给属性赋值)
依赖注入 中“依赖"是什么意思?"注入"是什么意思?
依赖:A对象和B对象的关系。
注入:是一种手段,通过这种手段,可以让A对象和B对象产生关系
依赖注入:对象A和对象B之间的关系,靠注入的手段来维护。而注入包括: set注入和构造注入。
Spring中八大模块
Spring特点
轻量
1.从大小与开销两方面而言Spring都是轻量的。完整的Spring框架可以在一个大小只有 1MB多的JAR文件里发布,并Spring所需的处理开销也是微不足道的
2.Spring是非侵入式的: Spring应用中的对象不依赖于Spring的特定类
控制反转
Spring通过一种称作控制反转 (loC) 的技术促进了松耦合。当应用了loC,一个对象依赖的其它对象会通过被动的方式传递进来,而不是这个对象自己创建或者查找依赖对象。你可以认为loC与JND相反一不是对象从容器中查找依赖,而是容器在对象初始化时不等对象清求就主动将依赖传递给它。
面向切面
Spring提供了面向切面编程的丰富支持,允许通过分离应用的业务逻与系统级服务(例审计(auditing)和事务transaction)管理)进行内聚性的开发。
应用对象只实现它们应该做的一一完成业务逻辑一仅此而已。它们并不负责甚至是意识)其它的系统级关注点,例如日志或事务支持.
容器
Spring包含并管理应用对象的配置和生命周期,在这个意义上它是一种容器,你可以配置你的每bean何被创建一基于一个可面置原型(prototype),你的oean可以创建一个单独的实例或者每次需要时都生成一个新的实例-以及它们是如何相与关联的。然而,Sring不应该被混同于传统的重量级的EJB容器,它价经常是庞大与笨重的,难以使用。
框架
Spring可以将简单的组件雷置、组合成为复杂的应用,在Soring中,应用对象被声明式地组合,典型地是在一个XML文件里,Spring也提供了很多基础功能(事务管理、持久化框架集成等等) ,将应用逻辑的开发留给了你。
所有Spring的这些特征使你能够编写更干净、更可管理、并且更易于测试的代码。它们也为Spring中的各种模块提供了基础支持
Spring的下载
一般在开发中,都直接用Idea自动引入Spring,下面演示如何自己去下载Spring
首先进入下面这个网站
Spring中文网
https://spring.p2hp.com/
项目下的Spring Framework就是Spring框架
点击GitHub
往下滑找到二进制文件,点击链接
进来之后点击仓库地址
依次点击文件目录
找到对应版本Spring,进行下载
Spring项目中常用的依赖
如果使用的是里程碑版本的Spring,如6.0.0,需要引入如下仓库依赖
<!--配置多个仓库-->
<repositories>
<!--spring里程碑版本的仓库-->
<repository>
<id>repository.spring.milestone</id>
<name>Spring Milestone Repository</name>
<url>https://repo.spring.io/milestone</url>
</repository>
</repositories>
Spring中最基础的依赖,SpringContext依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>6.0.0-M2</version>
</dependency>
引入该依赖后会自动引入IOC与AOP相关的jar包
Spring如何管理对象的
比如我们现在有个User对象
public classUser{
}
那么我们可以新建一个Spring.xml配置文件来管理它
<?xml version="1.0" encoding="UTF-8"?>
<beans
xmins="http://www.springframework.org/schema/beans"
xmins:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemalocation="ttp://www.sorinaframerork.org/schemabeans http://ww.springframework.org/schemabeans spring-beans.xsd">
<!--这就是Spring的配智文件-->
<bean id="userBean" class="com.powernode.spring6.bean.User">
</beans>
注意
1.IDEA工具为我们提供了这个文件的桃板,一定要使用这个模板来创建
2.这个文件名不一定叫做spring.xml,可以是其它名字
同时,这个配置文件也可以有多个,在不同的地方可以分别进行调用。
3.这个文件最好是放在类路径当中,方便后期的移植
4.放在resources根目录下,就相当于是放到了类的根路径下
5.配bean,这样spring才可以帮助我们管理这个对象
6.bean标签的两个重要属性:
id:是这个bean的身份证号,不能重复,是唯一的标识。
cLass: 必须城写类的全路径,全限定类名。(带包名的类名)
设置好配置文件之后,可以像如下代码这样来使用配置文件读取对象
public void testFistSpringCode(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
Object userBean = applicationContext.getBean(name:"userBean");
System.out.println(userBean):
}
步骤:
第一步:获取Spring容器对象。
ApplicationContext 翻泽为:应用上下文。其实就是Spring容器。
ApplicationContext 是一个接口
ApplicationContext 接口下有很多实现类。
其中有一个实现类叫做: CLassPathXmlApplicationContext
cLassPathXmLApplicationContext 是专门从类路径当中加载spring配置文件的一个Spring上下文对象。
这行代码只要执行:就相当于启动了Spring容器,解析spring.xml文件,并且实例化所有的ean对象,放到spring容器当中。
第二步:根据bean前id从Spring容器中获取这个对象。
Spring是怎么实例化对象的
默认情况下Spring 会通过反射机制,调用类的无参数构造方法来实例化对象
实现原理如下:
CLass clazz = Class.forName(“com.powernode.spring6.bean.User”);
clazz.newInstance();
注意
如果定义了有参构造方法那么JVM就不会在提供无参数构造了,
所以如果我们定义了有参构造那么无参构造也要显示的定义出来,
否则就无法通过反射创建对象了。
实例化对象之后存到什么数据结构中?
实际存到了一个map中
Spring配置文件可以同时存在多个吗?
可以的,ClassPathXmlApplicationContext可以传参多个配置文件,同时使用
ApplicationContext applicationContext = new
ClassPathXmlApplicationContext("spring6.xml","beans.xml");
Spring在配置文件中配置的类必须是自定义的吗,可以使用JDK中的类吗,例如: java.util.Date?
可以,使用方法都是一样的
Spring配置文件中getBean()方法调用时,如果指定的id不存在会怎样?
会报错,而不是返回空对象
Spring配置文件中getBean()方法返回的对象类型是什么?如何处理?
返回类型是Object
可以强制类型转换
Date nowTime = (Date) applicationContext.getBean(“nowTime”);
也可以在第二个参数传入要获取的类型
Date nowTime = applicationContext.getBean(“nowTime”, Date,class);
ClassPathXmlApplicationContext是从类路径中加载配置文件,如果没有在类路径当中,又应该如何加载配置文件呢?
比如Spring的配置文件现在没有在类的路径下,在D盘根路径中,如下所示
可以采取这种方式引入
ApplicationContext applicationContext = new FileSystemXmlApplicationContext("d:/spring6.xml");
ApplicationContext接口的来源
它的超级父接口是:BeanFactory
(翻译Bean工厂,就是能够生产Bean对象的一个工厂对象。)
BeanFactory.是IoC容器的顶级接口。
Spring的IoC容器底层实际上使用了:工厂模式。
Spring底层的IoC是怎么实现的?XML解析+工厂模式+反射机制
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring6.xml")
依赖注入方式一:set方法注入
采用xml文件配置对象之后,能够创建对象了,但是如下这样调用
public class UserService {
private UserDao userDao;
public void saveUser(){
userDao.insert();
}
}
会报错空指针异常,因为xml 文件目前仅能够创建对象,而没有实现对象注入的功能。
可以通过如下set方法方式进行注入
<!--配置dao-->
<bean id="userDaoBean"class="com.powernode.spring6.dao.UserDao"/>
<!--配置service-->
<bean id="userServiceBean" class="com.powernode.spring6.service.UserService">
<property name="mySQLUserDao" ref="userDaoBean"/>
</bean>
注意
- 想让Spring调用对应的set方法,需要配置property标签
- name属性怎么指定值:set方法的方法名,去掉set,然后把剩下的单词首字母变小写,写到这里
- ref翻译为引用。英语单词:refrences。ref后面指定的是要注入的bean的id
- 需要存在对应的set方法才能生效,如下所示
public class UserService {
private UserDao userDao;
public void setMySQLUserDao(UserDao xyz){
this.userDao xyz;
}
}
依赖注入方式二:构造方法注入
index属性指定参数下标。
第一个参数是0,第二个参数是1,第三个参数是2,以此类排。
ref属性用来指定注入bean的id
<bean id="userDaBean"class="com.powernode.spring6.dao.UserDao"/>
<bean id="vipDaoBean"class="com.powernode.spring6.dao.VipDao"/>
<bean id="csBean"class="com.powernode.spring6.service.CustomerService">
<!--构造注入-->
//index属性指定参数下标,第一个参数是0,第二个参数是1,第三个参数是2,以此类排。ref属性用来指定注入bean的id
//指定构造方法的第一个参数,下标是0
<constructor-arg index="0" ref="userDaoBean"/>
//指定构渣方法的第二个参数,下标是1
<constructor-arg index="1" ref="vipDaoBean"/>
</bean>
需要有指定的构造方法
public class CustomerService
private UserDao userDao;
private VipDao vipDao;
//指定构造方法
public CustomerService(UserDaouserDao,VipDao vipDao){
this.userDao userDao;
this.vipDao vipDao;
}
public void save(){
userDao.insert();
vipDao.insert();
}
}
注意
1.这里面的index属性也可以换成name,同样会生效
<constructor-arg name="0"ref="userDaoBean"/>
2.不用name也不用index,只用ref也可以
这种方式实际上是根据类型进行注入的。
spring会自动根据类型来判断把ref注入给哪个参数。
<constructor-arg ref="userDaoBean"/>
Set注入:Spring配置文件中的外部bean和内部bean是什么
内部bean,property标签中使用嵌套bean标签
<bean id="orderServiceBean2" class="com.powernode.spring6.service.OrderService">
<property name="orderDao">
<bean class="com.powernode.spring6.dao.OrderDao">
</bean>
</property>
</bean>
外部bean,通过ref来引用其他的Bean
<bean id="userDaoBean"class="com.powernode.spring6.dao.UserDao"/>
<bean id="userServiceBean" class="com.powernode.spring6.service.UserService">
<property name="mySQLUserDao" ref="userDaoBean"/>
</bean>
Set注入: 如何注入简单类型
如果是给简单类型赋值,就不能使ref了。就需要使value了
<bean id="userBean"class="com.powernode.spring6.bean.User">
<property name:="username" valve="张三"/>
<property name="password" value="123"/>
<property name="age" value="20"/>
</bean>
Set注入: 简单类型都包括哪些
可以进入BeanUtil类中的这个方法,里面列举了简单类型
public static boolean isSimpleValueType(Class<?>type){
return (Void.class !type &void.class !type &
(Classutils.isPrimitiveorWrapper(type)
Enum.class.isAssignableFrom(type)
CharSequence.class.isAssignableFrom(type)
Number.class.isAssignableFrom(type)
Date.class.isAssignableFrom(type)
Temporal.class.isAssignableFrom(type)
URI.class =type
URL.class =type
Locale.class =type
Class.class =type));
}
对于这些简单类型,都可以通过value来进行对象赋值
<bean id="svt"class="com.powernode.spring6.bean.SimpleValueType">
<property name="age"value="20"/>
<property name="age2"value="20"/>
<property name="username"value="zhangsan"/>
<property name="season"value="SPRING"/>
<property name="flag"value="false"/>
<property name="flag2"value="true"/>
<property name="c"value=""/>
<property name="c2"value=""/>
<property name="clazz" value="java.Lang.String"/>
</bean>
但是上述类型中有一个比较特殊的类型,Date类型直接用value会失败
<property name="birth" value="1970-10-11"/>
报错信息如下
这个报错是因为时间的格式不对
我们可以发现,时间的默认格式是这样的
经测试,这种格式是正确的
<property name="birth"value="Wed Oct 19 16:28:13 CST 2022"/>
但是实际开发中这种格式很麻烦,所以日期格式一般都用ref来引入