一、连接池
- 如果大雄每次需要使用法宝,多啦A梦都要去22世纪带过来,就会显得很麻烦,降低效率,浪费时间。所以多啦A梦需要百宝袋。
- (获取数据库连接需要消耗比较多的资源,而每次操作都要重新获取新的连接对象,执行一次操作就把连接关闭,而数据库创建连接通常需要消耗相对较多的资源,创建时间也较长。这样数据库连接对象的使用率低。)
- 连接池就像多啦A梦的百宝袋一样,用完的法宝不会丢掉,而是放回袋中,等再次需要的时候再拿出来使用。这样多啦A梦就省去了跑腿的力气了,大雄的问题也能更快的解决。
- (连接池就是一个容器,连接池中保存了一些数据库连接,这些链接是可以重复使用的。关闭连接的时候不是真正的关闭,而是将连接对象放回线程池中,等再次需要的时候再拿出来使用。减少了资源的消耗,提高了效率。)
(其中用到了JDBC的工具类,在其他文章里,如需用到请查阅)
- 每个连接池必须实现javax.sql.DataSource这个公共接口并重写里面的所有的抽象方法(里面用不到的方法全部空实现即可,这里只用到了getConnection())
- 定义连接池中相关参数( initCount初始化池子链接个数,最大链接个数maxCount,curCount当前已经创建的连接个数)
- 创建容器保存连接(LinkedList)
- 提供获取连接方法(重写的getConnection()方法)
- 提供关闭方法(关闭连接的时候不是真正的关闭,而是将连接对象放回线程池中,不是重写方法)
//1.每个连接池必须实现javax.sql.DataSource这个公共接口并重写里面的所有的抽象方法
public class MyPool implements DataSource {
//2.1初始化池子链接个数
private int initCount = 3;
//2.2最大链接个数
private int maxCount=10;
//2.3当前已经创建的连接个数
private int curCount=0;
//3.定义一个容器用于存储链接
private LinkedList<Connection> list = new LinkedList<>();
public MyPool(){
//一旦创建连接池的时候就会存在3个连接
for (int i = 0 ; i<initCount ; i++){
Connection connection = createConnection();
list.add(connection);
}
}
//创建链接(提供给下面 4.获取连接 里面的情况二使用)
public Connection createConnection(){
//(使用JDBC工具类的getConnection()方法)
Connection connection = JDBCUtils.getConnection();
//curCount创建连接个数增加1
curCount++;
return connection;
}
//4.获取连接(重写方法)
@Override
public Connection getConnection() throws SQLException {
//情况一: 池子中如果还有连接,那么直接获取即可,使用集合中的第一个元素。
if(list.size()>0){
Connection connection = list.removeFirst();
return connection;
}
//情况二: 当前创建的连接个数并没超出最大值
if(curCount<maxCount){
Connection connection = createConnection();
return connection;
}
//当不符合上面的情况,也就是超出最大值的时候
throw new RuntimeException("当前网站访问的人数多过,请稍后!");
}
//5.关闭连接,把链接还回给连接池(不是重写方法)
public void close(Connection connection){
//不是真正的关闭,只是将连接添加进集合(容器)
list.add(connection);
}
//空实现
@Override
public Connection getConnection(String username, String password) throws SQLException {
return null;
}
@Override
public PrintWriter getLogWriter() throws SQLException {
return null;
}
@Override
public void setLogWriter(PrintWriter out) throws SQLException {
}
@Override
public void setLoginTimeout(int seconds) throws SQLException {
}
@Override
public int getLoginTimeout() throws SQLException {
return 0;
}
@Override
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
return null;
}
@Override
public <T> T unwrap(Class<T> iface) throws SQLException {
return null;
}
@Override
public boolean isWrapperFor(Class<?> iface) throws SQLException {
return false;
}
}
二、定义好的连接池
其实现在已经有很多很成熟的线程池了,大家没有必要去自定义线程池,可以直接使用别人定义好的线程池
- C3P0地址:https://sourceforge.net/projects/c3p0/?source=navbar
- C3P0是一个开源的连接池。 Hibernate框架,默认推荐使用C3P0作为连接池实现。
- C3P0的jar包:
(c3p0-0.9.5.2.jar , mchange-commons-java-0.2.12.jar)
ComboPooledDataSource:只有一个getconnection()方法
- ComboPooledDataSource dataSource = new ComboPooledDataSource(“otherc3p0”);
- Druid(德鲁伊)是阿里巴巴开发的号称为监控而生的数据库连接池,Druid是目前最好的数据库连接池。在功能、性能、扩展性方面,都超过其他数据库连接池,同时加入了日志监控,可以很好的监控DB池连接和SQL的执行情况。Druid已经在阿里巴巴部署了超过600个应用,经过一年多生产环境大规模部署的严苛考验。
- Druid地址:https://github.com/alibaba/druid
- jar包:druid-1.0.9.jar
- 参数 说明
jdbcUrl 连接数据库的url:mysql : jdbc:mysql://localhost:3306/druid2
username:数据库的用户名 password: 数据库的密码
driverClassName:驱动类名。根据url自动识别,这一项可配可不配,如果不配置druid会根据url自动识别dbType,然后选择相应的driverClassName(建议配置下)
initialSize: 初始化时建立物理连接的个数。初始化发生在显示调用init方法,或者第一次getConnection时
maxActive: 最大连接池数量 maxIdle: 最大的空闲连接个数, 已经不再使用,配置了也没效果,默认值是8
minIdle: 最小连接池数量, maxWait :获取连接时最大等待时间,单位毫秒。
//使用前需要用如下代码:
public static void main(String[] args) throws Exception {
Properties properties = new Properties();
//让该properties加载src下面的druid.properties文件。
//使用类文件文件路径去找src目录下的资源是最方便的。
//如果需要使用类文件路径那么必须要先得到一个Class对象,只有class对象才能使用类文件路径(Druid.class)
// 类文件路径我们一定需要以“/”开头 , “/”代表了src目录。
InputStream inputStream = Druid.class.getResourceAsStream("/druid.properties"); //该方法返回的是一个资源文件的输入流
properties.load(inputStream);
//得到一个连接池
DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
- 声明静态数据源成员变量
- 创建连接池对象
- 定义公有的得到数据源的方法
- 定义得到连接对象的方法
- 定义关闭资源的方法
import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
public class JdbcUtils {
// 1. 声明静态数据源成员变量
private static DataSource ds;
// 2. 创建连接池对象
static {
// 加载配置文件中的数据
InputStream is = JdbcUtils.class.getResourceAsStream("/druid.properties");
Properties pp = new Properties();
try {
pp.load(is);
// 创建连接池,使用配置文件中的参数
ds = DruidDataSourceFactory.createDataSource(pp);
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
// 3. 定义公有的得到数据源的方法,上面的
public static DataSource getDataSource() {
return ds;
}
// 4. 定义得到连接对象的方法
public static Connection getConnection() throws SQLException {
return ds.getConnection();
}
// 5.定义关闭资源的方法
public static void close(Connection conn, Statement stmt, ResultSet rs) {
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {}
}
if (stmt != null) {
try {
stmt.close();
} catch (SQLException e) {}
}
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {}
}
}
// 6.重载关闭方法
public static void close(Connection conn, Statement stmt) {
close(conn, stmt, null);
}
}
三、代理模式:
代理(Proxy)是一种设计模式,提供了对目标对象另外的访问方式;即通过代理对象访问目标对象.这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能.
- 代理对象可以在调用者(我们使用测试类时)和目标对象之间(线程池中的close(Connection connection)方法)起到中介作用,从而增强某一个对象的某一个功能。
在实际开发中,我们经常调用别人写的代码完成业务需求开发。很多时候,我们想再原来实现的基础上稍微做一些调整扩展,但是又不能修改别人的代理,怎么办呢?代理可以解决。
java的代理总共有三种,分为:静态代理、jdk动态代理、cglib动态代理
动态代理技术是整个java技术中最重要的一个技术,它是学习java框架的基础,不会动态代理技术,那么在学习Spring等这些框架时是学不明白的。
- 在上面的自定义线程池中,我们发现在使用时存在一些不太舒服的操作。
- 在测试类中我们获得连接Connection connection=pool.getConnection();
- 用完后需要关闭连接pool.close(connection);
- 而我们习惯于直接用connection.close();来关闭连接,
- 但是这样就不是放回线程池了,而是真正直接的关闭连接,但是为了满足使用这个代码来放回线程池,我们需要用到动态代理。
- 在程序运行的过程中,动态创建出代理对象。
- 增强某一个对象的某一个功能。 (我们这里增强connection对象的close()方法)
- 调用者
- 代理对象
- 被代理对象
- 抽象对象(代理对象,被代理对象都实现了相同的接口,保证有相同的方法)
- 处理器内部一定要维护一个被代理对象 (connection)
- 在invoke方法内部获取方法名(close() )
- 如果方法不需要被代理,让被代理对象执行
- 如果 需要代理的,写上自己的业务逻辑
Proxy类
static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)
- 作用: 生成实现指定接口的代理对象
- 参数说明:
1.loader: 目标对象的类加载器
2.interfaces:代理对象实现的接口数组
3.h:(处理器)具体的代理操作, InvocationHandler是一个接口,需要传入一个实现了此接口的实现类。返回值:实现指定接口的代理对象。
- InvocationHandler接口
Object invoke(Object proxy, Method method,Object[] args)- 作用: 在这个方法中实现对真实方法的增强
参数说明:
1.proxy:即方法newProxyInstance()方法返回的代理对象,该对象一般不要在invoke方法中使用。
2.method:代理对象调用的方法对象。
3.args:代理对象调用方法时传递的参数。 返回值:是真实对象方法的返回值
public Connection createConnection(){
//1.处理器内部一定要维护一个被代理对象connection
//(使用JDBC工具类的getConnection()方法)
Connection connection = JDBCUtils.getConnection();
//产生一个代理对象,因为这个方法是object类所以需要强转
//代理对象connection本身就是一个接口所以用connection.class
//并使用匿名内部类重写invoke方法
Connection connectionProxy =(Connection) Proxy.newProxyInstance(ConnectionPool.class.getClassLoader(), new Class[]{Connection.class}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//2.获得方法名
String methodName = method.getName();
判断是否是需要代理的方法
if (method.getName().equals("close")) {
//4.如果 需要代理的,写上自己的业务逻辑
//因为proxy类型是object,所以需要强转为connection类型
list.add((Connection) proxy);
return null;
} else {
//3.如果方法不需要被代理,让被代理对象执行
return method.invoke(connection,args);
}
}
});
//curCount创建连接个数增加1
curCount++;
return connection;
}
- 由程序员创建代理类,再对其编译。在程序运行前,代理类的.class文件就已经存在了。
- 静态代理的实现比较简单,代理类实现与目标对象相同的接口。通过构造器或set方法给代理对象注入目标对象。在实现代理对象接口方法时候,内部调用目标对象真正实现方法并且可以添加额外的业务控制。从而实现在不修改目标对象的基础上,对目标对象进行扩展。
不需要修改目标对象就实现了目标对象功能的增加
- 一个真实对象必须对应一个代理对象,如果大量使用会导致类的急剧膨胀。
- 如果抽象对象中方法很多,则代理对象也要编写大量的代码。
(1) 明星接口(定义唱歌、拍戏方法)
(2) 刘德华作为明星(目标对象),需要实现明细接口
(3)经纪人也实现明星接口,唱歌拍戏前要先看看价格是否合适。
接口IStar
// 明星的接口规范定义
public interface IStar {
// 唱歌
void singing(double money);
//拍戏
void act(double money);
}
LiuDeHuaStar类实现IStar接口
/**
* 明星刘德华
*/
public class LiuDeHuaStar implements IStar {
@Override
public void singing(double money) {
System.out.println("我是刘德华,唱歌一级棒,收费标准"+money);
}
@Override
public void act(double money) {
System.out.println("我是刘德华,表演二级棒,收费标准"+money);
}
}
LiuDeHuaProxy代理类实现与被代理对象LiuDeHuaStar相同的IStar接口
public class LiuDeHuaProxy implements IStar {
// 明星刘德华(目标对象)
private IStar iStar = new LiuDeHuaStar();
@Override
public void singing(double money) {
if (money > 100000) {
// 调用目标对象
iStar.singing(money);
}else{
System.out.println("忙,没空开演唱会!");
}
}
@Override
public void act(double money) {
if (money > 1000000) {
// 调用目标对象
iStar.act(money);
} else {
System.out.println("忙,不接新戏!");
}
}
}
测试类App
public class App {
public static void main(String[] args) {
// 创建代理对象
IStar star = new LiuDeHuaProxy();
// 调用代理对象方法
star.singing(1);
star.act(9000000);
}
}
静态代理:
- 一个目标对象,对应一个代理对象
- 代理对象实现与目标对象一样的接口
- 优点:在不修改目标对象的功能前提下,对目标功能扩展.
- 缺点:
1.一个目标对象,一个代理对象,导致代理类过多;
2.代理对象需要与目标对象实现一样的接口,一旦接口增加方法,目标对象与代理对象都要维护.
1.代理对象,不需要实现接口
2.代理对象的生成,是利用JDK的API,动态的在内存中构建代理对象(需要我们指定创建代理对象/目标对象实现的接口的类型)
3.动态代理也叫做:JDK代理,接口代理
IStar明星接口、LiuDeHuaStar接口实现,与上面示例一样。(这里省略)
public class App {
public static void main(String[] args) {
// 目标对象
IStar liudehua = new LiuDeHuaStar();
// 对目标对象创建代理对象, 使用jdk动态代理方式创建
IStar proxy = (IStar) Proxy.newProxyInstance(
// 参数1:类加载器
App.class.getClassLoader(),
// 参数2: 目标对象接口类型数组
liudehua.getClass().getInterfaces(),
// 参数3:事件处理程序
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 获取方法名称
String methodName = method.getName();
// 获取方法参数
double money = (double) args[0];
// 方法返回值
Object reValue = null;
// 判断
if ("singing".equals(methodName)){
// 判断
if (money > 100000) {
// 调用唱歌方法
reValue = method.invoke(liudehua,args);
}else{
System.out.println("忙,没空开演唱会!");
}
}
else if ("act".equals(methodName)) {
if (money > 1000000) {
// 调用目标对象
reValue = method.invoke(liudehua,args);
} else {
System.out.println("忙,不接新戏!");
}
}
return reValue;
}
});
// 调用代理方法
proxy.singing(1);
proxy.act(2000000);
}
}
JDK动态代理也叫作接口代理。 JDK动态代理,要求目标对象一定要实现接口,否则不能用动态代理。
- 上面的静态代理和动态代理模式都是要求目标对象是实现一个接口的目标对象,但是有时候目标对象只是一个单独的对象,并没有实现任何的接口,这个时候就可以使用以目标对象子类的方式类实现代理,这种方法就叫做:Cglib代理
- Cglib代理,也叫作子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能的扩展.
- Cglib是一个强大的高性能的代码生成包,它可以在运行期扩展java类与实现java接口.它广泛的被许多AOP的框架使用,例如Spring、AOP和synaop,为他们提供方法的interception(拦截)
- Cglib包的底层是通过使用一个小而块的字节码处理框架ASM来转换字节码并生成新的类.不鼓励直接使用ASM,因为它要求你必须对JVM内部结构包括class文件的格式和指令集都很熟悉
(1)JDK动态代理只能对实现了接口的类生成代理,而不能针对类
(2)CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法 因为是继承,所以该类或方法不能声明成final
直接引入Spring-Core核心包,已经包含CGLIB功能。
<!-- https://mvnrepository.com/artifact/org.springframework/spring-![在这里插入图片描述](https://img-blog.csdn.net/20181017170714930?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MjY5MTE0OQ==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70)core -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
LiuDeHuaStar类
/**
* 明星刘德华
*/
public class LiuDeHuaStar {
public void singing(double money) {
System.out.println("我是刘德华,唱歌一级棒,收费标准"+money);
}
public void act(double money) {
System.out.println("我是刘德华,表演二级棒,收费标准"+money);
}
}
App类
public class App {
public static void main(String[] args) {
// 目标对象
LiuDeHuaStar liudehua = new LiuDeHuaStar();
// 对目标对象创建代理对象, 使用cglib代理
LiuDeHuaStar proxy = (LiuDeHuaStar)Enhancer.create(
LiuDeHuaStar.class,
new MethodInterceptor() {
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
// 获取方法名称
String methodName = method.getName();
// 获取方法参数
double money = (double) args[0];
// 方法返回值
Object reValue = null;
// 判断
if ("singing".equals(methodName)){
// 判断
if (money > 100000) {
// 调用唱歌方法
reValue = method.invoke(liudehua,args);
}else{
System.out.println("忙,没空开演唱会!");
}
}
else if ("act".equals(methodName)) {
if (money > 1000000) {
// 调用目标对象
reValue = method.invoke(liudehua,args);
} else {
System.out.println("忙,不接新戏!");
}
}
return reValue;
}
});
System.out.println(proxy.getClass());
// 调用代理方法
proxy.singing(1);
proxy.act(2000000);
}
}
使用CGLIB子类代理:
1.需要引入cglib的jar文件,但是Spring的核心包中已经包括了Cglib功能,
2.引入功能包后,就可以在内存中动态构建子类
3.代理的类不能为final,否则报错
4.目标对象的方法如果为final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法.