learning, progress, future.

skydh


  • 首页

  • 归档

spring 事务机制

发表于 2018-03-18

简介

  • spring事务通过这个接口来定义的:TransactionDefinition ,这个接口包含了和事务属性有关的方法和参数定义。
  • spring事务里面PlatformTransactionManager这个接口定义了事务的具体执行操作方法。其最终的实现类主要有:DataSourceTransactionManager:适用于使用JDBC和iBatis进行数据持久化操作的情况。
    HibernateTransactionManager:适用于使用Hibernate进行数据持久化操作的情况。
    JpaTransactionManager:适用于使用JPA进行数据持久化操作的情况。这个类也可以管理jdbc的事务。ps:(This transaction manager also supports direct DataSource access within a
    • transaction (i.e. plain JDBC code working with the same DataSource).)

事务隔离级别

  在这个接口里面定义了一些事务隔离级别,这和mysql的事务隔离界别没区别,都是RU,RC,RR,SE。我们可以使用符合自己业务需求的隔离级别。

事务传播级别

  在接口定义中,下面几个参数代表了不同传播级别。事务的传播就是在开启当前事务之前已经开启了一个事务。

int PROPAGATION_REQUIRED = 0;
int PROPAGATION_SUPPORTS = 1;
int PROPAGATION_MANDATORY = 2;
int PROPAGATION_REQUIRES_NEW = 3;
int PROPAGATION_NOT_SUPPORTED = 4;
int PROPAGATION_NEVER = 5;
int PROPAGATION_NESTED = 6;
  • PROPAGATION_REQUIRED:若存在事务,则加入当前事务,否则创建一个新事务。
  • PROPAGATION_SUPPORTS:若存在事务,则加入当前事务,否则以无事务的状态运行。
  • PROPAGATION_MANDATORY:如果存在事务则加入当前事务,没有则抛出异常。
  • PROPAGATION_REQUIRES_NEW:创建一个新事务,以这个新事务为主,把已存在的事务挂起。
  • PROPAGATION_NOT_SUPPORTED:以非事务的方式运行,若存在事务,则把当前事务挂起。
  • PROPAGATION_NEVER:以非事务的方向运行,若存在事务,则抛出异常。
  • PROPAGATION_NESTED:若存在事务,则创建一个事务作为当前事务的嵌套事务来执行,没有则是创建一个新事物和PROPAGATION_REQUIRED类似。内嵌事务并不是一个独立的事务,它依赖于外部事务的存在,只有通过外部的事务提交,才能引起内部事务的提交,嵌套的子事务不能单独提交。

编程式事务

  类似于JDBC编程实现事务管理。Spring框架提供一致的事务抽象,因此对于JDBC还是JTA事务都是采用相同的API进行编程。

声明式事务

  声明式事务实际上是通过aop实现的,我们一般也是采用这个方案,可以有效的减少代码code。并且把公共代码全部抽取了出来。我们一般也是采用这个方案。

redis入门学习

发表于 2018-03-18

NOSQL

  为什么需要NOSQL?
  1.高并发读写。
  2.海量数据的高效率的存储和访问。
  3.高可扩展和高可用性。
  什么是NOSQL?
  NOSQL是not only sql。非关系数据库。
  NOSQL数据库的四大分类。
  键值存储:redis.
  列存储:HBASE,
  文档数据库:MONGDB.
  图形数据库:Neo4j,

aaa

Redis概述

  高性能的键值对数据库,也是内存数据库很快,支持很多键值数据类型:
  字符串类型。
  列表类型。
  有序集合类型。
  散列类型。
  集合类型。
  Redis主要用于场景。
  缓存。 任务队列。网站访问统计等。

Redis安装

  工具:CentOS,虚拟机VM,FileZilla,SecureCRT,官网下载的redis压缩包这5个工具。
  1,打开虚拟机,用ifconfig查看网络地址,然后打开SecureCRT连接到这个虚拟机,然后进行操作.
  2.打开FileZilla,连上虚拟机,然后找到找到文件传送到虚拟机里面的centos里面。
  3.安装Gcc环境:yum install gcc c++,在线安装gcc。
  4,解压这个redis压缩包。命令如下: tar -zxvf redis-4.0.8.tar.gz。然后进入到解压的包里面,编译这个redis。命令是: make,好了之后就是安装了,命令如下所示:make PREFIX=/usr/local/redis install,中间的是我的安装路径。然后进入到解压路径,找到redis.conf这个文件,复制到安装路径。命令如下:cp redis.conf /usr/local/redis。然后进入到安装路径下,找到这个文件打开编辑这个文件,将 daemonize no,改成yes。再然后启动redis,进入到安装目录的redis包下命令如下: ./bin/redis-server ./redis.conf。然后用这个命令:ps -ef | grep -i redis查看redis是否成功启动。,进入到redis的操作界面命令如下: redis.cli进行交互。至此,安装完毕

jedis介绍

  Jedis是redis的基本连接java操作接口。是redis官网首选的java客户端开发包。注意地址你要先ifconfig,查看地址。
代码演示下


    public class JunitTest {
        @Test
        public void demo1() {
            //System.out.println("111");
            Jedis jedis = new Jedis("192.168.1.106", 6379);
            jedis.set("name1", "dh");
            System.out.println(jedis.get("name1"));
            jedis.close();
        }

    }


@Test
public void demo2() {
// System.out.println(“111”);

    JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
    // 最大数
    jedisPoolConfig.setMaxTotal(20);
    // 最大空闲数
    jedisPoolConfig.setMaxIdle(10);

    JedisPool jedisPool = new JedisPool(jedisPoolConfig, "192.168.1.106", 6379);

    Jedis jedis = null;
    try {
        jedis = jedisPool.getResource();
        jedis.set("name1", "dh");
        System.out.println(jedis.get("name1"));
    } catch (Exception e) {
    } finally {
        jedis.close();
        jedisPool.close();
    }

}


  相关的maven1依赖自己加。这里我要说的是以下几点,第一。直接运行会报错的,很多原因,CentOs方案如下:
  1.开启防火墙:systemctl start firewalld.service
  2.开启端口firewall-cmd –zone=public –add-port=80/tcp –permanent
  命令含义:
  –zone #作用域
  –add-port=80/tcp #添加端口,格式为:端口/通讯协议
  –permanent #永久生效,没有此参数重启后失效
  3.重启防火墙:firewall-cmd –reload
  4.修改上面redis包下的redis.conf文件1>注释掉bind
为#bind 127.0.0.1
  2>默认不是守护进程方式运行,这里可以修改
  daemonize yes
  3>禁用保护模式
  protected-mode no
  5.若是SecureCRT无法连接虚拟机,输入:systemctl restart sshd.service
  然后在运行,搞定。

redis基本数据结构

  五大基本数据类型:字符串(String),字符串列表(list),有序字符创集合(sorted set),哈希(hash),字符串集合(set)
  key定义的注意点:1,key值不要太长,不要超过1024字节。2,不要太短,可读性太差,3命名规范

字符串String

  存入redis是2进制操作的,最大是512M.
  基本命令:赋值:set company yonyou,取值 get company,取值并且赋值getset company baidu “mmm”,删除 del company.加值操作: incr num,若num不存在,那么创建值为0,且加一,若是存在那么加一,若是无法转为整形,报错。同样还有decr num是减一操作。incrby num 5,这是一次加5,其余的和incr一样,decrby num 5,这是和incryby相反的。append compay dh,是在company这个key的value的值后面添加一个字符串。返回值为字符串长度。若是不存在,那么直接创建这个值。
  基本用法:将用户信息序列化之后存到redis里面。然后取出反序列化。
  数据结构:数据结构类似ArrayList,是动态的,为了减少频繁的扩容,会预先分配冗余空间。当字符串大小小于1M时,扩容时翻倍扩容,大于等于1M时则是一次扩容1M。

存储Hash

  String key 和String Value的map容器,每个Hash可以存很多键值对。相当于java里面的HashMap.当第一维数组发生碰撞时,会将碰撞的数据形成链表。
  基本用法:也可以存储用户信息,可以把信息,也就是每个字段单独存储。用的时候不用一下子全部取出来,只用取出自己要用的部分。可以减少网络io,但是这种数据结构性能太差。
  赋值:hest myhash username dh,多个赋值,hmset myhash username dh age 21.取值:hget myhash username,一次取多个值hget myhash username age.全部取出来 hgetall myhash.
  删除:单个删除:hdel myhash user age,del myhash是删除整个集合。同一样可以加值操作, hincrby myhash age 5,也有删除操作hdecrby myhash age 5,判断hash中是否存在某个key:hexists myhash username,成功返回1,不成功返回0.判断hash中多少属性key:hlen myhash.获得所有的key:hkeys myhash,获取所有value:hvalue myhash

list

  按照插入顺序的链表。可以从头部和尾部插入,没有key时创建这个key,数据没了删除这个key,链表的操作特征,大家都懂的。可以用作队列和栈。由于该结构是链表结构,如果按照顺序获取数据,左出或者右出都可以很高效,但是如果按照下标获取数据,那么久可能会很慢,因为要一个个遍历,时间复杂度高。
  这个list的实际存储并不是纯粹的linkedList,而是一个叫做快速链表的一个结构,首先数据量小的时候使用一块连续的内存存储,这个结构就是ziplist,也就是压缩列表,所有元素紧挨一起存储,分配一块连续的内存,当数据量大的时候才会变成快速链表,就是多个ziplist连在一起。

  这些方式来存储数据ArrayList,linklist双向链表
  左边插入数据Ipush mylist a,b,c,建立一个list,并且把数据插进去,就像栈一样,返回值是链表的个数。
  右侧插入rpush mylist a b,c
  查看链表 lrange mylist 0 5,查看链表0-5的元素。0代表第一个元素,-1代表最后一个元素。那么查看索引信息的
  左侧弹出元素 lpop mylist
  右侧弹出元素 rpop mylist
  获取元素个数 llen mylist
  lpushx,rpushx这个和lpush差不多一样,但是没有这个key的时候是不会创建这个链表的。
  lrem mylist count 1,当cout>0,那么从头开始删除数目为count的1,当count<0,从尾部开始删除绝对值为count的1,如果count=0,那么删除所有的1,。
  lset mylist 3 mmm,效果是吧第四个元素替换为mmm,
  linsert mylist before b 11,在这个mylist的这个list里面的这个b元素前边插入11
  linsert mylist after b 11,在这个mylist的这个list里面的这个b元素后边插入11。
   rpoplpush mylist mylist1 将list1结尾的元素放到list2首部,这个主要用于消息队列。

存储set。

  和java的set集合类似。也类似于集合。
  sadd myset a b c,添加元素
  srem myset a删除元素
  smembers myset 返回集合的所有元素。
  sismember myset a,判断集合是否存在a,返回值为1表示存在,返回值为0表示不存在。
  sdiff myset myset1,返回myset1中没有,但是myset有的值。
  sinter myset myset1 返回这2个都有的值,也就是交集。
  sunion myset myset1 ,这个就是求着2个集合的并集。
  scard myset 返回集合的数目
  srandmember myset 返回随机一个集合的元素
  sdiffstore my1 myset myset1,就是将myset,myset1按这个顺序做一个差集,再把这个差集添加到my1这个集合里面去。
  sinterstore my1 myset myset1,就是将myset,myset1做一个交集,再把这个交集添加到my1这个集合里面去。
  sunionstore my1 myset myset1,就是将myset,myset1做一个并集,再把这个并集添加到my1这个集合里面去。   

Sorted-set

  和Set差不多属性,就是多了个排序的能力。
  zadd mysort 70 cs 80 ls 90 ss,增加3个元素,前面的是分数,代表其在sorted-set中的顺序,如果在添加zadd mysort 100 cs,这个value已经有了,那么就会用新的分数代替旧的分数。
  zscore mysort cs 获取cs这个value对应的分数。
  zcard mysort 返回这个sorted-set里面多少元素。
  zrem mysort tom ww,删除tom,ww这个2个值。
  zrange mysort 0 -1,和list的操作一样。这个只是返回值。
  zrange mysqrt 0 -1 withscores,多返回了分数,从小到大的顺序出来。
  zrevrange mysort 0 -1 withscores 从大到小排序。
  zremrangebyrank mysort 0 4,范围删除。
  zremrangebyscore mysort 80 100,按照分数范围删除。
  zrangebyscore mysort 0 100,分数从0到100的value
  zrangebyscore mysort 0 100 withscores 和上面一样就是多了分数。
  zrangebyscore mysort 0 100 withscores limit 0 2,选取前面2个数据
  zincrby mysort 3 ls,给ls这个元素的分数加3.
  zcount mysort 80 90,分数80到90的个数。
   底层实现:这个底层是基于跳跃表实现的。为了满足随机插入,删除,定位,因此采用跳跃表。跳跃表是类似于这种层级制。最下面一层元素都会串起来,然后每隔几个元素选出一个代表,将这几个代表串起来,然后在这些代表里面在选出代表,在串起来,不断向上。

keys操作

  keys * 查看所有的keys
  keys my? 以my开头的keys.
  del my1,my2,删除这些keys.
  exists mykeys判断这个key是否存在。
  rename company newname,给这个key重命名
  expire newname 1000.1000秒就过期。ke
  ttl newname 所剩超时时间
  type newname返回类型

Redis特性

多数据库

  一个Redis可以包含多个数据库。一个Redis最多16个数据库,客户端默认连接的是0号数据库。类似于mysql可以创建多个数据库。
  select 1,选择一号数据库
  move myset 1,这个就是把当前数据库的这个key移到1号数据库里面。

事务

  multi exec discard
  事务会让命令串行化执行,同时也不会向其他客户端提供服务。从而保证事务。
  用multi来开启事务,用exec来提交事务。用discard来进行回滚。
  当你用multi时,后续的命令都会被加到命令队列里面。直到遇见exec来提交。

Redis持久化

  就是把redis的数据同步到硬盘上,redis快是由于其运行在内存里面的。
  2种方式实现持久化。
  RDB,AOF这2个方式。
  RDB,就是指定时间内就把数据写入硬盘。
  配置:
  打开我们的redis的配置文件redis.conf,
save 900 1
save 300 10
save 60 10000
  就是每900秒若是有一个key变化就写入文件。第二个就是每300秒有10个key变化时就写入文件,第三个就是每60秒有10000key变化时就写入文件。
  dbfilename dump.rdb
  文件名字。
  dir ./当前路径。
  aof,将以日志记录每个数据操作,启动时,重新加载。
  配置:打开redis.conf这个文件找到这个appendonly no,这个是配置这个redis是否发开的方式。将之改为yes就是启动这个aof配置。
  3个同步方案;
  appendsync always ,每次操作都同步
  appendfsync

info 指令

  我们可以用info指令查看redis运行状态。其大致分为9大块。

Server 服务器运行的环境参数
Clients 客户端相关信息
Memory 服务器运行内存统计数据
Persistence 持久化信息
Stats 通用统计数据
Replication 主从复制相关信息
CPU CPU 使用情况
Cluster 集群信息
KeySpace 键值对统计数量信息

  我们可以根据这些信息判断这些时候redis是否需要集群啊,等问题,来调整redis。

mybatis简介

发表于 2018-03-12

优点和缺点

  sql和代码分离,利于管理和优化,但是sql代码量大,有些不好处理。MyBatis支持普通sql查询,存储过程,高级映射,其消除了所有的JDBC代码和参数的手工设置以及结果集的检索,使用简单的XML和注解来配置映射关系。恩,还有动态sql,避免了sql注入。

mybatis缓存机制

  Mybatis缓存是分为2级缓存。
  一级缓存是一个sqlSession级别的。就是一个sqlSession里面sql和参数一样的sql就会从从缓存里面取出来,默认开启。
  二级缓存:,多个SqlSession使用同一个Mapper的sql语句去操作数据库,得到的数据会存在二级缓存区域,它同样是使用HashMapper进行数据存储,相比一级缓存SqlSession,二级缓存的范围更大,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的。默认不开启


mybatis-config






  对于缓存数据更新机制,当某一个作用域(一级缓存Session/二级缓存Namespaces)的进行了 C/U/D 操作后,默认该作用域下所有 select 中的缓存将被clear。

#{}和${}的区别是什么?

第一个是解析传递进来的参数数据,是预编译处理,
第二个是对传递进来的参数原样拼接在SQL中,是字符串替换。
使用#{}可以有效的防止SQL注入,提高系统安全性。
看看案例实现

spring mvc介绍和demo

发表于 2018-03-11

基本概念

  DispatcherServlet:这个就类似于前端分发器。用于分发前端的请求到具体的Controller。然后获取数据到前端。
  Controller:就是控制层。
  HandlerAdapter:Handler是DispacherServlet使用的,是controller的一个表现形式。我们的DispatcherServlet调用Controller就是以调用Handle来调用的,而HanDlerAdapter则是将不同类型的类适配为我们这个DispacherServlet可以使用的类。
  handleInterceptor:就是拦截器,就是在Handle上加上一些拦截器。
  HandleMapping:顾名思义,Handle是DispacherServlet调用Controller的一个过渡中间对象,那么这个就是前段访问对应那个Controller。以及这个Controller的拦截器。
  HandlerExecutionChain:调用的Controller按照拦截器的模式前置拦截,以及后置拦截器等拦截器的链条。
  ModelAndView:这就是model的显示类型。
  ViewResolve:视图解析器。根据配置找到确切的视图对象告诉DispacherServlet。
  View:界面显示。
aaa
aaa
  请求从浏览器发送经过DispacherServlet的HandleMapping找到对应的Controller以及其拦截器。从而生成一个Handle给DispacherServlet,让其调用,生成一个model就是数据模型,然后DispacherServlet调用ViewResolve找到view,吧数据model注入进去,并且把这个view返回给Response。

配置解析

  web.xml文件解析。
  这个文件是用来加载初始化信息的,当服务容器开始启动时,先加载web.xml文件。里面的元素配置信息加载顺序为:context-param>listener>filter>servlet
  第一步,获取到context-param的值,然后创建这个web项目的上下文,然后创建listener的实例,从而获取到相关信息。最后则是加载filter的实例。
  web.xml文件启动大致分为2个部分。
  ContextLoad而Listener的初始化,实例化话ioc,并且将这个ioc注册到application中。
  DispacherServlet的初始化。
  我们知道Spring容器加载配置文件后,会生成一个上下文,而springmvc加载配置文件后也会生成一个上下文,为什么这样划分呢???这样的,我们的应用可能给不同类型的人分发请求,那么就会有多个DispacherServlet,呢么就有多个上下文,而spring的上下文则是公共的,那么就会少加载初始化一些类。

拦截器

aaa
  这里配置多个拦截器如下所示。
aaa

demo样例

  可以自己下载运行调试。
  点我一下就到了

  

java消息中间件入门

发表于 2018-03-02

消息中间件来解耦服务调用

  比如1个登录系统,登录的话需要调用很多系统的其他服务,如果中间调用失败,可能会导致登录信息一致无法返回,同时也增加了系统的耦合度。而用消息中间件的话,则是不发送服务到其他系统,而是发送服务到消息中间件,发完消息就直接返回结果,完美。而消息中间件则是自己吧消息发送给那些服务。

什么是中间件

  非底层操作系统软件。非业务应用软件,不是直接给用户的,不能给客户带来直接价值的软件叫中间件。

什么是消息中间件

  关注与数据的发送和接受,利用高效可靠的异步消息传递机制集成分布式系统。

什么是JMS

  java消息服务即是JMS,是一个java平台中关于面向消息中间件的API,用于2个程序啊件进行异步通信。就是一个规范。

什么是AMQP

  一个提供统一消息服务的应用层标准高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计。和JMS想对。

案例

简介

  第一步,下载安装activemq,第二步,编写发送端和接收端。
  下载后可以修改这个消息队列的配置文件,来修改持久化方式,有文件持久化,数据库持久化等。发送端发送消息给消息队列,接收端从消息队列中获取消息。有2个方式来消费消息,一个是队列方式,一个是主体模式。队列模式就是现有的消费者们平均或者按照一定规则消费队列里面的消息。主题模式则是每个消费者完整的消费消息队列里面的消息。
  这里写了一个案例来显示
  生产者代码


public class Producer {
public static final String url = “tcp://127.0.0.1:61616”;
public static final String queueName = “dh-test-queue”;
public static void main(String[] args) throws JMSException {
ConnectionFactory connectionFactory = new ActiveMQConnectionFactory(url);
Connection connection = connectionFactory.createConnection();
connection.start();
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
Destination destination = session.createQueue(queueName);
MessageProducer mp = session.createProducer(destination);
for (int i = 0; i < 100; i++) {
TextMessage textMessage = session.createTextMessage(“test” + i);
mp.send(textMessage);
System.out.println(“发送消息” + textMessage.getText());
}
connection.close();

}


  这个是先根据url生成一个连接工厂,然后根据这个工厂类生成一个连接。然后根据这个连接创建一个session,然后根据这个session创建一个目的地,这个就是消息队列上你的消息队列的名字,然后根据session和这个目的地,创建一个消息发送者,就可以发送消息到消息队列了。

  消费者代码


public class ConsumerApp {
public static final String url = “tcp://127.0.0.1:61616”;
public static final String queueName = “dh-test-queue”;
public static void main(String[] args) throws JMSException {
ConnectionFactory connectionFactory = new ActiveMQConnectionFactory(url);
Connection connection = connectionFactory.createConnection();
connection.start();
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
Destination destination = session.createQueue(queueName);
MessageConsumer mc= session.createConsumer(destination);
mc.setMessageListener(new MessageListener() {
@Override
public void onMessage(Message message)
TextMessage textMessage=(TextMessage) message;
try {
System.out.println(textMessage.getText());
} catch (JMSException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
});
//connection.close();

}


  前面代码都大致一样,后面不同的就是根据session和目的地生成一个消费者,然后用这个消费者调用一个监听器持续监听来自消息队列的消息,从而获取消息。

rabbitmq

生产者到消息队列确认机制,以及为何发生消息重复

  定义几个概念:
  A=生成者->消息队列,发送消息到消费者。
  B=消息队列加入消息持久化,
  C=消息队列->生产者。回复ack,
  D=消息队列->消费者,发送消息到消费者。
  E=消费者->消息队列,回复ack,
  F=消息队列删除消息,持久化。
  基本可以分为这几个步骤。
  1.第一个是前3个回合。
  生产端要维护一个消息发送的表,消息发送的时候记录消息id,这个消息id是消息队列自己生产的id,唯一。在消息成功落地broker磁盘并且进行回调确认(ack)的时候,根据本地消息表和回调确认的消息id进行对比,然后删除。如果这个表里面存在没有收到ack的消息,且超时了,那么生产者会再次发送。且消息id不变。
  我们分析,前面3个步骤A,B,C无论哪个出了问题,生产者都没有收到ack,那么都会发送再次发送消息到消息队列,那么势必造成重复,但是消息队列对这个做了去重处理,根据消息id.
  第二个回合就是后面3个回合了。
  对于消息队列来说,必须收到ack,我才会进行进行删除,不然我会持续发送,但是存在一个情况就是E的时候网络断了,但是消息是确实消费了,消息队列却没有删除,再次消费,则会造成消息重复,这个需要我们自己去处理。
  因此第一个回合消息是幂等的,第二个回合消息不是幂等的。

四种交换机

  1.扇形交换机
  广播模式,将消息发送到绑定到该交换机的所有队列上。
  2.直连交换机
  每个队列绑定到一个交换机上面,同时有个routing_key对应这个队列到交换机。同时消息也携带一个routing_key。交换机根据这个routing_key将消息指定到对应队列里面。如果希望一个消息发送到多个队列里面,那么就要发送多次.这里面的routing_key是写死的,比如某个队列绑定到交换机的routing_key=back,那么携带back的消息就会被路由分配到这个队列里面。
  3.主题交换机
  这个是上面直连交换机的加强版。不过routing_key加强了,可以用一部分规则了。规则如下。*:一个单词。#:任意单词。我们队列的routing_key=com.hehe.#,那么任何以com.hehe开头的消息都会被分配到这里。
  4.首部交换机
  首部交换机个人觉得类似于主题交换机,只不过规则变了,是基于hash了,队列和交换机绑定的时候声明一个hash数据结构,当消息发送的时候,会携带一个hash数组,然后进行匹配,如果匹配成功就会写入到相对应的队列。

持久化

  为了保证rabbitMq的可靠性,保证消息的可靠性。需要将quene,exchange,message都持久化。

  quene持久化就是将quene的持久化标示设置为true,当服务重启后,之前存在quene会被重洗放到rabbitmq里面。

  message持久化:quene持久化,如果message不持久化,那么重启后quene里面的没消费的消息会丢失。设置了队列和消息的持久化之后,当broker服务重启的之后,消息依旧存在。单只设置队列持久化,重启之后消息会丢失;单只设置消息的持久化,重启之后队列消失,既而消息也丢失。单单设置消息持久化而不设置队列的持久化显得毫无意义。
  exchange持久化:这个持久化与否没什么影响,但是建议持久化,不然重启后交换机就会丢失。

总结

  大致了解了一些消息队列,以及做出了一个简单demo,目前没有用到,用到在细看。

浅析spring aop

发表于 2018-03-02

解决了什么

  1,解决了代码的重复度。
  2,业务分离,就是把非业务代码抽离出来,可以集中管理,增加了可维护性。(比如缓存控制,事务控制,日志,权限,异常等)。

spring ioc浅析

发表于 2018-02-24

什么是ioc

  IOC是spring这个轻量级框架的核心内容,中文叫做控制反转。也叫作依赖注入,这里我们将其看作为一个概念。普通获取类对象时是我们主动获取也就是主动去new。但是IOC则是让我们不用去new这个对象直接去获取,因为我们想要得到这个对象也是调用这个对象所对应的服务。无论这个对象时容器给我们的还是我们自己new的都行。容器给我们的话就会方便很多,也利于管理和维护。

3种注入方法

  构造方法注入:就是被注入对象可以通过在其构造方法中声明依赖对象的参数列表让spring容器知道它需要哪些依赖对象。
  setter方法注入,通过其对应的setter方法来注入,更加灵活点。
  接口注入:被注入对象想要spring为其注入依赖对象必须要实现某个接口,这个接口提供一个方法用来为其注入依赖对象。

IOC service privider

  作用是对象的构建管理以及其对象之间的相互依赖
  这个是如何管理对象之间的相互依赖呢?
  1.直接编码
  在容器启动前,我们通过代码的形式将被注入对象和依赖对象注册到容器中,并且明确它们之间的依赖注入关系。
  2.配置文件方式
  我们可以通过spring的配置文件来完成对象之间的关联,在容器启动时在加载到容器中。
  3.元数据方式
  这个是java1.5之后的元数据结合的,个人觉得也是直接代码但是多了元数据这个方便的用法。

Spring 的ICO Service Privider

  Spring ICO容器包含了IOC Service Privider,AOP,线程管理等很多方面。因为他还是个容器。
  Spring ICO容器有2个类型:
  BeanFactory:基础类型的IOC容器,提供基本的IOC支持,默认为延迟加载机制,也就是只有当客户端对象需要访问容器里面的对象时才对受管理的对象进行初始化和注入操作,因此容器启动较快。
  ApplicationContext:这个是在BeanFactory的基础上构建的,是相对其高级的实现,提供了一些额外的高级特性。其这个容器在启动时会初始化加载绑定所有托管的对象。因此启动较慢。继承了BeanFactory这个接口。

源码的角度开始分析

  我们从springboot的run方法开始,一路点点,进入AbstractXmlApplicationContext 的refresh方法,这个方法就是bean加载的方法,至于为啥叫refresh方法呢,因为这个方法,不仅仅是初始化用的,里面有个obtainFreshBeanFactory方法里面的refreshBeanFactory里面有个判断当前是否已经有了BeanFactory,如果有就销毁,重新创建。。。所以叫做refresh方法。
  顺便说下,这个ApplicationContext的实现类是AnnotationConfigServletWebServerApplicationContext这个springboot包下的上下文类,他向上最终也继承了AbstractXmlApplicationContext类。其在run方法的context = createApplicationContext();最终创建

@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();
        }
    }
}

  这个就是refresh方法。

  1.首先加个锁,免得出现其他问题
  2.第一个方法是记录容器的启动时间,标记状态,校验参数。
  3. ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();这个方法是核心之一,他返回了一个单例的BeanFactory,这是基于springboot的。
  4.invokeBeanFactoryPostProcessors这个方法里面扫描所有的bean加载进入到BeanFactory的map里面,没有初始化。
  5.这个方法 finishBeanFactoryInitialization(beanFactory);来实例化bean,并且将bean存到ConfigurableListableBeanFactory的父类:DefaultSingletonBeanRegistry里面有2个map。其中singletonObjects这个map存了beanname和实例的映射关系。其中earlySingletonObjects存放了bean实例的早期版本,用于解决循环依赖问题。
  6.不断点点点,进入最后创建bean,结束。。。其中创建bean的时候,遇到有依赖的,先创建其依赖,

Tomcat浅析

发表于 2018-02-24

Tomcat结构

  Tomcat最顶层的容器叫做server。代表整个服务器,Server中至少包含一个Service用于提供服务。而Service主要包含以下2个。Connector:用于处理连接相关的事情,且提供Socket和request,response的装换。
  Container则是用于封装和管理Servlet以及具体处理request请求。一个Tomcat只能有一个Server,一个Server可以包含多个Service,一个Service中只有一个Container,但是可以有多个Connectors(因为一个服务可以有多个连接)。
  Tomcat中的管理类是Catalina,它用来管理Server。其有3个方法来管理Server的整个生命周期。
  load方法用于根据conf/server.xml文件创建Server并调用Server的init方法来进行初始化。
  start方法用于启动服务器。stop方法用于停止服务器。这些方法会逐层调用其下级的方法来启动整个服务器。比如,Server的stat方法调用所有的Service的stat方法,Service的stat方法调用Connectors和Container的stat方法。
  Tomcat的入口main不在CatAlina类里。在BootStrap里。

tomcat启动过程

BootStrap

  启动Tomcat就是调用BootStrap的main方法。在这个main方法里面主要做了这么几个事情。新建BootStrap实例,然后赋值给catalinaDaemon变量。然后根据不同命令不同操作。
  start:调用了3个方法setAwaut(true),load(args),start().这3个方法内部都会调用相应的Catalina进行具体处理执行。
  Stop:调用StopServer方法。

Catalina

  可以看到Catalina是上面BootStrap的3个方法来启动的,setAwait设置Server启动后是否进入等待状态(true是,false不是)。load方法用来加载配置文件。创建并且初始化Server,Start方法用来启动服务器。在这个方法里面主要是调用了server的start方法来启动服务器。

Server

  Server接口包含了一个个的serveice,因此在server中,提供了addService(Service service),removeService(Service service)来添加和删除service。server的init,start方法则是循环调用了每个Service的init,start方法。来启动所有的service。
  Server默认实现是StandardServer,StandardServer的上级父类有LifeCycleBase的类,init,start方法就定义在这个类里面,这个类的init和start方法调用的是initInternal方法和startinternal方法,而这2个方法是模板方法,由子类具体实现,因此调用StandardServer的init,stat方法就是执行StandardServer的initInternal方法和startinternal方法,而在
  StandardServer的initInternal方法和startinternal方法中分别循环调用了每一个Service的start和init方法。在StandardServer实现了await方法,让服务器进入等待状态。

service

  Service的默认实现是StandardService。而这个类也继承了LifeCycleBase这个类,因此其init和stat方法最终也会调用其自身的initInternal方法和startinternal方法。在initInternal方法和startinternal方法的实现中,其主要是调用了其containner(容器),executors(线程池),mapperListener是Mapper的监听器,可以监听container的变化。connectors(连接器)的init和start方法。

tomcat生命周期

  tomcat通过Lifecycle接口统一管理生命周期,所有生命周期组件都要实现这个接口。
  做了4件事。
  1.定义了13个String类型的常量,用于LifecycleEvent事件的type属性中,作用是区分组件发出的lifecycleEvent事件的状态。
  2.定义了3个管理监听器方法:addLifecycleListener,findLifecycleListener,removeLifecycleListener,用来添加,查找,删除LifecycleListener类型的监听器。
  3.定义了4个生命周期的方法init,start,stop,destroy,用于执行生命周期的各个阶段的操作。
  4.定义了getState,getStateName,用来获取当前状态,getState的返回值是LifecycleState的枚举类型。里面列举了生命周期的各个节点。而getStateName则是返回String类型的状态的名字。
  lifecycle的默认实现是LifecycleBase,所有实现了生命周期的组件基本都是直接或者间接继承这个类,其为接口Lifecycle的接口方法都给了实现。
  监听器的管理是LifecycleSupport类完成的,其定义了一个LifecycleListenner数组属性来保存所有的监听器。然后定义了添加,删除,查找,执行监听器的方法;

Container简述

  容器:存放编写的代码。分为四层容器。
  1.Wrapper:对应一个Servlet。
  2.Context:对应一个应用。
  3.Host:对应一个站点。
  4.Engine对应引擎。每个service只有一个Engine。

Connector简述

  负责处理网络连接相关的事情,比如,Socket连接,将其根据HTTP协议,TCP/IP协议封装为Request,Response来具体处理,将其交给Container进行处理,Container就是Servlet的容器。Container处理完就返回给Connector,而Connector使用Socket将处理结果返回给客户端。

java NIO.

发表于 2018-02-24

阻塞和非阻塞

  阻塞是函数没有返回结果就一直等待,知道得到返回结果。非阻塞则是函数没有返回结果自己不用等待可以去干别的事情,这是函数内部的的实现区别,也就是函数未就绪时是直接返回结果。   

同步和异步

  同步是指主动请求并且等待结果。当数据就绪后在读写时必须阻塞,异步则是请求到数据后可以继续处理其他任务,随后等待结果,这可以使得进程读写时不阻塞。

小结

  所以说,阻塞,非阻塞和同步,异步是不一样的概念,阻塞和非阻塞是函数的实现方式就是未就绪时是否直接返回结果,阻塞和非阻塞在程序中也算是同步的,而异步则是不管是不是直接返回结果,我都可以干别的事,等到返回结果再处理。

java NIO

  java的最初io流是阻塞的BIO流,不适合于多连接的处理情况。因为多个连接就要多个线程。而NIO的出现解决了这个问题。而且NIO的操作方式类似于操作系统,因此效率会更加高一些。
  io的操作是基于字节流和字符流,是单向的比如inputStreanm,只能读,而NIO则是基于Channel和Buffer,是双向,比如Channel是双向的既可以用来读也可以来写。
  NIO是非阻塞的,IO则是阻塞的,一个线程调用read()方法时,如果没有操作完,该线程就会一直阻塞。对于这个线程而言,这时他不能干别的事了,因为程序时顺序执行的。NIO则是不一样的,无论是读取数据还是写入数据都不会使得这个线程阻塞下去。可以继续干自己的事情。

代码示例

  直接代码示例吧,具体的Channel,Buffer,Selector的用法可以查询java官方api。

  这里直接上几个代码案例,直接对比。


public class Test {

public static void main(String[] args) throws Exception {
    File file = new File("data.txt");
    FileInputStream fileOutputStream = new FileInputStream(file);
    FileChannel fileChannel = fileOutputStream.getChannel();
    ByteBuffer byteBuffer = ByteBuffer.allocate(10);
    // String string = "nio test";
    int a = fileChannel.read(byteBuffer);
    System.out.println(a);

    while (a != -1) {
        byteBuffer.flip();
        while (byteBuffer.hasRemaining()) {
            System.out.print((char) byteBuffer.get());
        }
        byteBuffer.compact();
        a = fileChannel.read(byteBuffer);
    }
}

}

  这个代码是读取一个文件,并且运用NIO的方式输出出来。有了大概的印象,那么我们来看看在socket中,这些操作时怎么处理的,非阻塞机制到底怎么提高了这个的优化效率。
  第一个是io的服务器


public class Snippet {
public static void main(String[] args) {
ServerSocket serverSocket = null;
InputStream in = null;
try {
serverSocket = new ServerSocket(8088);
int recvMsgSize = 0;
byte[] recvBuf = new byte[1024];
while (true) {
Socket clntSocket = serverSocket.accept();
SocketAddress clientAddress = clntSocket.getRemoteSocketAddress();
System.out.println(“Handling client at “ + clientAddress);
in = clntSocket.getInputStream();
while ((recvMsgSize = in.read(recvBuf)) != -1) {
byte[] temp = new byte[recvMsgSize];
// System.arraycopy(recvBuf, 0, temp, 0, recvMsgSize);
System.out.println(new String(temp));
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (serverSocket != null) {
serverSocket.close();
}
if (in != null) {
in.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}


  客户端代码直接用NIO的模式写吧


public class NioClient {
public static void main(String[] args) {
ByteBuffer buffer = ByteBuffer.allocate(1024);
SocketChannel socketChannel = null;
try {
socketChannel = SocketChannel.open();
socketChannel.configureBlocking(false);
socketChannel.connect(new InetSocketAddress(“127.0.0.1”, 8088));
System.out.println(“aaa”);

        if (socketChannel.finishConnect()) {
            int i = 0;
            while (i<10) {
                TimeUnit.SECONDS.sleep(1);
                String info = "I'm " + i++ + "-th information from client";
                buffer.clear();
                buffer.put(info.getBytes());
                buffer.flip();
                while (buffer.hasRemaining()) {// 判断是否数据读完
                    System.out.println(buffer);
                    socketChannel.write(buffer);
                }
            }
        }
    } catch (IOException | InterruptedException e) {
        e.printStackTrace();
    } finally {
        try {
            if (socketChannel != null) {
                socketChannel.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

}


  假设服务器是这种代码,客户端有多个连接到来,那么一次只能连接一个,直到用完,或许有的小伙伴说,多线程啊,每个线程一个连接,线程池管理。恩,但是线程太消耗资源,而且线程转换也会消耗资源。那么nio来了,我们来看看Nio的实现。

public class ServerConnect
{
    private static final int BUF_SIZE=1024;
    private static final int PORT = 8080;
    private static final int TIMEOUT = 3000;

    public static void main(String[] args)
    {
        selector();
    }

    public static void handleAccept(SelectionKey key) throws IOException{
        ServerSocketChannel ssChannel = (ServerSocketChannel)key.channel();
        SocketChannel sc = ssChannel.accept();
        sc.configureBlocking(false);
        sc.register(key.selector(), SelectionKey.OP_READ,ByteBuffer.allocateDirect(BUF_SIZE));
    }

    public static void handleRead(SelectionKey key) throws IOException{
        SocketChannel sc = (SocketChannel)key.channel();
        ByteBuffer buf = (ByteBuffer)key.attachment();
        long bytesRead = sc.read(buf);
        while(bytesRead>0){
            buf.flip();
            while(buf.hasRemaining()){
                System.out.print((char)buf.get());
            }
            System.out.println();
            buf.clear();
            bytesRead = sc.read(buf);
        }
        if(bytesRead == -1){
            sc.close();
        }
    }

    public static void handleWrite(SelectionKey key) throws IOException{
        ByteBuffer buf = (ByteBuffer)key.attachment();
        buf.flip();
        SocketChannel sc = (SocketChannel) key.channel();
        while(buf.hasRemaining()){
            sc.write(buf);
        }
        buf.compact();
    }

    public static void selector() {
        Selector selector = null;
        ServerSocketChannel ssc = null;
        try{
            selector = Selector.open();
            ssc= ServerSocketChannel.open();
            ssc.socket().bind(new InetSocketAddress(PORT));
            ssc.configureBlocking(false);
            ssc.register(selector, SelectionKey.OP_ACCEPT);

            while(true){
                if(selector.select(TIMEOUT) == 0){
                    System.out.println("==");
                    continue;
                }
                Iterator<SelectionKey> iter = selector.selectedKeys().iterator();
                while(iter.hasNext()){
                    SelectionKey key = iter.next();
                    if(key.isAcceptable()){
                        handleAccept(key);
                    }
                    if(key.isReadable()){
                        handleRead(key);
                    }
                    if(key.isWritable() && key.isValid()){
                        handleWrite(key);
                    }
                    if(key.isConnectable()){
                        System.out.println("isConnectable = true");
                    }
                    iter.remove();
                }
            }

        }catch(IOException e){
            e.printStackTrace();
        }finally{
            try{
                if(selector!=null){
                    selector.close();
                }
                if(ssc!=null){
                    ssc.close();
                }
            }catch(IOException e){
                e.printStackTrace();
            }
        }
    }
}

Selector运行单线程处理多个Channel,如果你的应用打开了多个通道,但每个连接的流量都很低,使用Selector就会很方便。例如在一个聊天服务器中。要使用Selector, 得向Selector注册Channel,然后调用它的select()方法。这个方法会一直阻塞到某个注册的通道有事件就绪。一旦这个方法返回,线程就可以处理这些事件,事件的例子有如新的连接进来、数据接收。
  我们在上面的代码里面将ServerSocketChannel注册到Selector里面,接受accept事件,每当有一个连接到来,select()就会有返回值,因为这个事件完成了,就会触发下面的操作,然后我们把这个新加入的连接也放到selector里面,等待读事件,或者写事件,当这个事件准备好了,就会启动返回。

浅析http协议

发表于 2018-02-09

Http协议的报文结构

  Http协议分为请求报文和响应报文。分为3部分:首行,头部,主体三部分。
  首行:http版本,状态码,url等。
  头部:保存的是一些键值对信息。
  主体:保存的是具体内容。请求报文主体保存的是post请求参数信息,响应报文保存的是页面要显示的结果。首行,头部,主体用回车换行分割,林外头部和主体之间多了一个空行。
  请求报文的方法有:GET,HEAD,POST,PUT,DELETE.
  响应报文状态码大概有5类:
  1XX:信息状态码。
  2XX:成功状态码。如200表示成功
  3XX:重定向状态码。如301表示重定向。
  4XX:客户端错误状态码。如404表示没找到请求的资源。
  5XX:服务端错误状态码。如500表示内部错误。

1…10111213

skydh

skydh

126 日志
© 2020 skydh
本站访客数:
由 Hexo 强力驱动
|
主题 — NexT.Pisces v5.1.3