Spring-工厂
一、简单工厂和通用工厂
EJB(Enterprise Java Bean)技术的问题
- 运行环境苛刻
- 代码移植性差
EJB是一个重量级的框架
什么是Spring
Spring是一个轻量级的JavaEE解决方案,整合众多优秀的设计模式
- 轻量级
- 对于运行环境是没有额外要求
- 开源 tomcat resion jetty
- 收费 weblogic websphere
- 代码移植性高
- 不需要实现额外接口
- 对于运行环境是没有额外要求
- JavaEE的解决方案
- 对Java开发的每一层都有相关的解决方案
- 整合设计模式
例如:工厂 代理 模板 策略...
设计模式
- 广义概念:
解决面向对象设计中,解决特定问题的经典代码 - 狭义概念:
GOF4人帮定义的23种设计模式
工厂设计模式
什么是工厂设计模式
-
通过工厂类,创建对象
User user = new User(); UserDao userDao = new UserDaoImpl();
-
好处:解耦合
- 耦合:指定是代码间的强关联关系,一方的改变会影响到另一方
- 问题:不利于代码维护
- 例如:UserService userService = new UserServiceImpl();
简单工厂的实现
public void test1(){
UserService userService = BeanFactory.getService();
userService.login("name","zhl");
User user = new User("zhl","123");
userService.register(user);
}
public class BeanFactory{
public static UserService getUserService(){
return new UserServiceImpl();
}
}
对象的创建方式:
- 直接调用构造方法 创建对象 UserService userService = new UserServiceImpl
- 通过反射的形式 创建对象 解耦合
简单工厂的创建
public class BeanFactory {
private static Properties properties = new Properties();
/*
对象的创建方式:
1. 直接调用构造方法 创建对象 UserService userService = new UserServiceImpl();
2. 通过反射的形式 创建对象 解耦合
Class clazz = Class.forName("com.mysite.service.UserServiceImpl");
userService = ((UserService) clazz.newInstance());
*/
static {
try {
InputStream is = BeanFactory.class.getClassLoader().getResourceAsStream("applicationContext.properties");
properties.load(is);
is.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static UserService getUserService(){
UserService userService = null;
try {
Class clazz = Class.forName(properties.getProperty("userService"));
userService = ((UserService) clazz.newInstance());
} catch (Exception e) {
throw new RuntimeException(e);
}
return userService;
}
public static UserDao getUserDao(){
UserDao userDao = null;
try {
Class clazz = Class.forName(properties.getProperty("userDao"));
userDao = ((UserDao) clazz.newInstance());
} catch (Exception e) {
throw new RuntimeException(e);
}
return userDao;
}
}
通用工厂的实现
在一个项目中,需要创建的对象很多,为了避免代码的冗余,重复操作。
解决方案:创建一个通用工厂
/**
* 通用工厂的实现
* @param key applicationContext.properties配置文件中 key的值
* @return Object对象
*/
public static Object getBean(String key){
Object ret = null;
try {
Class clazz = Class.forName(properties.getProperty(key));
ret = clazz.newInstance();
}catch (Exception e){
e.printStackTrace();
}
return ret;
}
二、第一个Spring程序
- 导入pom依赖
<!--导入SpringFrameWord的依赖--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.3.24</version> </dependency>
- Spring的配置文件
- 配置文件的放置位置:任意位置 没有硬性要求
- 配置文件的命名 :没有要求 建议:applicationContext.xml
Spring的核心API
- ApplicationContext
作用:Spring提供的ApplicationContext这个工厂,用于对象的创建
优点:解耦合
- ApplicationContext接口类型
- 接口:屏蔽实现的差异
- 非web环境:ClassPathXmlApplicationContext(main junit)
- web环境:XmlWebApplicationContext
- 重量级资源
- ApplicationContext工厂的对象占用大量内存
- 不会频繁的创建对象:一个应用只会创建一个工厂对象
- ApplicationContext工厂是线程安全的(多线程并发访问)
程序开发步骤:
- 创建类型
- 配置文件的配置 applicationContext.xml
id属性 名字(唯一标识)
class属性 配置全限定类名
\ - 通过工厂类,获得对象
//ApplicationContext
// |-ClassPathXmlApplicationContext
ApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext.xml);
Person person = (Person) ctx.getBean("person");
/**
* Spring工厂提供的其他方法
*/
@Test
public void test3(){
ApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext.xml");
//不需要手动进行强制类型转换
Person person = ctx.getBean("person", Person.class);
System.out.println("person = "+ person); //person = com.mysite.bean.Person@1e7c7811
//保证Spring的配置文件中,只能由一个bean Class 是Person类型
Person person = ctx.getBean(Person.class);
System.out.println("person = "+ person); //person = com.mysite.bean.Person@77f99a05*/
//获取Spring工厂配置文件中所有bean标签的id值
String[] beanDefinitionNames = ctx.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames){
System.out.println("beanDefinitionName ="+beanDefinitionName);
}
//获取某类型的所有名字
String[] beanNamesForType = ctx.getBeanNamesForType(Person.class);
for (String beanName : beanNamesForType){
System.out.println("beanName = " + beanName);
}
//判断是否存在指定Id 值的bean ,不能判断name值
System.out.println(ctx.containsBeanDefinition("person"));
//判断是否存在指定Id 或name(别名)属性 值的bean
System.out.println(ctx.containsBean("person"));
}
- 配置文件中需要注意的细节
- 只配置class属性
使用上述这种配置 Spring默认会生成一个id 值:com.mysite.bean.Person#0
应用场景:如果这个bean 只需要使用一次,那么就可以省略id值
如果这个bean会使用多次,或者被其他bean引用则需要设置id值
- 只配置class属性
- name 属性
作用:用于在Spring的配置文件中,为bean 对象定义别名 (小名)
相同:- ctx.getBean("id|name") -> Object
- <bean id="" class=""
- <bean name="" class=""
区别:
- 别名可以定义多个,id 属性只能有一个值
- 目前 id 的命名是没有要求的
name属性的值,命名没有要求 ,一般很少应用
name属性会应用在特殊命名的场景下:/person (spring + structs1)
- ctx.getBean("id|name") -> Object
Spring工厂的底层实现原理(简易版)
三、Spring5.x与日志框架的整合
Spring 与日志框架进行整合,日志框架就可以在控制台中,输出Spring框架运行过程中的一些重要的信息。
- Spring如何整合日志框架
- Spring 1.2.3x 早期都是以 commons-logging.jar
- Spring5.x默认整合的日志框架 logback log4j2
- Spring5.x整合log4j
- 引入log4j jar包
- 引入log4.properties 配置文件
<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.12</version>
</dependency>
# 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)
什么是注入?
通过Spring工厂及配置文件,为所创建对象的成员变量赋值
为什么需要注入?
如何进行注入?[开发步骤]
- 类的成员变量提供get,set方法
- 配置Spring的配置文件
<bean id="person" class="com.mysite.bean.Person"> <property name="name"> <value>lisi</value> </property> <property name="id"> <value>20</value> </property> </bean>
注入好处
解耦合
Spring注入的原理分析(简易版)
Spring通过底层调用对象属性对应的set方法,完成成员变量的赋值
注入的两种方式
五、Set注入详解
JDK内置类型
- String + 8种基本类型
使用\标签 <property name="name"> <value>lisi</value> </property>
- 数组
<property name="emails"> <list> <value>zhangsan@foxmail.com</value> <value>lisi@foxmail.com</value> <value>wangwu@foxmail.com</value> </list> </property>
- set集合
<!--Set<String> tels--> <property name="tels"> <set> <value>138888888</value> <value>122222222</value> <value>100000000</value> <value>100000000</value> <value>100000000</value> </set> </property>
- List集合
<!--List<String> addresses--> <property name="addresses"> <list> <value>郑州</value> <value>zpark</value> <value>zzuli</value> <value>zzuli</value> </list> </property>
- map集合
<!--Map<String,String> qqs--> <!--注意: map entry key 特有的标签 值 根据对应类型选择 对应类型的标签--> <property name="qqs"> <map> <entry> <key><value>zhl</value></key> <value>23223333</value> </entry> <entry> <key><value>zzz</value></key> <value>99999999</value> </entry> </map> </property>
- Properties
Properties 类型 特殊的Map key=String value=String ,只能存储String类型
value写在标签体之间<property name="p"> <props> <prop key="key1">value1</prop> <prop key="key1">value1</prop> </props> </property>
- 复杂的JDK类型(Date)
需要程序员自定义类型转化器,处理。
用户自定义类型
第一种方式
- 为成员变量提供set get方法
- 配置文件中进行注入(赋值)
<bean name="userService" class="xxx.UserServiceImpl"> <property name="userDao"> <bean class="xxx.UserDaoImpl"/> </property> </bean>
第二种方式
第一种方式存在问题,配置文件的代码冗余,被注入对象(UserDao),多次创建,浪费(JVM)内存资源
- 为成员变量提供set get方法
- 配置文件中进行配置
Set注入的简化写法
基于属性简化
<!--JDK类型注入-->
<property name="name">
<value>zhangsan</value>
</property>
<property name="name" value="zhangsan">
<!--用户自定义类型-->
<property name="userDao">
<ref bean="userDao">
</property>
<property name="userDao" ref="userDao">
注意:value属性 只能简化 8 种基本类型 + String 注入标签
基于p命名空间简化
<!--JDK类型注入-->
<bean id="person" class="xxx.Person">
<property name="name">
<value>zhangsan</value>
</property>
</bean>
<bean id="" class="" p:name="zhangsan"/>
<!--用户自定义类型-->
<bean id="userService" class="xxx.UserServiceImpl">
<property name="userDao">
<ref bean="userDao"/>
</property>
</bean>
<bean id="userService" class="xxx.UserServiceImpl" p:userDao-ref="userDao"/>
六、构造注入
注入:通过Spring的配置文件,为成员变量赋值
Set注入:Spring调用Set方法,通过配置文件,为成员变量赋值
构造注入:Spring调用构造方法,通过配置文件,为成员变量赋值
开发步骤
- 提供有参构造方法
public class Customer implements Serializable { private String name; private Integer age; public Customer(String name, Integer age) { this.name = name; this.age = age; } }
- Spring的配置文件
<bean id="customer" class="com.mysite.bean.Customer"> <constructor-arg> <value>wangwu</value> </constructor-arg> <constructor-arg> <value>32</value> </constructor-arg> </bean>
构造方法重载
参数个数不同时
通过控制\
参数个数相同时
通过在标签中引入 type 属性 进行类型的区分 \
注入的总结
未来的实战中,应用set注入还是构造注入?
答案:set注入更多
- 构造注入麻烦(重载)
- Spring底层大量 使用了 set注入
七、反转控制与依赖注入
反转控制(IOC Inverse Of Control)
控制:对于成员变量赋值的控制权
反转控制:把对于成员变量赋值的控制权,从代码中反转(转移)到Spring工厂和Spring配置文件中完成
好处:解耦合
底层实现:工厂设计模式
依赖注入(DI Dependency Injection)
注入:通过Spring的工厂及配置文件,为对象(bean,组件)的成员变量赋值
依赖注入:当一个类需要另一个类时,就意味着依赖,一旦出现依赖,就可以把另一个类作为本类的成员变量,最终通过Spring配置文件进行注入 (赋值)
好处:解耦合
八、Spring工厂创建复杂对象
- 什么是复杂对象
复杂对象:不能直接通过new 构造方法创建的对象,如 Connection、
SqlSessionFactory
Spring工厂创建复杂对象的三种方式
FactoryBean接口
- 开发步骤
- 实现FactoryBean接口
- Spring配置文件的配置
<bean id="conn" class="com.mysite.factorybean.ConnectionFactoryBean"/>
如果Class 中指定的类型 是FactoryBean 接口的实现类,那么通过id 值获得的是这个类所创建的复杂对象
- 实现FactoryBean接口
正常情况:
<bean id="user" class="com.mysite.bean.User">
ctx.getBean("user"); 获得的是User这个类的对象 User对象
易错点:
<bean id="conn" class="com.mysite.factorybean.ConnectionFactoryBean">
错误认知:
ctx.getBean("conn"); 获得的是ConnectionFactoryBean这个类的对象
FactoryBean接口的实现类:ConnectionFactoryBean
ctx.getBean("conn"); 获得的是它所创建的复杂对象Connection
-
细节
- 如果就想获得FactoryBean类型的对象
- 通过 ctx.getBean("&conn"); 获取ConnectionFactoryuBean对象
- isSingleton方法
- 返回true只会创建一个复杂对象
- 返回false 每一次都会创建新的对象
- 问题:根据这个对象的特点,决定是返回true(SqlSessionFactory) 还是 false(Connection)
- 依赖注入的体会(DI)
- 把ConnectionFactoryBean中依赖的4 个字符串信息,进行依赖注入
- 好处:解耦合
<bean id="conn" class="com.mysite.factorybean.ConnectionFactoryBean"> <property name="driver" value="com.mysql.cj.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/mybatis"/> <property name="user" value="root"/> <property name="password" value="15737979065"/> </bean>
-
FactoryBean的实现原理[简易版]
- 接口回调
- 为什么Spring规定Factory接口 并且 实现 getObject()
- ctx.getBean("conn"); 获得是复杂对象 Connection 而没有获得 ConnectionFactoryBean()
- Spring内部执行流程
- 通过conn 获得 ConnectionFactoryBean类的对象,进而通过instanceof 判断出是FactoryBean接口的实现类
- Spring按照规定 getObject() ----> Connection
- 返回Connection
- FactoryBean总结
- Spring中用于创建复杂对象的一种方式,也是Spring原生提供的,后续使用Spring整合其他框架,大量应用FactoryBean
实例工厂
- 避免Spring框架的侵入
- 避免 implements FactoryBean
- 整合遗留系统
public class ConnectionFactory { public Connection getConnection(){ } } // 接盘的系统 是遗留下来的。我们想要通过这种方式 创建复杂对象,但是由于一些原因 // 此文件可能是.class 文件,我们不能对它直接修改。 // 这时我们可以通过 Spring配置文件的方式 ,以实例工厂的方式 使用此方法创建复杂对象
- 开发步骤
<bean id="connFactory" class="com.mysite.factorybean.ConnectionFactory"/> <bean id="conn" factory-bean="connFactory" factory-method="getConnection"/> <!--创建 ConnectionFactory的对象connFactory调用其中的 getConnection的方法创建 Connection复杂对象-->
- 开发步骤
静态工厂
- 开发步骤
<bean id="conn" class="com.mysite.factorybean.StaticConnectionFactory" factory-method="getConnection">
不需要创建对象,与实例工厂的区别就是 静态工厂里的方法是 静态的,Spring配置文件中不需要实例化工厂,都是为了避免Spring框架的侵入
九、控制Spring工厂创建对象的次数
在复杂对象的创建中:
实现FactoryBean接口,通过isSingleton可以控制工厂创建对象的次数
如何控制简单对象的创建次数?
<bean id="account" scope="singleton|prototype" class="xxx.Account">
singleton:只会创建一次简单对象
prototype:每一次都会创建新的对象
默认值是 singleton
如何控制复杂对象的创建次数?
public class xxx implements FactoryBean{
public boolean isSingleton() {
return false|true;
}
}
实现FactoryBean接口的方式 可以 通过isSingleton方法中
返回值为 true ,表示只会创建一次
返回值为 false ,每一次都会创建新的
除了使用这个方法,都可以通过 scope属性,进行对象创建次数的控制
为什么要控制对象的创建次数?
有些对象只需要创建一次,例如SqlSessionFactory: 一个数据库对应一个单例对象。还有Dao,Service
有些对象需要创建多次,例如Connection:涉及到控制事务,一次连接就创建一个Connection对象。还有SqlSession,Session
- 好处:节省不必要的内存浪费