完整的后端开发流程-深⼊浅出Java线程池:使⽤篇
⼿动步骤⾛⼀种完整的后端开发流程
服务端
1、将远程仓库的jar包拷贝到本地仓库
2、将项⽬代码拷贝到本地并建⽴路径能够执⾏编译
3、编译打包项⽬(package)⾄项⽬下,项⽬跑起来后进⾏本地测试
4、版本稳定后,上测试环境
上测试环境
1、将远程仓库的jar包拷贝到测试环境
2、将本地的项⽬代码上传到测试环境 pom能建⽴路径执⾏mvn脚本进⾏编译打包
3、编译打包项⽬(package)⾄项⽬下,项⽬跑起来后进⾏测试
4、版本在测试环境稳定后,install⾄本地仓库,在上传⾄远程仓库
5、不推荐嫌⿇烦直接上传本地jar包的⽅式,因为这样⽆法发现由于环境造成的错误⽽且传输速度没有直接编译的快
客户端联调
1、将远程仓库的jar包(包括刚刚上传的服务端jar) 拷贝到本地仓库
2、将项⽬代码拷贝到本地并建⽴路径能够执⾏编译
3、编译打包项⽬(package)⾄项⽬下,项⽬跑起来后进⾏本地测试
4、项⽬注册⾄RPC服务中来访问跑在测试环境的服务端项⽬
5、版本稳定后,上测试环境联调。
团队的技术栈,基于这个背景再展开后⾯将提到的⼏个问题,将会有更深刻的体会。
控制层基于SpringMvc,数据持久层基于JdbcTemplate⾃⼰封装了⼀套类MyBatis的Dao框架,视图层基于Velocity模板技术,其余组件基于SpringCloud全家桶。
问题1
某应⽤发布以后开始报数据库连接池不够⽤异常,⽇志如下:
1com.alibaba.druid.pool.GetConnectionTimeoutException: wait millis 60000, active 500, maxActive 500, creating 0   
很明显这是数据库连接池满了,当时处于业务低峰期,所以很显然并不是由于流量突发造成的,另⼀种可能性是长事务导致,⼀般是事务中掺杂了外部⽹络调⽤,最终跟业务负责⼈⼀起排除了长事务的可能性。
还有什么可能呢?我随即想到了是不是没有释放连接导致,我跟业务负责⼈说了这个想法,他说这种可能性不⼤,连接的获取和释放都是由框架完成的,如果这块有问题早反映出来了,我想也是。
框架的确给我们带来了很⼤的便利性,将业务中⼀些重复性的⼯作下沉到框架中,提⾼了研发效率,不夸张的说有些⼈脱离了
Spring,MyBatis,SpringMvc这些框架,都不会写代码了。
那会是什么原因呢?我⼜冒出来⼀个想法,有没有可能是某些功能框架⽀持不了,所以开发绕过了框架
⾃⼰实现,进⽽导致连接没有释放,我跟业务负责⼈说了这个想法以后,他说:“这个有可能,这次有个功能需要获取到数据库名,所以⾃⼰通过Connection对象获取的”,说到这⼉答案⼤概已经出来了,⼀起看下这段代码:
public String getSchema(String tablename, boolean cached) throws Exception {
JdbcTemplate(tablename).getDataSource().getConnection().getCatalog();
}
代码很简单通过JdbcTemplate获取DataSource,再通过DataSource获取Connection,最终通过Connection获取数据库名,就是这⼀⾏简单的代码将数据库连接耗尽,因为这⾥并没有释放连接的动作,之前的为什么都没有问题呢,因为普通的查询都是委派给JdbcTemplate来实现的,它内部会释放连接,⼀个简单的query⽅法看下:
public <T> T query(PreparedStatementCreator psc, @Nullable final PreparedStatementSetter pss, final ResultSetExtractor<T> rse) throws DataAccessException {
this.logger.debug("Executing prepared SQL query");
ute(psc, new PreparedStatementCallback<T>() {
@Nullable
public T doInPreparedStatement(PreparedStatement ps) throws SQLException {
ResultSet rs = null;
Object var3;
try {
if (pss != null) {
pss.setValues(ps);
}
rs = ps.executeQuery();
var3 = actData(rs);
} finally {
JdbcUtils.closeResultSet(rs);
if (pss instanceof ParameterDisposer) {
((ParameterDisposer)pss).cleanupParameters();
}
}
return var3;
}
});
}
public <T> T execute(PreparedStatementCreator psc, PreparedStatementCallback<T> action) throws DataAccessException {
if (this.logger.isDebugEnabled()) {
String sql = getSql(psc);
this.logger.debug("Executing prepared SQL statement" + (sql != null ? " [" + sql + "]" : ""));
}
Connection con = Connection(this.obtainDataSource());
PreparedStatement ps = null;
Object var13;
try {
ps = atePreparedStatement(con);
this.applyStatementSettings(ps);
T result = action.doInPreparedStatement(ps);
this.handleWarnings((Statement)ps);
var13 = result;
} catch (SQLException var10) {
if (psc instanceof ParameterDisposer) {
((ParameterDisposer)psc).cleanupParameters();
}
String sql = getSql(psc);
psc = null;
JdbcUtils.closeStatement(ps);
ps = null;
con = null;
anslateException("PreparedStatementCallback", sql, var10);
} finally {
if (psc instanceof ParameterDisposer) {
((ParameterDisposer)psc).cleanupParameters();
}
JdbcUtils.closeStatement(ps);
}
return var13;
}
query⽅法基于execute这个模板⽅法实现,在execute内部会通过finally来确保连接的释放
1.显⽰的关闭连接,这⾥可以借助jdk的try resource语句,简单明了。
public String getSchema(String tablename, boolean cached) throws Exception {
try(Connection connection = JdbcTemplate(tablename).getDataSource().getConnection()){
Catalog();
}
}
2.借助于JdbcTemplate的模板⽅法设计思想来解决,它提供了⼀个execute⽅法,⽤户只要实现ConnectionCallback这个接⼝就可以获取到Connection对象,在内部执⾏获取数据库名的逻辑,最终关闭连接由finally完成。
/**
* Execute a JDBC data access operation, implemented as callback action
* working on a JDBC Connection. This allows for implementing arbitrary
* data access operations, within Spring's managed JDBC environment:
* that is, participating in Spring-managed transactions and converting
* JDBC SQLExceptions into Spring's DataAccessException hierarchy.
* <p>The callback action can return a result object, for example a domain
* object or a collection of domain objects.
* @param action a callback object that specifies the action
* @return a result object returned by the action, or {@code null} if none
* @throws DataAccessException if there is any problem
*/
@Nullable
public <T> T execute(ConnectionCallback<T> action) throws DataAccessException {
Connection con = Connection(this.obtainDataSource());
Object var10;
try {
Connection conToUse = ateConnectionProxy(con);
var10 = action.doInConnection(conToUse);
} catch (SQLException var8) {
String sql = getSql(action);
con = null;
anslateException("ConnectionCallback", sql, var8);
} finally {
}
return var10;
}
@Override
public Object doInConnection(Connection connection) throws SQLException, DataAccessException {
Catalog();
}
});
虽然两种都能解决问题,但我还是更推崇第⼆种⽅式,因为这种更贴合框架的设计思想,将⼀些重复性的逻辑继续交给框架去实现,这⾥也体现出框架很重要的⼀个特点,就是对使⽤者提供扩展。
问题2
前⼏天写了⼀个Spring AOP的拦截功能,发现怎么也进不到拦截逻辑中,表达式确定没问题,让我百思不得其解,最终冷静下来逐步排错。
第⼀个很明显的错误是被拦截的对象并没有纳⼊Spring管理,所以当即把对象交由Spring管理,问题依然没有解决,我开始回想代理的原
理。
Spring代理提供了两种实现⽅式,⼀种是jdk的动态代理,另⼀种是cglib代理,这两种⽅式分别适⽤于代理类实现了接⼝和代理类未实现接⼝的情况,其内部思想都是基于某种规约(接⼝或者⽗类)来⽣成⼀个Proxy对象,在Proxy对象⽅法调⽤时先调⽤InvocationHandler的invoke⽅法,在invoke⽅法内部先执⾏代理逻辑,再执⾏被代理对象的真实逻辑,这⾥贴⼀段jdk动态代理⽣成的Proxy对象的源⽂件供⼤家阅读:
public class ProxyTest {
/**
定义⽬标接⼝,内部包含⼀个hello⽅法(这其实就是⼀个规约)
*/
public interface ProxyT{
void hello();
}
/**
实现类,实现了ProxyT接⼝
*/
public static class ProxyTImpl implements ProxyT{
@Override
public void hello() {
System.out.println("aaaa");
}
}
public static void main(String[] args) {
//设置⽣成Proxy对象的源⽂件
ProxyT proxyT1 = (wProxyInstance(ClassLoader(),new Class[]{ProxyT.class}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("invoke");
return method.invoke(proxyT,args);
}
});
proxyT1.hello();
}
}
最终⽣成的Proxy源⽂件如下:
package com.sun.proxy;
import coding4fun.ProxyTest.ProxyT;
import flect.InvocationHandler;java技术栈图
import flect.Method;
import flect.Proxy;
import flect.UndeclaredThrowableException;
/**
⽣成的proxy源码,继承jdk的Proxy类,实现了ProxyT接⼝
(这⾥其实也解释了为什么jdk的动态代理只能基于接⼝实现,不能基于⽗类,因为Proxy
必须继承jdk的Proxy,⽽java⼜是单继承,所以Proxy只能基于接⼝这个规约来⽣成)
*/
public final class $Proxy0 extends Proxy implements ProxyT {
private static Method m1;
private static Method m3;
private static Method m2;
private static Method m0;
public $Proxy0(InvocationHandler var1) throws  {
super(var1);
}
public final boolean equals(Object var1) throws  {
try {
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
//hello⽅法将调⽤权交给了InvocationHandler
public final void hello() throws  {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final String toString() throws  {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final int hashCode() throws  {
try {
return (Integer)super.h.invoke(this, m0, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m3 = Class.forName("coding4fun.ProxyTest$ProxyT").getMethod("hello");
m2 = Class.forName("java.lang.Object").getMethod("toString");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new Message());
} catch (ClassNotFoundException var3) {
throw new Message());
}
}
}
到这⾥其实我已经有了答案,是我给Spring的规约(接⼝或者⽗类)出现了问题,⾸先我要代理的类并没有实现接⼝,所以这⾥的规约不是接⼝,⽽是我这个类本⾝,从cglib的原理来讲,它是将要代理的类作为⽗类来⽣成⼀个Proxy类,重写要代理的⽅法,进⽽添加代理逻辑,问题就在于我那个类的⽅法是static的,⽽static⽅法是没法重写的,所以导致⼀直没有进拦截逻辑,将static⽅法改为实例⽅法就解决了问题,这⾥贴⼀段cglib动态代理⽣成的Proxy对象的源⽂件供⼤家阅读:
public class cglibtest {
//定义被代理的类ProxyT,内部有⼀个hello⽅法
public static class ProxyT{
public void hello() {
System.out.println("aaaa");
}
}
//定义⼀个⽅法,和jdk的InvocationHandler类似
public static class Interceptor implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
//简单的打印
System.out.println("before invoke hello");
//执⾏被代理类的⽅法(hello)
return methodProxy.invokeSuper(o,objects);
}
}
public static void main(String[] args) {
// 设置CGLib代理类的⽣成位置
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "./cg");
// 设置JDK代理类的输出
MethodInterceptor methodInterceptor = new Interceptor();
Enhancer enhancer = new Enhancer();
//设置⽗类
enhancer.setSuperclass(ProxyT.class);
/
/设置⽅法回调
enhancer.setCallback(methodInterceptor);
ProxyT proxy = (ate();
proxy.hello();
}
}
最终⽣成的Proxy源⽂件如下(删除了部分代码,只保留了重写hello⽅法逻辑):