框架前奏
穿插之前的JAVAWEB项目,之后的Spring框架。
JAVAWEB项目:用到技术点
-
MVC模式
-
数据库连接技术(JDBC)
-
分层思想
1.先创建JAVAWEB项目(IDEA)
使用tomcat,配置运行一下
2.src下的目录
某新闻系统,主题为例,Topic
搭建框架
-
com.aaa.dao:数据访问层的包
-
com.aaa.dao.impl:数据访问层接口的实现类
-
com.aaa.service:业务层的包,主要放业务层接口
-
com.aaa.service.impl:业务层包的实现类:主要放业务层接口实现类
-
com.aaa.controller:Servlet,去调用业务层的接口及其实现类。
controller是之前的写法,今天的测试,换成了一个test包,在test包下建立一个test类,来测试业务层接口及其实现类。
这个其实和使用servlet测试是一样。
刚才我们讲了,搭建。在控制台做了测试输出。
3.setter解耦
数据库mysql,oracle
有mysql的数据访问层的实现,有oracle的数据访问层的实现。
业务层,这里实现的是TopicDaoMysqlImpl,针对的mysql数据库的操作。
我们现在的操作都变了,要去操作oracle数据库。怎么办呢???
TopicDao topicDao=new TopicDaoMysqlImpl();
代码改变:
TopicDao topicDao=new TopicDaoOracleImpl();
运行之后,可以看到,会改变为删除oracle数据库中的主题编号:
大家要总结一下,当我们的数据库变更的时候,这样操作的方式好不好呢???
假如说,我们的项目已经部署上线了,项目在运行,你来改变代码来了。不能说肯定会出现问题,但是出现问题的几率一定是比较大。
能不能有一种方式,由客户自己来决定变更使用哪种数据库或者是客户自己决定某种操作。
这种问题,其实就是程序的耦合问题。
之前学习过OOP,三大特征:封装、继承、多态。
要实现的目标是:高内聚、低耦合、高复用
我们在写程序的时候,尽量低耦合。不new具体的对象;
重点代码:
package com.aaa.service.impl;
import com.aaa.dao.TopicDao;
import com.aaa.dao.impl.TopicDaoMysqlImpl;
import com.aaa.dao.impl.TopicDaoOracleImpl;
import com.aaa.service.TopicService;
/**
* Created by 张晨光 on 2020/6/1 16:19
* 主题业务层的实现类;java程序员 调用dao层,不改.
* 调用dao层的接口及其实现;
*/
public class TopicServiceImpl implements TopicService {
//定义dao层对象,及其实现类;
//TopicDao topicDao=new TopicDaoMysqlImpl();
//TopicDao topicDao=new TopicDaoOracleImpl();
//这种方式:是通过new 出来dao层对象的具体的实现类。
//能不能有一种方式,交由客户自己来调用的时候来实现呢???
//尽量少的动代码;
//这种其实就是程序之间的耦合问题。
//********************松耦合的实现************
TopicDao topicDao;
//创建它的getter/setter
public TopicDao getTopicDao() {
return topicDao;
}
//重点看这个:参数是一个TopicDao的对象;
public void setTopicDao(TopicDao topicDao) {
this.topicDao = topicDao;
}
@Override
public void deleteTopicById(Integer id) {
//在方法里面,通过dao层对象去执行删除操作;
//注意看:这里面有topicDao的对象的new ???没有了
topicDao.deleteTopicById(id);
}
}
客户端的使用:
TopicService topicService=new TopicServiceImpl();
//在业务层实现类代码里面,已经没有了new TopicDaoOracleImpl的代码,我们的目标就是去掉这个
//代码,实现松耦合
//通过setter来实现
//((TopicServiceImpl) topicService).setTopicDao(new TopicDaoOracleImpl());
//客户变更为了mysql
((TopicServiceImpl) topicService).setTopicDao(new TopicDaoMysqlImpl());
//由原来在业务层实现类的具体的new xxxImpl,交由客户端test类自动选择,我要new。
//
topicService.deleteTopicById(3);//模拟实现;
总结:
-
耦合度:模块间的耦合度是指模块之间的依赖关系;java里面的耦合度指的是类、接口、实现之间的关联程度。
-
解耦:通过把创建接口对象的setter()方法,在客户端由客户决定哪种数据库的使用。
-
高内聚:尽可能类的每个成员方法只完成一件事(最大限度的聚合)
-
之前的数据库连接代码,放在了BaseDao里面,是写死的,需要修改也得改代码。可以放在db.properties配置文件,也是解耦的一种表现。
作业:
- 使用UserDao来解耦之前JAVAWEB代码,模仿TopicDao。
---************
首先是熟练操作IDEA,创建WEB项目,使用tomcat,配置、部署运行等。
复习:
面向对象的目标:高内聚、低耦合、高复用
原来JAVAWEB项目存在高耦合的情况,我们要解耦,我们使用的接口对象的setter()方法进行解耦操作。
4.BeanFactory解耦
程序的耦合
耦合:程序间的依赖关系,包括:
a.类之间的关系
b.方法间的关系
解耦:降低程序间的依赖关系
为bug生,为bug死,为bug奋斗终生
为解耦生,为解耦死,为解耦奋斗终生--》只要项目你不断完善,就存在类、方发、模块、项目之间的耦合度。
实际开发中:
应该做到,编译时不依赖,运行时依赖
解耦的思路:
第一步:使用反射来创建对象,而避免使用new关键字。Class.forName("")
第二步:通过读取配置文件来获取要创建的对象全限定类名。xxx.properties/xxx.xml文件
使用xml文件的解耦,这个将来和spring框架会联系起来,将我们的技术串起来。以听为主,不用记忆。
1.在src下建立beans.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<beans>
<!--beans:指的所有bean的集合,使用xml文件进行解耦-->
<!--bean:单个bean的配置文件;id:bean的编号;class:bean路径-->
<!--class:mysql下数据库的实现类;给这个类起id名topicDaoMysqlImpl-->
<bean id="topicDaoMysqlImpl" class="com.aaa.dao.impl.TopicDaoMysqlImpl"></bean>
<!--topicServiceImpl解耦-->
<bean id="topicServiceImpl" class="com.aaa.service.impl.TopicServiceImpl"></bean>
</beans>
bean:咖啡豆;在java 组件
JAVABEAN:指的就是JAVA里面的可重用组件,玩具:可以由若干零部件组成,组件(组成的零件),在JAVA里面,业务层的接口、接口的实现类、dao层接口、dao层接口的实现类,可以被多次调用(被重用),这些被重用的类、接口都是JAVABEAN。
JAVABEAN的范围远大于实体类,实体类仅仅指的是业务传递数据时的一种重用形态。
2.建立BeanFactory类,操作xml文件
2.1 jar包准备
IDEA的web-INF创建,lib目录,然后把xml的jar放进去,然后Add as Library
2.2 使用BeanFactory来获取bean
package com.aaa.util;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
/**
* Created by 张晨光 on 2020/6/2 15:01
* Bean:重用组件;--》JAVABEAN;
* Factory:工厂的意思;BeanFactory:Bean的工厂;
* 顾名思义:批量创建Bean;操作beans.xml文件,创建bean
*/
public class BeanFactory {
//定义静态方法根据id来获取Bean对象;
public static Object getBean(String id){
//用类加载器解析xml文件
try {
SAXReader reader = new SAXReader(); //xml的reader对象
//getClassLoader():类加载器,getResourceAsStream:将资源加载为流;
Document document = reader.read(BeanFactory.class.getClassLoader().getResourceAsStream("beans.xml"));
//xpath技术获得bean元素;相当于之前js:document.getElementById("id")
Element element = (Element) document.selectSingleNode("//bean[@id='"+id+"']");
//拿到class属性里面的值
String value = element.attributeValue("class");
//获得反射对象
System.out.println(value);
//通过类的全限定名加载类对象[包名+类名];得到value类的class(TopicDaoMysqlImpl):它的类信息;
//是不是可以想到:Class.forName("com.mysql.cj.jdbc.Driver")
Class clazz = Class.forName(value);
//获得实例
return clazz.newInstance();
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException();
}
}
}
3.测试类
//2.1通过BeanFactory来获取一下;
TopicService topicService = (TopicService) BeanFactory.getBean("topicService");
//2.直接调用即可
topicService.deleteTopicById(2);
总结:
1.通过建立配置文件xml解耦
2.建立BeanFactory解析xml文件,使用反射机制来读取类信息,创建类实例,实现自动创建对象的操作。
5.数据连接方式解耦
5.1 BaseDao的问题
BaseDao这个JAVA类,包含如下java代码,与mysql数据库进行链接。
当我们的项目部署到客户服务器上的时候,客户的user,password,news(dbname)有变化,可能也有多次更改,不可能由程序员再多次更改我们的java代码。这是不合理的。怎么办?
思路:
将这些需要配置的内容,放置到外部的配置文件
static String driver="com.mysql.cj.jdbc.Driver";
static String url="jdbc:mysql://localhost:3306/news?useTimezone=true&serverTimezone=CTT&useUnicode=true&characterEncoding=utf8&useSSL=false";
static String user="root";
static String password="root";
5.2 使用外部的db.properties文件
在src下根目录下,建立db.properties文件,这种文件和xml文件不同,里面放置的是key=value,格式的字符串
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/news? useTimezone=true&serverTimezone=CTT&useUnicode=true&characterEncoding=utf8&useSSL=false
user=root
password=root
5.3 使用Properties类
我们这时候,已经使用了外部的配置文件,读取这个外部的配置文件。
Properties(Java.util.Properties),该类主要用于读取Java的配置文件,不同的编程语言有自己所支持的配置文件,配置文件中很多变量是经常改变的,为了方便用户的配置,能让用户够脱离程序本身去修改相关的变量设置。就像在Java中,其配置文件常为.properties文件,是以键值对的形式进行参数配置的。
//读取外部的配置文件,直接写,使用类加载机制,读取;将读取的文件,转换为输入字节流对象
InputStream is=BaseDaoTwo.class.getClassLoader().getResourceAsStream("db.properties");
load(InputStream inStream) 从输入字节流中读取属性列表(键和元素对)。
getProperty(String key) 在此属性列表中搜索具有指定键的属性。
static Properties properties=new Properties();
//读取外部的配置文件,直接写,使用类加载机制,读取;将读取的文件,转换为输入字节流对象
static InputStream is=BaseDaoTwo.class.getClassLoader().getResourceAsStream("db.properties");
//如何读取,读取外部4个key
//定义四个静态的String变量;
static String driver,url,user,password;
//1.获取公共的静态连接资源;
public static Connection getConn(){
try {
//加载到properties对象里面
properties.load(is);
//利用getProperty()得到key信息
driver=properties.getProperty("driver");
url=properties.getProperty("url");
user=properties.getProperty("user");
password=properties.getProperty("password");
//加载驱动
Class.forName(driver);
//返回连接;
conn= DriverManager.getConnection(url,user,password);
} catch (Exception e) {
e.printStackTrace();
}
return conn;
}
完毕之后,大家自行测试以下即可,发现可以实现与之前一样的连接功能。
好处:
是将之前在BaseDao java类里面写的数据库连接代码,配置信息,放到了外部文件。注意,以后,配置文件基本上都会存储数据库配置信息。
6.连接池技术
6.1什么情况下使用连接池?
对于一个简单的数据库应用,由于对于数据库的访问不是很频繁。这时可以简单地在需要访问数据库时,就新创建一个连接,用完后就关闭它,这样做也不会带来什么明显的性能上的开销。如果程序出现异常而未能关闭,将会导致数据库系统中的内存泄漏,最终将导致重启数据库。
但是对于一个复杂的数据库应用,有若干个数据库连接,比如说10000个,情况就完全不同了。数据库连接频繁的建立、关闭连接,会极大的减低系统的性能,因为对于连接的使用成了系统性能的瓶颈。连接过多,也可能导致内存泄漏,服务器崩溃。
6.2连接池的思想
为解决传统开发中的数据库连接问题,可以采用数据库连接池技术。
数据库连接池的基本思想就是为数据库连接建立一个“缓冲池”。预先在缓冲池中放入一定数量的连接,当需要建立数据库连接时,只需从“缓冲池”中取出一个,使用完毕之后再放回去。
数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是重新建立一个。
数据库连接池在初始化时将创建一定数量的数据库连接放到连接池中,这些数据库连接的数量是由最小数据库连接数来设定的。无论这些数据库连接是否被使用,连接池都将一直保证至少拥有这么多的连接数量。连接池的最大数据库连接数量限定了这个连接池能占有的最大连接数,当应用程序向连接池请求的连接数超过最大连接数量时,这些请求将被加入到等待队列中。
用户每次请求都需要向数据库获得链接,而数据库创建连接通常需要消耗相对较大的资源,创建时间也较长。假设网站一天10万访问量,数据库服务器就需要创建10万次连接,极大的浪费数据库的资源,并且极易造成数据库服务器内存溢出、拓机。如下图所示:
数据库连接是一种关键的有限的昂贵的资源,这一点在多用户的网页应用程序中体现的尤为突出.对数据库连接的管理能显著影响到整个应用程序的伸缩性和健壮性,影响到程序的性能指标.数据库连接池正式针对这个问题提出来的.数据库连接池负责分配,管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是重新建立一个。如下图所示:
数据库连接池的最小连接数和最大连接数的设置要考虑到以下几个因素:
- 最小连接数:是连接池一直保持的数据库连接,所以如果应用程序对数据库连接的使用量不大,将会有大量的数据库连接资源被浪费.
- 最大连接数:是连接池能申请的最大连接数,如果数据库连接请求超过次数,后面的数据库连接请求将被加入到等待队列中,这会影响以后的数据库操作
- 如果最小连接数与最大连接数相差很大:那么最先连接请求将会获利,之后超过最小连接数量的连接请求等价于建立一个新的数据库连接.不过,这些大于最小连接数的数据库连接在使用完不会马上被释放,他将被放到连接池中等待重复使用或是空间超时后被释放.
6.3数据库连接池技术的优点
1.资源重用
由于数据库连接得以重用,避免了频繁创建,释放连接引起的大量性能开销。在减少系统消耗的基础上,另一方面也增加了系统运行环境的平稳性。
2.更快的系统反应速度
数据库连接池在初始化过程中,往往已经创建了若干数据库连接置于连接池中备用。此时连接的初始化工作均已完成。对于业务请求处理而言,直接利用现有可用连接,避免了数据库连接初始化和释放过程的时间开销,从而减少了系统的响应时间
3.新的资源分配手段
对于多应用共享同一数据库的系统而言,可在应用层通过数据库连接池的配置,实现某一应用最大可用数据库连接数的限制,避免某一应用独占所有的数据库资源
4.统一的连接管理,避免数据库连接泄露
在较为完善的数据库连接池实现中,可根据预先的占用超时设定,强制回收被占用连接,从而避免了常规数据库连接操作中可能出现的资源泄露
6.4连接池的实现
c3p0 dhcp
数据库连接池的基本原理是在内部对象池中维护一定数量的数据库连接,并对外暴露数据库连接获取和返回方法。
外部使用者可通过 getConnection 方法获取连接,使用完毕后再通过 close 方法将连接返回,注意此时连接并没有关闭,而是由连接池管理器回收,并为下一次使用做好准备。
Java 中有一个 DataSource 接口, 数据库连接池就是 DataSource 的一个实现。
Druid(德鲁伊)是阿里巴巴开发的号称为监控而生的数据库连接池,Druid是目前最好的数据库连接池。在功能、性能、扩展性方面,都超过其他数据库连接池,同时加入了日志监控,可以很好的监控DB池连接和SQL的执行情况。Druid已经在阿里巴巴部署了超过600个应用,经过一年多生产环境大规模部署的严苛考验。
属性 | 说明 | 建议值 |
---|---|---|
url | 数据库的jdbc连接地址。一般为连接oracle/mysql。示例如下: | |
mysql : jdbc:mysql://ip:port/dbname?option1&option2&… | ||
oracle : jdbc:oracle:thin:@ip:port:oracle_sid | ||
username | 登录数据库的用户名 | |
password | 登录数据库的用户密码 | |
initialSize | 启动程序时,在连接池中初始化多少个连接 | 10-50已足够 |
maxActive | 连接池中最多支持多少个活动会话 | |
maxWait | 程序向连接池中请求连接时,超过maxWait的值后,认为本次请求失败,即连接池 | 100 |
没有可用连接,单位毫秒,设置-1时表示无限等待 | ||
minEvictableIdleTimeMillis | 池中某个连接的空闲时长达到 N 毫秒后, 连接池在下次检查空闲连接时,将 | 见说明部分 |
回收该连接,要小于防火墙超时设置 | ||
net.netfilter.nf_conntrack_tcp_timeout_established的设置 | ||
timeBetweenEvictionRunsMillis | 检查空闲连接的频率,单位毫秒, 非正整数时表示不进行检查 | |
keepAlive | 程序没有close连接且空闲时长超过 minEvictableIdleTimeMillis,则会执 | true |
行validationQuery指定的SQL,以保证该程序连接不会池kill掉,其范围不超 | ||
过minIdle指定的连接个数。 | ||
minIdle | 回收空闲连接时,将保证至少有minIdle个连接. | 与initialSize相同 |
removeAbandoned | 要求程序从池中get到连接后, N 秒后必须close,否则druid 会强制回收该 | false,当发现程序有未 |
连接,不管该连接中是活动还是空闲, 以防止进程不会进行close而霸占连接。 | 正常close连接时设置为true | |
removeAbandonedTimeout | 设置druid 强制回收连接的时限,当程序从池中get到连接开始算起,超过此 | 应大于业务运行最长时间 |
值后,druid将强制回收该连接,单位秒。 | ||
logAbandoned | 当druid强制回收连接后,是否将stack trace 记录到日志中 | true |
testWhileIdle | 当程序请求连接,池在分配连接时,是否先检查该连接是否有效。(高效) | true |
validationQuery | 检查池中的连接是否仍可用的 SQL 语句,drui会连接到数据库执行该SQL, 如果 | |
正常返回,则表示连接可用,否则表示连接不可用 | ||
testOnBorrow | 程序 申请 连接时,进行连接有效性检查(低效,影响性能) | false |
testOnReturn | 程序 返还 连接时,进行连接有效性检查(低效,影响性能) | false |
poolPreparedStatements | 缓存通过以下两个方法发起的SQL: | true |
public PreparedStatement prepareStatement(String sql) | ||
public PreparedStatement prepareStatement(String sql, | ||
int resultSetType, int resultSetConcurrency) | ||
maxPoolPrepareStatementPerConnectionSize | 每个连接最多缓存多少个SQL | 20 |
filters | 这里配置的是插件,常用的插件有: | stat,wall,slf4j |
监控统计: filter:stat | ||
日志监控: filter:log4j 或者 slf4j | ||
防御SQL注入: filter:wall | ||
connectProperties | 连接属性。比如设置一些连接池统计方面的配置。 | |
druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000 | ||
比如设置一些数据库连接属性: |
步骤:
1.在src下新建一个druid.properties 连接池配置文件
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/news?useTimezone=true&serverTimezone=CTT&useUnicode=true&characterEncoding=utf8&useSSL=false
user=root
password=root
initialSize=5
maxActive=10
解耦大总结:
我建议大家学完JAVAWEB和听完老师的Spring前奏课之后,都自己画画这个图。
正是我们写完项目,再结合这个图,就需要去解决这些问题(耦合问题),自然而然的就需要去学习框架。
框架一个目的就是解耦。
JAVAWEB问题二
jar问题,面对这么多的jar包,如何办???如之何???
jar包放置在WEB-INF下的lib下:
spring-aop-5.2.6.RELEASE.jar spring-beans-5.2.6.RELEASE.jar spring-context-5.2.6.RELEASE.jar spring-core-5.2.6.RELEASE.jar spring-expression-5.2.6.RELEASE.jar
mysql-connector-java-8.0.19.jar
log4j-1.2.17.jar
druid-1.1.22.jar
fastjson-1.2.13.jar
jstl-impl.jar
jaxen-1.1.6.jar
javax.servlet.jsp.jstl.jar
commons-fileupload-1.3.3.jar
commons-io-1.4.jar
假设我们要创建一个web项目,需要导入jar包。JAVA/JAVAWEB project,都需要这一步的操作。
这么多的jar,如何办呢???