在讲Spring之前我们先简单的来看看JavaEE在web应用上的开发演变
1.最初Java在web应用开发上使用Servlet+Html的方案进行开发,无论是初学者还是资深的技术大牛,经历过这一阶段的人都会感受到由Servlet控制所有的逻辑,HTML则进行页面的展示,这种方式在维护和需求变更的情况下是极其麻烦
2.后来为了简化开发,开发人员对Servlet进行了升级,所以就衍生出了Jsp,但是Jsp本质上来说就是将java代码混合在Html页面,它只适合于业务流程简单的应用, 如果系统复杂,Jsp代码严重缺乏可读性,页面显示和业务逻辑混杂,维护仍旧很艰难!
3.再后来为解决程序可读性和可维护性,Sun公司使用Jsp+JavaBean来开发Web应用,前者负责页面显示,后者负责业务逻辑,看起来分工明确,各司其职,一定程度上确实是提高了程序的可读性和可维护性,但是这样的开发模式仍旧存在不足之处,那就是Jsp将显示和流程控制齐聚一身,一方面是极大增加了产品的复杂性,部署起来存在一定的难度;另外,从效率上看由于Jsp中混合大量的Jsp代码,在执行时仍旧需要先转成.java文件,再生成.class字节码文件,最终运行,很显然这一过程是比较繁杂的,再者,如果在这一过程中出现错误,错误指向的是java文件而不是Jsp本身,那么中间就少了一个有Jsp到.java文件的解析过程,Debug的时候是非常痛苦的,这样的模式仅仅适合小型项目的快速构建与运行
4.接着Sun公司又再次升级,引入了MVC思想模式,使用JSP+Servlet+JavaBean开发Web应用。JSP负责页面显示、Servlet负责流程控制、JavaBean负责业务逻辑。web应用开发的分级也愈发清晰,很显然MVC的这一思想还是挺香的,直至如今,近乎所有的框架技术都是沿用MVC设计思想,但是这种模式仍旧不是较为标准化的开发模式,让程序员们无法评估开发周期,难以在时间效率上得到很好的控制。
5.随着时代的进步,成熟的MVC开发框架也随之诞生——那就是Struts,对于一些大型的项目,Struts框架会提高开发效率,并对后期的维护有很大好处
M: 通常在Structs中使用其他模型组建来实现业务逻辑, 如:JavaBean技术、EJB技术、Hibernates设计模式
V: 主要由JSP页面构成,还包括HTML文档、标准标签库(JSTL)和Struts标签库、 JavaScript脚本和CSS样式、多媒体文件、消息资源文件、ActionForm类
C: ActionServlet组件: Struts框架的中央控制器,RequestProcessor组件: 每个子模块都具有的请求处理器,Action组件: 业务代理,它将调用模型进行一项具体的业务逻辑处理
如今,JavaEE在Web应用上的开发已经发展到非常成熟的阶段了,今天要讲的就是如今火热的核心技术框架——Spring
EJB不是挺香的吗?为什么还需要Spring?
虽然EJB在很大程度上解决了开发者在业务逻辑上的难题,但是于逻辑之外而言,EJB本身还是太过复杂臃肿的,而且它的运行环境也较为苛刻,如果了解EJB的胖友都会知道,使用EJB在Web端开发就得先实现WebSphere、WebLogic、JBoss等服务器提供的接口之后才能运行在相应的服务器上,从灵活性上来说它是存在局限性的,另外就是这三个服务器只有JBoss是开源的,相信在这一点上就过滤掉很多想要使用他的人,毕竟现在是开源统治世界嘛!
第二个就是使用EJB时,我们的代码移植性是极差的,前面说到EJB要想运行在支持它的服务器上就得先实现相应的接口,如果说,我现在不想运行在WebLogic上了,换一个Tomcat,很显然后者对前者是不支持的
所以总的来说,EJB可以说是非常重量级的技术框架,现今社会大多数都是业务繁杂和需求多变的项目开发,那么它很显然是难以满足的
说了那么多Spring有什么好处?它就那么香吗?
先来一句高大上的话:Spring是⼀个轻量级的JavaEE解决⽅案,整合众多优秀的设计模式
首先Spring相对前面发展史中提到的绝大多数麻烦以及局限性来说,在它这都能很好的解决,最重要的一点是Spring它是开源的,正当EJB在圈内混的风生水起的时候,弱小无助的Spring选择了开源,为自己赞些人气,毕竟很多优秀的框架大多数都是收费的,对于大公司还好,小公司就很难支付巨额费用,就这样Spring渐渐的得到了中小型公司的认可,因为Spring提供了很多整合第三方框架的接口,逐渐的圈内局形成了Spring+Struts+Hibernate的一股清流,就是大名鼎鼎的SSH,后来几经波折之后又出现了SSM(这里主要讲Spring,想了解细节需自行百度)
好了,不闲扯了,我们正式来讲Spring
说到Spring,我们必须要从它的几个核心技术去讲——IOC、DI、AOP
IOC ——Inversion of Control,直译为控制反转
什么是控制?什么是反转?
在Java的王国里流行着一句话——万事万物,一切皆对象
控制:
传统模式下,对象的创建、管理、销毁等操作都是需要者(也是对象)去自行控制完成的,所谓的控制就是对自己成员的控制权
反转:
在Spring中反转就是让出这个控制权,把这个权利交给Spring,让Spring去帮我们创建、管理、销毁对象,我们只需要拿来就用
总结:
控制反转:
程序放弃对对象的创建、管理、销毁等权利,将这个权利交给Spring去管理,当我们需要的时候只需要从Spring那里拿来就用
DI——Dependency Injection,直译为依赖注入
为什么需要依赖?
对象A在完成一些功能时,是需要一些外部的资源对象B来帮助自己共同完成的,有需要就会产生依赖,就是说对象A依赖对象B
谁依赖谁?
当然是外部类依赖内部成员属性!如下
class A{
B
C
D
...
}
此时,类A依赖内部成员属性 B C D ...
谁注入了谁?
很显然是我们的IOC容器将资源对象注入所需这些对象的对象里
注入了什么?
外部类或对象需要什么,就注入什么。比如资源、对象、数据等
Spring实现的原理:
通过Java的反射可以获取类的所有属性和方法,再通过配置文件(xml)或者注解来描述类与类之间的关系,进而对其进行管理
有什么好处?
1.解耦合
2.通过第三方来管理,只需要修改第三方数据就可管理对象之间的关系,易维护性
3.不需要多次创建相同的对象,节省资源
说了这么多原理,是不是听烦了,那就直接来上代码!
上代码之前我们先来了解一下怎么来创建一个实例对象
1.————>从本地或者网络上加载.class文件(方法区)
2.————>根据.class文件创建java.lang.class对象(JVM)
3.————>再根据.class对象模板来创建实例对象(堆)
了解了如何创建对象,我们再来看常见的创建对象的方式有哪些
1.通过new关键字来创建对象
2.通过反射机制获取类的全限定类名,在运行时获取对象的所有属性及方法
(这里为了研究我们暂时只说这两种创建对象的方式,也是日常常用到的方式,需要了解其他方式的自行百度)
日常开发中,如果我们需要完成某个功能,首先我们需要有接口和实现类
#接口
package com.xiaozhao.spring.service;
import com.xiaozhao.spring.entity.Student;
/**
* @author : Carson-Zhao
* @date : 2020/8/15 16:44
*/
public interface StudentService {
public Student login(String user, String password);
}
#实现类
package com.xiaozhao.spring.service.impl;
import com.xiaozhao.spring.service.StudentService;
/**
* @author : Carson-Zhao
* @date : 2020/8/16 16:02
*/
public class StudentServiceImpl implements StudentService {
@Override
public Student login(String user, String password) {
System.out.println("调用此方法!");
return null;
}
}
然后通过创建实现类,调用里面的方法来完成操作
package com.xiaozhao.spring;
import com.xiaozhao.spring.service.StudentService;
import com.xiaozhao.spring.service.impl.StudentServiceImpl;
/**
* @author : Carson-Zhao
* @date : 2020/8/15 16:36
*/
public class Demo {
public static void main(String[] args) {
StudentService studentService = new StudentServiceImpl();
studentService.login("xiaozhao","123456");
}
}
运行结果:
D:\Environment\Java\jdk1.8.0_251\bin\java.exe "...
调用此方法!
Process finished with exit code 0
传统模式创建对象存在的问题:
1.对象之间存在耦合,需要用new关键字来进行强关联,⼀⽅的改变会影响到另⼀⽅
2.将接⼝的实现类,通过new硬编码在程序中
3.不利于代码的维护
Spring创建对象的方式:
基于工厂设计模式来创建的
简单的工厂模式:
package com.xiaozhao.spring.factory;
import com.xiaozhao.spring.service.StudentService;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
/**
* @author : Carson-Zhao
* @date : 2020/8/16 16:23
*/
public class BeanFactory {
private static Properties pro = new Properties();
static {
try {
//通过读取applicationContext.properties文件获取流
InputStream inputStream = BeanFactory.class.getResourceAsStream("/applicationContext.properties");
//从流中获取key-value对
pro.load(inputStream);
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
public static StudentService getStudentService(){
StudentService studentService = null;
try {
//根据value(全限定类名)通过反射机制创建class对象
Class clazz = Class.forName(pro.getProperty("studentService"));
//通过class对象创建实例对象
studentService = (StudentService) clazz.newInstance();
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
e.printStackTrace();
}
//返回所需实例对象
return studentService;
}
}
在这里我们要在applicationContext.properties文件中配置我们所需创建的对象的key-value对
studentService=com.xiaozhao.spring.service.impl.StudentServiceImpl
在测试类Demo中通过工厂类BeanFactory的静态方法创建实例对象
package com.xiaozhao.spring;
import com.xiaozhao.spring.factory.BeanFactory;
import com.xiaozhao.spring.service.StudentService;
/**
* @author : Carson-Zhao
* @date : 2020/8/15 16:36
*/
public class Demo {
public static void main(String[] args) {
StudentService studentService = BeanFactory.getStudentService();
studentService.login("xiaozhao","123456");
}
}
运行结果:
这里可以看出,通过工厂我们也能创建实现类的实例对象,那么这样有什么好处呢?
1.解耦合,在测试类Demo中我们不再使用new关键字进行强关联,如果在StudentServiceImpl中添加新方法或者删除某些方法的时候,只要不涉及新的方法,调用者则不会有影响,就算需要使用新方法也可以直接调用,即——调用者只需要关注自己的业务需要什么就调用有该功能的方法,不再受额外的影响
2.易维护,如果有一天我们不想要StudentServiceImpl这个对象了,我们只需要重新创建一个新的实现类,通过配置properties文件,修改key的值,就可以获取任意的对象
3.在我们的测试代码中再也不需要new关键字的强关联
思考:这样的工厂依然存在缺点,如果我们需要其他的实例对象,是不是还需要去工厂类中修改XXX getXXX方法,有没有一个通用的方法能让我们想要什么对象它就会给我们返回什么对象呢?
那行!我们来变形一下工厂类
package com.xiaozhao.spring.factory;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
/**
* @author : Carson-Zhao
* @date : 2020/8/16 16:23
*/
public class BeanFactory {
private static Properties pro = new Properties();
static {
try {
//通过读取applicationContext.properties文件获取流
InputStream inputStream = BeanFactory.class.getResourceAsStream("/applicationContext.properties");
//从流中获取key-value对
pro.load(inputStream);
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
public static Object getBean(String key){
Object ret = null;
try {
//根据value(全限定类名)通过反射机制创建class对象
Class clazz = Class.forName(pro.getProperty(key));
//通过class对象创建实例对象
ret = clazz.newInstance();
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
e.printStackTrace();
}
//返回所需实例对象
return ret;
}
}
变形了之后,我们怎么创建StudentService的实例对象呢?
package com.xiaozhao.spring;
import com.xiaozhao.spring.factory.BeanFactory;
import com.xiaozhao.spring.service.StudentService;
/**
* @author : Carson-Zhao
* @date : 2020/8/15 16:36
*/
public class Demo {
public static void main(String[] args) {
StudentService studentService = (StudentService) BeanFactory.getBean("studentService");
studentService.login("xiaozhao","123456");
}
}
我们修改一下StudentServiceImpl里面打印的那句话
package com.xiaozhao.spring.service.impl;
import com.xiaozhao.spring.entity.Student;
import com.xiaozhao.spring.service.StudentService;
/**
* @author : Carson-Zhao
* @date : 2020/8/16 16:02
*/
public class StudentServiceImpl implements StudentService {
@Override
public Student login(String user, String password) {
System.out.println("调用此方法!11111");
return null;
}
}
运行一下试试
完全没问题,如果我们想要其他的实现类对象呢?没问题!仍旧Very Easy.!
步骤:
1.创建接口
2.实现接口
3.配置properties文件
4.测试类中传入相应的key值
#接口
package com.xiaozhao.spring.service;
/**
* @author : Carson-Zhao
* @date : 2020/8/16 17:28
*/
public interface UserService {
public void register();
}
#实现类
package com.xiaozhao.spring.service.impl;
import com.xiaozhao.spring.service.UserService;
/**
* @author : Carson-Zhao
* @date : 2020/8/16 17:28
*/
public class UserServiceImpl implements UserService {
@Override
public void register() {
System.out.println("我是新对象!!!");
}
}
#修改properties配置文件
userService=com.xiaozhao.spring.service.impl.UserServiceImpl
#测试类中传入相应的key,并调用相应方法
UserService userService = (UserService) BeanFactory.getBean("userService");
userService.register();
看一下结构:
运行结果:
虽然艰辛,但是我们总算是了解了利用工厂创建对象的一个简单模式
使用工厂设计模式来给我们创建实例对象
好处:
1.避免new关键字的强关联
2.使代码变得易维护,对原有对象不满意,只需要关注新对象的设计,不需要再修改原有对象(开闭原则)
3.解耦合
总结:
Spring的本质:⼯⼚ ApplicationContext (applicationContext.xml)
其实在我们的Spring的底层的设计思想也是按照这个思路去设计的,当然Spring的工厂必须是比我们的完善的提供的功能比我们更强大的,设计复杂度也是不能比拟的,但是万变不离其宗,就是按照这个思路!
好了,了解了Spring的本质,那么我们就开始来弄一下我们的第一个Spring程序!
环境:
1.JDK————1.8
2.IDEA————2020.1.3
3.Maven————3.6+
4.SpringFramework————5.0+
1.引入依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.xiaozhao</groupId>
<artifactId>Spring</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.8.RELEASE</version>
</dependency>
</dependencies>
</project>
效果:
配置文件我们通过Spring Config来创建一份,命名为applicationContext.xml(之前的配置文件是applicationContext.properties)
准备好环境和配置文件之后
我们来思考一个问题——普通的Java对象和Spring Bean有什么区别?是一样的吗?
不知道没关系,我们先来创建一个Spring Bean
步骤:
1.在applicationContext.xml文件中告诉Spring有哪些类需要被创建
2.在测试类中调用Spring的核心接口方法getBeanl()来获取Spring Bean
#在applicationContext.xml文件中告诉Spring有哪些类需要被创建
<bean id="userService" class="com.xiaozhao.spring.service.impl.UserServiceImpl"/>
#在测试类中调用Spring的核心接口方法getBeanl()来获取Spring Bean
public class Test {
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("/applicationContext.xml");
UserService userService = (UserService) context.getBean("userService");
userService.register();
}
}
插播:
工厂对象的getBean()方法有这么多重载,在传入相应的参数时,一定要注意参数类型和每个重载的使用意义
例子:
//通过这种⽅式获得对象,就不需要强制类型转换
Person person = ctx.getBean("person", Person.class);
System.out.println("person = " + person);
//当前Spring的配置⽂件中 只能有⼀个<bean class是Person类型
Person person = ctx.getBean(Person.class);
System.out.println("person = " + person);
..............
Spring核心接口ApplicationContext提供
ClassPathXmlApplicationContext、XmlWebApplicationContext和AnnotationConfigApplicationContext三个创建工厂类
简述Spring Bean的过程:
1.通过构造函数或者Set的方式进行对象的构建
2.将依赖的成员属性或者对象进行注入
3.调用BeanPostProcessor接口的实现类,在对象初始化前进行额外功能的加工
4.Spring对对象进行初始化操作
5.可以进行人工初始化(一般选择不进行人工初始化)
6.调用BeanPostProcessor接口的实现类,在对象初始化后进行额外功能的加工
7.创建完成,返回创建好的对象
通过以上的过程我们了解到Spring对我们的Java对象进行了一系列的加工
以上问题的答案就可以解决了:
Spring对Java对象的加工分7步,目的是给Java对象加上额外的扩展功能(日志、事务、性能测试......)
注意:
ApplicationContext⼯⼚的对象占⽤⼤量内存
不会频繁创建对象 : ⼀个应⽤只会创建⼀个⼯⼚对象
ApplicationContext⼯⼚:⼀定是线程安全的(多线程并发访问)
我们来捋一捋使用spring开发的流程
1. 创建类型
2. 配置配置文件 applicationContext.xml
3. 通过⼯⼚类,获得对象
ApplicationContext
|
接口的实现类
|
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext.xml");
在实际开发中我们经常把Spring⼯⼚创建的对象,叫做bean或者组件(componet)
Spring工厂的一些方法:
getBeanDefinitionNames()---->获取spring工厂中所有的bean的名字,返回的是一个String[]数组
getBean("类名",类.getClass())---->方法返回的对象不需要强制转换
containsBeanDefinition("配置文件bean的id值")---->判断是否包含指定id的Spring bean
getBeanNamesForType(类.getClass())------>根据类型获取配置文件中对应的id值,返回是一个string数组
.........
好了,又有新问题了
既然有了这么好使的Spring工厂,那么是不是我们可以将所有的Bean都交给Spring工厂去管理呢?有没有例外?
答案是否定的!
因为我们日常开发中有些类是需要映射我们对应的业务逻辑需求的,所以这些类是不建议交给Spring工厂去管理的
例如:实体类 pojo以及它的衍生
讲了这么多的工厂,是为了让我们很好理解Spring的底层设计思想,有利于之后的学习,相信经历以上的各种骚操作之后,对Spring工厂应该有了一定的了解,那么接下来,就开始来讲——DI
在前面我们已经解释了什么是DI,为什么需要DI,以及相关的注入问题,那么现在我们来干什么呢?——编码
先上步骤:
1.创建类并为其添加set、get方法
2.配置application.xml
3.创建工厂
4.通过工厂获取对象
1.创建类
2.配置application.xml
<bean id="student" class="com.xiaozhao.spring.entity.Student">
<property name="name" value="小赵"/>
<property name="age" value="24"/>
<property name="sex" value="男"/>
</bean>
3和4,创建工厂并获取对象
运行效果:
从配置文件中我们可以看出<property />属性中的name对应着我们类中的每一个成员名字,value对应着我们要给它附上的值,这样就能实现给student对象中的属性赋值
有一个问题,就是以上代码中我们采用的注入方式是什么方式?有哪些注入方式?
在上面步骤中提到为类添加set方法,这里采用的就是set的方式注入
注入方式有哪些:
1.set
2.构造器注入
3.反射注入
这里我们先来讲最简单的也是我们工作中常见的set注入:
通过上面的图我们一目了然,下面重点来说一下几种类型的编写
废话少说先上代码!
这是被注入对象Student,它的属性基本上涵盖了我们常用的基本类型
package com.xiaozhao.spring.entity;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* @author : Carson-Zhao
* @date : 2020/8/15 16:37
*/
public class Student {
private String name;
private Integer age;
private String sex;
private String[] s;
private List<String> list;
private Set<String> set;
private Map<String,String> map;
private User user;
public String[] getS() {
return s;
}
public void setS(String[] s) {
this.s = s;
}
public List<String> getList() {
return list;
}
public void setList(List<String> list) {
this.list = list;
}
public Set<String> getSet() {
return set;
}
public void setSet(Set<String> set) {
this.set = set;
}
public Map<String, String> getMap() {
return map;
}
public void setMap(Map<String, String> map) {
this.map = map;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
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 String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", sex='" + sex + '\'' +
", s=" + Arrays.toString(s) +
", list=" + list +
", set=" + set +
", map=" + map +
", user=" + user +
", properties=" + properties +
'}';
}
}
————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
这是程序员自定义类型的注入类型
package com.xiaozhao.spring.entity;
/**
* @author : Carson-Zhao
* @date : 2020/8/28 23:20
*/
public class User {
private int id;
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
接下来就是配置xml
<bean id="student" class="com.xiaozhao.spring.entity.Student">
<property name="name" value="小赵"/>
<property name="age" value="24"/>
<property name="sex" value="男"/>
<property name="s">
<list>
<value>ceshi</value>
<value>xiaoxiao</value>
</list>
</property>
<property name="list">
<list>
<value>cheche</value>
<value>huaihaui</value>
</list>
</property>
<property name="set">
<set>
<value>xiaoming</value>
<value>xiaojiu</value>
</set>
</property>
<property name="map">
<map>
<entry key="ceshi" value="youyou"/>
</map>
</property>
<property name="user" ref="user"/>
<property name="properties">
<props>
<prop key="nihao">enen</prop>
<prop key="haha">hehe</prop>
</props>
</property>
</bean>
<bean id="user" class="com.xiaozhao.spring.entity.User">
<property name="name" value="小花"/>
<property name="id" value="20"/>
</bean>
接下来就是获取student
D:\Environment\Java\jdk1.8.0_251\bin\java.exe "...
student = Student{name='小赵', age=24, sex='男', s=[ceshi, xiaoxiao], list=[cheche, huaihaui], set=[xiaoming, xiaojiu], map={ceshi=youyou}, user=com.xiaozhao.spring.entity.User@5abca1e0}, properties={nihao=enen, haha=hehe}}
Process finished with exit code 0
这些基本操作应该是没什么问题的,那就接着上构造器注入
在这里我将就以上的User来改造
package com.xiaozhao.spring.entity;
/**
* @author : Carson-Zhao
* @date : 2020/8/28 23:20
*/
public class User {
private int id;
private String name;
public User(int id, String name) {
this.id = id;
this.name = name;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
——————————————————————————————————————————————————————————————————————————————————————————————————————————————————
配置文件xml中
<bean name="user" class="com.xiaozhao.spring.entity.User">
<constructor-arg value="15"/>
<constructor-arg value="nihao"/>
</bean>
——————————————————————————————————————————————————————————————————————————————————————————————————————————————————
运行结果:
D:\Environment\Java\jdk1.8.0_251\bin\java.exe "...
user = User{id=15, name='nihao'}
student = Student{name='小赵', age=24, sex='男', s=[ceshi, xiaoxiao], list=[cheche, huaihaui], set=[xiaoming, xiaojiu], map={ceshi=youyou}, user=User{id=15, name='nihao'}, properties={nihao=enen, haha=hehe}}
Process finished with exit code 0
要注意的点:
构造器注入时: