一、Spring引言
1、EJB(Enterprise Java Beans)技术
(1)EJB简介
EJB是Enterprise Java Beans技术的简称, 又被称为企业Java Beans。
EJB可以说像是一个Web Service,但也不完全是,比如EJB将编写好的业务组件放置在EJB容器上,然后提供接口给客户端访问;但是功能不仅限如此,EJB标准中提供了很多规范等,而这些规范只有在EJB容器才能正常运行。还可以说是RPC(Remote Procedure Call远程过程调用)。
EJB提供了一种组件模式,该模式可以让开发人员仅关注系统业务方面的开发,而忽略中间件需求,比如组件、事务管理、持久化操作、安全性、资源池、线程、分发、远程处理等。开发人员可以非常容易地在任何时候将中间件需求的服务添加到系统中。
(2)EJB存在的问题
运行环境苛刻,造价高昂,对服务器要求高( 需要运行在EJB容器中,服务器有:Weblogic,WebSphere)
代码移植性差
总结:EJB是重量级的框架
2、什么是Spring
Spring(春天),诞生2002年,是一个开源的,轻量级的项目管理框架,JavaEE解决方案,整合众多优秀的设计模式
spring核心技术 ioc , aop 。能实现模块之间,类之间的解耦合。**
开源的,轻量级的项目管理框架
# 1. 对运行环境没有额外的要求 开源服务器 tomcat resion jetty 收费服务器 webLogic WebSphere # 2. 代码移植性高 不需要实现额外接口 # 3.轻量级 学习使用简单,内存资源占用较少,方便维护扩展 # 4.项目管理框架 Struts替换Servlet充当控制器 (负责某一层次) Mybatis替换JDBC 完成数据库访问 (负责某一层次) Spring不是替换某一层次的技术替换,而是统筹有全局,将当前的技术整合进行整合管理,柔和在一起
致力于javaEE(企业级)轻量级解决方案
# 轻量级解决方案 提供一个以简单的,统一的,高效的方式构造整个应用,并且可以将单层框架 以最佳的组合柔和在一起建立一个连贯的体系,Struts2+Spring(中间件 中介)+Mybatis
整合设计模式
1. 工厂 2. 代理 3. 模板 4. 策略 5. 单例
Spring总结思路图
3、Spring如何对项目进行管理?
核心作用:用Spring来管理组件,负责组件的创建,管理,销毁
Spring框架用来管理(创建,使用,销毁)组件 ,由于Spring框架可以帮我们生成项目中组件对象,因此Spring是一个工厂/容器
注意:Spring一般不管理实体类对象(entity)
传统的使用JVM进行垃圾回收销毁,Spring自动进行销毁
二、工厂设计模式手写一个Spring
设计模式
1. 广义概念 面向对象设计中,解决特定问题的经典代码 2. 狭义概念 GOF4人帮定义的23种设计模式:工厂、适配器、装饰器、门面、代理、模板...
1、什么是工厂设计模式
1. 概念:通过工厂类,创建对象 User user = new User(); UserDAO userDAO = new UserDAOImpl(); 2. 好处:解耦合 耦合:指定是代码间的强关联关系,一方的改变会影响到另一方 问题:不利于代码维护 简单:把接口的实现类,硬编码在程序中 UserService userService = new UserServiceImpl();
2、简单的工厂设计
原始的方法:硬编码,耦合
对象的创建方式:
对象的创建方式: 1. 直接调用构造方法 创建对象 UserService userService = new UserServiceImpl(); 2. 通过反射的形式 创建对象 解耦合 Class clazz = Class.forName("com.tjcu.factory.service.UserService"); UserService userService = (UserService)clazz.newInstance();
new创建对象
/** * @author 王恒杰 * @version 1.0 * @date 2021/11/9 13:49 * @email 1078993387@qq.com * @Address 天津 * @Description: 直接调用构造方法 创建对象 */ public class BeanFactory { public static UserService getUserService(){ return new UserServiceImpl(); }
通过反射创建
public static UserService getUserService(){ UserService userService =null; try { Class clazz = Class.forName("com.tjcu.factory.service.impl.UserServiceImpl"); userService = (UserService) clazz.newInstance(); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } return userService; }
优化反射的方式
1.创建application.properties
2.application.properties文件加载进工厂里面
/** * @author 王恒杰 * @version 1.0 * @date 2021/11/9 13:49 * @email 1078993387@qq.com * @Address 天津 * @Description:通过反射的形式 创建对象 解耦合 */ public class BeanFactory { private static Properties properties = new Properties(); static { //第一步 获得IO输入流 InputStream inputStream = BeanFactory.class.getResourceAsStream("/application.properties"); //第二步 文件内容 封装Properties集合中key=UserService ,value=com.tjcu.factory.service.impl.UserServiceImpl try { properties.load(inputStream); } catch (IOException e) { e.printStackTrace(); } } /** * 对象的创建方式: * 1. 直接调用构造方法 创建对象 UserService userService = new UserServiceImpl(); * 2. 通过反射的形式 创建对象 解耦合 * Class clazz = Class.forName("com.tjcu.factory.service.UserService"); * UserService userService = (UserService)clazz.newInstance(); */ public static UserService getUserService() { UserService userService = null; try { Class clazz = Class.forName(properties.getProperty("userService")); userService = (UserService) clazz.newInstance(); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } return userService; } }
测试
3、通用工厂的设计
问题:简单工厂会存在大量的代码冗余
通用工厂代码
/** * @author 王恒杰 * @version 1.0 * @date 2021/11/9 13:49 * @email 1078993387@qq.com * @Address 天津 * @Description:通过反射的形式 创建对象 解耦合 */ public class BeanFactory { private static Properties properties = new Properties(); static { //第一步 获得IO输入流 InputStream inputStream = BeanFactory.class.getResourceAsStream("/application.properties"); //第二步 文件内容 封装Properties集合中key=UserService ,value=com.tjcu.factory.service.impl.UserServiceImpl try { properties.load(inputStream); } catch (IOException e) { e.printStackTrace(); } } /** * 对象的创建方式: * 1. 直接调用构造方法 创建对象 UserService userService = new UserServiceImpl(); * 2. 通过反射的形式 创建对象 解耦合 * Class clazz = Class.forName("com.tjcu.factory.service.UserService"); * UserService userService = (UserService)clazz.newInstance(); */ public static Object getBean(String key){ Object o=null; try { Class clazz= Class.forName(properties.getProperty(key)); o= clazz.newInstance(); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } return o; } }
测试
/** * 用来测试工厂解耦合的操作 */ @Test public void loginTest(){ //UserServiceImpl userService = new UserServiceImpl(); UserService userService = (UserService) BeanFactory.getBean("userService"); User user = new User(); user.setUsername("王恒杰"); user.setPassword("123456"); User login = userService.login(user.getUsername(), user.getPassword()); System.out.println(login); } }
4、通用工厂的使用方式
1. 定义类型 (类) 2. 通过配置文件的配置告知工厂(applicationContext.properties) key = value 3. 通过工厂获得类的对象 Object ret = BeanFactory.getBean("key")
5、工厂总结
Spring本质:工厂 ApplicationContext(应用程序上下文) (applicationContext.xml)
三、第一个Spring程序
1、软件版本
1. JDK1.8+ 2. Maven3.5+ 3. IDEA2018+ 4. SpringFramework 5.1.4 官方网站 www.spring.io
2、环境搭建
Spring的jar包
#设置pom 依赖 <!-- https://mvnrepository.com/artifact/org.springframework/spring-context --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.1.4.RELEASE</version> </dependency>
Spring相关依赖
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>4.3.2.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> <version>4.3.2.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>4.3.2.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-expression</artifactId> <version>4.3.2.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>4.3.2.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>4.3.2.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId> <version>4.3.2.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>4.3.2.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>4.3.2.RELEASE</version> </dependency>
3、Spring配置文件创建
1. bean标签负责组件对象的管理,一个bean标签只负责一个组件的管理, 2. class:要管理的组件,java类的全限定名 包名.类名 3. id: 组件对象在工厂中的唯一标识 建议命名规则: (1)存在接口: 接口首字母小写 (2)没有接口:类名首字母小写
<!-- 1. bean标签负责组件对象的管理,一个bean标签只负责一个组件的管理, 2. class:要管理的组件,java类的全限定名 包名.类名 3. id: 组件对象在工厂中的唯一标识 建议命名规则: (1)存在接口: 接口首字母小写 (2)没有接口:类名首字母小写 --> <bean class="com.tjcu.dao.UserDaoImpl" id="userDao"></bean>
4、测试(所有错误:从下往上看)
# 通过工厂获取组件对象 //1.启动工厂 参数:工厂Spring配置文件的位置 路径 文件路径用/ Application ctx=new ClassPathXmlApplicationContext("init/spring.xml"); //2、获取组件对象 参数:组件对象在工厂中的唯一标识 UserDao userDao=(UserDao)ctx.getBean("aa"); //3.调用方法 userDao.add("王恒杰");
5、Spring核心API之ApplicationContext
ApplicationContext
作用:Spring提供的ApplicationContext这个⼯⼚,⽤于对象的创建 好处:解耦合
ApplicationContext接口类型
接⼝:屏蔽实现的差异 ⾮web环境 : ClassPathXmlApplicationContext (main junit) web环境 : XmlWebApplicationContext
重量级资源
ApplicationContext⼯⼚的对象占⽤⼤量内存。 不会频繁的创建对象 : ⼀个应⽤只会创建⼀个⼯⼚对象。 ApplicationContext⼯⼚:⼀定是线程安全的(多线程并发访问)
四、Spring的核心思想之IOC
1、Spring细节分析
名词解释
Spring⼯⼚创建的对象,叫做bean或者组件(componet)
Spring⼯⼚的相关的⽅法
//通过这种⽅式获得对象,就不需要强制类型转换 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⼯⼚配置⽂件中所有bean标签的id值 person person1 String[] beanDefinitionNames = ctx.getBeanDefinitionNames(); for (String beanDefinitionName : beanDefinitionNames) { System.out.println("beanDefinitionName = " +beanDefinitionName); } //根据类型获得Spring配置⽂件中对应的id值 String[] beanNamesForType = ctx.getBeanNamesForType(Person.class); for (String id : beanNamesForType) { System.out.println("id = " + id); } //⽤于判断是否存在指定id值得bean if (ctx.containsBeanDefinition("a")) { System.out.println("true = " + true); }else{ System.out.println("false = " + false); } //⽤于判断是否存在指定id值得bean if (ctx.containsBean("person")) { System.out.println("true = " + true); }else{ System.out.println("false = " + false); }
Spring的核心思想
#1. IOC:控制反转|翻转控制 把对象的创建,赋值,管理工作都交给代码之外的容器实现, 也就是对象的创建是有其它外部资源完成。 #2. AOP 面向切面编程
2、IOC 思想:翻转控制
(1)什么是IOC控制反转
IOC: inversion of Controll 翻转控制,控制反转
控制反转:控制权力的反转
将原有手工通过new关键字创建对象的权力,反转给Spring负责,由Spring负责对象的创建
1. 控制: 创建对象,对象的属性赋值,对象之间的关系管理。 2. 反转: 把原来的开发人员管理,创建对象的权限转移给代码之外的容器实现。 由容器代替开发人员管理对象。创建对象,给属性赋值。 3. 正转:由开发人员在代码中,使用new 构造方法创建对象, 开发人员主动管理对象。 public static void main(String args[]){ // 在代码中, 创建对象。--正转。 Student student = new Student(); } 4.容器:是一个服务器软件, 一个框架(spring) 5.问题:未来在开发过程中,是不是所有的对象,都会交给Spring⼯⼚来创建呢? 回答:理论上 是的,但是有特例 :实体对象(entity)是不会交给Spring创建,它是由持久层 框架进⾏创建。
(2)为什么要使用 ioc ?
目的就是减少对代码的改动, 也能实现不同的功能。 实现解耦合。
(3)java中创建对象的方式有哪些?
1. 构造方法 , new Student() 2. 反射 3. 序列化 4. 克隆 5. ioc :容器创建对象 6. 动态代理
(4)案例
UserDao
public interface UserDao { public void add(String name); }
UserDaoImpl
public class UserDaoImpl implements UserDao{ @Override public void add(String name) { System.out.println(name+"被添加进数据库了!"); } }
Spring配置文件
<?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"> <!-- 1. bean标签负责组件对象的管理,一个bean标签只负责一个组件的管理, 2. class:要管理的组件,java类的全限定名 包名.类名 3. id: 组件对象在工厂中的唯一标识 建议命名规则: (1)存在接口: 接口首字母小写 (2)没有接口:类名首字母小写 --> <bean class="com.tjcu.dao.UserDaoImpl" id="userDao"></bean> </beans>
测试类
/** * IOC 思想:翻转控制 测试类 */ @Test public void add(){ ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("/init/Spring.xml"); UserDao userDao = (UserDao) ctx.getBean("userDao"); userDao.add("王恒杰"); }
(5)为什么使用DI(依赖注入)
不通过new创建对象的容器除了Spring还有
servlet 1: 创建类继承HttpServelt 2: 在web.xml 注册servlet , 使用 <servlet-name> myservlet </servlet-name> <servelt-class>com.bjpwernode.controller.MyServlet1</servlet-class> 3. 没有创建 Servlet对象, 没有 MyServlet myservlet = new MyServlet() 4. Servlet 是Tomcat服务器它能你创建的。 Tomcat也称为容器 Tomcat作为容器:里面存放的有Servlet对象, Listener , Filter对象
👨💻注意 :因为ioc不是Spring独有(通过手工new创建对象交给Spring创建对象),所以提出新的概念:DI(依赖注入)
1. 将原来手工new 创建对象交给Spring创建对象 2. 将另外一个组件作为当前组件的成员变量,进行依赖注入
3、DI:依赖注入
- spring是一个容器,管理对象,给属性赋值, 底层是反射创建对象。
- spring-context: 是ioc功能的,创建对象的。
- 基于IOC提出的思想:DI (dependency injection 依赖注入 )
- 依赖的概念: 在一个组件需要另外一个组件,将另外一个组件作为这个组件的成员变量(没有注入 )
注入:因为将另外一个组件作为这个组件的成员变量,但是这个成员变量是空的,我们将所依赖的成员变量赋值,就是注入 (提供set方法),在工厂中进行注入
为依赖的成员变量进行注入(赋值)
name:成员变量名字 赋值:依赖的组件是工厂中另外一个组件,需要使用ref进行引用 ref:引用 值:依赖组件的唯一标识 <property name="xxxDao" ref=""></property>
【案例】
UserDao
public interface UserDao { public void add(String name); }
UserDaoImpl
public class UserDaoImpl implements UserDao { @Override public void add(String name) { System.out.println(name+"Dao层被添加进数据库了!"); } }
UserService
public interface UserService { public void add(String name); }
UserServiceImpl【重点】
/** * @author 王恒杰 * @version 1.0 * @date 2021/11/10 14:27 * @email 1078993387@qq.com * @Address 天津 * @Description: */ public class UserServiceImpl implements UserService { /** * 在UserService组件需要UserDAO组件,我们可以将UserDao组件当做成员变量 */ private UserDao userDao; @Override public void add(String name) { System.out.println(name+"实现了IOC:翻转控制,原先手工new创建对象现在交给Spring创建对象了"); userDao.add(name); } /** * 为userdao提供set方法用于依赖注入 * @param userDao */ public void setUserDao(UserDao userDao) { this.userDao = userDao; } }
Spring配置文件
<!-- 1. bean标签负责组件对象的管理,一个bean标签只负责一个组件的管理, 2. class:要管理的组件,java类的全限定名 包名.类名 3. id: 组件对象在工厂中的唯一标识 建议命名规则: (1)存在接口: 接口首字母小写 (2)没有接口:类名首字母小写 --> <bean class="com.tjcu.dao.UserDaoImpl" id="userDao"></bean> <!-- UserService --> <bean id="userService" class="com.tjcu.Service.UserServiceImpl"> <!-- DI:dependency injection 依赖注入 name:成员变量名字 赋值:依赖的组件是工厂中另外一个组件,需要使用ref进行引用 ref:引用 值:依赖组件的唯一标识 --> <property name="userDao" ref="userDao"></property> </bean>
测试
/** * DI:dependency injection 依赖注册 测试类 */ @Test public void AddByDI(){ ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("/init/Spring.xml"); UserService userService = (UserService) ctx.getBean("userService"); userService.add("王恒杰"); }
五、Spring中注入
1、Spring中的注入方式
1. SET方式注入【重点】 2. 构造注入 3. 自动注入
2、Set方式注入
通过成员变量 提供set方法
- 依赖 定义成员变量 提供set方法
- 注入 在Spring 的配置文件中通过标签进行赋值
(1)注入对象类型的数据 ref
<!-- 1. bean标签负责组件对象的管理,一个bean标签只负责一个组件的管理, 2. class:要管理的组件,java类的全限定名 包名.类名 3. id: 组件对象在工厂中的唯一标识 建议命名规则: (1)存在接口: 接口首字母小写 (2)没有接口:类名首字母小写 --> <bean class="com.tjcu.dao.UserDaoImpl" id="userDao"></bean> <!-- UserService --> <bean id="userService" class="com.tjcu.Service.UserServiceImpl"> <!-- DI:dependency injection 依赖注入 name:成员变量名字 赋值:依赖的组件是工厂中另外一个组件,需要使用ref进行引用 ref:引用 值:依赖组件的唯一标识 --> <property name="userDao" ref="userDao"></property> </bean>
(2)八种基本数据类型+字符串+日期 value
private int id; private String name; private Date birthday private Double score; 提供对应的set方法
八种基本数据类型+字符串+日期 value注入
<!--注入八种基本数据类型+字符串+日期 --> <property name="id" value="110" /> <property name="name" value="王恒杰" /> <property name="birthday" value="1999/12/03" /> <property name="score" value="100.0" />
(3)数组类型
数组类型
private int[] ids;
数组类型注入
<!--数组类型--> <property name="ids"> <array> <value>123</value> <value>223</value> <value>323</value> <value>423</value> </array> </property>
(4)集合类型之list
a、元素是基本类型
list类型
/
//lsit集合 private List<String> names;
list类型注入
<!--list注入--> <property name="names"> <list> <value>张三</value> <value>李四</value> <value>王五</value> <value>王恒杰</value> </list> </property>
b、元素是对象类型的数据
<property name="maps"> <map> <entry key="1" value-ref="empDAO" /> <entry key="2" value-ref="empDAO" /> <entry key="3" value-ref="empDAO" /> </map> </property>
(5)集合类型之set
set类型
//Set集合 private Set<String> sets;
set类型注入
<!--注入 Set集合--> <property name="sets"> <set> <value>xixi</value> <value>haha</value> <value>hehe</value> </set> </property>
(6)集合类型之Map
1️⃣a、元素是基本类型
map类型
//map private Map<String,String> js; //map键值遍历 for(Map.Entry entry:js.entrySet()){ System.out.println(entry.getKey()+":"+entry.getValue()); }
map类型注入
<!--map--> <property name="js"> <map> <entry key="一号技师" value-ref="吴泓旭"></entry> <entry key="二号技师" value="张希"></entry> <entry key="三号技师" value="mm"></entry> </map> </property>
2️⃣b、元素是对象类型的数据
map类型
map类型注入
(7)集合类型之properties
properties概念:
properties是Map的子实现类 非范型集合 键值都是String类型
properties类型
//properties集合 private Properties properties; //properties集合遍历 for(Map.Entry entry:properties.entrySet()){ System.out.println(entry.getKey()+":"+entry.getValue()); }
properties类型注入
<property name="properties"> <props> <prop key="key1">value1</prop> <prop key="key2">value2</prop> <prop key="key3">value3</prop> <prop key="key4">value4</prop> </props> </property>
(8)Set的总结
3、构造注入(onstructor)
构造方法
public UserServiceImpl(UserDao userDao) { this.userDao = userDao; }
构造注入
<!--构造注入--> <constructor-arg name="userDao" ref="userDao"></constructor-arg>
面试题:set注入和构造注入有什么区别
1. set注入:通过set方法进行赋值 先创建对象再赋值 2. 构造注入:通过构造方法进行赋值,创建对象的同时进行赋值,强制注入 现实开发中set注⼊更多 1. 构造注⼊麻烦 (重载) 2. Spring框架底层 ⼤量应⽤了 set注⼊
4、自动注入
概念:由Spring工厂自动为成员变量赋值
底层:使用set注入
语法:
- 依赖:将依赖的组件作为本主键的成员变量,提供公开set方法
- 注入 在bean标签中使用autowire属性完成自动注入
<bean id="" class="" autowire="byName|byType"> byName:spring工厂根据成员变量的名字匹配工厂中的组件 (在工厂中找bean的id是成员变量名字的组件) byType:spring工厂根据成员变量的类型匹配工厂中相同类型的组件,如果有赋值,没有不赋值
六、Spring工厂特性
1、Spring工厂创建对象的特性
spring创建对象默认使用单列模式
singleton:单例 默认
在工厂中全局唯一,只创建一次
- prototype: 多例
全局不唯一,每次使用都会创建一个新的对象
<bean id="" class="xxxx.userAction" scope="prototype|singleton"> service,dao -----> singleton(单例) struts2 action -----> prototype(原型,多例,每次都创建一个新的对象)
注意:在项目开发中service,dao组件单例,struts2的Action必须为:多例
prototype:多例 地址不一样
2、Spring工厂创建对象的底层原理 反射+无参构造
Class.forName("bean标签class属性值").new Instances();
3、工厂创建对象的生命周期
何时创建 init-method方法监听,创建时自动调用方法
随着工厂启动, 所有单例bean随之创建 非单例的bean,每次使用时创建
何时销毁 destroy-method方法监听,销毁时自动调用方法
工厂关闭,所有bean随之销毁( 注意: spring对多例bean管理松散,不会负责多例bean的销毁)
单例模式下(饿汉式) Servlet属于懒汉式
创建:启动工厂创建对象
销毁:工厂关闭时组件会自动销毁
多例模式下:
创建:在使用时创建组件对象
销毁:Spring工厂不负责多例组件的销毁
4、bean工厂创建对象的好处
使用配置文件管理java类,再生产环境中更换类的实现时不需要重新部署,修改文件即可
spring默认使用单例的模式创建bean,减少内存的占用
通过依赖注入建立了类与类之间的关系(使java之间关系更为清晰,方便了维护与管理)
七、Spring⼯⼚的底层实现原理(简易版)