文章目录
1. 从jdbc的标准代码了解程序的耦合性
import java.sql.Connection;
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
public class JdbcDemo1 {
public static void main(String[] args) throws Exception{
//Class.forName("com.mysql.jdbc.Driver");
DriverManager.registerDriver(new com.mysql.jdbc.Driver());
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test?characterEncoding=utf-8", "root", "123456");
PreparedStatement ps = conn.prepareStatement("insert into account(name, money) values(?,?)");
ps.setString(1,"bbb");
ps.setFloat(2,2000);
ps.executeUpdate();
ps.close();
conn.close();
}
}
程序执行过程,
- 注册驱动
- 获取链接
- 获取操作数据库的预处理对象
- 执行sql语句,得到结果集(本例是新增操作,故没有结果集)
- 遍历结果集
- 依次关闭结果集,预处理对象,连接。
当我们把相关jar包去掉
可以发现,没有myql的驱动,将无法编译。此时可以理解为程序的耦合。
简单理解耦合:程序之间的依赖关系
耦合包括:
- 类之间的依赖关系
- 方法之间的依赖关系
如何解耦
降低程序之间的依赖
- 编译期间不依赖,运行时才依赖(典型例子:
DriverManager.registerDriver()
转化为用反射来加载驱动Class.forName
)
解耦的思路:
- 使用反射来创建对象,避免使用new关键字。
- 通过读取配置文件来获取要创建的对象全限定类名。
2. 基本的三层
dao层
package com.ssm.dao.impl;
import com.ssm.dao.IAccountDao;
public class AccountDao implements IAccountDao {
public void saveAccount() {
System.out.println("账户已保存!");
}
}
package com.ssm.dao;
/**
* 账户的持久层接口
*/
public interface IAccountDao {
//模拟保存账户
void saveAccount();
}
service层
package com.ssm.service.impl;
import com.ssm.dao.IAccountDao;
import com.ssm.dao.impl.AccountDao;
import com.ssm.factory.BeanFactory;
import com.ssm.service.IAccountService;
import java.util.PriorityQueue;
public class AccountServiceImpl implements IAccountService {
private IAccountDao accountDao = new AccountDao();
public void saveAccount(){
accountDao.saveAccount();
}
}
package com.ssm.service;
/**
* 账户业务层
*/
public interface IAccountService {
//保存账户
void saveAccount();
}
模拟的controller层(servlet)
package com.ssm.ul;
import com.ssm.factory.BeanFactory;
import com.ssm.service.IAccountService;
import com.ssm.service.impl.AccountServiceImpl;
/**
模拟表现层
*/
public class Client {
public static void main(String[] args){
IAccountService as = new AccountServiceImpl();
as.saveAccount();
}
}
存在的问题
- controller层调用service层使用了new关键字,如果
AccountServiceImpl.java
不存在,则会编译不通过。代码耦合性太强。 - 假如有多次调用controller(main()多次执行),每次执行都会new一个AccountServiceImpl,AccountDao。假如执行5次controller,就会有10个service和dao对象在内存中。在高并发下性能不好
3. 使用工厂模式解决代码耦合性问题
什么是简单工厂模式
简单工厂模式又称为静态工厂模式,它属于创建型模式,但非23种设计模式之一。它可以根据传入的参数来返回不同类的实例。简单工厂模式专门定义一个类来负责创建其他类的实例,被创建的实例通常都具有共同的父类。
角色 | 作用 |
---|---|
工厂角色(Creator) | 是简单工厂模式的核心,它负责实现创建所有具体产品类的实例。工厂类可以被外界直接调用,创建所需的产品对象。 |
抽象产品角色(Product) | 是所有具体产品角色的父类,它负责描述所有实例所共有的公共接口。 |
具体产品角色(Concrete Product) | 继承自抽象产品角色,一般为多个,是简单工厂模式的创建目标。工厂类返回的都是该角色的某一具体产品。 |
简单工厂模式如何解耦(后续有待深究)
直接new一个对象是最简单的创建对象的方式。但是它也加强了两个对象A和B之间的耦合性。使用简单工厂模式,在A想使用B对象的时候,可以向工厂角色(Creator)请求创建一个实例对象,这样就避免了直接实例化B对象。降低了耦合性。
使用工厂模式解决代码耦合
package com.ssm.factory;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
/**
* 创建一个bean对象的工厂
*/
public class BeanFactory {
private static Properties props;
static {
try {
props = new Properties();
//getResourceAsStream(String path):默认则是从ClassPath根下获取,path不能以’/'开头,最终是由ClassLoader获取资源。
InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
System.out.println(in);
props.load(in);
} catch (IOException e) {
e.printStackTrace();
throw new ExceptionInInitializerError("初始化properties失败!");
}
}
/**
* 根据Bean名称获取Bean对象
* @param beanName
* @return
*/
public static Object getBean(String beanName){
Object bean = null;
try {
String beanPath = props.getProperty(beanName);
System.out.println("beanPath:"+beanPath);
bean = Class.forName(beanPath).newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return bean;
}
}
工厂读取的配置文件
accountService=com.ssm.service.impl.AccountServiceImpl
accuountDao=com.ssm.dao.impl.AccountDao
修改各处的对象实例化方式
比如controller
//IAccountService as = new AccountServiceImpl();
IAccountService as = (IAccountService) BeanFactory.getBean("accountService");
4. 使用单例模式调对象复用性
前面已经使用简单工厂模式降低了Client
对AccountServiceImpl
的耦合性,但是代码仍然存在一个问题,那就是每次调用main()
都会让BeanFactory.getBean()
实例化一个对象,多次调用就会有多个实例对象产生,从而影响代码性能。那么如何让程序在运行中只让一个类只实例化一个对象呢?
让工厂不仅负责实例化对象还管理对象
前面实例化对象的工作是交给BeanFactory
的,每次有实例化对象的请求过来,它就是实例化一个请求。那么能不能让BeanFactory
先实例化所有对象,把它们管理起来。每次有实例化请求过来,就返回需要实例化对象的引用。这样就能保证内存中IAccountService
只有一个实例化对象。
修改BeanFactory
修改静态代码块
static {
try {
props = new Properties();
//getResourceAsStream(String path):默认则是从ClassPath根下获取,path不能以’/'开头,最终是由ClassLoader获取资源。
InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
props.load(in);
beans = new HashMap<String, Object>();
Enumeration keys = props.keys();
while (keys.hasMoreElements()){
String key = keys.nextElement().toString();
String keyPath = props.getProperty(key);
Object bean = Class.forName(keyPath).newInstance();
beans.put(key,bean);
}
} catch (Exception e) {
e.printStackTrace();
throw new ExceptionInInitializerError("初始化properties失败!");
}
}
此时,BeanFactory中已经有所需要的对象实例了,那么获取bean的方法也要改
public static Object getBean(String beanName){
return beans.get(beanName);
}
什么是单例模式
单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。与前面说的简单工厂模式一样,这种类型的设计模式也属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
5. 开始Spring框架
抽象前面的模型
更改前的
更改后的
5.1 控制反转IOC
百度词条解释
作用:降低程序代码的耦合(解除代码间的依赖关系)
5.2 创建一个spring工程
首先,创建一个基本的maven工程。
配置pom文件,导入jar包
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.0.RELEASE</version>
</dependency>
创建配置文件bean.xml
- 创建配置文件
- 导约束
- 把对象的创建交给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">
<!--把对象的创建交给spring来管理-->
<bean id="accountService" class="com.ssm.service.impl.AccountServiceImpl" ></bean>
<bean id="accuountDao" class="com.ssm.dao.impl.AccountDao"></bean>
</beans>
Client的配置
public class Client {
public static void main(String[] args){
ApplicationContext applicationContext = null;
applicationContext = new ClassPathXmlApplicationContext("bean.xml");
//两种获取实例的方式
IAccountService as = (IAccountService) applicationContext.getBean("accountService");
IAccountDao accountDao = applicationContext.getBean("accuountDao",IAccountDao.class);
System.out.println(as);
System.out.println(accountDao);
}
}
ApplicationContext
的三个实现类:
ClassPathXmlApplicationContext
:加载类路径下的配置文件,要求配置文件必须在类路径下。否则加载不了。FileSystemXmlApplicationContext
:加载磁盘任意路径下的配置文件(必须要有访问权限)AnnotationConfigApplicationContext
:用于读取注解创建容器。
5.3 Spring是如何加载配置文件(Spring是什么时候实例化对象的)
前面说过,Spring的IOC指的是Spring代替Application管理我们的JavaBean。那么他是什么时候实例化这些JavaBean的呢?
一般有两种情况
- 一次性创建所有的对象,就是说Spring一次性实例化所有的对象
- 按需要加载对象,就是说需要哪个对象,Spring再来帮我们实例化它
这就对应着Spring核心容器的两个接口,ApplicationContext
和 BeanFactory
。其中
ApplicationContext
:构建核心容器时,创建对象采取的策略是立即加载的方式。读完配置文件就创建配置文件中的对象。BeanFactory
:构建核心容器时,创建对象采取的策略是延迟加载的方式。什么时候根据id获取对象,什么时候创建对象。
实验代码
public static void main(String[] args){
// ApplicationContext applicationContext = null;
applicationContext = new FileSystemXmlApplicationContext("");
applicationContext = new AnnotationConfigApplicationContext("");
// applicationContext = new ClassPathXmlApplicationContext("bean.xml");
//
// //两种获取实例的方式
// IAccountService as = (IAccountService) applicationContext.getBean("accountService");
// IAccountDao accountDao = applicationContext.getBean("accuountDao",IAccountDao.class);
//
// System.out.println(as);
// System.out.println(accountDao);
//==============使用BeanFactory创建对象==============
BeanFactory factory = null;
Resource rs = new ClassPathResource("bean.xml");
factory = new XmlBeanFactory(rs);
IAccountService as = (IAccountService) factory.getBean("accountService");
System.out.println(as);
}
对应的AccountServiceImpl
代码
public class AccountServiceImpl implements IAccountService {
private IAccountDao accountDao = new AccountDao();
//重写构造器,看看该对象是什么时候创建的
public AccountServiceImpl(){
System.out.println("AccountServiceImpl被实例化了!");
}
public void saveAccount(){
accountDao.saveAccount();
}
}
6. Spring对bean的管理
6.1 创建bean的三种方式
6.1.1 使用默认构造函数创建bean
在spring的配置文件中使用<bean></bean>
标签,配以id
和class
属性,并且标签中没有其他属性的时候。采用的就是默认类的构造函数创建bean对象。
<bean id="accountService" class="com.ssm.service.impl.AccountServiceImpl" ></bean>
此时如果AccountServiceImpl
没有默认构造函数(构造函数被重写了),则无法创建对象。
报错,没有发现默认的构造器。
Failed to instantiate [com.ssm.service.impl.AccountServiceImpl]: No default constructor found; nested exception is java.lang.NoSuchMethodException: com.ssm.service.impl.AccountServiceImpl.<init>()
6.1.2 使用普通工厂中的方法创建对象
假设一个情景
如果我们现在要用第三方jar包中的某个类,但是这个类的构造函数被重写了。那么我们该如何实例化这个类?
比如上面的AccountServiceImpl
没有默认的构造函数,如何实例化它呢?
此时需要借助一个工厂类InstanceFactory
,通过它来实例化我们的对象。
package com.ssm.factory;
import com.ssm.service.IAccountService;
import com.ssm.service.impl.AccountServiceImpl;
public class InstanceFactory {
public IAccountService getAccountService(){
return new AccountServiceImpl("用工厂实例化AccountServiceImpl");
}
}
<bean id="instanceFactory" class="com.ssm.factory.InstanceFactory"></bean>
<bean id="accountService" factory-bean="instanceFactory" factory-method="getAccountService"></bean>
过程是先实例化instanceFactory
,再调用instanceFactory
中的getAccountService
方法。
6.1.3 使用工厂中的静态方法创建对象
与上面的类似,只是不需要实例化工厂类了。直接调用工厂类中的静态方法。具体如下
<!--使用工厂中的静态方法创建对象-->
<bean id="accountService" class="com.ssm.factory.StaticFactory" factory-method="getAccountService"></bean>
package com.ssm.factory;
import com.ssm.service.IAccountService;
import com.ssm.service.impl.AccountServiceImpl;
public class StaticFactory {
public static IAccountService getAccountService(){
return new AccountServiceImpl("使用静态方法实例化AccountServiceImpl");
}
}
可以在没有实例化StaticFactory
情况下,使用它创建一个AccountServiceImpl
对象。
回顾下static的知识
在Java类中,有六种成员:属性、方法、构造器、初始化块、内部类和枚举类,其中static可以修饰属性、方法、初始化块、内部类和枚举类。
以static修饰的的成员就是类成员。类成员属于整个类,而不是单个对象。
所以上面可以无需实例化StaticFactory 来创建AccountServiceImpl。因为类方法在类加载到jvm的时候就可以用了。
6.2 bean的作用范围
bean
标签有一个scope属性:用于指定bean的作用范围。
取值 | 作用 |
---|---|
singleton | 单例(默认值) |
prototype | 多例 |
request | 作用于web应用的请求范围 |
session | 作用于web应用的会话范围 |
global-session | 作用于集群环境的会话范围(全局会话范围),当不是集群环境时,等价于session |
6.3 bean的生命周期
单例对象:我与容器共生死!!!
多例对象:
出生:使用对象时,由spring框架为我们创建
或者:只要是使用,就一直活着
死亡:长时间不用,且没有别的对象引用,有GC负责回收
实验,单例对象
<bean id="accountService" class="com.ssm.service.impl.AccountServiceImpl" scope="singleton" init-method="init" destroy-method="destory"></bean>
public class AccountServiceImpl implements IAccountService {
//private IAccountDao accountDao = new AccountDao();
public AccountServiceImpl(){
System.out.println("AccountServiceImpl被实例化了!");
}
public void saveAccount(){
System.out.println("保存账户成功!");
//accountDao.saveAccount();
}
public void init(){
System.out.println("AccountServiceImpl对象创建了");
}
public void destory(){
System.out.println("AccountServiceImpl对象销毁了");
}
}
Client片段
public static void main(String[] args) throws Exception{
ClassPathXmlApplicationContext applicationContext = null;
// applicationContext = new FileSystemXmlApplicationContext("");
// applicationContext = new AnnotationConfigApplicationContext("");
applicationContext = new ClassPathXmlApplicationContext("bean.xml");
//两种获取实例的方式
IAccountService as = (IAccountService) applicationContext.getBean("accountService");
System.out.println(as);
as.saveAccount();
//手动关闭容器
applicationContext.close();
Thread.sleep(3000);
}
结果:
spring创建时,先调用构造器,在调用init-method
对应的方法,摧毁时调用destroy-method
对应的方法