实现SpringBoot项目的多数据源配置的两种方式(dynamic-datasource-spring-boot-starter和自定义注解的方式)
1. 简介最近项目需要配置多数据源,本项目采用的技术是SpringBoot+mybatis-plus+Druid。为了图个方便直接想直接集成dynamic-datasource-spring-boot-starter进行多数据源配置。dynamic-datasource-spring-boot-starter 是一个基于springboot的快速集成多数据源的启动器。其支持 Jdk 1.7+, SpringBoot 1.4.x 1.5.x 2.x.x。其官方文档的地址是:https://www.kancloud.cn/tracy5546/dynamic-datasource/2264611该官方文档分为免费部分和付费部分。付费部分也仅仅只需要29块,29块也不多,就算原作者的支持,个人觉得这29块花的值。强烈建议使用最新版本,可以在版本记录里查找最新版本前提这里默认你已经集成并配置好了mybatis-plus。集成(第一种实现方式)仅仅只看基础部分的集成手册是远远不够的。网上好多博客也仅仅只是介绍了基础部分的内容,最终还是达不到想要的效果。本文的集成亲测有效,欢迎读者老爷们阅读。这里再次强烈建议采用最新版本的dynamic-datasource-spring-boot-starter,具体的版本记录请点击1. 添加依赖<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>3.5.1</version>
</dependency>2. 添加数据源配置在application.yml文件中将单数据源配置成多数据源,数据源配置的语法结构如下所示:spring:
datasource:
dynamic:
primary: master #设置默认的数据源或者数据源组,默认值即为master
strict: false #严格匹配数据源,默认false. true未匹配到指定数据源时抛异常,false使用默认数据源
datasource:
master:
url: jdbc:mysql://xx.xx.xx.xx:3306/dynamic
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver # 3.2.0开始支持SPI可省略此配置
slave_1:
url: jdbc:mysql://xx.xx.xx.xx:3307/dynamic
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver
slave_2:
url: ENC(xxxxx) # 内置加密,使用请查看详细文档
username: ENC(xxxxx)
password: ENC(xxxxx)
driver-class-name: com.mysql.jdbc.Driver
#......省略
#以上会配置一个默认库master,一个组slave下有两个子库slave_1,slave_2此处我的配置实例是:spring:
datasource:
dynamic:
primary: master #设置默认的数据源或者数据源组,默认值即为master
strict: false #严格匹配数据源,默认false. true未匹配到指定数据源时抛异常,false使用默认数据源
datasource:
master :
url: jdbc:mysql://127.0.0.1:23306/db1?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai&zeroDateTimeBehavior=CONVERT_TO_NULL&autoReconnect=true
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver # 3.2.0开始支持SPI可省略此配置
slave:
url: jdbc:mysql://127.0.0.1:23306/db2?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai&zeroDateTimeBehavior=CONVERT_TO_NULL&autoReconnect=true
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver3. 使用 @DS 切换数据源。@DS 可以注解在方法上或类上,同时存在就近原则 方法上注解 优先于 类上注解。注解结果没有@DS使用默认数据源@DS(“dsName”)dsName可以为组名也可以为具体某个库的名称官方文档里配置到这里就结束了,实际上还远远不够。4. 排除掉DruidDataSourceAutoConfigure在启动类中需要排除掉DruidDataSourceAutoConfigure.class,就是取消Druid的数据源的自动配置类。@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class, DruidDataSourceAutoConfigure.class})
@MapperScan(basePackages = {"com.jay.multidatasource.mapper"})
public class MultidatasourceApplication {
public static void main(String[] args) {
SpringApplication.run(MultidatasourceApplication.class, args);
}
}原理解释(第二种实现方式)多数据源的配置本质上就是加载多个数据源,并设置默认数据源,给每个数据源设置不同的键值对,当需要切换数据源时就传入目标数据源的键,然后重新设置数据源。下面就做一个简单的演示,就是不使用dynamic-datasource-spring-boot-starter。1. 定义数据源配置在application.yml文件中将单数据源配置成多数据源spring:
datasource:
druid:
db1:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://127.0.0.1:23306/db1?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai&zeroDateTimeBehavior=CONVERT_TO_NULL&autoReconnect=true
username: root
password: 123456
db2:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://127.0.0.1:23306/db2?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai&zeroDateTimeBehavior=CONVERT_TO_NULL&autoReconnect=true
username: root
password: 123456
test-on-borrow: true2. 定义全局的数据源构造类DynamicDataSourceContextHolder这个类的作用就是管理每个数据源的键,设置当前数据源的键,获取当前数据源的键。public class DynamicDataSourceContextHolder {
private static ThreadLocal<Object> CONTEXT_HOLDER = ThreadLocal.withInitial(() -> DataSourceKey.DCS.getName());
public static List<Object> dataSourceKeys = new ArrayList<Object>();
public static void setDataSourceKey(String key){
CONTEXT_HOLDER.set(key);
}
public static Object getDataSourceKey(){
return CONTEXT_HOLDER.get();
}
public static void clearDataSourceKey(){
CONTEXT_HOLDER.remove();
}
public static Boolean containDataSourceKey(String key){
return dataSourceKeys.contains(key);
}
}2. 自定义DynamicRoutingDataSource/**
* 该类继承自 AbstractRoutingDataSource 类,
* 在访问数据库时会调用该类的 determineCurrentLookupKey() 方法获取数据库实例的 key
*/
public class DynamicRoutingDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
logger.info("current datasource is : {}", DynamicDataSourceContextHolder.getDataSourceKey());
return DynamicDataSourceContextHolder.getDataSourceKey();
}
}3. 定义数据源配置类该类的作用就是初始化数据源DataSource实例,以及初始化SqlSessionFactory实例。这里需要注意的是必须使用MybatisSqlSessionFactoryBean来获取会话工厂SqlSessionFactory,不然的话,baseMapper中的生成动态SQL的方法就不能使用了。@Configuration
public class DataSourceConfigurer {
/**
* 配置数据源
*
* @return
*/
@Bean(name = "db1")
@Primary
@ConfigurationProperties(prefix = "spring.datasource.druid.db1")
public DataSource db1() {
return DruidDataSourceBuilder.create().build();
}
/**
* 配置数据源
*
* @return
*/
@Bean(name = "db2")
@ConfigurationProperties(prefix = "spring.datasource.druid.db2")
public DataSource db2() {
return DruidDataSourceBuilder.create().build();
}
@Bean(name = "dynamicDataSource")
public DataSource dynamicDataSource() {
DynamicRoutingDataSource dynamicRoutingDataSource = new DynamicRoutingDataSource();
Map<Object, Object> dataSourceMap = new HashMap<Object, Object>(2);
dataSourceMap.put("db1", db1());
dataSourceMap.put("db2", db2());
dynamicRoutingDataSource.setDefaultTargetDataSource(dcs());
dynamicRoutingDataSource.setTargetDataSources(dataSourceMap);
DynamicDataSourceContextHolder.dataSourceKeys.addAll(dataSourceMap.keySet());
return dynamicRoutingDataSource;
}
@Bean
public SqlSessionFactory sqlSessionFactory() throws Exception {
//MybatisPlus使用的是MybatisSqlSessionFactory
MybatisSqlSessionFactoryBean sqlSessionFactoryBean = new MybatisSqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dynamicDataSource());
//此处设置为了解决找不到mapper文件的问题
sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/*.xml"));
return sqlSessionFactoryBean.getObject();
}
@Bean
public SqlSessionTemplate sqlSessionTemplate() throws Exception {
return new SqlSessionTemplate(sqlSessionFactory());
}
/**
* 事务
*
* @return
*/
@Bean
public PlatformTransactionManager transactionManager() {
return new DataSourceTransactionManager(dynamicDataSource());
}4. 自定义注解TargetDataSource该注解只是作用在方法上,这里默认的数据源是db1.@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TargetDataSource {
String value() default "db1";
}5. 定义切面DynamicDataSourceAspect切面顾名思义就是拦击标注TargetDataSource注解的方法,并且根据注解指定的数据源的key切换数据源。@Aspect
@Component
public class DynamicDataSourceAspect {
private Logger logger = LoggerFactory.getLogger(DynamicDataSourceAspect.class);
@Before("@annotation(targetDataSource))")
public void switchDataSource(JoinPoint joinPoint, TargetDataSource targetDataSource) {
if (!DynamicDataSourceContextHolder.containDataSourceKey(targetDataSource.value().getName())) {
logger.error("DataSource [{}] doesn't exist, use default DataSource [{}]", targetDataSource.value());
} else {
DynamicDataSourceContextHolder.setDataSourceKey(targetDataSource.value().getName());
logger.info("Switch DataSource to [{}] in Method [{}]", DynamicDataSourceContextHolder.getDataSourceKey(), joinPoint.getSignature());
}
}
@After("@annotation(targetDataSource))")
public void restoreDataSource(JoinPoint joinPoint, TargetDataSource targetDataSource) {
DynamicDataSourceContextHolder.clearDataSourceKey();
logger.info("Restore DataSource to [{}] in Method [{}]", DynamicDataSourceContextHolder.getDataSourceKey(), joinPoint.getSignature());
}
}6. 使用注解没有添加注解的方法使用的是默认数据源,当需要使用非默认数据源时,则需要在方法上添加 @TargetDataSource("db2") 注解。需要注意的是,该注解最好添加到xxxMapper类的方法上。@TargetDataSource("db2")
ClassVO getClassStudent(@Param("open_id") String openId);总结本文详细介绍了两种数据源配置的方式
MSE微服务引擎Druid 数据源,且 agent 为最新版本,数据库治理无法显示数据,怎么办?
MSE微服务引擎Druid 数据源,且 agent 为最新版本,数据库治理无法显示数据,怎么办?
SpringBoot配置Druid数据库连接池
一、Druid 下载的官网下载官网:https://mvnrepository.com/artifact/com.alibaba/druid/1.2.7可以选择依赖下载,也可以直接下载jar包,需要注意的一点就是新版本可能不稳定,选择下载量最多的那个版本。如果下不了的话直接用以下网盘的方式下载链接:https://pan.baidu.com/s/1-bx4d21vuQhdzdK49fjQnA 提取码:w130二、SpringBoot配置配置在application.properties里面在这里插入图片描述url=jdbc:mysql://localhost:3306/数据库名
#远程连接用下面这种格式,本地连接上面那种就可以了
#url=jdbc:mysql://120.1.2.1/数据库名
username=用户名
password=密码
#这个驱动和电脑里面的数据库有关,这里选择的是mysql
driverClassName=com.mysql.jdbc.Driver
initialSize=10
maxActive=20
maxWait=1000
filters=wall附:mysql网盘链接,是旧版本的,新版本的有点问题链接:https://pan.baidu.com/s/1doJxd02gybt1z30UrWSkyg 提取码:yywt三、连接数据库代码1.创建一个连接/**
* 这边采用单例模式进行创建连接,返回一个connection
*/
public class TestDruid {
private TestDruid(){}
private static TestDruid testDruid;
public Connection getConnection() throws Exception {
//创建一个连接
Properties pro = new Properties();
//采用反射机制获取的配置文件,填上我们刚刚进行配置的配置文件名即可
pro.load(TestDruid.class.getClassLoader().getResourceAsStream("application.properties"));
DataSource ds = DruidDataSourceFactory.createDataSource(pro);
Connection conn = ds.getConnection();
return conn;
}
public static TestDruid getTestDruid() throws Exception {
if(testDruid == null) {
testDruid = new TestDruid();
}
return testDruid;
}
}2.连接数据库并且做一下简单的查询Connection con = TestDruid.getTestDruid().getConnection();
String sql = "SELECT * FROM cat";
PreparedStatement ps = con.prepareStatement(sql);
ResultSet resultSet = ps.executeQuery();
if(resultSet.next){
//简单查询cat表的name字段的第一个数据
String name = resultSet.getString("name");
System.out.println(name);
}
数据库连接池(Druid(德鲁伊))
JDBC数据库连接池的必要性在使用开发基于数据库的web程序时,传统的模式基本是按以下步骤在主程序(如servlet、beans)中建立数据库连接进行sql操作断开数据库连接 这种模式开发,存在的问题:普通的JDBC数据库连接使用 DriverManager 来获取,每次向数据库建立连接的时候都要将 Connection加载到内存中,再验证用户名和密码(得花费0.05s~1s的时间)。需要数据库连接的时候,就向数据库要求 一个,执行完成后再断开连接。这样的方式将会消耗大量的资源和时间。数据库的连接资源并没有得到很 好的重复利用。若同时有几百人甚至几千人在线,频繁的进行数据库连接操作将占用很多的系统资源,严 重的甚至会造成服务器的崩溃。对于每一次数据库连接,使用完后都得断开。否则,如果程序出现异常而未能关闭,将会导致数据库系统 中的内存泄漏,最终将导致重启数据库。(回忆:何为Java的内存泄漏?)这种开发不能控制被创建的连接对象数,系统资源会被毫无顾及的分配出去,如连接过多,也可能导致内 存泄漏,服务器崩溃。 数据库连接池技术为解决传统开发中的数据库连接问题,可以采用数据库连接池技术。 数据库连接池的基本思想:就是为数据库连接建立一个“缓冲池”。预先在缓冲池中放入一定数量的连接,当需要 建立数据库连接时,只需从“缓冲池”中取出一个,使用完毕之后再放回去。数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是重 新建立一个。数据库连接池在初始化时将创建一定数量的数据库连接放到连接池中,这些数据库连接的数量是由最小数据库 连接数来设定的。无论这些数据库连接是否被使用,连接池都将一直保证至少拥有这么多的连接数量。连接池 的最大数据库连接数量限定了这个连接池能占有的最大连接数,当应用程序向连接池请求的连接数超过最大连 接数量时,这些请求将被加入到等待队列中。工作原理: 数据库连接池技术的优点1. 资源重用 由于数据库连接得以重用,避免了频繁创建,释放连接引起的大量性能开销。在减少系统消耗的基础上,另一 方面也增加了系统运行环境的平稳性。2. 更快的系统反应速度 数据库连接池在初始化过程中,往往已经创建了若干数据库连接置于连接池中备用。此时连接的初始化工作均 已完成。对于业务请求处理而言,直接利用现有可用连接,避免了数据库连接初始化和释放过程的时间开销, 从而减少了系统的响应时间3. 新的资源分配手段 对于多应用共享同一数据库的系统而言,可在应用层通过数据库连接池的配置,实现某一应用最大可用数据库 连接数的限制,避免某一应用独占所有的数据库资源4. 统一的连接管理,避免数据库连接泄漏 在较为完善的数据库连接池实现中,可根据预先的占用超时设定,强制回收被占用连接,从而避免了常规数据 库连接操作中可能出现的资源泄露多种开源的数据库连接池JDBC 的数据库连接池使用 javax.sql.DataSource 来表示,DataSource 只是一个接口,该接口通常由服务器(Weblogic, WebSphere, Tomcat)提供实现,也有一些开源组织提供实现:DBCP 是Apache提供的数据库连接池。tomcat 服务器自带dbcp数据库连接池。速度相对c3p0较快,但因 自身存在BUG,Hibernate3已不再提供支持。C3P0 是一个开源组织提供的一个数据库连接池,速度相对较慢,稳定性还可以。hibernate官方推荐使用Proxool 是sourceforge下的一个开源项目数据库连接池,有监控连接池状态的功能,稳定性较c3p0差一 点BoneCP 是一个开源组织提供的数据库连接池,速度快Druid 是阿里提供的数据库连接池,据说是集DBCP 、C3P0 、Proxool 优点于一身的数据库连接池,但是 速度不确定是否有BoneCP快DataSource 通常被称为数据源,它包含连接池和连接池管理两个部分,习惯上也经常把 DataSource 称为连接 池DataSource用来取代DriverManager来获取Connection,获取速度快,同时可以大幅度提高数据库访问速 度。 特别注意:数据源和数据库连接不同,数据源无需创建多个,它是产生数据库连接的工厂,因此整个应用只需要一个 数据源即可。当数据库访问结束后,程序还是像以前一样关闭数据库连接:conn.close(); 但conn.close()并没有关闭数 据库的物理连接,它仅仅把数据库连接释放,归还给了数据库连接池。Druid(德鲁伊)数据库连接池Druid是阿里巴巴开源平台上一个数据库连接池实现,它结合了C3P0、DBCP、Proxool等DB池的优点,同时加入了 日志监控,可以很好的监控DB池连接和SQL的执行情况,可以说是针对监控而生的DB连接池,可以说是目前最好的 连接池之一。public class TestDruid {
public static void main(String[] args) throws Exception {
Properties pro = new Properties();
pro.load(TestDruid.class.getClassLoader().getResourceAsStream("druid.properties"));
DataSource ds = DruidDataSourceFactory.createDataSource(pro);
Connection conn = ds.getConnection();
System.out.println(conn);
}
}url=jdbc:mysql://localhost:3306/test?rewriteBatchedStatements=true
username=root
password=123456
driverClassName=com.mysql.jdbc.Driver
initialSize=10
maxActive=20
maxWait=1000
filters=wall
Spring Boot开发的导师管理系统,可做毕设,增加项目经验
导师管理系统一、介绍导师管理系统是一个用于导师与学生双向选择,集成布置任务、提交成果、审批及双向评分为一体,功能强大,操作简单。系统分为四大模块:系统管理、教师信息、学生信息和任务系统。系统默认有三个角色:管理员:可查看和操作所有菜单、配置用户权限学生:查看教师信息、选择导师、与教师交流、评价导师、上传研究成果、查看指导记录教师:查看选择自己的学生、与学生交流、评价学生、给学生布置任务、查看学生研究成果、添加指导记录、为学生的研究成果评分二、角色运行图管理员学生教师三、系统所有功能说明1、登录登录地址:http://localhost:80/账号密码:admin/admin123[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jNgaIdaX-1633664657224)(https://gitee.com/zhourui111/images/raw/master/img/image-20210801154330829.png)]2、个人中心教师和学生登录系统之后,可以在此模块维护自己的信息,让对方进一步了解自己。3、系统管理此模块包括用户管理、角色管理、菜单管理、学院/专业管理和字典管理 ,仅管理员可操作。(1)用户管理新增、修改、删除教师/学生用户。(2)角色管理配置新角色,为每个角色配置权限,包括菜单权限,数据权限(3)、菜单管理系统菜单与权限配置(4)学院/专业管理用户管理左侧树,配置学院以及专业列表(5)字典管理管理系统常用字典值,只有管理员可用3、教师信息此模块有教师列表和我的导师两大模块,只有学生可操作。(1)教师列表选择导师:学生可点击此按钮选择导师,待导师同意之后即建立教师-学生关系详情:查看教师信息[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-evGGCaxz-1633664657284)(https://gitee.com/zhourui111/images/raw/master/img/image-20210801161501023.png)](2)我的导师每位学生可选择一名导师。交流:学生可与导师互发消息评价:学生可对导师进行评价详情:查看导师详情4、学生信息教师专用模块。(1)选择我的学生教师在此列表可查看到选择自己的所有学生,点击选择学生即可与学生建立 导师-学生关系,点击详情可查看学生详细信息。(2)我的学生每位教师可有多名学生,此列表显示了与自己已建立‘导师-学生关系’的所有学生。交流:与学生交流评价:评价学生布置任务:为学生布置研究课题详情:查看学生详情5、任务系统此模块教师和学生公用,各自的功能用权限分开。(1)学生学生在列表可看到教师给自己发布的任务。上传作业:上传自己的研究成果详情:查看任务详情、任务进度、成绩等信息指导记录:查看导师添加的指导记录(2)教师教师可在此处看到自己布置的所有任务,并对其进行以下操作。评分:对学生提交的成果进行评分详情:看看任务详情,以及完成进度添加指导记录:添加指导记录指导记录:查看知道记录删除:删除此任务四、软件架构基础环境:JDK:1.8MySQL:5.7Maven3.0使用框架:核心框架:Spring Boot视图框架:Spring MVCORM框架:MyBatis数据库连接池:Druid 1.1安全框架:Apache Shiro 1.4日志:SLF4J 1.7、Log4j前端框架:jQury,bootStrap,ztree五、安装说明1.导入mysql脚本2.修改数据库配置3.启动程序4、访问http://localhost:80(账号admin/admin123)六、项目地址导师管理系统
Dao层设计思想、JdbcUtils类、数据库连接池技术
1 Dao设计思想Java语言要操作数据库表里面的数据时,要使用jdbc,但是jdbc的代码非常的繁琐,如果经常操作数据的话,这些代码的重复度会很高。所以我们把经常对表的相关操作封装到我们的dao类。这样就提高了代码的利用率,提高了系统的可维护性,可扩展性。Dao(data access object)数据访问对象。Dao里面需要有以下几个组件:1.Person类。实体类。a) 这个类跟表是一一对应的。b) 他里面的属性跟表里面的字段(列)是一一对应的。c) 属性私有,要有get和set方法。以及构造方法。d) 实现序列化接口。2.PersonDao类。Dao类a) 里面五个方法,增删改查。b) Void insert(Person p)c) Void delete(int id)d) Vodi update(Person p)e) List selectAll()f) Person selectOne(int id)3.Test类测试类a) 调用dao方法,对dao方法进行测试。Dao的作用:1.提高代码的可利用率2.让各个组件功能更加单一,满足单一职能原则。下面是一个Dao类的增删改查方法:package jdb
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
public class PersonDao {
/*
* 到数据库表里面把p插入进去
*/
public void insert(Person p){
// System.out.println(p.getId()+"\t"+p.getName());
Connection conn=null;
PreparedStatement ps=null;
ResultSet rs=null;
try{
conn=JdbcUtils.getConnection();
//3.写sql
String sql="insert into person values(person_seq.nextval,?,?,?,?)";
//4.创建ps
ps=conn.prepareStatement(sql);
//5.给占位符赋值
ps.setString(1,p.getName());
ps.setInt(2,p.getAge());
ps.setString(3,p.getTel());
ps.setString(4,p.getAddress());
//6.执行
ps.executeUpdate();
}catch(Exception e){
e.printStackTrace();
}finally{
JdbcUtils.closeAll(conn,ps,rs);
}
}
/**
* 根据id把数据库表里面对应的数据删除掉
* @param id 要删除的数据的编号
*/
public void delete(int id){
Connection conn=null;
PreparedStatement ps=null;
ResultSet rs=null;
try{
conn=JdbcUtils.getConnection();
//3.写sql
String sql="delete from person where id=?";
//4.创建ps
ps=conn.prepareStatement(sql);
//5.给占位符赋值
ps.setInt(1,id);
//6.执行
ps.executeUpdate();
}catch(Exception e){
e.printStackTrace();
}finally{
JdbcUtils.closeAll(conn, ps, rs);
}
}
public void update(Person p){
Connection conn=null;
PreparedStatement ps=null;
ResultSet rs=null;
try{
conn=JdbcUtils.getConnection();
//3.写sql
String sql="update person set name=?,age=?,tel=?,address=? where id=?";
//4.创建ps
ps=conn.prepareStatement(sql);
//5.给占位符赋值
ps.setString(1,p.getName());
ps.setInt(2,p.getAge());
ps.setString(3,p.getTel());
ps.setString(4,p.getAddress());
ps.setInt(5,p.getId());
//6.执行
ps.executeUpdate();
}catch(Exception e){
e.printStackTrace();
}finally{
JdbcUtils.closeAll(conn,ps,rs);
}
}
public Person selectOne(int id){
Connection conn=null;
PreparedStatement ps=null;
ResultSet rs=null;
Person p=null;
try{
conn=JdbcUtils.getConnection();
//3.写sql
String sql="select * from person where id=?";
//4.创建ps
ps=conn.prepareStatement(sql);
//5.执行
ps.setInt(1, id);
rs=ps.executeQuery();
if(rs.next()){
//获取表中每一个字段的值
String name=rs.getString("name");
int age=rs.getInt("age");
String tel=rs.getString("tel");
String address=rs.getString("address");
//把上面这些数据封装到person对象里面
p=new Person(id,name,age,tel,address);
}
}catch(Exception e){
e.printStackTrace();
}finally{
JdbcUtils.closeAll(conn,ps,rs);
}
return p;
}
/*
* 到数据库里面把所有的person实体查询出来,封装到list集合,然后返回该list集合。
*/
public List<Person> selectAll(){
Connection conn=null;
PreparedStatement ps=null;
ResultSet rs=null;
List<Person> perList=new ArrayList<Person>();
try{
conn=JdbcUtils.getConnection();
//3.写sql
String sql="select * from person";
//4.创建ps
ps=conn.prepareStatement(sql);
//5.执行
rs=ps.executeQuery();
while(rs.next()){
//获取表中每一个字段的值
int id=rs.getInt("id");
String name=rs.getString("name");
int age=rs.getInt("age");
String tel=rs.getString("tel");
String address=rs.getString("address");
//把上面这些数据封装到person对象里面
Person p=new Person(id,name,age,tel,address);
//把person放入list集合
perList.add(p);
}
}catch(Exception e){
e.printStackTrace();
}finally{
JdbcUtils.closeAll(conn,ps,rs);
}
return perList;
}
}
main, Test测试类:package jdbc;
import java.util.List;
public class Test {
public static void main(String[] args) {
//实例化对象,才能调用该类里面的方法。
PersonDao pd=new PersonDao();
/*//调用insert方法时,需要的实参
Person p=new Person(1,"zhangsan",20,"152226565","郑州市");
Person p2=new Person("lisi",20,"152226565","郑州市");
//调用pd对象的insert方法。
pd.insert(p);*/
// List<Person> perList=pd.selectAll();
// for(Person p:perList){
// System.out.println(p.getName()+"====="+p.getAge());
// }
// pd.delete(76);
// Person p=new Person(77,"wangwu",21,"152226566","郑州市");
// pd.update(p);
Person p=pd.selectOne(77);
System.out.println(p.getName()+"\t"+p.getAge());
}
}
上面代码存在的问题:1.dao类中的增删改查方法里面的代码的重复度非常高2.每个方法开始都是要建立数据库连接3.每个方法的结尾都是要释放资源。4.我们要去除代码的重复度。解决办法:在dao类抽取两个方法。Public Connection getConnection();Public void closeAll(Connection conn,PreparedStatement ps,ResultSet rs);这样,增删改查方法就可以重用上面两个方法的代码。代码改进后又有新的问题:1.后期项目复杂后,一个项目中对应多个表。2.多个表就会有多个dao类3.每个dao类中都需要数据库连接和释放资源。4.因为刚才我们把建立连接和释放资源的代码封装到了PersonDao。那么其他的dao类调用这两个方法就非常麻烦。2 JdbcUtils类为了解决上面的问题,我们可以把建立连接和释放资源这两个方法封装到一个公共的工具类中JdbcUtils.这个类为所有dao类服务。因为getConnection方法和closeAll跟JdbcUtils的对象没有关系。我们就把这两个方法定义成静态方法。这样dao类在使用这个两个方法时,就可以直接使用类名调用,而不用创建JdbcUtils对象了。Dao类做增删改查操作,JdbcUtils类和实体类协助dao。3 在JdbcUtils类里面使用Properties对象虽然抽出来两个方法使得我们在开发的时候代码的重复度降低,但是现在项目还存在一个问题,就是加载驱动和创建数据库连接都是我们直接写死的,但是这些字符串常量是有可能要修改的。在真实运行环境中是没有源码的。没有源码就不能修改JdbcUtils类。所以我们就可以把这些字符串常量抽取出来存放在一个文件中,然后每次读取的时候从这个文件中读取数据,这样就很好的解决了可以单独修改连接条件。java Jdk中有一个类叫Properties。1.他本身是一个map集合,可以存储数据。2.他有load(Reader r);方法,该方法可以自动加载文本文件中的数据。3.要使用文本文件中的数据时,可以调用getProperties(String)获取文本文件中的数据。JdbcUtil类:package com.macw.util;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Properties;
/**
* @author 超伟
* @2019年5月28日 下午1:51:58
* @博客:https://blog.csdn.net/MacWx
*/
public class JdbcUtil {
public static final Properties prop = new Properties();
//把流操作提取到静态代码块里面。
static{
InputStream in = null;
//使用类加载读取jdbc.properties文件
in = JdbcUtil.class.getResourceAsStream("/jdbc.properties");
try {
//读取文件中的数据
prop.load(in);
Class.forName(prop.getProperty("DriverClassName"));
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
throw new RuntimeException(e);
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
throw new RuntimeException(e);
}finally{
if (in!=null) {
try {
in.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
/**
* @return Connection
*/
public static Connection getConnection(){
Connection conn = null;
try {
conn = DriverManager.getConnection(prop.getProperty("url"),prop.getProperty("username"),prop.getProperty("password"));
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
throw new RuntimeException(e);
}
return conn;
}
/**
*
* @param conn
* @param ps
* @param rs
*/
public static void toClose(Connection conn,PreparedStatement ps,ResultSet rs){
try {
if (rs!=null) {
rs.close();
}
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try {
if(ps!=null){
ps.close();
}
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try {
if (conn!=null) {
conn.close();
}
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static void toClose(Connection conn,PreparedStatement ps){
try {
if(ps!=null){
ps.close();
}
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try {
if (conn!=null) {
conn.close();
}
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
优化JdbcUtils类1.在src里面创建一个jdbc.properties文件。在src右键—》new—》file—>jdbc.propertis2.在这个文件中去写键值对,键值之间用=隔开DriverClassName=oracle.jdbc.OracleDriver
url=jdbc:oracle:thin:@localhost:1521:xe
username=hr
password=mcw4 数据库连接池技术为什么要使用数据库连接池:1.频繁建立连接和释放连接非常浪费资源。2.可以把一些连接存储起来,有人需要使用时,就从里面获取空闲连接怎么使用数据库连接池?C3P0,DBCP,DRUID,JDNIDRUID是阿里实现的一种数据库连接池技术。1.导入druid的相关jar包2.将druid内部使用的字符串常量配置到properties配置文件中。3.在代码中使用数据库连接池。a) 创建出数据库连接池对象。(静态代码块)b) 从数据库连接池对象里面获取连接。(getConnection())c) 使用完后,把数据库连接归还给连接池。(closeAll())可以参考JdbcUtils工具类里面的代码。
【深入浅出Spring原理及实战】「源码调试分析」结合DataSourceRegister深入分析ImportBeanDefinitionRegistrar
注入案例代码如何通过实现SpringBoot框架带有的ImportBeanDefinitionRegistrar注册器,注入我们想要注册的bean对象实例。只需要采用@Import的注解进行注入对应的一类相关的bean对象。@Import({DataSourceRegister.class,A.class})
@SpringBootApplication
@ComponentScan("com.libo")
public class LiboApplication {
public static void main(String[] args) {
SpringApplication sa = new SpringApplication(LiboApplication.class);
sa.run(args);
}
}
复制代码DataSourceRegister的开发实现在springboot启动的时候,loader模块会根据“清单文件”加载该Application类,并反射调用psvm入口函数main,@Import注解也可以导入一个常规类,并且创建注入很多对象实例。DataSourceRegister类是用来进行初始化数据源和并提供了执行动态切换数据源的工具类。DataSourceRegister注入主数据源和从数据源这里DataSourceRegister继承的EnvironmentAware接口,没有真正意义上去用它的用途,本身可以通过这个setEnvironment方法,进行注入Environment对象,从而可以读取其他的配置信息,目前主要用作一个hook方法。读取对应的环境变量public final void setEnvironment(Environment environment) {
DruidEntity druidEntity = FileUtil.
readYmlByClassPath("db_info", DruidEntity.class);
defaultTargetDataSource =
DataSourceUtil.createMainDataSource(druidEntity);
}
复制代码主要用于读取Druid的数据源模型信息。进行创建对应的数据源对象defaultTargetDataSource。注入Bean到Spring容器中registerBeanDefinitions注册BeanDefinition对象模型public final void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {
// 0.将主数据源添加到数据源集合中
DataSourceSet.putTargetDataSourcesMap(MAINDATASOURCE,defaultTargetDataSource);
//1.创建DataSourceBean
GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
beanDefinition.setBeanClass(DataSource.class);
beanDefinition.setSynthetic(true);
MutablePropertyValues mpv = beanDefinition.getPropertyValues();
//spring名称约定为defaultTargetDataSource和targetDataSources
mpv.addPropertyValue("defaultTargetDataSource",defaultTargetDataSource);
mpv.addPropertyValue("targetDataSources",DataSourceSet.getTargetDataSourcesMap());
beanDefinitionRegistry.registerBeanDefinition("dataSource", beanDefinition);
}
复制代码完整的DataSourceRegister的案例public class DataSourceRegister<T> implements EnvironmentAware, ImportBeanDefinitionRegistrar {
private javax.sql.DataSource defaultTargetDataSource;
static final String MAINDATASOURCE = "mainDataSource";
public final void setEnvironment(Environment environment) {
DruidEntity druidEntity = FileUtil.
readYmlByClassPath("db_info", DruidEntity.class);
defaultTargetDataSource =
DataSourceUtil.createMainDataSource(druidEntity);
}
public final void registerBeanDefinitions(AnnotationMetadata
annotationMetadata, BeanDefinitionRegistry
beanDefinitionRegistry) {
// 0.将主数据源添加到数据源集合中
DataSourceSet.putTargetDataSourcesMap(MAINDATASOURCE,
defaultTargetDataSource);
//1.创建DataSourceBean
GenericBeanDefinition beanDefinition = new
GenericBeanDefinition();
beanDefinition.setBeanClass(DataSource.class);
beanDefinition.setSynthetic(true);
MutablePropertyValues mpv = beanDefinition.getPropertyValues();
//spring名称约定为defaultTargetDataSource和targetDataSources
mpv.addPropertyValue("defaultTargetDataSource",
defaultTargetDataSource);
mpv.addPropertyValue("targetDataSources",
DataSourceSet.getTargetDataSourcesMap());
beanDefinitionRegistry.registerBeanDefinition("dataSource", beanDefinition);
}
}
复制代码执行流程解读动态数据源注册器类实现了ImportBeanDefinitionRegistrar接口,没错就是这个原因,由于实现了该接口让该类成为了拥有注册bean的能力。原理上也能说得通作为一个Bean的注册类是没有必要被注册为Spring容器的Bean对象。虽然这样解释也不为过但我仍然想一探究竟,本来想大概找找spring涉及关键类如:ConfigurationClass,ConfigurationClassParser等,接下来我们需要看一下SpringBoot的总体加载流程。SpringApplication类的run方法SpringBoot启动时使用了SpringApplication类的run方法来牵引整个spring的初始化过程,源码如下。public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
// 错误原因分析器
FailureAnalyzers analyzers = null;
this.configureHeadlessProperty();
// 重点分析:启动所有的运行的监听器
SpringApplicationRunListeners listeners = this.getRunListeners(args);
listeners.starting();
try {
// 解析ApplicationArgument数据
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// 通过监听器和传递的参数,实现相关的配置环境对象信息,预先
// 进行配置Environment对象,设置全局环境变量容器
ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
// 输出相关的Banner控制,根据配置以及相关的Environment
Banner printedBanner = this.printBanner(environment);
// 创建Spring容器上下文。
context = this.createApplicationContext();
//创建和赋值错误解析器
analyzers = new FailureAnalyzers(context);
// 准备环境上下文进行配置相关的容器的上下文的参数。
this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
// 刷新上下文容器
this.refreshContext(context);
// 后置执行上下文操作
this.afterRefresh(context, applicationArguments);
// 完成监听器的后置完成处理操作
listeners.finished(context, (Throwable)null);
stopWatch.stop();
if(this.logStartupInfo) {
(new StartupInfoLogger(this.mainApplicationClass)).
logStarted(this.getApplicationLog(), stopWatch);
}
return context;
} catch (Throwable var9) {
this.handleRunFailure(context, listeners,
(FailureAnalyzers)analyzers,
var9);
throw new IllegalStateException(var9);
}
}
复制代码根据上面的源码流程可以分析重点的加载过程重点分析:启动所有的运行的监听器,获取SpringApplicationRunListeners; 从类路径下META-INF/spring.factories。回调所有的获取SpringApplicationRunListener.starting() 方法。SpringApplicationRunListeners listeners = this.getRunListeners(args);
listeners.starting();
复制代码解析ApplicationArgument数据以及封装命令行参数,通过program参数的部分进行解析,并且加载到PropertiesSourcePlaceHolder中的环境变量容器内部。ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
复制代码通过上面的监听器和传递的参数,实现相关的配置环境对象信息,预先进行配置Environment对象,设置全局环境变量容器根据上面的全局变量容器进行配置以及初始化相关的Environment对象。ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
复制代码创建Spring容器上下文,在createApplicationContext当中,由于我们是web项目,则spring默认给我们创建了一个context = this.createApplicationContext();
复制代码AnnotationConfigEmbeddedWebApplicationContext,当然它也是继承GenericWebApplicationContext类和GenericApplicationContext类的,那么他默认会持有一个DefaultListableBeanFactory对象,这个对象可以用来创建Bean。准备环境上下文进行配置相关的容器的上下文的参数this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
复制代码刷新上下文容器,refreshContext就是在做spring运行后的初始化工作。this.refreshContext(context);
复制代码接着往下走,进入refreshContext中会调用一系列的refresh方法,最终进入AbstractApplicationContext中,主要将SpringBoot的容器对象数据和原本基础的Spring Framework的框架对象进行加载到容器中。@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
prepareRefresh();
// Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory =
obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);
try {
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);
// Initialize message source for this context.
initMessageSource();
// Initialize event multicaster for this context.
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
onRefresh();
// Check for listener beans and register them.
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
finishRefresh();
}
catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context
initialization - " +"cancelling refresh attempt: " + ex);
}
// Destroy already created singletons to avoid dangling resources.
destroyBeans();
// Reset 'active' flag.
cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
}
finally {
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
}
}
}
复制代码invokeBeanFactoryPostProcessors() 方法就是Bean在注册前期做的一系列数据收集工作,BeanDefinitionRegistry的容器注册BeanDefinition之前,调用相关的跟着堆栈继续深入,会进入到这个方法中,这个方法就是初始化bean前的所有轨迹:在invokeBeanFactoryPostProcessors方法中继续跟进一系列方法就会看到在一开始的时候spring会初始化几个系统固有的Bean:继续调试后的关键点出现在这个方法中:public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
List<BeanDefinitionHolder> configCandidates = new ArrayList<BeanDefinitionHolder>();
String[] candidateNames = registry.getBeanDefinitionNames();
for (String beanName : candidateNames) {
BeanDefinition beanDef = registry.getBeanDefinition(beanName);
if (ConfigurationClassUtils.isFullConfigurationClass(beanDef) ||
ConfigurationClassUtils.isLiteConfigurationClass(beanDef)) {
if (logger.isDebugEnabled()) {
logger.debug("Bean definition has already been processed
as a configuration class: " + beanDef);
}
}
else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
}
}
// Return immediately if no @Configuration classes were found
if (configCandidates.isEmpty()) {
return;
}
// Sort by previously determined @Order value, if applicable
Collections.sort(configCandidates, new
Comparator<BeanDefinitionHolder>() {
@Override
public int compare(BeanDefinitionHolder bd1,
BeanDefinitionHolder bd2) {
int i1 =
ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
int i2 =
ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
return (i1 < i2) ? -1 : (i1 > i2) ? 1 : 0;
}
});
// Detect any custom bean name generation strategy supplied through the enclosing application context
SingletonBeanRegistry sbr = null;
if (registry instanceof SingletonBeanRegistry) {
sbr = (SingletonBeanRegistry) registry;
if (!this.localBeanNameGeneratorSet && sbr.containsSingleton(CONFIGURATION_BEAN_NAME_GENERATOR)) {
BeanNameGenerator generator = (BeanNameGenerator) sbr.getSingleton(CONFIGURATION_BEAN_NAME_GENERATOR);
this.componentScanBeanNameGenerator = generator;
this.importBeanNameGenerator = generator;
}
}
// Parse each @Configuration class
ConfigurationClassParser parser = new ConfigurationClassParser(
this.metadataReaderFactory, this.problemReporter, this.environment, this.resourceLoader, this.componentScanBeanNameGenerator, registry);
Set<BeanDefinitionHolder> candidates = new LinkedHashSet<BeanDefinitionHolder>(configCandidates);
Set<ConfigurationClass> alreadyParsed = new HashSet<ConfigurationClass>(configCandidates.size());
do {
parser.parse(candidates);
parser.validate();
Set<ConfigurationClass> configClasses = new LinkedHashSet<ConfigurationClass>(parser.getConfigurationClasses());
configClasses.removeAll(alreadyParsed);
// Read the model and create bean definitions based on its content
if (this.reader == null) {
this.reader = new ConfigurationClassBeanDefinitionReader(
registry, this.sourceExtractor, this.resourceLoader, this.environment,
this.importBeanNameGenerator, parser.getImportRegistry());
}
this.reader.loadBeanDefinitions(configClasses);
alreadyParsed.addAll(configClasses);
candidates.clear();
if (registry.getBeanDefinitionCount() > candidateNames.length) {
String[] newCandidateNames = registry.getBeanDefinitionNames();
Set<String> oldCandidateNames = new HashSet<String>(Arrays.asList(candidateNames));
Set<String> alreadyParsedClasses = new HashSet<String>();
for (ConfigurationClass configurationClass : alreadyParsed) {
alreadyParsedClasses.add(configurationClass.getMetadata().getClassName());
}
for (String candidateName : newCandidateNames) {
if (!oldCandidateNames.contains(candidateName)) {
BeanDefinition bd = registry.getBeanDefinition(candidateName);
if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) &&
!alreadyParsedClasses.contains(bd.getBeanClassName())) {
candidates.add(new BeanDefinitionHolder(bd, candidateName));
}
}
}
candidateNames = newCandidateNames;
}
}
while (!candidates.isEmpty());
// Register the ImportRegistry as a bean in order to support ImportAware @Configuration classes
if (sbr != null) {
if (!sbr.containsSingleton(IMPORT_REGISTRY_BEAN_NAME)) {
sbr.registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser.getImportRegistry());
}
}
if (this.metadataReaderFactory instanceof CachingMetadataReaderFactory) {
((CachingMetadataReaderFactory) this.metadataReaderFactory).clearCache();
}
}
复制代码而通过不断重复调试确定获得注册Bean的列表应该发生在配置的“剖析阶段”,也就是parser.parse(candidates);这个方法的内部,到了这里基本问题的答案已经要浮出水面了,我也不再粘贴无用的代码,如果你真的对这个问题比骄傲好奇可以自己跟踪并练习调试的源码技巧!当然在ConfigurationClassParser这个类中parse方法也是不少,只要静下心来逐渐分析,马上就能准确的找到Override的parse方法。protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
return;
}
ConfigurationClass existingClass = this.configurationClasses.get(configClass);
if (existingClass != null) {
if (configClass.isImported()) {
if (existingClass.isImported()) {
existingClass.mergeImportedBy(configClass);
}
// Otherwise ignore new imported config class; existing non-imported class overrides it.
return;
}
else {
// Explicit bean definition found, probably replacing an import.
// Let's remove the old one and go with the new one.
this.configurationClasses.remove(configClass);
for (Iterator<ConfigurationClass> it = this.knownSuperclasses.values().iterator(); it.hasNext();) {
if (configClass.equals(it.next())) {
it.remove();
}
}
}
}
// Recursively process the configuration class and its superclass hierarchy.
SourceClass sourceClass = asSourceClass(configClass);
do {
sourceClass = doProcessConfigurationClass(configClass, sourceClass);//处理定义的配置类
}
while (sourceClass != null);
this.configurationClasses.put(configClass, configClass);
}
复制代码protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)
throws IOException {
// Recursively process any member (nested) classes first
processMemberClasses(configClass, sourceClass);
// Process any @PropertySource annotations
for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
sourceClass.getMetadata(), PropertySources.class,
org.springframework.context.annotation.PropertySource.class)) {
if (this.environment instanceof ConfigurableEnvironment) {
processPropertySource(propertySource);
}
else {
logger.warn("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +
"]. Reason: Environment must implement ConfigurableEnvironment");
}
}
// Process any @ComponentScan annotations Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
if (!componentScans.isEmpty() &&
!this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
for (AnnotationAttributes componentScan : componentScans) {
// The config class is annotated with @ComponentScan -> perform the scan immediately
Set<BeanDefinitionHolder> scannedBeanDefinitions =
this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
// Check the set of scanned definitions for any further config classes and parse recursively if needed
for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
if (ConfigurationClassUtils.checkConfigurationClassCandidate(
holder.getBeanDefinition(), this.metadataReaderFactory)) {
parse(holder.getBeanDefinition().getBeanClassName(), holder.getBeanName());
}
}
}
}
// Process any @Import annotations
processImports(configClass, sourceClass, getImports(sourceClass), true);//处理注解导入的类型
// Process any @ImportResource annotations
if (sourceClass.getMetadata().isAnnotated(ImportResource.class.getName())) {
AnnotationAttributes importResource =
AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
String[] resources = importResource.getStringArray("locations");
Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");
for (String resource : resources) {
String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);
configClass.addImportedResource(resolvedResource, readerClass);
}
}
// Process individual @Bean methods
Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
for (MethodMetadata methodMetadata : beanMethods) {
configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
}
// Process default methods on interfaces
processInterfaces(configClass, sourceClass);
// Process superclass, if any
if (sourceClass.getMetadata().hasSuperClass()) {
String superclass = sourceClass.getMetadata().getSuperClassName();
if (!superclass.startsWith("java") && !this.knownSuperclasses.containsKey(superclass)) {
this.knownSuperclasses.put(superclass, configClass);
// Superclass found, return its annotation metadata and recurse
return sourceClass.getSuperClass();
}
}
// No superclass -> processing is complete
return null;
}
复制代码以上两个方法中标红的就是关键点。而且spring的大师们也把注释写的十分明显:”//Process any @Import annotations“,到这里已经彻底豁然开朗!spring会先去处理scan,将你程序内部的所有要注册的Bean全部获得(自然包括那些configuration),这里统称为ConfigurationClass,scan全部整理完毕后才会去处理@Import注解时导入的类!我们回到最初的问题 DataSourceRegister和A两个类为什么A成为了Bean但DataSourceRegister却未成为Bean呢?在processImports方法中,很明显candidate.isAssignable(ImportBeanDefinitionRegistrar.class)时操作为:configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());而普通的类通过processConfigurationClass(candidate.asConfigClass(configClass));方法,最终会被放在ConfigurationClassParser类的成员变量configurationClasses中,最终被初始化为Bean。private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
Collection<SourceClass> importCandidates, boolean checkForCircularImports) throws IOException {
if (importCandidates.isEmpty()) {
return;
}
if (checkForCircularImports && isChainedImportOnStack(configClass)) {
this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
}
else {
this.importStack.push(configClass);
try {
for (SourceClass candidate : importCandidates) {
if (candidate.isAssignable(ImportSelector.class)) {
// Candidate class is an ImportSelector -> delegate to it to determine imports
Class<?> candidateClass = candidate.loadClass();
ImportSelector selector = BeanUtils.instantiateClass(candidateClass, ImportSelector.class);
ParserStrategyUtils.invokeAwareMethods(
selector, this.environment, this.resourceLoader, this.registry);
if (this.deferredImportSelectors != null && selector instanceof DeferredImportSelector) {
this.deferredImportSelectors.add(
new DeferredImportSelectorHolder(configClass, (DeferredImportSelector) selector));
}
else {
String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);
processImports(configClass, currentSourceClass, importSourceClasses, false);
}
}
else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
// Candidate class is an ImportBeanDefinitionRegistrar ->
// delegate to it to register additional bean definitions
Class<?> candidateClass = candidate.loadClass();
ImportBeanDefinitionRegistrar registrar =
BeanUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class);
ParserStrategyUtils.invokeAwareMethods(
registrar, this.environment, this.resourceLoader, this.registry);
configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
}
else {
// Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar ->
// process it as an @Configuration class
this.importStack.registerImport(
currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
processConfigurationClass(candidate.asConfigClass(configClass));
}
}
}
catch (BeanDefinitionStoreException ex) {
throw ex;
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to process import candidates for configuration class [" +
configClass.getMetadata().getClassName() + "]", ex);
}
finally {
this.importStack.pop();
}
}
}
复制代码后置执行上下文操作this.afterRefresh(context, applicationArguments);
复制代码完成监听器的后置完成处理操作listeners.finished(context, (Throwable)null);
复制代码至此,总体的mportBeanDefinitionRegistrar的对象注入体系就基本介绍完了
SQL结构化系列一、基础知识
一、术语关键词(Lexis)关键词是一组预定义的单词,为系统内置,区别于变量(Variable)。语法(Syntax)关键词、变量的摆放顺序语义(Semantics)由关键词和语法共同表达出的含义,即语言的含义。在编程语言中,通常不考虑修辞等语用信息。词法分析给定一段文本,建立文本中的单词和关键词/变量之间的映射的过程。产生符号表(Token List)图 1.1 词法分析示意图语法分析将词法分析的产物与语法关联的过程。产生抽象语法树(Abstract Syntax Tree)抽象语法树:一种基于多叉树的数据结构,定义了SQL语句中,关键词(Lexis)的摆放顺序,即:语法(Syntax)图 1.2 语法分析示意图终结符集合:一个有限集合,其元素称为 终结符(terminal);编程语言中,将保留词定义为终结符。非终结符集合:一个有限集合,与终结符集合无公共元素,其元素称为 非终结符(non-terminal);语义分析基于抽象语法树和预定义的分析原则,也称为语言规范、产生式等,对语法树进行的遍历,通常产生与语法分析输入的文本不同类型的另一种语言。上下文无关的分析:在实现上即基于上下文无关的产生式,对抽象语法树进行遍历,根据产生式决定同时参考的节点数量,生成目标语言的过程。一段语义可能需要多个关键词和非关键词进行组合才可以表达,因此有时需要参考抽象语法树的子树才可以确定语义。SQL语法是上下文无关文法用常见的select子句的产生式为例:querySpecificationNointo ⇒ SELECT selectSpec* selectElements fromClause? groupByClause? havingClause windowClause? orderByClause? limitClause?;产生式的右侧为:querySpecificationNointo是(非终结符),描述了一段语义;表达式右侧不包含SQL关键字(终结符);图 1.3 语义分析示意图执行方案(Execution Plan)执行方案是执行引擎的运行时配置文件,程序读取执行方案,对执行方案进行解释--执行对应指令,产生目标数据。在执行之前,可以利用各种策略,进行执行方案的优化。二、SQL 执行过程2.1 SQL执行步骤SQL执行本质上是将SQL语句翻译成执行方案,再由执行引擎读取执行方案进行执行文件操作、计算的过程。具体来讲,包含词法分析(Lexical Analysis)、语法分析(Syntax Analysis)、语义分析(Semantic Analysis)、执行方案生成(Execution Plan Generation)、执行方案优化(Execution Plan Optimization)等阶段。2.2 关键阶段其中,难度较大的步骤是语义分析与执行方案优化。语义分析是整个流程中,第一次将输入的语言翻译为另外一种语言的过程。由于语言之间的转译并不是一一对应的关系,在语言翻译时,需要综合考虑,调整单词的顺序、减少单词数量等。举一个不恰当的例子,比如汉语“我 来自 中国。”翻译成英语“I come from China.”两句话表达了相同的语义,汉语用到了3个单词,英语用到了4个,因此在翻译时需要综合考虑。编程语言的转译过程与此类似,只不过编程语言没有修辞手法并且语法固定,比较适合用形式化的产生式进行描述,进而实现自动化翻译(即,编译)。执行方案优化是按照各种场景构造优化策略加以解决的问题,并无通法。比如常见的where子句优化:where a.id = 1 and 1 = 1 ⇔ where true;执行计划优化时,此where子句会被删去,因为它并没有对最终结果产生任何影响。2.2 AST VisitorAST Visitor是对SQL抽象语法树进行访问的工具,方便我们对抽象语法树进行遍历,在遍历语法树节点时作相应的动作,即副作用,是生成执行方案的基础。参考资料SQL解析工具库druid.sql中的ast模块:https://github.com/alibaba/druiddruid中的抽象语法树和Visitor是预先构造好的。antlr可以用于构造抽象语法树和Visitor:https://github.com/antlr/antlr4构造抽象语法树和Visitor需要基于语法文件(关键词+语法规则产生式)https://github.com/antlr/grammars-v4Apache calcite:用于构建数据库和数据管理系统的开源框架。它包括一个SQL解析器,基于关系代数构建表达式的API和查询计划引擎。https://github.com/apache/calcite编译原理学习自己动手写编译器:https://pandolia.net/tinyc/index.html 结合实践,讲解了编译原理。
【SpringBoot技术指南】「开发实战系列」动态化Quartz任务调度机制+实时推送任务数据到前端
前提介绍SpringBoot2.0整合quartz实现多定时任务动态配置,实现任务增删改,生成Cron表达式动态化任务调度添加依赖包配置化相关的Maven的配置机制控制<!-- quartz -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
复制代码DynamicSchedulerConfig动态化任务调度的配置服务类机制控制import org.springframework.boot.autoconfigure.quartz.SchedulerFactoryBeanCustomizer;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
@Configuration
public class DynamicSchedulerConfig implements SchedulerFactoryBeanCustomizer{
@Override
public void customize(SchedulerFactoryBean schedulerFactoryBean) {
schedulerFactoryBean.setStartupDelay(2);
schedulerFactoryBean.setAutoStartup(true);
schedulerFactoryBean.setOverwriteExistingJobs(true);
}
}
复制代码application.yml配置这里是配置druid连接池,以下都是druid的配置信息配置相关的quartz任务调度的配置服务以及相关的数据库相关的维护操作机制控制server:
port: 8101
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
url: jdbc:mysql://127.0.0.1:3306/task?useUnicode=true&characterEncoding=utf-8&useSSL=false
driver-class-name: com.mysql.jdbc.Driver
username: root
password: 123456
#quartz相关属性配置
quartz:
properties:
org:
quartz:
scheduler:
instanceName: clusteredScheduler
instanceId: AUTO
jobStore:
class: org.quartz.impl.jdbcjobstore.JobStoreTX
driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate
tablePrefix: QRTZ_
isClustered: true
clusterCheckinInterval: 10000
useProperties: false
threadPool:
class: org.quartz.simpl.SimpleThreadPool
threadCount: 10
threadPriority: 5
threadsInheritContextClassLoaderOfInitializingThread: true
#数据库方式
job-store-type: jdbc
# mybatisplus配置
mybatis-plus:
mapper-locations: classpath*:/mapper/**Mapper.xml
#把xml文件放在com.XX.mapper.*中可能会出现找到的问题,这里把他放在resource下的mapper中
typeAliasesPackage: com.task.entity
#这里是实体类的位置,#实体扫描,多个package用逗号或者分号分隔
configuration:
map-underscore-to-camel-case: true
cache-enabled: false
logging:
file: task-info.log
level:
com.task: debug
复制代码TaskInfoService业务服务类任务相关的实现接口@Service
public class TaskInfoService extends IService<TaskInfoBO> {
/**
* @Title: getPageJob
* @Description: TODO(查询定时任务,分页)
* @param @param search
* @param @return 参数
* @return Map<String,Object> 返回类型
* @throws
*/
IPage<TaskInfoBO> getPageJob(Pageable pageable, MultiValueMap queryParam);
/**
* @Title: getPageJobmod
* @Description: TODO(查询定时任务)
* @param @return 参数
* @return TaskInfoBO 返回类型
* @throws
*/
TaskInfoBO getPageJobmod();
/**
* @Title: addJob
* @Description: TODO(添加任务)
* @param @param jobClassName 任务路径名称
* @param @param jobGroupName 任务分组
* @param @param cronExpression cron时间规则
* @param @throws Exception 参数
* @return void 返回类型
* @throws
*/
void addJob(String jobClassName, String jobGroupName, String cronExpression) throws Exception;
/**
* @Title: addJob
* @Description: TODO(添加动态任务)
* @param @param jobClassName 任务路径名称
* @param @param jobGroupName 任务分组
* @param @param cronExpression cron时间规则
* @param @param jobDescription 参数
* @param @param params
* @param @throws Exception 参数说明
* @return void 返回类型
* @throws
*/
void addJob(String jobClassName, String jobGroupName, String cronExpression, String jobDescription, Map<String, Object> params) throws Exception;
/**
* @Title: updateJob
* @Description: TODO(更新定时任务)
* @param @param jobClassName 任务路径名称
* @param @param jobGroupName 任务分组
* @param @param cronExpression cron时间规则
* @param @throws Exception 参数
* @return void 返回类型
* @throws
*/
void updateJob(String jobClassName, String jobGroupName, String cronExpression) throws Exception;
/**
* @Title: deleteJob
* @Description: TODO(删除定时任务)
* @param @param jobClassName 任务路径名称
* @param @param jobGroupName 任务分组
* @param @throws Exception 参数
* @return void 返回类型
* @throws
*/
void deleteJob(String jobClassName, String jobGroupName) throws Exception;
/**
* @Title: pauseJob
* @Description: TODO(暂停定时任务)
* @param @param jobClassName 任务路径名称
* @param @param jobGroupName 任务分组
* @param @throws Exception 参数
* @return void 返回类型
* @throws
*/
void pauseJob(String jobClassName, String jobGroupName) throws Exception;
/**
* @Title: resumejob
* @Description: TODO(恢复任务)
* @param @param jobClassName 任务路径名称
* @param @param jobGroupName 任务分组
* @param @throws Exception 参数
* @return void 返回类型
* @throws
*/
void resumejob(String jobClassName, String jobGroupName) throws Exception;
}
复制代码TaskInfoServiceImpl业务服务类任务相关的实现接口实现类@Slf4j
@Service
@Transactional
public class TaskInfoServiceImpl extends ServiceImpl<JobAndTriggerMapper, TaskInfoBO> implements TaskInfoService {
@Autowired
private Scheduler scheduler;
@Override
public IPage<TaskInfoBO> getPageJob(Pageable pageable, MultiValueMap queryParam) {
IPage<TaskInfoBO> page = new Page<>(pageable.getPageNumber(), pageable.getPageSize());
return baseMapper.getJobAndTriggerDetails(page);
}
@Override
public TaskInfoBO getPageJobmod() {
return baseMapper.getJobAndTriggerDto();
}
@Override
public void addJob(String jobClassName, String jobGroupName, String cronExpression) throws Exception {
// 启动调度器
scheduler.start();
// 构建job信息
JobDetail jobDetail = JobBuilder.newJob(getClass(jobClassName).getClass())
.withIdentity(jobClassName, jobGroupName).build();
// 表达式调度构建器(即任务执行的时间)
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression);
// 按新的cronExpression表达式构建一个新的trigger
CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(jobClassName, jobGroupName)
.withSchedule(scheduleBuilder).build();
try {
scheduler.scheduleJob(jobDetail, trigger);
} catch (SchedulerException e) {
throw new Exception("创建定时任务失败");
}
}
@Override
public void addJob(String jobClassName, String jobGroupName, String cronExpression, String jobDescription,
Map<String, Object> params) throws Exception {
// 启动调度器
scheduler.start();
// 构建job信息
JobDetail jobDetail = JobBuilder.newJob(TaskInfoServiceImpl.getClass(jobClassName).getClass())
.withIdentity(jobClassName, jobGroupName).withDescription(jobDescription).build();
Iterator<Map.Entry<String, Object>> var7 = params.entrySet().iterator();
while(var7.hasNext()) {
Map.Entry<String, Object> entry = var7.next();
jobDetail.getJobDataMap().put((String)entry.getKey(), entry.getValue());
}
// 表达式调度构建器(即任务执行的时间)
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression);
// 按新的cronExpression表达式构建一个新的trigger
CronTrigger trigger = (CronTrigger)TriggerBuilder.newTrigger().withIdentity(jobClassName, jobGroupName)
.withSchedule(scheduleBuilder).build();
try {
scheduler.scheduleJob(jobDetail, trigger);
} catch (SchedulerException e) {
throw new Exception("创建定时任务失败");
}
}
@Override
public void updateJob(String jobClassName, String jobGroupName, String cronExpression) throws Exception {
try {
TriggerKey triggerKey = TriggerKey.triggerKey(jobClassName, jobGroupName);
// 表达式调度构建器(动态修改后不立即执行)
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression).withMisfireHandlingInstructionDoNothing();
CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
// 按新的cronExpression表达式重新构建trigger
trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).build();
// 按新的trigger重新设置job执行
scheduler.rescheduleJob(triggerKey, trigger);
} catch (SchedulerException e) {
throw new Exception("更新定时任务失败");
}
}
@Override
public void deleteJob(String jobClassName, String jobGroupName) throws Exception {
scheduler.pauseTrigger(TriggerKey.triggerKey(jobClassName, jobGroupName));
scheduler.unscheduleJob(TriggerKey.triggerKey(jobClassName, jobGroupName));
scheduler.deleteJob(JobKey.jobKey(jobClassName, jobGroupName));
}
@Override
public void pauseJob(String jobClassName, String jobGroupName) throws Exception {
scheduler.pauseJob(JobKey.jobKey(jobClassName, jobGroupName));
}
@Override
public void resumejob(String jobClassName, String jobGroupName) throws Exception {
scheduler.resumeJob(JobKey.jobKey(jobClassName, jobGroupName));
}
public static BaseJob getClass(String classname) throws Exception {
Class<?> class1 = Class.forName(classname);
return (BaseJob) class1.newInstance();
}
}
复制代码TaskInfoMapper任务信息控制的数据访问层接口实现机制public interface JobAndTriggerMapper extends BaseMapper<TaskInfoBO> {
IPage<TaskInfoBO> getJobAndTriggerDetails(IPage<TaskInfoBO> page);
TaskInfoBO getTaskInfoModel;
}
复制代码TaskInfoMapper.xml任务信息控制的数据访问层接口实现机制xml文件的配置机制控制<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.south.data.mapper.JobAndTriggerMapper">
<select id="getJobAndTriggerDetails" resultType="com.data.vo.TaskInfoBO">
SELECT
jd.JOB_NAME AS jobName,
jd.DESCRIPTION AS jobDescription,
jd.JOB_GROUP AS jobGroupName,
jd.JOB_CLASS_NAME AS jobClassName,
t.TRIGGER_NAME AS triggerName,
t.TRIGGER_GROUP AS triggerGroupName,
FROM_UNIXTIME(t.PREV_FIRE_TIME/1000,'%Y-%m-%d %T') AS prevFireTime,
FROM_UNIXTIME(t.NEXT_FIRE_TIME/1000,'%Y-%m-%d %T') AS nextFireTime,
ct.CRON_EXPRESSION AS cronExpression,
t.TRIGGER_STATE AS triggerState
FROM
qrtz_job_details jd
JOIN qrtz_triggers t
JOIN qrtz_cron_triggers ct ON jd.JOB_NAME = t.JOB_NAME
AND t.TRIGGER_NAME = ct.TRIGGER_NAME
AND t.TRIGGER_GROUP = ct.TRIGGER_GROUP
</select>
<select id="getJobAndTriggerDto" resultType="com.south.data.vo.JobAndTriggerDto">
SELECT
jd.JOB_NAME AS jobName,
jd.DESCRIPTION AS jobDescription,
jd.JOB_GROUP AS jobGroupName,
jd.JOB_CLASS_NAME AS jobClassName,
t.TRIGGER_NAME AS triggerName,
t.TRIGGER_GROUP AS triggerGroupName,
FROM_UNIXTIME(t.PREV_FIRE_TIME/1000,'%Y-%m-%d %T') AS prevFireTime,
FROM_UNIXTIME(t.NEXT_FIRE_TIME/1000,'%Y-%m-%d %T') AS nextFireTime,
ct.CRON_EXPRESSION AS cronExpression,
t.TRIGGER_STATE AS triggerState
FROM
qrtz_job_details jd
JOIN qrtz_triggers t
JOIN qrtz_cron_triggers ct ON jd.JOB_NAME = t.JOB_NAME
AND t.TRIGGER_NAME = ct.TRIGGER_NAME
AND t.TRIGGER_GROUP = ct.TRIGGER_GROUP
</select>
</mapper>
复制代码BaseJob类与Quartz服务的任务调度直接关联的Job服务机制控制,执行的基础!public interface BaseJob extends Job {
public void execute(JobExecutionContext context) throws JobExecutionException;
}
复制代码TaskController控制器与页面相关的执行业务控制器@RestController
@RequestMapping(value = "/job")
public class JobController {
@Autowired
private TaskInfoService taskInfoService;
public JobController(TaskInfoService taskInfoService){
this.taskInfoService = taskInfoService;
}
@PostMapping(value = "/page")
public ResponseEntity<List<TaskInfoBO>> queryjob(Pageable pageable, @RequestParam MultiValueMap<String, String> queryParams, UriComponentsBuilder uriBuilder) {
IPage<TaskInfoBO> page = taskInfoService.getPageJob(pageable, queryParams);
HttpHeaders headers = PaginationUtil.generatePaginationHttpHeaders(uriBuilder.queryParams(queryParams), page);
return ResponseEntity.ok().headers(headers).body(page.getRecords());
}
/**
* @Title: addJob
* @Description: TODO(添加Job)
* @param jobClassName
* 类名
* @param jobGroupName
* 组名
* @param cronExpression
* 表达式,如:0/5 * * * * ? (每隔5秒)
*/
@PostMapping(value = "/add")
public ResponseEntity addJob(
@RequestParam(value = "jobClassName") String jobClassName,
@RequestParam(value = "jobGroupName") String jobGroupName,
@RequestParam(value = "cronExpression") String cronExpression){
try {
jobAndTriggerService.addJob(jobClassName, jobGroupName, cronExpression);
return ResponseEntity.ok().body("操作成功");
} catch (Exception e) {
return ResponseEntity.ok().body("操作失败");
}
}
/**
* @Title: pauseJob
* @Description: TODO(暂停Job)
* @param jobClassName
* 类名
* @param jobGroupName
* 组名
*/
@PostMapping(value = "/pause")
public ResponseEntity pauseJob(
@RequestParam(value = "jobClassName") String jobClassName,
@RequestParam(value = "jobGroupName") String jobGroupName) {
try {
taskInfoService.pauseJob(jobClassName, jobGroupName);
return ResponseEntity.ok().body("操作成功");
} catch (Exception e) {
return ResponseEntity.ok().body("操作失败");
}
}
/**
* @Title: resumeJob
* @Description: TODO(恢复Job)
* @param jobClassName
* 类名
* @param jobGroupName
* 组名
*/
@PostMapping(value = "/resume")
public ResponseEntity resumeJob(
@RequestParam(value = "jobClassName") String jobClassName,
@RequestParam(value = "jobGroupName") String jobGroupName) {
try {
taskInfoService.resumejob(jobClassName, jobGroupName);
return ResponseEntity.ok().body("操作成功");
} catch (Exception e) {
return ResponseEntity.ok().body("操作失败");
}
}
/**
* @Title: rescheduleJob
* @Description: TODO(重新设置Job)
* @param jobClassName
* 类名
* @param jobGroupName
* 组名
* @param cronExpression
* 表达式
*/
@PostMapping(value = "/reschedule")
public ResponseEntity rescheduleJob(
@RequestParam(value = "jobClassName") String jobClassName,
@RequestParam(value = "jobGroupName") String jobGroupName,
@RequestParam(value = "cronExpression") String cronExpression) {
try {
taskInfoService.updateJob(jobClassName, jobGroupName, cronExpression);
return ResponseEntity.ok().body("操作成功");
} catch (Exception e) {
return ResponseEntity.ok().body("操作失败");
}
}
/**
* @Title: deleteJob
* @Description: TODO(删除Job)
* @param jobClassName
* 类名
* @param jobGroupName
* 组名
*/
@RequestMapping(value = "/del", method = RequestMethod.POST)
public ResponseEntity deleteJob(@RequestParam(value = "jobClassName") String jobClassName, @RequestParam(value = "jobGroupName") String jobGroupName) {
try {
taskInfoService.deleteJob(jobClassName, jobGroupName);
return ResponseEntity.ok().body("操作成功");
} catch (Exception e) {
return ResponseEntity.ok().body("操作失败");
}
}
}
复制代码DeferredResult实现实时推送浏览器要实时展示服务端计算出来的数据。一种可能的实现是:浏览器频繁向服务端发起请求以获得服务端数据。若定时周期为S,则数据延迟周期最大即为S。若想缩短数据延迟周期,则应使S尽量小,而S越小,浏览器向服务端发起请求的频率越高,又造成网络握手次数越多,影响了效率。因此,此场景应使用服务端实时推送技术。这里说是推送,其实还是基于请求-响应机制,只不过发起的请求会在服务端挂起,直到请求超时或服务端有数据推送时才会做出响应,响应的时机完全由服务端控制。所以,整体效果看起来就像是服务端真的在“实时推送”一样。可以利用DeferredResult来实现异步长连接的服务端实时推送。使用案例@RequestMapping("/call")
@ResponseBody
public DeferredResult<Object> call() {
// 泛型Object表示返回结果的类型
DeferredResult<Object> response = new DeferredResult<Object>(10000, // 请求的超时时间
null); // 超时后响应的结果
response.onCompletion(new Runnable() {
@Override
public void run() {
// 请求处理完成后所做的一些工作
}
});
// 设置响应结果
// 调用此方法时立即向浏览器发出响应;未调用时请求被挂起
response.setResult(new Object());
return response;
}
复制代码执行逻辑浏览器发起异步请求请求到达服务端被挂起(使用浏览器查看请求状态,此时为pending)向浏览器进行响应,分为两种情况:服务端调用DeferredResult.setResult(),请求被唤醒,返回结果。超时,返回一个你设定的结果。浏览得到响应,再次重复1,处理此次响应结果实现DeferResult传输模型public interface DeferredData {
String getId(); // 唯一标识
}
复制代码DeferredResult的持有者public interface IDeferredResultHolder<DeferredData> {
DeferredResult<DeferredData> newDeferredResult(String key, long timeout, Object timeoutResult);
void add(String key, DeferredResult<DeferredData> deferredResult);
DeferredResult<DeferredData> get(String key);
void remove(String key);
void handleDeferredData(DeferredData deferredData);
}
复制代码DeferredResult的持有者实现public class DeferredResultHolder implements IDeferredResultHolder<DeferredData> {
private Map<String, DeferredResult<DeferredData>> deferredResults = new ConcurrentHashMap<String, DeferredResult<DeferredData>>();
public DeferredResult<DeferredData> newDeferredResult(String key) {
return new DeferredResult(key, 30 * 1000L, null);
}
public DeferredResult<DeferredData> newDeferredResult(String key, long timeout) {
return new DeferredResult(key, timeout, null);
}
public DeferredResult<DeferredData> newDeferredResult(String key, Object timeoutResult) {
return new DeferredResult(key, 30 * 1000L, timeoutResult);
}
@Override
public DeferredResult<DeferredData> newDeferredResult(String key, long timeout, Object timeoutResult) {
DeferredResult<DeferredData> deferredResult = newDeferredResult<DeferredData>(timeout, timeoutResult);
add(key, deferredResult);
deferredResult.onCompletion(new Runnable() {
@Override
public void run() {
remove(key);
}
});
return deferredResult;
}
@Override
public void add(String key, DeferredResult<DeferredData> deferredResult) {
deferredResults.put(key, deferredResult);
}
@Override
public DeferredResult<DeferredData> get(String key) {
return deferredResults.get(key);
}
@Override
public void remove(String key) {
deferredResults.remove(key);
}
@Override
public void handleDeferredData(DeferredData deferredData) {
String key = deferredData.getId();
DeferredResult<DeferredData> deferredResult = get(key);
if (deferredResult != null) {
deferredResult.setResult(deferredData);
}
}
}
复制代码调用端@RequestMapping
@Controller
public class CallController {
@Autowired
private DeferredResultHolder deferredResultHolder;
@RequestMapping("/call")
@ResponseBody
public DeferredResult<DeferredData> call() {
String id = "abc";
return deferredResultHolder.newDeferredResult(id, 10 * 1000L, null);
}
}
复制代码触发返回端此处的CustomerDeferredData为实现了DeferredData接口的实现模型@RequestMapping
@Controller
public class CallController {
@Autowired
private DeferredResultHolder deferredResultHolder;
@RequestMapping("/finished")
@ResponseBody
public void finished() {
String id = "abc";
DeferredData defdatq = new CustomerDeferredData(id); 、
return deferredResultHolder.handleDeferredData(defdatq);
}
}
复制代码补充一个Spring针对于泛型的小tips随着泛型用的越来越多,获取泛型实际类型信息的需求也会出现,如果用原生API,需要很多步操作才能获取到泛型,比如:ParameterizedType parameterizedType =
(ParameterizedType) ABService.class.getGenericInterfaces()[0];
Type genericType = parameterizedType.getActualTypeArguments()[1];
复制代码 Spring提供的ResolvableType API,提供了更加简单易用的泛型操作支持,如:ResolvableType resolvableType1 = ResolvableType.forClass(ABService.class);
resolvableType1.as(Service.class).getGeneric(1).resolve()
复制代码对于获取更复杂的泛型操作ResolvableType更加简单。假设我们的API是:public interface Service<N, M> { }
@org.springframework.stereotype.Service
public class ABService implements Service<A, B> {}
@org.springframework.stereotype.Service
public class CDService implements Service<C, D> {}
复制代码如上泛型类非常简单。 ResolvableType resolvableType1 = ResolvableType.forClass(ABService.class);
复制代码通过如上API,可以得到类型的ResolvableType,如果类型被Spring AOP进行了CGLIB代理,请使用ClassUtils.getUserClass(ABService.class)得到原始类型。 可以通过如下得到泛型参数的第1个位置(从0开始)的类型信息 resolvableType1.getInterfaces()[0].getGeneric(1).resolve()。因为我们泛型信息放在 Service<A, B> 上,所以需要resolvableType1.getInterfaces()[0]得到;通过getGeneric(泛型参数索引)得到某个位置的泛型,resolve()把实际泛型参数解析出来得到字段级别的泛型信息假设我们的字段如下:@Autowired
private Service<A, B> abService;
@Autowired
private Service<C, D> cdService;
private List<List<String>> list;
private Map<String, Map<String, Integer>> map;
private List<String>[] array;
复制代码通过如下API可以得到字段级别的ResolvableTypeResolvableType resolvableType2 =
ResolvableType.forField(ReflectionUtils.findField(GenricInjectTest.class, "cdService"));
复制代码然后通过如下API得到Service<C, D>的第0个位置上的泛型实参类型,即C:resolvableType2.getGeneric(0).resolve(),比如List<List> list;是一种嵌套的泛型用例,我们可以通过如下操作获取String类型:ResolvableType resolvableType3 =
ResolvableType.forField(ReflectionUtils.findField(GenricInjectTest.class, "list"));
resolvableType3.getGeneric(0).getGeneric(0).resolve();
复制代码更简单的写法resolvableType3.getGeneric(0, 0).resolve(),List<List<String>> 即String
复制代码比如Map<String, Map<String, Integer>> map;我们想得到Integer,可以使用:ResolvableType resolvableType4 = ResolvableType.forField(ReflectionUtils.findField(GenricInjectTest.class, "map")); resolvableType4.getGeneric(1).getGeneric(1).resolve();
复制代码更简单的写法 resolvableType4.getGeneric(1, 1).resolve()
复制代码 得到方法返回值的泛型信息**假设我们的方法如下:private HashMap<String, List<String>> method() {
return null;
}
复制代码得到Map中的List中的String泛型实参:ResolvableType resolvableType5 = ResolvableType.forMethodReturnType(ReflectionUtils.findMethod(GenricInjectTest.class, "method"));
resolvableType5.getGeneric(1, 0).resolve();
复制代码得到构造器参数的泛型信息假设我们的构造器如下:public Const(List<List<String>> list, Map<String, Map<String, Integer>> map) { }
复制代码我们可以通过如下方式得到第1个参数( Map<String, Map<String, Integer>>)中的Integer:ResolvableType resolvableType6 = ResolvableType.forConstructorParameter(ClassUtils.getConstructorIfAvailable(Const.class, List.class, Map.class), 1);
resolvableType6.getGeneric(1, 0).resolve();
复制代码 得到数组组件类型的泛型信息如对于private List[] array; 可以通过如下方式获取List的泛型实参String:ResolvableType resolvableType7 = ResolvableType.forField(ReflectionUtils.findField(GenricInjectTest.class, "array"));
resolvableType7.isArray();//判断是否是数组
resolvableType7.getComponentType().getGeneric(0).resolve();
复制代码 自定义泛型类型ResolvableType resolvableType8 = ResolvableType.forClassWithGenerics(List.class, String.class); ResolvableType resolvableType9 = ResolvableType.forArrayComponent(resolvableType8);
resolvableType9.getComponentType().getGeneric(0).resolve();
复制代码ResolvableType.forClassWithGenerics(List.class, String.class)相当于创建一个List类型;ResolvableType.forArrayComponent(resolvableType8);:相当于创建一个List[]数组;resolvableType9.getComponentType().getGeneric(0).resolve():得到相应的泛型信息; 从如上操作可以看出其泛型操作功能十分完善,尤其在嵌套的泛型信息获取上相当简洁。目前整个Spring4环境都使用这个API来操作泛型信息。
SpringBoot框架:第一章:基础配置和介绍
什么是Spring Boot?Spring Boot就是一些库的集合,它能够被任意项目的构建系统所使用,简化新Spring应用的初始搭建以及开发过程,简化配置,用更简单的办法整合第三方其他技术。使用spring boot有什么好处其实就是简单、快速、方便!平时如果我们需要搭建一个spring web项目的时候需要怎么做呢?配置web.xml,加载spring和spring mvc配置数据库连接、配置spring事务配置加载配置文件的读取,开启注解配置日志文件...配置完成之后部署tomcat 调试使用sping boot到底有多爽系统性归纳总结一、SpringBoot的作用简化配置文件以更简单的方式整合第三方技术RedisElasticSearchMyBatis二、使用SpringBoot的套路加入需要的场景starter依赖配置properties或yml创建主启动类通过注解开启相关功能运行主启动类三、HelloWorld1.操作步骤①创建Maven工程②加入依赖<!-- 继承SpringBoot官方指定的父工程 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.8.RELEASE</version>
</parent>
<dependencies>
<!-- 加入Web开发所需要的场景启动器 -->
<dependency>
<!-- 指定groupId和artifactId即可,版本已在父工程中定义 -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>③创建主启动类④创建HelloWorldHandler,现在创建的hanlder不会自动扫描,因为只有主启动类的子包才会自动扫描下面这个才正确SpringBootHelloWorld/**
*
* 启动类
*
*/
@SpringBootApplication
public class SpringBootHelloWorld {
public static void main(String[] args) {
SpringApplication.run(SpringBootHelloWorld.class, args);
}
}
⑤启动运行主启动类中的main方法启动SpringBoot程序。⑥通过网页访问handler方法2.原理探究①核心机制注意:spring boot默认会加载org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration类,DataSourceAutoConfiguration类使用了@Configuration注解向spring注入了dataSource bean。因为工程中如果没有关于dataSource相关的配置信息,当spring创建dataSource bean因缺少相关的信息就会报错。在Application类上增加@EnableAutoConfiguration(exclude={DataSourceAutoConfiguration.class})阻止spring boot自动注入dataSource bean,后面加上了druid有了dataSoure配置,就不需要加这个注解@EnableAutoConfiguration(exclude={DataSourceAutoConfiguration.class})@SpringBootConfiguration:组合了 @Configuration 注解,实现配置文件的功能。@EnableAutoConfiguration:打开自动配置的功能,也可以关闭某个自动配置的选项,如关闭数据源自动配置功能:@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })。@ComponentScan:Spring组件扫描。四、SpringBoot环境下配置文件1.总述SpringBoot环境下常用的配置文件有两种,一种是properties属性文件,一种是yml文件。二者各有特点,语法也有很大区别,但是最终效果基本一致。2.properties文件使用文件名:application.propertiesproperties文件语法格式:server.port=8074
spring.datasource.url=jdbc:mysql://localhost:3306/gmall?characterEncoding=UTF-8
spring.datasource.username=root
spring.datasource.password=root
mybatis.configuration.map-underscore-to-camel-case=true
mybatis.mapper-locations=classpath:mapper/*Mapper.xml
spring.dubbo.registry.protocol=zookeeper
spring.dubbo.registry.address=192.168.0.100:2181
spring.dubbo.application.name=gmall-cart-service
spring.dubbo.protocol.name=dubbo
spring.dubbo.base-package=com.javawxid
logging.level.root=info
spring.redis.host=192.168.0.100
spring.redis.port=6379
spring.redis.database=0
3.yml文件的使用①yml简介yml是YAML(YAML Ain't Markup Language)语言的文件,以数据为中心,比json、xml等更适合做配置文件。②yml语法使用缩进表示层级关系缩进时不允许使用Tab键,只允许使用空格。缩进的空格数目不重要,只要相同层级的元素左侧对齐即可大小写敏感③YAML 支持的三种数据结构对象:键值对的集合数组:一组按次序排列的值字面量:单个的、不可再分的值yml文件语法格式:server:
port: 8443 #开发环境会开启https
ssl:
key-store: keystore.p12
key-alias: tomcat
key-store-password: 123456
key-store-type: PKCS12
spring:
datasource:
url: jdbc:mysql://localhost:3306/mall?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
username: root
password: 123456
druid:
initial-size: 5 #连接池初始化大小
min-idle: 10 #最小空闲连接数
max-active: 20 #最大连接数
web-stat-filter:
exclusions: "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*" #不统计这些请求数据
stat-view-servlet: #访问监控网页的登录用户名和密码
login-username: druid
login-password: druid
data:
mongodb:
host: localhost
port: 27017
database: mall-port
redis:
host: 192.168.0.135 # Redis服务器地址
database: 0 # Redis数据库索引(默认为0)
port: 6379 # Redis服务器连接端口
password: # Redis服务器连接密码(默认为空)
jedis:
pool:
max-active: 8 # 连接池最大连接数(使用负值表示没有限制)
max-wait: -1ms # 连接池最大阻塞等待时间(使用负值表示没有限制)
max-idle: 8 # 连接池中的最大空闲连接
min-idle: 0 # 连接池中的最小空闲连接
timeout: 3000ms # 连接超时时间(毫秒)
rabbitmq:
host: 192.168.0.135
port: 5672
virtual-host: /mall
username: mall
password: mall
publisher-confirms: true #如果对异步消息需要回调必须设置为true
# 日志配置
logging:
level:
org.springframework.data.mongodb.core: debug
com.macro.mall.mapper: debug
com.macro.mall.portal.dao: debug
常见的SpringBoot依赖pom.xml <!--继承spring-boot-starter-parent,要成为一个spring boot项目,首先就必须在pom.xml中继承spring-boot-starter-parent,同时指定其版本-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.8.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<!-- 环境参数,在普通maven项目中,需要在pom.xml中配置插件来修改jdk版本,utf-8编码等环境参数,在spring boot中则更加简单。
在eclipse中按ctrl+左键点击上面的spring-boot-starter-parent,查看其源码 -->
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><!-- 编译字符编码为utf-8 -->
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding><!-- 输出字符编码为UTF-8 -->
<java.version>1.8</java.version><!-- jdK版本 -->
</properties>
<dependencies>
<!--核心依赖,包括auto-configuration , logging和YAML。-->
<!-- <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>-->
<!-- 测试 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--springmvc,代表web模块,在这个模块中含了许多JAR包,有spring相关的jar,内置tomcat服务器,jackson等,这些web项目中常用的的功能都会自动引入-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--数据库连接池-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!-- MyBatis 生成器 -->
<dependency>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-core</artifactId>
<version>1.3.3</version>
</dependency>
<!-- MyBatis-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.6</version>
</dependency>
<!--Mysql数据库驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.15</version>
</dependency>
<!-- dataSource相关的配置信息DruidDataSource-->
<!--集成druid连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>
</dependencies>
<!-- 编译生成可执行jar文件,默认情况下,maven打包生成的jar文件是用来给其他项目依赖用的,是无法直接运行的。
spring boot根据自生需要,提供了一个插件来生成可执行jar文件。在spring-boot-starter-parent源码中可以找到 -->
<build>
<!--在浏览器中的访问路径,如果将它改成helloworld,再执行maven&#45;&#45;update,这时运行项目的访问路径是
http://localhost:8080/helloworld/ 而不是项目名的 http://localhost:8080/test-->
<!--<finalName>demo</finalName>-->
<!-- 在自己项目的pom.xml中声明这个插件,就会生效 -->
<plugins>
<!-- maven插件 -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>