类型是List<com.alibaba.druid.filter.Filter>,如果同时配置了filters和proxyFilters,是组合关系,并非替换关系
spring.datasource.filters=stat,wall,log4j spring.datasource.connectionProperties=druid.stat.mergeSql=true;druid.stat.slowSqlMillis=1000
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
#配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
spring.datasource.druid.stat-view-servlet.enabled= true spring.datasource.druid.StatViewServlet.loginUsername=druid spring.datasource.druid.StatViewServlet.loginPassword=druid spring.datasource.druid.StatViewServlet.urlPattern=/druid/* spring.datasource.druid.filters=stat,wall,log4j,config
import java.io.IOException; import java.util.Map; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.annotation.WebServlet; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.lang3.StringUtils; import com.alibaba.druid.support.http.StatViewServlet; import cn.com.geostar.common.utils.CommonUtils; @WebServlet("/druid/*") public class DruidAccessTokenServlet extends StatViewServlet { /** * */ private static final long serialVersionUID = 1L; @Override public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException { super.service(req, res); } @Override public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String contextPath = request.getContextPath(); String servletPath = request.getServletPath(); String requestURI = request.getRequestURI(); response.setCharacterEncoding("utf-8"); String accessToken = request.getParameter("accessToken"); //如果url或者在参数为空就从headers获取 if(StringUtils.isBlank(accessToken)) { accessToken = request.getHeader("accessToken"); } if(StringUtils.isBlank(accessToken)) { Cookie[] cookies = request.getCookies(); if(cookies!=null) { for (Cookie cookie : cookies) { if ("accessToken".equals(cookie.getName())) { accessToken=cookie.getValue(); } } } } if(StringUtils.isBlank(accessToken)) { response.getWriter().write("token is null"); return; }else { Map<String, Object> userByToken = CommonUtils.getUserByToken(accessToken,false); if(userByToken != null && !userByToken.isEmpty()) { Cookie cookie=new Cookie("accessToken", accessToken); cookie.setHttpOnly(true); response.addCookie(cookie); }else { response.getWriter().write("token 非法"); return; } } if (contextPath == null) { // root context contextPath = ""; } String uri = contextPath + servletPath; String path = requestURI.substring(contextPath.length() + servletPath.length()); if (!isPermittedRequest(request)) { path = "/nopermit.html"; returnResourceFile(path, uri, response); return; } if ("".equals(path)) { if (contextPath.equals("") || contextPath.equals("/")) { response.sendRedirect("/druid/index.html"); } else { response.sendRedirect("druid/index.html"); } return; } if ("/".equals(path)) { response.sendRedirect("index.html"); return; } if (path.contains(".json")) { String fullUrl = path; if (request.getQueryString() != null && request.getQueryString().length() > 0) { fullUrl += "?" + request.getQueryString(); } response.getWriter().print(process(fullUrl)); return; } // find file in resources path returnResourceFile(path, uri, response); } @Override public boolean ContainsUser(HttpServletRequest request) { return true; } @Override public boolean isRequireAuth() { return false; } }
关于druid重试机制的坑:
2019-04-09 10:09:36 [Druid-ConnectionPool-Create-2053591126] [ com.alibaba.druid.pool.DruidDataSource ] [ 53 ] [ ERROR ] create connection SQLException, url: jdbc:mysql://*.*.*.*:3306/*?characterEncoding=utf-8&useSSL=false, errorCode 1045, state 28000 java.sql.SQLException: Access denied for user 'malluser'@'*.*.*.*' (using password: YES) at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:545) ~[mysql-connector-java-6.0.6.jar:6.0.6] at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:513) ~[mysql-connector-java-6.0.6.jar:6.0.6] at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:115) ~[mysql-connector-java-6.0.6.jar:6.0.6] at com.mysql.cj.jdbc.ConnectionImpl.createNewIO(ConnectionImpl.java:1606) ~[mysql-connector-java-6.0.6.jar:6.0.6] at com.mysql.cj.jdbc.ConnectionImpl.<init>(ConnectionImpl.java:633) ~[mysql-connector-java-6.0.6.jar:6.0.6] at com.mysql.cj.jdbc.ConnectionImpl.getInstance(ConnectionImpl.java:347) ~[mysql-connector-java-6.0.6.jar:6.0.6] at com.mysql.cj.jdbc.NonRegisteringDriver.connect(NonRegisteringDriver.java:219) ~[mysql-connector-java-6.0.6.jar:6.0.6] at com.alibaba.druid.filter.FilterChainImpl.connection_connect(FilterChainImpl.java:149) ~[druid-1.1.6%20.jar:1.1.6] at com.alibaba.druid.filter.stat.StatFilter.connection_connect(StatFilter.java:218) ~[druid-1.1.6%20.jar:1.1.6] at com.alibaba.druid.filter.FilterChainImpl.connection_connect(FilterChainImpl.java:143) ~[druid-1.1.6%20.jar:1.1.6] at com.alibaba.druid.pool.DruidAbstractDataSource.createPhysicalConnection(DruidAbstractDataSource.java:1512) ~[druid-1.1.6%20.jar:1.1.6] at com.alibaba.druid.pool.DruidAbstractDataSource.createPhysicalConnection(DruidAbstractDataSource.java:1575) ~[druid-1.1.6%20.jar:1.1.6] at com.alibaba.druid.pool.DruidDataSource$CreateConnectionThread.run(DruidDataSource.java:2450) [druid-1.1.6%20.jar:1.1.6]
如果密码错误会报此错,并且会无限重试。错误日志疯狂刷屏,看着头都大了 。为什么会出现这种情况呢,源码分析:
public class CreateConnectionTask implements Runnable { private int errorCount = 0; @Override public void run() { runInternal(); } private void runInternal() { for (;;) { // addLast lock.lock(); try { if (closed || closing) { createTaskCount--; return; } boolean emptyWait = true; if (createError != null && poolingCount == 0) { emptyWait = false; } if (emptyWait) { // 必须存在线程等待,才创建连接 if (poolingCount >= notEmptyWaitThreadCount // && !(keepAlive && activeCount + poolingCount < minIdle)) { createTaskCount--; return; } // 防止创建超过maxActive数量的连接 if (activeCount + poolingCount >= maxActive) { createTaskCount--; return; } } } finally { lock.unlock(); } PhysicalConnectionInfo physicalConnection = null; try { physicalConnection = createPhysicalConnection(); setFailContinuous(false); } catch (OutOfMemoryError e) { LOG.error("create connection OutOfMemoryError, out memory. ", e); errorCount++; if (errorCount > connectionErrorRetryAttempts && timeBetweenConnectErrorMillis > 0) { // fail over retry attempts setFailContinuous(true); if (failFast) { lock.lock(); try { notEmpty.signalAll(); } finally { lock.unlock(); } } if (breakAfterAcquireFailure) { lock.lock(); try { createTaskCount--; } finally { lock.unlock(); } return; } this.errorCount = 0; // reset errorCount if (closing || closed) { createTaskCount--; return; } createSchedulerFuture = createScheduler.schedule(this, timeBetweenConnectErrorMillis, TimeUnit.MILLISECONDS); return; } } catch (SQLException e) { LOG.error("create connection SQLException, url: " + jdbcUrl, e); errorCount++; if (errorCount > connectionErrorRetryAttempts && timeBetweenConnectErrorMillis > 0) { // fail over retry attempts setFailContinuous(true); if (failFast) { lock.lock(); try { notEmpty.signalAll(); } finally { lock.unlock(); } } if (breakAfterAcquireFailure) { lock.lock(); try { createTaskCount--; } finally { lock.unlock(); } return; } this.errorCount = 0; // reset errorCount if (closing || closed) { createTaskCount--; return; } createSchedulerFuture = createScheduler.schedule(this, timeBetweenConnectErrorMillis, TimeUnit.MILLISECONDS); return; } } catch (RuntimeException e) { LOG.error("create connection RuntimeException", e); // unknow fatal exception setFailContinuous(true); continue; } catch (Error e) { lock.lock(); try { createTaskCount--; } finally { lock.unlock(); } LOG.error("create connection Error", e); // unknow fatal exception setFailContinuous(true); break; } catch (Throwable e) { LOG.error("create connection unexecpted error.", e); break; } if (physicalConnection == null) { continue; } boolean result = put(physicalConnection); if (!result) { JdbcUtils.close(physicalConnection.getPhysicalConnection()); LOG.info("put physical connection to pool failed."); } break; } } } public class CreateConnectionThread extends Thread { public CreateConnectionThread(String name){ super(name); this.setDaemon(true); } public void run() { initedLatch.countDown(); long lastDiscardCount = 0; int errorCount = 0; for (;;) { // addLast try { lock.lockInterruptibly(); } catch (InterruptedException e2) { break; } long discardCount = DruidDataSource.this.discardCount; boolean discardChanged = discardCount - lastDiscardCount > 0; lastDiscardCount = discardCount; try { boolean emptyWait = true; if (createError != null && poolingCount == 0 && !discardChanged) { emptyWait = false; } if (emptyWait && asyncInit && createCount.get() < initialSize) { emptyWait = false; } if (emptyWait) { // 必须存在线程等待,才创建连接 if (poolingCount >= notEmptyWaitThreadCount // && !(keepAlive && activeCount + poolingCount < minIdle)) { empty.await(); } // 防止创建超过maxActive数量的连接 if (activeCount + poolingCount >= maxActive) { empty.await(); continue; } } } catch (InterruptedException e) { lastCreateError = e; lastErrorTimeMillis = System.currentTimeMillis(); if (!closing) { LOG.error("create connection Thread Interrupted, url: " + jdbcUrl, e); } break; } finally { lock.unlock(); } PhysicalConnectionInfo connection = null; try { connection = createPhysicalConnection(); setFailContinuous(false); } catch (SQLException e) { LOG.error("create connection SQLException, url: " + jdbcUrl + ", errorCode " + e.getErrorCode() + ", state " + e.getSQLState(), e); errorCount++; if (errorCount > connectionErrorRetryAttempts && timeBetweenConnectErrorMillis > 0) { // fail over retry attempts setFailContinuous(true); if (failFast) { lock.lock(); try { notEmpty.signalAll(); } finally { lock.unlock(); } } if (breakAfterAcquireFailure) { break; } try { Thread.sleep(timeBetweenConnectErrorMillis); } catch (InterruptedException interruptEx) { break; } } } catch (RuntimeException e) { LOG.error("create connection RuntimeException", e); setFailContinuous(true); continue; } catch (Error e) { LOG.error("create connection Error", e); setFailContinuous(true); break; } if (connection == null) { continue; } boolean result = put(connection); if (!result) { JdbcUtils.close(connection.getPhysicalConnection()); LOG.info("put physical connection to pool failed."); } errorCount = 0; // reset errorCount } } }
从源码中我们可以看到,线程中使用了无参for循环再一直尝试进行数据源连接,代码中【errorCount > connectionErrorRetryAttempts && timeBetweenConnectErrorMillis > 0】当满足该判断条件时就会进行重试连接,接下来我们看一下源码中这两个属性值设置的是什么呢?(源码过长,只展示我们需要的代码,其他属性信息可自行扒源码~)
private static final long serialVersionUID = 1L; public final static int DEFAULT_INITIAL_SIZE = 0; public final static int DEFAULT_MAX_ACTIVE_SIZE = 8; public final static int DEFAULT_MAX_IDLE = 8; public final static int DEFAULT_MIN_IDLE = 0; public final static int DEFAULT_MAX_WAIT = -1; public final static String DEFAULT_VALIDATION_QUERY = null; // public final static boolean DEFAULT_TEST_ON_BORROW = false; public final static boolean DEFAULT_TEST_ON_RETURN = false; public final static boolean DEFAULT_WHILE_IDLE = true; public static final long DEFAULT_TIME_BETWEEN_EVICTION_RUNS_MILLIS = 60 * 1000L; public static final long DEFAULT_TIME_BETWEEN_CONNECT_ERROR_MILLIS = 500; public static final int DEFAULT_NUM_TESTS_PER_EVICTION_RUN = 3; /*****************************华丽的分割线中间省略代码若干行*********************************/ protected volatile long timeBetweenEvictionRunsMillis = DEFAULT_TIME_BETWEEN_EVICTION_RUNS_MILLIS; protected int connectionErrorRetryAttempts = 1; protected boolean breakAfterAcquireFailure = false;
从中这段代码中我们可以看到connectionErrorRetryAttempts值为1,timeBetweenConnectErrorMillis值为60000,而breakAfterAcquireFailure值为false,因此当我们数据源连接失败后,就会不断的进行重试连接,因此我对于对于该如何解决这样的问题我们就有了答案:
1.若不想让重试,我们可以设置breakAfterAcquireFailure(true);connectionErrorRetryAttempts(0);
2.若想要设置多久重试,我们只需要设置timeBetweenConnectErrorMillis(time);
action:经过亲测,直接在配置文件中配置属性并不能读取到(Druid设计时就这样,大神的思维暂时还不能参悟~),我们可直接将值写入程序当中,如下: