搭建基于OAuth2和SSO的开放平台

搭建基于OAuth2和SSO的开放平台

原创文章,转载或摘录请说明文章来源,谢谢!

开放平台介绍

什么是开放平台

开放平台在百科中的定义:
开放平台(Open Platform) 在软件行业和网络中,开放平台是指软件系统通过公开其应用程序编程接口(API)或函数(function)来使外部的程序可以增加该软件系统的功能或使用该软件系统的资源,而不需要更改该软件系统的源代码。

通俗或者说应景点的说法,开放平台,就是互联网企业,将其内部的资源(一般是数据),比如用户数据,平台业务数据,以技术的手段(一般是RESTFul接口API),开放给受控的第三方合作伙伴,活公司内部的其它一些产品,形成一个安全受控的资源暴露平台。

为什么要搭建开放平台

搭建开放平台的意义,一般在于:
1.搭建基于API的生态体系
2.利用开放平台,搭建基于计费的API数据平台
3.为APP端提供统一接口管控平台,类似于网关的概念
4.为第三方合作伙伴的业务对接提供授信可控的技术对接平台

开放平台体系结构图

open

开放平台核心模块

一个典型的开放平台,至少包含以下几个核心模块:
1.平台门户
平台门户负责向第三方展示用于进行业务及技术集成的管理界面,至少包含以下几个功能:
1.服务商入住(第三方合作伙伴入住)
2.应用配置(第三方应用管理)
3.权限申请(一般包括接口权限和字段权限)
4.运维中心(开放平台当前服务器、接口状态,服务商接口告警等)
5.帮助中心(入住流程说明,快速接入说明,API文档等)

2.鉴权服务
鉴权服务负责整个平台的安全性
1.接口调用鉴权(第三方合作伙伴是否有权限调用某接口)
2.用户授权管理(用户对某个第三方应用获取改用户信息的权限管理)
3.用户鉴权(平台用户的鉴权)
4.应用鉴权(第三方合作伙伴的应用是否有权调用该平台)

3.开放接口
开放接口用于将平台数据暴露给合作伙伴
1.平台用户接口(用于获取公司APP生态链中的用户信息)
2.平台数据接口(平台中的一些开放数据)
3.其它业务接口(平台开放的一些业务数据)

4.运营系统
运营系统是整个平台的后台业务管理系统,负责对第三方合作伙伴提出的各种申请进行审核操作,对当前应用的操作进行审计工作,对当前业务健康度进行监控等
1.服务商管理(对第三方合作伙伴的资质进行审核、操作)
2.应用管理(对第三方应用进行审核、上下线管理)
3.权限管理(对合作伙伴申请的资源进行审核、操作)
4.统计分析(监控平台当前运行状态,统计平台业务数据)

OAuth2介绍

什么是OAuth2

百科:OAUTH协议为用户资源的授权提供了一个安全的、开放而又简易的标准。与以往的授权方式不同之处是OAUTH的授权不会使第三方触及到用户的帐号信息(如用户名与密码),即第三方无需使用用户的用户名与密码就可以申请获得该用户资源的授权,因此OAUTH是安全的。oAuth是Open Authorization的简写。

简单来说:OAuth2协议,定义了一套用户、第三方服务和存储着用户数据的平台之间的交互规则,可以使得用户无需将自己的用户名和密码暴露给第三方,即可使第三方应用获取用户在该平台上的数据,最常见的场景便是现在互联网上的各种使用XXX账号登录。

OAuth2协议中角色介绍

OAuth2协议中,共有四个参与方(角色):
1.resource owner:资源拥有者
即用户
2.resource server:资源服务器
即存储用户数据的服务器,一般对外都以RESTFul API的形式暴露用户数据,client使用access token访问resource server申请被保护起来的用户数据
3.client:客户端
即第三方应用
4.authorization server:授权服务器
用来鉴权第三方应用合法性,并对用户登录、是否授权第三方应用获取数据进行响应,并根据用户操作,想第三应用颁发用户token或者告知授权失败

OAuth2常用协议介绍

OAUTH2标准业务协议,如下图所示
oauth
A.第三方应用向用户请求授权,希望获取用户数据
B.用户同意授权
C.第三方应用拿着用户授权,向平台索要用户access token
D.平台校验第三应用合法性及用户授权真实性后,向平台发放用户access token
E.第三方应用拿着用户access token向平台索要用户数据
F.平台在校验用户access token真实性后,返回用户数据

OAuth2使用场景介绍

目前,OAuth2协议使用最多的场景还是用以给第三方应用获取用户信息,业务流程如下图所示
case
1.在浏览器中,用户点击第三方应用按钮,由第三方应用发起请求,向平台发起授权请求。
2.平台在接收到第三方应用请求后,浏览器跳转用户登录界面,请求用户进行登录。
3.用户在平台登录界面输入用户名、密码进行登录
4.平台判断用户合法性,校验失败,在浏览器中提示错误原因
5.平台判断用户是否需要对该第三方应用进行授权。(不需要授权的情况有两种:a.平台信任该第三方应用,如公司内部应用,无需用户进行授权,默认给予用户数据。b.该用户之前已经给该应用授予过权限,并且仍在有效期内)
6.如需授权,平台跳转浏览器界面至授权界面,告知用户将授予哪个第三方哪些数据权限
7.用户授权后,将用户授权码回调给第三方url
8.第三方在获取用户授权码后,带着用户授权码访问平台鉴权接口,请求用户token
9.平台在收到第三方请求后,校验授权码真实性,并返回用户token
10.第三方使用用户token向平台请求用户接口
11.平台接口判断用户token真实性,并向第三方返回用户数据

OAuth2核心功能说明

1.应用注册
应用注册后,OAuth2会下发应用app_id和app_secret,用以标记该应用的唯一性,并且这两个参数将贯穿整个OAuth协议,用以对应用合法性进行校验。同时,应用需要提供redirect_uri,用以和平台进行异步交互,获取用户令牌及错误信息。
2.授权/鉴权中心
a.对用户的应户名、密码进行鉴权
b.对第三方应用的app_id,app_secret进行鉴权
c.展示授权界面,并对用户对第三方应用的授权操作进行响应
d.对用户授权码及用户token的真实性进行鉴权
3.token管理
a.创建token、刷新token
b.查询token详细数据
c.校验token时效性

OAuth2体系结构

case

开放平台集成OAuth2体系

1.平台门户:
门户应用入住界面,需要集成OAuth2应用创建接口,录入第三方回调地址,并回显app_id和app_secret参数
2.鉴权服务:
鉴权服务需集成OAuth2的authorize及token接口,用以提供用户授权及code/token鉴权功能
3.开放接口:
开放接口需集成OAuth2的resource server角色,对用户数据进行安全管理,对第三方应用发起的请求做出响应,并对token进行真实性校验
4.运营系统:
运营系统需提供对当前OAuth2应用的管理功能,用户授权列表管理,用户token管理等OAuth2协议相关管理功能。

SSO介绍

什么是SSO

百科:SSO英文全称Single Sign On,单点登录。SSO是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。它包括可以将这次主要的登录映射到其他应用中用于同一个用户的登录的机制。它是目前比较流行的企业业务整合的解决方案之一。

简单来说,SSO出现的目的在于解决同一产品体系中,多应用共享用户session的需求。SSO通过将用户登录信息映射到浏览器cookie中,解决其它应用免登获取用户session的问题。

为什么需要SSO

开放平台业务本身不需要SSO,但是如果平台的普通用户也可以在申请后成为一个应用开发者,那么就需要将平台加入到公司的整体账号体系中去,另外,对于企业级场景来说,一般都会有SSO系统,充当统一的账号校验入口。

CAS协议中概念介绍

SSO单点登录只是一个方案,而目前市面上最流行的单端登录系统是由耶鲁大学开发的CAS系统,而由其实现的CAS协议,也成为目前SSO协议中的既定协议,下文中的单点登录协议及结构,均为CAS中的体现结构
CAS协议中有以下几个概念:
1.CAS Client:需要集成单点登录的应用,称为单点登录客户端
2.CAS Server:单点登录服务器,用户登录鉴权、凭证下发及校验等操作
3.TGT:ticker granting ticket,用户凭证票据,用以标记用户凭证,用户在单点登录系统中登录一次后,再其有效期内,TGT即代表用户凭证,用户在其它client中无需再进行二次登录操作,即可共享单点登录系统中的已登录用户信息
4.ST:service ticket,服务票据,服务可以理解为客户端应用的一个业务模块,体现为客户端回调url,CAS用以进行服务权限校验,即CAS可以对接入的客户端进行管控
5.TGC:ticket granting cookie,存储用户票据的cookie,即用户登录凭证最终映射的cookies

CAS核心协议介绍

case
1.用户在浏览器中访问应用
2.应用发现需要索要用户信息,跳转至SSO服务器
3.SSO服务器向用户展示登录界面,用户进行登录操作,SSO服务器进行用户校验后,映射出TGC
4.SSO服务器向回调应用服务url,返回ST
5.应用去SSO服务器校验ST权限及合法性
6.SSO服务器校验成功后,返回用户信息

CAS基本流程介绍

以下为基本的CAS协议流程,图一为初次登录时的流程,图二为已进行过一次登录后的流程
case
case

代码及示例

spring提供了整套的开源包,用以搭建OAUTH2+SSO的体系:
1.spring-oauth2:用以实现OAuth2协议,提供了上述所有四个角色提供的功能
2.spring-cas:用以实现和cas的集成,将OAuth2的登录、登出功能委托给CAS处理,并提供了统一的回调机制及凭证校验机制
3.CAS,耶鲁大学官方提供的SSO开源实现,本文的单点登录协议即按照CAS进行的说明

本文还提供了基于GO语言实现的简单OAuth2+SSO功能,详见github:
https://github.com/janwenjohn/go-oauth2-sso

hibernate自动添加永真1=1,导致Druid sql防火墙报错的问题

最近工程突然报错:
java.sql.SQLException: sql injection violation, part alway true condition not allow : select count(*) where this_.id<>? and 1=1
at com.alibaba.druid.wall.WallFilter.check(WallFilter.java:671)
at com.alibaba.druid.wall.WallFilter.connection_prepareStatement(WallFilter.java:214)

错误内容是druid的sql防火墙报警,发现是hibernate自动拼接了1=1的永真条件,而druid只会放行排在第一的永真条件,查看hibernate源代码后发现,当引用Junction生成sql时,如果条件为空,则会自动拼接1=1的永真条件。

解决办法也很简单,修改自己的代码,将引用junction的条件拼接放在第一位即可。

druid升级到最新的1.0.16-SNAPSHOT引发的配置问题

盲升druid至最新的1.0.16-SNAPSHOT版本后,启动工程报如下错误:
[com.alibaba.druid.pool.vendor.MySqlValidConnectionChecker]-[WARN] Unexpected error in ping
Caused by: java.lang.IllegalArgumentException: timeout can’t be negative

查源码后发现,原来validationQueryTimeout变量默认值为-1,而不是0,导致socket接口报错。

解决办法:
在datasource中增加以下配置:

<property name="validationQueryTimeout" value="10000" />

vert.x笔记:6.vert.x集群化部署

vert.x支持集群化部署,默认封装使用的是一个叫Hazelcast的框架,从官方github上看到的开发进度表示,3.1可能会引入比较大众点的zookeeper作为集群的协作框架。

demo工程还是使用第5章中的dubbo服务demo代码

修改启动类:

package com.heartlifes.vertx.demo.dubbo;

import io.vertx.core.AsyncResult;
import io.vertx.core.Vertx;
import io.vertx.core.VertxOptions;
import io.vertx.core.spi.cluster.ClusterManager;
import io.vertx.spi.cluster.hazelcast.HazelcastClusterManager;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.hazelcast.config.Config;
import com.hazelcast.config.GroupConfig;

public class SpringMain {

    private static Vertx vertx = Vertx.vertx();// 集群初始化失败的情况下,可以使用默认vertx实例
    private static ApplicationContext ctx = null;

    public static void main(String[] args) {
        // 配置文件方式
        ctx = new ClassPathXmlApplicationContext("dubbo-consumer.xml");
        // Hazelcast配置类
        Config cfg = new Config();
        // 加入组的配置,防止广播环境下,负载串到别的开发机中
        GroupConfig group = new GroupConfig();
        group.setName("p-dev");
        group.setPassword("p-dev");
        cfg.setGroupConfig(group);
        // 申明集群管理器
        ClusterManager mgr = new HazelcastClusterManager(cfg);
        VertxOptions options = new VertxOptions().setClusterManager(mgr);
        // 集群化vertx
        Vertx.clusteredVertx(options, SpringMain::resultHandler);

    }

    private static void resultHandler(AsyncResult<Vertx> res) {
        // 如果成功,使用集群化的vertx实例
        if (res.succeeded()) {
            vertx = res.result();
            // 这里要注意,一定要在异步回调中,获取了vertx实例后,再去部署模块
            // 由于vert.x所有内部逻辑都是异步调用的,所以,如果你在异步回调前就去部署模块,最终会导致集群失败
            deploy(vertx);
        } else {
            System.out.println("cluster failed, using default vertx");
            deploy(vertx);
        }
    }

    private static void deploy(Vertx vertx) {
        vertx.deployVerticle(new SpringVerticle(ctx));
        vertx.deployVerticle(new ServerVerticle());
    }

}

启动多个主程序,会发现后台输出类似如下的日志信息

八月 04, 2015 2:05:09 下午 com.hazelcast.instance.DefaultAddressPicker
信息: [LOCAL] [p-dev] [3.5] Prefer IPv4 stack is true.
八月 04, 2015 2:05:09 下午 com.hazelcast.instance.DefaultAddressPicker
信息: [LOCAL] [p-dev] [3.5] Picked Address[192.168.1.119]:5701, using socket ServerSocket[addr=/0:0:0:0:0:0:0:0,localport=5701], bind any local is true
八月 04, 2015 2:05:09 下午 com.hazelcast.spi.OperationService
信息: [192.168.1.119]:5701 [p-dev] [3.5] Backpressure is disabled
八月 04, 2015 2:05:09 下午 com.hazelcast.spi.impl.operationexecutor.classic.ClassicOperationExecutor
信息: [192.168.1.119]:5701 [p-dev] [3.5] Starting with 2 generic operation threads and 4 partition operation threads.
八月 04, 2015 2:05:10 下午 com.hazelcast.system
信息: [192.168.1.119]:5701 [p-dev] [3.5] Hazelcast 3.5 (20150617 - 4270dc6) starting at Address[192.168.1.119]:5701
八月 04, 2015 2:05:10 下午 com.hazelcast.system
信息: [192.168.1.119]:5701 [p-dev] [3.5] Copyright (c) 2008-2015, Hazelcast, Inc. All Rights Reserved.
八月 04, 2015 2:05:10 下午 com.hazelcast.instance.Node
信息: [192.168.1.119]:5701 [p-dev] [3.5] Creating MulticastJoiner
八月 04, 2015 2:05:10 下午 com.hazelcast.core.LifecycleService
信息: [192.168.1.119]:5701 [p-dev] [3.5] Address[192.168.1.119]:5701 is STARTING
八月 04, 2015 2:05:14 下午 com.hazelcast.cluster.impl.MulticastJoiner
信息: [192.168.1.119]:5701 [p-dev] [3.5] 


Members [1] {
    Member [192.168.1.119]:5701 this
}

八月 04, 2015 2:05:14 下午 com.hazelcast.core.LifecycleService
信息: [192.168.1.119]:5701 [p-dev] [3.5] Address[192.168.1.119]:5701 is STARTED
八月 04, 2015 2:05:15 下午 com.hazelcast.partition.InternalPartitionService
信息: [192.168.1.119]:5701 [p-dev] [3.5] Initializing cluster partition table first arrangement...
八月 04, 2015 2:05:22 下午 com.hazelcast.nio.tcp.SocketAcceptor
信息: [192.168.1.119]:5701 [p-dev] [3.5] Accepting socket connection from /192.168.1.119:59906
八月 04, 2015 2:05:22 下午 com.hazelcast.nio.tcp.TcpIpConnectionManager
信息: [192.168.1.119]:5701 [p-dev] [3.5] Established socket connection between /192.168.1.119:5701
八月 04, 2015 2:05:28 下午 com.hazelcast.cluster.ClusterService
信息: [192.168.1.119]:5701 [p-dev] [3.5] 

Members [2] {
    Member [192.168.1.119]:5701 this
    Member [192.168.1.119]:5702
}

八月 04, 2015 2:05:29 下午 com.hazelcast.partition.InternalPartitionService
信息: [192.168.1.119]:5701 [p-dev] [3.5] Re-partitioning cluster data... Migration queue size: 135
八月 04, 2015 2:05:30 下午 com.hazelcast.partition.InternalPartitionService
信息: [192.168.1.119]:5701 [p-dev] [3.5] All migration tasks have been completed, queues are empty.

vert.x笔记:5.vert.x集成dubbo服务

vert.x

基础介绍:

dubbo是阿里巴巴内部的rpc远程调用框架,和spring无缝对接,自带loadbalance,是用来搭建soa服务架构的利器,可惜听说在阿里内部斗争中,已经被hsf干掉了。但是,对于我们这种小企业来说,dubbo还是搭建高可用服务的不二选择。dubbo官方地址:http://dubbo.io

vert.x+dubbo可以搭建一个逼格很高的微服务架构,即vert.x用于发布服务,通过事件总线,调用后端的dubbo业务处理服务。从而完成rest服务与业务代码的完美解耦。

本章基用到前序章节的所有知识,并且这里将不会介绍dubbo服务的开发,默认你会玩dubbo服务。

pom中加入以下依赖:

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>dubbo</artifactId>
    <version>${dubbo.version}</version>
    <exclusions>
            <exclusion>
            <groupId>org.springframework</groupId>
            <artifactId>spring</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>org.apache.zookeeper</groupId>
    <artifactId>zookeeper</artifactId>
    <version>3.3.6</version>
    <exclusions>
        <exclusion>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>com.github.sgroschupf</groupId>
    <artifactId>zkclient</artifactId>
    <version>0.1</version>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.1.41</version>
</dependency>
<dependency>
        <groupId>org.jboss.netty</groupId>
    <artifactId>netty</artifactId>
    <version>3.2.5.Final</version>
</dependency>

引用一个dubbo服务:

所谓集成dubbo,从本质来讲就是配置dubbo服务,并且以xml形式集成spring。
我们假设后台有这么一个dubbo服务,现在要在vert.x中调用,那么我们的做法和普通的dubbo consumer一样,申明一个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" xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:jdbc="http://www.springframework.org/schema/jdbc" xmlns:jee="http://www.springframework.org/schema/jee"
    xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd
        http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-3.2.xsd
        http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.2.xsd
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd
        http://code.alibabatech.com/schema/dubbo 
        http://code.alibabatech.com/schema/dubbo/dubbo.xsd
        http://www.springframework.org/schema/aop 
        http://www.springframework.org/schema/aop/spring-aop-3.2.xsd ">

    <!-- 使用Annotation自动注册Bean,解决事物失效问题:在主容器中不扫描@Controller注解,在SpringMvc中只扫描@Controller注解。 -->
    <context:component-scan base-package="com"/>
    <dubbo:application name="demo-consumer" />
    <dubbo:registry address="multicast://224.5.6.7:1234" />
    <dubbo:protocol name="dubbo" host="127.0.0.1" port="20802"
        serialization="hessian2" threadpool="cached" threads="1000" />
    <!-- dubbo引用的服务 -->
    <dubbo:reference id="dubboService"
        interface="com.heartlifes.dubbo.DubboService" />
</beans> 

创建SpringVerticle:

package com.heartlifes.vertx.demo.dubbo;

import io.vertx.core.AbstractVerticle;
import io.vertx.core.Handler;
import io.vertx.core.eventbus.Message;

import org.springframework.context.ApplicationContext;

public class SpringVerticle extends AbstractVerticle {

    private DubboService service;

    public static final String PRINT_MSG_SERVICE_ADDRESS = "print_msg_service_address";

    public static final String GET_MSG_SERVICE_ADDRESS = "get_msg_service_address";

    public SpringVerticle(ApplicationContext ctx) {
        // 从spring上下文获取service
        this.service = (DubboService) ctx.getBean("dubboService");
    }

    @Override
    public void start() throws Exception {
        // 唤起事件总线,注册一个事件处理者,或者直译叫事件消费者
        vertx.eventBus().<String> consumer(PRINT_MSG_SERVICE_ADDRESS)
                .handler(msg -> {
                    // 获取事件内容后,调用service服务
                    // 这里是非阻塞式调用
                        service.printMsg("Asynchronous call dubbo service!!!");
                        msg.reply("success");
                    });

        vertx.eventBus().<String> consumer(GET_MSG_SERVICE_ADDRESS, printMsg());
    }

    // 模拟dubbo服务要从后台数据库获取数据,所以这里就是vert.x中的阻塞式调用
    // vert.x中规定,所有调用不可以阻塞其eventloop,所以当有数据库调用、thread.sleep等可能会阻塞线程的服务调动时
    // 需要使用vertx接口中的阻塞式调用接口
    private Handler<Message<String>> printMsg() {
        return msg -> {
            System.out.println("bus msg body is:" + msg.body());
            // 阻塞式接口调用
            vertx.<String> executeBlocking(future -> {
                // 通过future等待调用返回结果
                    String dubboMsg = "";
                    try {
                        dubboMsg = this.service.getMsg();
                    } catch (Exception e) {
                        e.printStackTrace();
                        future.fail(e);
                    }
                    // 把结果放到result中
                    future.complete(dubboMsg);
                }, result -> {
                    // 判断接口调用结果,成功的话讲结果放到事件总线的msg中传递给server端展示
                    if (result.succeeded()) {
                        System.out.println("msg from dubbo service is: "
                                + result.result());
                        msg.reply(result.result());
                    }
                    if (result.failed()) {
                        msg.fail(400, result.cause().getMessage());
                    }
                });
        };
    }

}

创建SpringVerticle:

package com.heartlifes.vertx.demo.dubbo;

import io.vertx.core.AbstractVerticle;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.handler.BodyHandler;

/**
 * 基本代码注释,请参见vert.x笔记:3.使用vert.x发布restful接口
 * 
 * @author john
 *
 */
public class ServerVerticle extends AbstractVerticle {

    @Override
    public void start() throws Exception {
        Router router = Router.router(vertx);
        router.route().handler(BodyHandler.create());
        router.route("/dubbo/get").handler(
        // 唤起vert.x的事件总线,并发送一个简单消息
                ctx -> vertx.eventBus().<String> send(
                        SpringVerticle.GET_MSG_SERVICE_ADDRESS,// 消息地址
                        "event bus calls dubbo service",// 消息内容
                        result -> {// 异步结果处理
                            if (result.succeeded()) {
                                // 成功的话,返回处理结果给前台,这里的处理结果就是service返回的一段字符串
                                ctx.response()
                                        .putHeader("content-type",
                                                "application/json")
                                        .end(result.result().body());
                            } else {
                                ctx.response().setStatusCode(400)
                                        .end(result.cause().toString());
                            }
                        }));
        router.route("/dubbo/print").handler(
        // 唤起vert.x的事件总线,并发送一个简单消息
                ctx -> vertx.eventBus().<String> send(
                        SpringVerticle.PRINT_MSG_SERVICE_ADDRESS,// 消息地址
                        "event bus calls dubbo service",// 消息内容
                        result -> {// 异步结果处理
                            if (result.succeeded()) {
                                // 成功的话,返回处理结果给前台
                                ctx.response()
                                        .putHeader("content-type",
                                                "application/json")
                                        .end("success");
                            } else {
                                ctx.response().setStatusCode(400)
                                        .end(result.cause().toString());
                            }
                        }));
        vertx.createHttpServer().requestHandler(router::accept).listen(8080);
    }
}

创建启动器:

package com.heartlifes.vertx.demo.dubbo;

import io.vertx.core.Vertx;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SpringMain {

    public static void main(String[] args) {
        // 配置文件方式
        ApplicationContext ctx = new ClassPathXmlApplicationContext(
                "dubbo-consumer.xml");
        Vertx vertx = Vertx.vertx();
        // 部署spring模块
        vertx.deployVerticle(new SpringVerticle(ctx));
        // 部署服务器模块
        vertx.deployVerticle(new ServerVerticle());
    }

}

vert.x笔记:4.vert.x中调用spring服务

evenbus事件总线介绍:

在介绍怎么在vert.x中集成spring服务前,我们要先简单介绍一下什么是vert.x的事件总线。
eventbus是vert.x的神经总线,每个vert.x实例维护了一个事件总线。简单来说,vert.x有以下几个概念

寻址:

vert.x将事件消息,通过地址发送到后端的处理程序上。一个地址就是一个全局唯一的字符串。

处理程序:

后端的处理程序,通过地址,将自己注册到事件总线上,并告诉事件总线,我是这个地址的处理程序。

发布/订阅模式:

消息被发布到一个地址,后台所有注册过这个地址的处理程序接收消息并进行处理。

修改pom,加入依赖

在pom.xml中加入以下配置和依赖包:

<properties>
    <spring.version>4.1.7.RELEASE</spring.version>
</properties>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-tx</artifactId>
    <version>${spring.version}</version>
</dependency>

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>${spring.version}</version>
</dependency>

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context-support</artifactId>
    <version>${spring.version}</version>
</dependency>

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>${spring.version}</version>
</dependency>

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-orm</artifactId>
    <version>${spring.version}</version>
</dependency>

vert.x集成spring:

创建一个spring service

很简单的服务,输出一个hello spring字符串。

package com.heartlifes.vertx.demo.hello;

import org.springframework.stereotype.Component;

@Component(value = "springService")
public class SpringService {

    public String getHello() {
        return "hello spring";
    }
}

创建SpringVerticle

springVerticle作为事件总线中的后台处理程序,接收事件总线消息,并调用springService完成服务处理。

package com.heartlifes.vertx.demo.hello;

import io.vertx.core.AbstractVerticle;

import org.springframework.context.ApplicationContext;

public class SpringVerticle extends AbstractVerticle {

    private SpringService service;

    public static final String GET_HELLO_MSG_SERVICE_ADDRESS = "get_hello_msg_service";

    public SpringVerticle(ApplicationContext ctx) {
        // 从spring上下文获取service
        this.service = (SpringService) ctx.getBean("springService");
    }

    @Override
    public void start() throws Exception {
        // 唤起事件总线,注册一个事件处理者,或者直译叫事件消费者
        vertx.eventBus()
                .<String> consumer(GET_HELLO_MSG_SERVICE_ADDRESS)
                .handler(msg -> {
                    // 获取事件内容后,调用service服务
                        System.out.println("bus msg body is:" + msg.body());
                        String helloMsg = service.getHello();
                        System.out.println("msg from hello service is: "
                                + helloMsg);
                        // 将service返回的字符串,回应给消息返回体
                        msg.reply(helloMsg);
                    });
    }

}

创建ServerVerticle

serverVerticle负责接收前端http请求,并将消息发布到事件总线上,等待后台处理程序处理完该事件后,返回事件处理结果。

package com.heartlifes.vertx.demo.hello;

import io.vertx.core.AbstractVerticle;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.handler.BodyHandler;

/**
 * 基本代码注释,请参见vert.x笔记:3.使用vert.x发布restful接口
 * 
 * @author john
 *
 */
public class ServerVerticle extends AbstractVerticle {

    @Override
    public void start() throws Exception {
        Router router = Router.router(vertx);
        router.route().handler(BodyHandler.create());
        router.route("/spring/hello").handler(
        // 唤起vert.x的事件总线,并发送一个简单消息
                ctx -> vertx.eventBus().<String> send(
                        SpringVerticle.GET_HELLO_MSG_SERVICE_ADDRESS,// 消息地址
                        "event bus calls spring service",// 消息内容
                        result -> {// 异步结果处理
                            if (result.succeeded()) {
                                // 成功的话,返回处理结果给前台,这里的处理结果就是service返回的一段字符串
                                ctx.response()
                                        .putHeader("content-type",
                                                "application/json")
                                        .end(result.result().body());
                            } else {
                                ctx.response().setStatusCode(400)
                                        .end(result.cause().toString());
                            }
                        }));
        vertx.createHttpServer().requestHandler(router::accept).listen(8080);
    }
}

模块部署

整个demo的启动类,负责启动spring容器,部署上面的两个模块,分别是spring模块和服务模块。

package com.heartlifes.vertx.demo.hello;

import io.vertx.core.Vertx;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class SpringMain {

    public static void main(String[] args) {
        // 注解方式配置,不需要配置文件
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
        // 扫描哪些包内的注解
        ctx.scan("com.heartlifes.vertx.demo.hello");
        ctx.refresh();
        Vertx vertx = Vertx.vertx();
        // 部署spring模块
        vertx.deployVerticle(new SpringVerticle(ctx));
        // 部署服务器模块
        vertx.deployVerticle(new ServerVerticle());
    }

}

http://localhost:8080/spring/hello,界面输出hello spring。
可以看到,使用事件总线后,可以将模块间的耦合度降到最低,仅仅通过事件的发布和订阅,就可以将原来揉成一块的显示服务调用,变成y

vert.x笔记:3.使用vert.x发布restful接口

vert.x重要概念介绍:

在第2偏笔记中,我们写了第一个vert.x的hello world代码,这里,我们把代码中用到的几个重要概念解释下。

Vertx基类:

Vertx类,是所有vert.x代码的入口,官方代码注释为:

The entry point into the Vert.x Core API.

即该类是所有vert.x core包API的总入口,简单理解就是,所有核心功能的API,都需要该类去调用,所有的核心功能也都需要该类提供环境上下文。

HttpServer:

官方注释:

An HTTP and WebSockets server

http/https/websockets服务器,vert.x发布restful服务,不需要tomcat提供servlet容器,它自己就是一台性能强大的web服务器,并且原生支持负载均衡,后面我们会讲到。

Router类:

先看官方代码注释:

A router receives request from an HttpServer and routes it to the first matching Route that it contains. A router can contain many routes.

Router类可以理解为一个路由器,他接收httpserver带来的请求,并将不同的请求分发到不同的路由中,如果简单对比一下spring mvc的话,可以将router理解为spring mvc中的dispatcher。

route:

route代表一条路由,同样,对比spring mvc,相当于spring中的@RequestMapping,他指定了restful api的请求接口路径,并将其交给handler来处理该条路由。

Handler:

首先来看官方代码注释:

Specify a request handler for the route. The router routes requests to handlers depending on whether the various criteria such as method, path, etc match. There can be only one request handler for a route. If you set this more than once it will overwrite the previous handler.

handler处理具体的路由请求,字面上讲就是处理某个具体的restful api。他与httpserver,router,route的关系可以用如下流程表示:

来自httpserver的request请求-->交由路由器做分发处理-->路由器匹配到具体的路由规则-->路由到最终的handler去处理请求

vert.x默认提供了很多处理器,包括但不局限于以下:

AuthHandler 处理权限校验支持
BodyHandler 提供所有请求上下文
CookieHandler 提供cookie支持
SessionHandler 提供session支持

RoutingContext:

官方代码注释:

Represents the context for the handling of a request in Vert.x-Web.

很简单,请求上下文,可以理解为servlet中的httprequest和httpresponse
####Verticle:

A verticle is a piece of code that can be deployed by Vert.x.

verticle是vert.x中,可被部署运行的最小代码块,可以理解为一个verticle就是一个最小化的业务处理引擎。
verticle被发布部署后,会调用其内部的start方法,开始业务逻辑处理,完成后会调用stop方法,对该代码块执行销毁动作

vert.x发布restufl api

新建类:RestServer,代码如下

package com.heartlifes.vertx.demo.simple;

import io.vertx.core.AbstractVerticle;
import io.vertx.core.Vertx;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.RoutingContext;
import io.vertx.ext.web.handler.BodyHandler;

public class RestServer extends AbstractVerticle {

    public static void main(String[] args) {
        // 获取vertx基类
        Vertx vertx = Vertx.vertx();
        // 部署发布rest服务
        vertx.deployVerticle(new RestServer());
    }

    // 重写start方法,加入我们的rest服务处理逻辑
    @Override
    public void start() throws Exception {
        // 实例化一个路由器出来,用来路由不同的rest接口
        Router router = Router.router(vertx);
        // 增加一个处理器,将请求的上下文信息,放到RoutingContext中
        router.route().handler(BodyHandler.create());
        // 处理一个post方法的rest接口
        router.post("/post/:param1/:param2").handler(this::handlePost);
        // 处理一个get方法的rest接口
        router.get("/get/:param1/:param2").handler(this::handleGet);
        // 创建一个httpserver,监听8080端口,并交由路由器分发处理用户请求
        vertx.createHttpServer().requestHandler(router::accept).listen(8080);
    }

    // 处理post请求的handler
    private void handlePost(RoutingContext context) {
        // 从上下文获取请求参数,类似于从httprequest中获取parameter一样
        String param1 = context.request().getParam("param1");
        String param2 = context.request().getParam("param2");

        if (isBlank(param1) || isBlank(param2)) {
            // 如果参数空,交由httpserver提供默认的400错误界面
            context.response().setStatusCode(400).end();
        }

        JsonObject obj = new JsonObject();
        obj.put("method", "post").put("param1", param1).put("param2", param2);

        // 申明response类型为json格式,结束response并且输出json字符串
        context.response().putHeader("content-type", "application/json")
                .end(obj.encodePrettily());
    }

    // 逻辑同post方法
    private void handleGet(RoutingContext context) {
        String param1 = context.request().getParam("param1");
        String param2 = context.request().getParam("param2");

        if (isBlank(param1) || isBlank(param2)) {
            context.response().setStatusCode(400).end();
        }
        JsonObject obj = new JsonObject();
        obj.put("method", "get").put("param1", param1).put("param2", param2);

        context.response().putHeader("content-type", "application/json")
                .end(obj.encodePrettily());
    }

    private boolean isBlank(String str) {
        if (str == null || "".equals(str))
            return true;
        return false;
    }

}

执行代码,打开浏览器,输入以下接口

http://localhost:8080/get/1/2
http://localhost:8080/post/1/2

处理session代码示例:

package com.heartlifes.vertx.demo.simple;

import io.vertx.core.AbstractVerticle;
import io.vertx.core.Vertx;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.Session;
import io.vertx.ext.web.handler.CookieHandler;
import io.vertx.ext.web.handler.SessionHandler;
import io.vertx.ext.web.sstore.LocalSessionStore;

public class SessionServer extends AbstractVerticle {

    public static void main(String[] args) {
        Vertx vertx = Vertx.vertx();
        vertx.deployVerticle(new SessionServer());
    }

    @Override
    public void start() throws Exception {
        Router router = Router.router(vertx);
        // 增加cookies处理器,解码cookies,并将其放到context上下文中
        router.route().handler(CookieHandler.create());
        // 增加session处理器,为每次用户请求,维护一个唯一的session,这里使用内存session,后面会讲分布式的session存储
        router.route().handler(
                SessionHandler.create(LocalSessionStore.create(vertx)));
        router.route().handler(routingContext -> {
            // 从请求上下文获取session
                Session session = routingContext.session();
                Integer count = session.get("count");
                if (count == null)
                    count = 0;
                count++;
                session.put("count", count);

                routingContext.response()
                        .putHeader("content-type", "text/html")
                        .end("total visit count:" + session.get("count"));
            });

        vertx.createHttpServer().requestHandler(router::accept).listen(8080);
    }

}

处理cookies代码示例:

package com.heartlifes.vertx.demo.simple;

import io.vertx.core.AbstractVerticle;
import io.vertx.core.Vertx;
import io.vertx.ext.web.Cookie;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.handler.CookieHandler;

public class CookieServer extends AbstractVerticle {

    public static void main(String[] args) {
        Vertx vertx = Vertx.vertx();
        vertx.deployVerticle(new CookieServer());
    }

    @Override
    public void start() throws Exception {
        Router router = Router.router(vertx);
        router.route().handler(CookieHandler.create());
        router.route().handler(
                routingContext -> {
                    Cookie cookie = routingContext.getCookie("testCookie");
                    Integer c = 0;
                    if (cookie != null) {
                        String count = cookie.getValue();
                        try {
                            c = Integer.valueOf(count);
                        } catch (Exception e) {
                            c = 0;
                        }
                        c++;
                    }

                    routingContext.addCookie(Cookie.cookie("testCookie",
                            String.valueOf(c)));
                    routingContext.response()
                            .putHeader("content-type", "text/html")
                            .end("total visit count:" + c);
                });

        vertx.createHttpServer().requestHandler(router::accept).listen(8080);
    }

}

vert.x笔记:2.hello vert.x–第一个vert.x hello world工程

假设:

本文及以下系列文章,假设你已经对jdk1.8新特性中的函数式编程及lambda匿名函数有一定了解,并会熟练使用maven。

开发环境配置:

使用最新版的vert.x 3.0,需要安装jdk1.8
maven需要3.0以上版本,推荐直接使用最新版
jdk及maven如何配置,参考百度教程

ide需求:myeclipse 2015 stable1.0及以上或者eclipse 4.4及以上

第一个maven工程

新建pom.xml文件

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.heartlifes</groupId>
    <artifactId>vertx-demo</artifactId>
    <version>3.0.0</version>
    <dependencies>
        <dependency>
        <groupId>io.vertx</groupId>
        <artifactId>vertx-core</artifactId>
        <version>${project.version}</version>
    </dependency>
        <dependency>
            <groupId>io.vertx</groupId>
            <artifactId>vertx-web</artifactId>
            <version>${project.version}</version>
        </dependency>
    </dependencies>
    <build>
        <pluginManagement>
            <plugins>
            <!-- We specify the Maven compiler plugin as we need to set it to Java 1.8 -->
                <plugin>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>3.1</version>
                    <configuration>
                        <source>1.8</source>
                        <target>1.8</target>
                        <compilerArgs>
                            <arg>-Acodetrans.output=${project.basedir}/src/main</arg>
                        </compilerArgs>
                    </configuration>
                </plugin>
            </plugins>
        </pluginManagement>
    </build>
</project>

生成eclipse工程:

mvn eclipse:eclipse

至此,一个基于vert.x框架空工程就创建完成了

第一个hello world代码

新建HelloWorld类:

package com.heartlifes.vertx.demo.hello;

import io.vertx.core.AbstractVerticle;
import io.vertx.core.Vertx;
import io.vertx.ext.web.Router;

public class HelloWorld extends AbstractVerticle {

    @Override
    public void start() throws Exception {
        Router router = Router.router(vertx);
        router.route().handler(
                routingContext -> {
                    routingContext.response()
                            .putHeader("content-type", "text/html")
                            .end("hello vert.x");
                });

        vertx.createHttpServer().requestHandler(router::accept).listen(8080);
    }

    public static void main(String[] args) {
        Vertx vertx = Vertx.vertx();
        vertx.deployVerticle(new HelloWorld());
    }
}

执行代码,在浏览器中输入localhost:8080,看看返回了什么。

至此,第一个vert.x的hello world工程搭建完毕,我们会在后面的章节里解释每行代码的作用。

vert.x笔记:1.vert.x介绍

直接转载csdn上的文章:http://www.csdn.net/article/2015-05-20/2824733-Java

Vert.x简介

在Java20周年之际,Java用户对Java的抱怨与日俱增,比如内存管理、笨重的JavaEE等。而Java依然在TIOBE编程语言排行榜上艰难的维持第一名的位置,随着一些新编程语言的兴起,这个领域目前呈现一种混战的态势。

在这种背景下,Java届的小鲜肉框架——Vert.x于2015年5月7日发布了3.0-milestone5版本,距离计划6月22日发布Vert.x3.0.0-final越来越近了,Vert.x用户组的粉丝们近期已经迫不及待地在宇宙中心(注:北京五道口)组织了一次Vert.x中国用户组Meetup,针对Vert.x工程化开发问题以及Vert.x3新特性展开了探讨。Vert.x(http://vertx.io/)是一个基于JVM、轻量级、高性能的应用平台,非常适用于最新的移动端后台、互联网、企业应用架构。

Vert.x基于全异步Java服务器Netty,并扩展出了很多有用的特性。Vert.x的亮点有:

同时支持多种编程语言——目前已经支持了Java、JavaScript、Ruby、Python、Groovy、Clojure、Ceylon等。对程序员来说,直接好处就是可以使用各种语言丰富的LIB,同时也不再为编程语言选型而纠结;

异步无锁编程——经典的多线程编程模型能满足很多Web开发场景,但随着移动互联网并发连接数的猛增,多线程并发控制模型性能难以扩展,同时要想控制好并发锁需要较高的技巧,目前Reactor异步编程模型开始跑马圈地,而Vert.x就是这种异步无锁编程的一个首选;

对各种IO的丰富支持——目前Vert.x的异步模型已支持TCP、UDP、FileSystem、DNS、EventBus、Sockjs等;

极好的分布式开发支持——Vert.x通过EventBus事件总线,可以轻松编写分布式解耦的程序,具有很好的扩展性;

生态体系日趋成熟——Vert.x归入Eclipse基金会门下,异步驱动已经支持了Postgres、MySQL、MongoDB、Redis等常用组件,并且有若干Vert.x在生产环境中的应用案例。

Reactor模式

和传统Java框架的多线程模型相比,Vert.x Netty是 Reactor模式的Java实现。考古了一下Reactor模式, 其理论最早由Washington University的Douglas C. Schmidt教授在1995年提出,在《Proactor – An Object Behavioral Pattern for Demultiplexing and Dispatching Handlers for Asynchronous Events 》这篇论文中做了 完整介绍。

图1-6是对其关键原理部分展开分析。

[请输入图片描述](http://img.ptcms.csdn.net/article/201505/20/555c4aa7a1d95.jpg)

图1 一个经典Web Server在收到Web浏览器请求后的处理过程

[请输入图片描述](http://img.ptcms.csdn.net/article/201505/20/555c4ac80015a.jpg)

图2 一个经典Web Server使用多线程模型,并发处理来自多个Web浏 览器的请求

[请输入图片描述](http://img.ptcms.csdn.net/article/201505/20/555c4ad096c8f.jpg)

图3 Web浏览器连接到一个Reactor模式的Web Server处理过程。利 用了Initiation Dispatcher组件,把耗时的IO操作事件注册到Initiation Dispatcher组件

[请输入图片描述](http://img.ptcms.csdn.net/article/201505/20/555c4aeec8ad2.jpg)

图4 Web浏览器访问一个Reactor模式的Web Server处理过程。耗时IO 操作由其它线程执行,IO执行完成后通知Initiation Dispatcher,再回到 Http Handler执行

[请输入图片描述](http://img.ptcms.csdn.net/article/201505/20/555c4b083e44c.jpg)

图5 Web浏览器连接一个Proactor模式的Web Server处理过程。和Reactor的主要区别是耗时IO操作交给操作系统异步IO库执行(例如 GNU/Linux aio),操作系统异步IO库执行完毕后,通过异步IO通知机制(例如epoll)触发Completion Dispatch,再交给Http Handler执行

[请输入图片描述](http://img.ptcms.csdn.net/article/201505/20/555c4b1251f6b.jpg)

图6 Web浏览器访问一个Proactor模式的Web Server处理过程。和Reactor的主要区别是耗时IO操作交给操作系统异步IO库执行(例如 GNU/Linux aio),操作系统异步IO库执行完毕后,通过异步IO通知机制(例如epoll)触发Completion Dispatch,再交给Http Handler执行

事实上,Vert.x/Netty的Reactor实现部分是在Netty 4.0如上述所示的代码中实现,和上述图中能对应的几个类是io.netty.channel.nio.NioEventLoop,io.netty. channel.epoll.EpollEventLoop,java.nio.channels.spi.SelectorProvide。

Vert.x3.0的更新

Vert.x3.0是对Vert.x2.x的重大升级,不仅仅是package从org.vertx到io.vertx的全面替换,一些重要的核心类也都做了破坏式的重构,几乎很难从vert.x2程序升级到vert.x3.0程序。建议新项目直接从Vert.x3.0开始。以下是Vert.x3的一些功能升级:

Vert.x2.x中的模块体系去掉了。目前Vert.x3.0推荐用Maven的模块体系,当然不仅限于Maven;支持其他语言在Vert.x上的代码生成;
Vert.x3.0项目构建,从Gradle改为Maven;为了更好地利用Java8的Lambdas表达式,只支持Java8;默认采用扁平的classpath结构;
Verticle工厂方式简化;支持用编程的方式实例化Verticle、以及部署Verticle实例;当你阻塞Eventloop主线程时警告,阻塞Reactor主线程是一种错误的使用方式;移除了PlatformManager模块;集群管理可以用编程的方式调用支持集群节点之间的共享数据;完全重写了HTTPclient,更完善;
WebSocketAPI改善;
SSL/TLS的改善;
Eventbus的API改善;
支持Eventbus代理;增加了扩展项目集’ext’stack;
增加了MongoService,支持MongoDB的纯异步驱动;
实现ReactiveStreams;
对reactive-streams的实现;
支持Options类的使用,可以构造函数带参数进去;
更完整的样例工程。请见:https://github.com/vert-x3/example-proj

druid监控配置及sql注入防火墙配置

druid是阿里巴巴开发的为监控而生的数据库连接池,可以非常直观的看到当前应用的数据源、sql执行情况、sql防火墙、web应用、uri监控、spring接口调用监控等。

数据源配置:

<bean id="readAccount" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
    <!-- 数据源驱动类可不写,Druid默认会自动根据URL识别DriverClass -->
    <property name="driverClassName" value="${jdbc_read.driver}" />
    <!-- 基本属性 url、user、password -->
    <property name="url" value="${jdbc_read.url}" />
    <property name="username" value="${jdbc_read.username}" />
    <property name="password" value="${jdbc_read.password}" />
    <!-- 配置初始化大小、最小、最大 -->
    <property name="initialSize" value="${jdbc.pool.minIdle}" />
    <property name="minIdle" value="${jdbc.pool.minIdle}" />
    <property name="maxActive" value="${jdbc.pool.maxActive}" />
    <!-- 配置获取连接等待超时的时间 -->
    <property name="maxWait" value="30000" />
    <!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
    <property name="timeBetweenEvictionRunsMillis" value="30000" />
    <!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
    <property name="minEvictableIdleTimeMillis" value="90000" />
    <property name="validationQuery" value="SELECT 'x'" />
    <property name="testWhileIdle" value="true" />
    <property name="testOnBorrow" value="false" />
    <property name="testOnReturn" value="false" />
</bean>

开启web监控:

在数据源配置中,增加以下属性

<property name="filters" value="stat" />

在web.xml中增加以下配置

<filter>
    <filter-name>DruidWebStatFilter</filter-name>
    <filter-class>com.alibaba.druid.support.http.WebStatFilter</filter-class>
    <init-param>
        <param-name>exclusions</param-name>
        <param-value>*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>DruidWebStatFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

<servlet>
    <servlet-name>DruidStatView</servlet-name>
    <servlet-class>com.alibaba.druid.support.http.StatViewServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>DruidStatView</servlet-name>
    <url-pattern>/druid/*</url-pattern>
</servlet-mapping>

开启sql防火墙:

在数据源配置中,增加以下属性

<property name="filters" value="stat,wall"/>

开启spring方法调用监控:

在spring配置文件中增加以下配置

<bean id="druid-stat-interceptor" class="com.alibaba.druid.support.spring.stat.DruidStatInterceptor"></bean>
<bean id="druid-stat-pointcut" class="org.springframework.aop.support.JdkRegexpMethodPointcut" scope="prototype">
    <property name="patterns">
        <list>
            <value>com.xxx.*</value>
            <value>com.xxx1.*</value>
        </list>
    </property>
</bean>
<aop:config>
    <aop:advisor advice-ref="druid-stat-interceptor" pointcut-ref="druid-stat-pointcut" />
</aop:config>