SpringFramework5.x–学习笔记
一、引言
1.EJB存在的概念
2. 什么是Spring
Spring是一个轻量级的JavaEE解决方案,整合众多优秀的设计模式
- 轻量级
1、对运行环境是没有额外要求的、
开源:tomcat resion jetty
收费:weblogic websphere
2、代码移植性框架
不需要实现额外接口
- JavaEE的解决方案
-整合设计模式
1、工厂模式
2、代理模式
3、模板模式
4、策略模式
。。。
3.设计模式
1).广义概念
面向对象设计中,解决特定问题的经典代码
2).狭义概念
GOF四人帮定义的23种设计模式:工厂、适配器、装饰器、门面、代理、模板。。。。
想了解更多设计模式可以去看小编的另一篇文章 “23种设计模式你知道有哪些吗?”
4.工厂模式
4.1 什么是工厂模式
1.概念:通过工厂类,创建对象
通常我们都是通过下面的方式来创建对象
User user = new User();
UserDAO userDAO = new UserDAOImpl;
2.好处:解耦合
耦合:指的是代码间的强关联关系,一方面的改变会影响到另一方面
问题:不利于代码维护
简单:把接口的实现类,硬编码在程序中
4.2 简单工厂的设计
package com.myspring.test;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
public class BeanFactory {
//通过使用配置文件的方式解耦合工厂方法
private static Properties properties = new Properties();
static {
try {
//第一步 获取iO输入流 读取配置文件
InputStream springContent = BeanFactory.class.getResourceAsStream("/applicationSpringContent.properties");
//第二部 文件内容 封装到 properties集合中 key=ccom.myspring.test.UserServiceImpl
properties.load(springContent);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 创建对象的方式:
* 通过构造方法直接创建
* UserService userService = new UserServiceImpl();
* 通过反射的方式创建 解耦合
* Class clazz =Class.forName("com.myspring.test.UserService");类全限定名
* UserService userService=(UserService) clazz.newInstance();
*
* @return
*/
//通过工厂类创建的UserService工厂方法
public static UserService getUserService() {
UserService userService = null;
try {
Class clazz = Class.forName(properties.getProperty("UserService"));
userService = (UserService) clazz.newInstance();
} catch (ClassNotFoundException e) {
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return userService;
}
//通过工厂类创建的UserDAO工厂方法
public static UserDAO getUserDAO() {
UserDAO userDAO = null;
try {
Class clazz = Class.forName(properties.getProperty("UserDAO"));
userDAO = (UserDAO) clazz.newInstance();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return userDAO;
}
}
//我们后面就可以通过这样的方式来获取对象
UserService userService = BeanFactory.getUserService();
UserDAO userDAO=BeanFactory.getUserDAO();
//配置文件applicationSpringContent.properties
UserService = com.myspring.test.UserServiceImpl
UserDAO = com.myspring.test.UserDAOImpl
4.3 通用工厂设计
- 问题
简单工厂会出现大量冗余
- 解决 通用工厂的代码
//通用工厂创建对象(key代表配置文件种的key)
public static Object getBean(String key){
Object obj=null;
try {
Class clazz=Class.forName(properties.getProperty(key));
obj=clazz.newInstance();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return obj;
}
4.4 通用工厂的使用方式
1.定义类型(类)
2.通过配置文件的配置告知工厂(applicationSpringContent.properties)
key=value
3.通过工厂获取类的对象
Object obj = BeanFactory.getBean(key);
5.总结
spring本质:工厂 ApplicationContent 配置文件(applicationContent.xml)
二、第一个Spring程序
1.软件版本
1.JDK1.8+
2.Maven3.5+
3.IDEA2018+
4.SpringFramework 5.1.4
Spring官方网站 www.spring.io
2.环境搭建
- Spring的jar包
#设置 pom 依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.4.RELEASE</version>
</dependency>
- Spring的配置文件
1.配置文件的放置位置:任意位置 没有硬性要求
2.配置文件的命名 :没有硬性要求 建议: applicationContext.xml
思考:日后应用Spring框架时,需要进行配置文件路径的设置。
下面是我们新建配置文件的方式,聪明的idea给我们提供了快捷的方式来创建配置文件
3.Spring的核心API
- ApplicationContext
作用: Spring提供的ApplicationContext这个工厂,用于对象的创建
好处:解耦合
- ApplicationContext接口类型
接口:屏蔽实现的差异
非web环境:ClassPathXmlApplicationContext (用于main函数、junit单元测试)
web环境:XmlWebApplicationContext
- 重量级资源
AppilicationContext工厂的对象占用大量内存
不会频繁的创建对象:一个应用只会创建一个工厂对象
ApplicationContext:一定是线程安全的(多线程并发访问)
4.程序开发
- 创建类型
这里的类型指的是我们需要的类=====我们这里随便创建了一个person类
2.配置文件配置 applicationContext.xml
<!--id : 唯一名
class:类的全限定名
这里类似我们的Properties文件中的 key和value id = class -->
<bean id="person" class="com.myspring.classdemo.Person"/>
3.通过工厂类获得对象
ApplicatonContext
|-ClassPathXmlApplicationContext
//Spring第一个程序开发
@Test
public void test2() {
//1.获取Spring工厂
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("/applicationContext.xml");
//2.通过工厂 获取对象
Person person = (Person) applicationContext.getBean("person");
//打印一下对象
System.out.println(person);
}
// 控制台输出:com.myspring.classdemo.Person@6b0c2d26
5.细节分析
- 名词解释
Spring工厂创建对象,叫做bean或者组件(component)
-Spring工厂相关的方法
//Spring工厂方法细节分析
@Test
public void test3() {
//获取Spring工厂
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("/applicationContext.xml");
//通过这种重载的方式获取对象 就不需要强制类型转换了
Person person = applicationContext.getBean("person", Person.class);
System.out.println("person="+person);
//通过这种方式获取对象 当前Spring配置文件种只能有一个<bean class为该类的bean标签 否则会报错
//报错信息:No qualifying bean of type 'com.myspring.classdemo.Person' available: expected single matching bean but found 2: person,person1
Person bean = applicationContext.getBean(Person.class);
System.out.println("person=="+person);
//获取Spring配置文件中 所有bean标签的 的id值
String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
System.out.println(beanDefinitionName);
}
//获取Spring配置文件中 所有bean的个数
int beanDefinitionCount = applicationContext.getBeanDefinitionCount();
System.out.println(beanDefinitionCount);
//根据类型xxx.class 获取Spring配置文件中该类型bean标签的id
String[] beanNamesForType = applicationContext.getBeanNamesForType(Person.class);
for (String s : beanNamesForType) {
System.out.println(s);
}
//判断Spring配置文件中是否存在指定id值的bean
//当配置文件显式定义了id时,只判断id 不判断name ,没有显式定义id时,也可以判断name
boolean a = applicationContext.containsBeanDefinition("a");
System.out.println(a);
//判断Spring配置文件中是否存在指定id值的bean
//id 和 name 都可以判断
boolean person1 = applicationContext.containsBean("person");
System.out.println(person1);
//判断当前容器是否包含某个bean,忽略了父类容器中的bean。
boolean person2 = applicationContext.containsLocalBean("person");
System.out.println(person2);
}
- 配置文件中需要注意的细节
1.只配置class属性
<bean class="com.myspring.classdemo.Person"/>
问题:上述这种配置有没有id值?
答:有 “com.myspring.classdemo.Person#0”Spring工厂通过算法帮我们默认生成了一个id值
应用场景:如果这个bean只需要使用一次,那么就可以省略id值
注意事项:如果这个bean会使用多次,或者被其他bean引用则需要设置id值
2.name属性
作用:用于在Spring配置文件中,为bean对象定义别名(小名)
————————————————————————————————————
相同:
1) applicationContext.getBean(“id/name”)—>object 通过这种方式id或者name都可以获取对象
2)<bean id=” “ class=” “/bean> 等效 <bean name=” “ class=” “/bean>
—————————————————————————————————————
区别:
1)别名可以定义多个,但是id属性只能有一个值
2)历史遗留问题>>>Xml的id属性的值,命名要求:必须以字母开头。字母、数字、下划线、连字符、不能以特殊字符开头
name属性的值,命名没有要求 /person BUT(但是)-> name属性会应用在特殊命名的场景下
XML的命名方式到现在ID属性的限制以及不存在了3)代码
//判断Spring配置文件中是否存在指定id值的bean //当配置文件显式定义了id时,只判断id 不判断name ,没有显式定义id时,也可以判断name boolean a = applicationContext.containsBeanDefinition("a"); System.out.println(a); //判断Spring配置文件中是否存在指定id值的bean //id 和 name 都可以判断 boolean person1 = applicationContext.containsBean("person"); System.out.println(person1);
6.Spring工厂的底层实现原理(简易版)
Spring工厂是可以调用对象私有的构造方法创建对象
7.思考
问题:未来在开发过程中,是不是所有的对象,都会交给Spring工厂来创建呢?
回答:理论上 是的,但是有特例:实体对象(entity)是不会交给Spring创建,它是由持久层框架进行创建。
三、Spring5.x与日志框架的整合
Spring与日志框架进行整合,日志框架就可以在控制台中输出Spring框架运行过程中的一些重要信息。
好处:便于了解Spring框架的运行过程,利于程序的调试
- Spring如何整合日志框架
默认
Spring1.2.3早期都是于commons-logging.jar整合
Spring5.x 默认整合的日志框架是 logback/log4j2
我们这里不使用默认的logbak与log4j2,使用log4集成。
Spring5.x 整合log4j
1)引入log4j-jar包
2引入log4j。properties配置文件
pom.xml文件
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.25</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
log4j.properties文件
#resources文件夹根目录下
###配置根
log4j.rootLogger = debug,console
###日志输出到控制台显示
log4j.appender.console=org.apache.log4j.ConsoleAppender
log4j.appender.console.Target=System.out
log4j.appender.console.layout=org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n
四、注入(Injection)
1、什么是注入
通过Spring工厂及配置文件,为所创建对象的成员变量赋值
1.1 为什么需要注入
通过编码的方式为成员变量进行赋值,存在耦合👇
1.2 如何进行注入【开发步骤】
- 类的成员变量提供set get方法
- 配置Spring的配置文件
<bean id="person" class="com.myspring.classdemo.Person">
<property name="id">
<value>111</value>
</property>
<property name="name">
<value>yuxw</value>
</property>
</bean>
1.3注入好处
解耦合
2.Spring注入的原理分析(简易版)
Spring通过底层调用对象属性对应的set方法,完成成员变量的赋值,这种方式我们也称之为set注入
五、set注入详解
针对于不同类型的成员变量,在 《property》 标签,需要嵌套其他标签
《property》
XXXX
《/property》
1. JDK内置类型
1.1 String + 8种基本类型
<value>xxxx</value>
1.2数组
//成员变量:private String[] emils;
<property name="emils">
<list>
<value>This A</value>
<value>This B</value>
<value>This C</value>
<value>This D</value>
</list>
</property>
1.3set集合
//成员变量:private Set<String> tels;
<property name="tels">
//基本类型+String
<set>
<value>123456</value>
<value>23456</value>
<value>3456</value>
</set>
//或者自定义类型的值 指:泛型中的对象类型 如果没有泛型则是Object类型
<ref bean>
<set>
</property>
1.4List集合
//成员变量:private List<String> addresses;
<property name="addresses">
<list>
//基本类型+String
<value>一条大河</value>
<value>两条大河</value>
<value>三条大河</value>
//或者自定义类型的值 指:泛型中的对象类型 如果没有泛型则是Object类型
<ref bean>
<set>
</list>
</property>
1.5Map集合
注意:map ---entry---key有特定的标签<key></key>
值:根据对应类型选择对应的标签
//成员变量: private Map<String,String> qqs;
<property name="qqs">
<map>
<entry>
<key><value>key1</value></key>
<value>value1</value>
</entry>
<entry>
<key><value>key2</value></key>
<ref bean>
</entry>
</map>
</property>
1.6Properties集合
//成员变量:private Properties properties;
<property name="properties">
<props>
<prop key="key1">This value1</prop>
<prop key="key2">This value2</prop>
</props>
</property>
1.7复杂的JDK类型(Date)
需要程序员自定义类型转换器,处理。
在赋值集合时我们要去考虑泛型的问题!!!
因为我们集合中可以存储自定义类的数据类型所以我们要特别的关注一下。
2. 用户自定义类型
2.1 第一种方式
- 为成员变量提供get set 方法
- 配置文件中进行注入(赋值)
<bean name="userService" class="com.myspring.test.UserServiceImpl">
<property name="userDAO">
<bean class="com.myspring.test.UserDAOImpl"></bean>
</property>
</bean>
2.2 第二种方式
- 第一种赋值方式存在的问题
1.配置文件代码冗余
2.被注入的对象(UserDAO),多次创建,浪费(JVM)内存资源
- 为成员变量提供set/get方法
- 配置文件中进行配置
<!--先将需要的对象 声明-->
<bean id="userDAO" class="com.myspring.test.UserDAOImpl"/>
<bean name="userService" class="com.myspring.test.UserServiceImpl">
<property name="userDAO">
<!--再将需要的对象进行引用-->
<ref bean="userDAO"/>
</property>
</bean>
#Spring4.x 废除了 <ref local=""/> 基本等效于 <ref bean=""/>
3.Set注入的简化写法
3.1基于属性简化
JDK类型注入
<property name="name">
<value>xxx</value>
</property>
简化👇
<property name="name" value="xxx"/>
注意!!! value属性只要能简化 8种基本类型+String 注入标签
-------------------------------------------
用户自定义类型
<property name="userDAO">
<ref bean ="userDAO">
</property>
<property name="userDAO" ref="userDAO"/>
3.2基于p命名空间简化
<bean id="person" class="com.myspring.classdemo.Person">
<property name="name">
<value>bugz</value>
</property>
<property name="id">
<value>1233333</value>
</property>
</bean>
简化👇
<bean id="person" class="com.myspring.classdemo.Person" p:name="bugz" p:id="1233333"/>
注意!!! value属性只要能简化 8种基本类型+String 注入标签
-------------------------------------------
用户自定义类型
<!--先将需要的对象 声明-->
<bean id="userDAO" class="com.myspring.test.UserDAOImpl"/>
<bean name="userService" class="com.myspring.test.UserServiceImpl">
<property name="userDAO">
<!--再将需要的对象进行引用-->
<ref bean="userDAO"/>
</property>
</bean>
简化👇
<bean name="userService" class="com.myspring.test.UserServiceImpl" p:userDAO-ref="userDAO" />
六、构造注入
注入:通过Spring的配置文件,为成员变量赋值
Set注入:Spring 调用Set方法 通过配置文件为成员变量赋值
构造注入:Spring 调用构造方法 通过配置文件 为成员变量赋值
1.开发步骤
- 提供有参构造方法
public class Customer implements Serializable {
private Integer id;
private String name;
public Customer(Integer id, String name) {
this.id = id;
this.name = name;
}
}
- Spring的配置文件
<!--构造注入-->
<bean id="customer" class="com.myspring.classdemo.Customer">
<constructor-arg>
<value>12333</value>
</constructor-arg>
<constructor-arg>
<value>bugz</value>
</constructor-arg>
</bean>
2.构造方法重载
2.1 参数个数不同时
通过控制<constructor-arg>标签的数量进行区分
2.2 构造参数个数相同时
通过在标签引入type属性 进行类型的区分<constructor-arg type="类型">
**3.注入的总结
未来实战种,应用set注入还是构造注入?
答案:set注入更多
1.构造注入麻烦(重载)
2. Spring框架底层 大量运用了set注入
七、 反转控制与依赖注入
1.反转控制(IOC Inverse of Control)
控制:对于成员变量赋值的控制权
反转控制:把对于成员变量的赋值的控制权,从代码中反转(转移)到Spring工厂和配置文件中完成
好处:解耦合
底层实现:工厂模式
2.依赖注入
注入:通过Spring的工厂及配置文件,为对象(bean,组件)的成员变量赋值
依赖注入:当一个类需要另一个类时,就意味着依赖,一旦出现依赖,就可以把另一个类作为本类的成员变量,最终通过Spring配置文件进行注入(赋值)。
八、Spring工厂创建复杂对象
1.什么是复杂对象
复杂对象:指的就是不能直接通过new构造方法创建的对象
Connection
SqlSessionFactory
2.Spring工厂创建复杂对象的3种方式
2.1 FactoryBean接口
- 开发步骤
1) 实现FactoryBean接口
2)Spring 配置文件的配置
<!--如果Class中指定的类型是FactoryBean接口的实现类,那么通过id值获得的是这个类所创建的复杂对象 Connection-->
<bean id="conn" class="com.myspring.factorybean.ConnFactoryBean" />
- 细节
1)如果想要获取复杂对象实现类本身的对象 需要这样获取
applicationContext.getBean(“&conn”, ConnFactoryBean.class)
在第一个参数"&XXX" 加一个&符号
2)isSingleton方法
返回 true 只会创建一个复杂对象
返回 false 每次都会创建一个新的复杂对象
问题:根据这个对象的特点,决定是返回true(SqlSessionFactory)还是false(Connection)
3)mysql高版本链接创建时,需要制定SSL证书,解决问题的方式👇
jdbc:mysql://localhost:3306/log?useSSL=false
4)依赖注入体会(DI)
<!--把ConnFactoryBean中依赖的4个字符串信息,进行配置文件的注入-->
<!--好处: 解耦合 -->
<bean id="conn" class="com.myspring.factorybean.ConnFactoryBean">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/log?useSSL=false"/>
<property name="root" value="root"/>
<property name="password" value="123456"/>
</bean>
- FactoryBean的实现原理(简易版)
基于接口回调
1、为什么Spring规定FactoryBean接口 实现 并且 getObeject()?
2、ctx.getBean(“conn”) 获得是复杂对象 Connection 而没有 获得 ConnectionFactoryBean(&)Spring内部运行流程
1、通过conn获得 CnnectionFactoryBean类的对象,进而通过instanceof 判断出是FactoryBean接口的实现类
2、Spring按照规定 getObject —>Connection
3、返回Connection
- FactoryBean总结
Spring中用于创建复杂对象的一种方式,也是Spring原生提供的,后续讲解Spring整合其它框架时会大量应用FactoryBean
2.2 实例工厂
1、避免Spring框架的侵入
2、整合遗留系统
- 开发步骤
public class ConnectionFactory {
public Connection getConnection(){
Connection connection=null;
try {
Class.forName("com.mysql.jdbc.Driver");
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/log?useSSL=false","root","123456");
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
return connection;
}
}
<bean id="connFatory" class="com.myspring.factorybean.ConnectionFactory" ></bean>
<bean id="conn" factory-bean="connFatory" factory-method="getConnection"></bean>
2.3 静态工厂
- 开发步骤
public class StaticConnectionFactory {
public static Connection getConnection(){
Connection conn=null;
try {
Class.forName("com.mysql.jdbc.Driver");
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/log?useSSL=false", "root", "123456");
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
return conn;
}
}
<!--静态工厂配置方式-->
<bean id="conn" class="com.myspring.factorybean.StaticConnectionFactory" factory-method="getConnection"></bean>
3.Spring工厂创建对象的总结
九、控制Spring工厂创建对象的次数
1.如何控制简单对象的创建次数
使用scope标签内属性
<!--控制简单对象创建的次数-->
<bean id="teacher" scope="singleton/prototype" class="com.myspring.classdemo.Teacher" />
singleton:只会创建一次简单对象 默认值
prototype:每次都会创建新的对象
2.如何控制复杂对象的创建次数
FactoryBean{
public Boolean isSingleton(){
return true; 只会创建一次
return false; 每一次都会创建新的
}
}
如果没有isSingleton方法 还是通过scope属性 进行对象创建次数的控制
3.为什么要控制对象的创建次数?
好处:节省不必要的内存浪费
- 什么样的对象之创建一次?
(1)SqlSessionFaction
(2)DAO
(3)Service
。。。
- 什么样的对象每一次都要创建新的?
(1)Connection
(2)SqlSession | Session
(3)Struts2 中的Action
。。。