搜索词>>SpringBoot 耗时0.0050
  • springboot 使用thymeleaf url get请求传多个参数问题解决

    springboot 使用thymeleaf 模板引擎中url中的&引起的org.xml.sax.SAXParseException: 对实体 "uid" 的引用必须以 ';' 分隔符结尾。问题解决<h2>1.问题描述</h2> 在使用thymeleaf模板中,有些时候需要绝对路径带多个参数的地址访问其他页面,这时候由于&字符的问题会报错:<br /> org.xml.sax.SAXParseException: 对实体 "xxx" 的引用必须以 ';' 分隔符结尾。 <h2>2.问题解决</h2> 使用逆转字符代替&,>,<这些符号<br /> 例如: <pre> <code class="language-html"><a class="third-login-qq" href="https://graph.qq.com/oauth2.0/authorize? response_type=code&amp;client_id=101395666&amp;redirect_uri=http://www.leftso.com/user/login.html&amp;scope=get_user_info&amp;state=test" id="qqLoginBtn"></a></code></pre>
  • spring boot filter 配置使用

    在Java编程中,web项目通常会使用一些filter处理一些特定的业务。这里主要讲解springboot中filter的几种配置和使用。<h2>引言</h2>     在Java编程中,web项目通常会使用一些filter处理一些特定的业务。这里主要讲解springboot中filter的几种配置和使用。 <h2>一.创建一个spring boot 项目</h2> 创建spring boot项目的方法这里就不详细介绍了,不清楚的可以参考:<a rel="" href="http://www.leftso.com/blog/64.html" rel="" target="_blank">spring boot 入门项目创建</a><br /> 下面是一个创建好的项目结构图:<br /> <img alt="项目结构图" class="img-thumbnail" src="/assets/upload/blog/thumbnail/2017-11/33b5edbb638b4554b07ee0e9de2c5bbe.png" /><br /> 其中主要的在于上面的config目录下的FilterConfig.java和下面的filter中两个filter的实现类。 <h2>二.Filter的实现类</h2> 这里的filter指的是javax.servlet.Filter;接口。在spring boot框架中有很多方式来实现Filter接口。下面就讲常用的两种。 <h3>2.1javax.servlet.Filter;原生接口的实现</h3> <strong>MyFilter.java:</strong> <pre> <code class="language-java">package net.xqlee.project.filter; import java.io.IOException; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * 测试用filter * * @author xqlee * */ public class MyFilter implements Filter { private static final Logger log = LoggerFactory.getLogger(MyFilter.class); @Override public void init(FilterConfig filterConfig) throws ServletException { log.info("MyFilter 初始化中..."); } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { log.info("进入 MyFilter"); chain.doFilter(request, response); } @Override public void destroy() { log.info("MyFilter 销毁中..."); } } </code></pre> 上面的代码就是一个简单的Filter接口实现类.其中需要处理的业务一般放在doFilter方法中。 <h3>2.2继承spring的OncePerRequestFilter来实现Filter</h3> <pre> <code class="language-java">package net.xqlee.project.filter; import java.io.IOException; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.filter.OncePerRequestFilter; /** * 通过继承spring提供的filter来实现filter * * @author xqlee * */ public class MyOncePerRequestFilter extends OncePerRequestFilter { private static final Logger log = LoggerFactory.getLogger(MyOncePerRequestFilter.class); @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { log.info("进入 MyOncePerRequestFilter"); filterChain.doFilter(request, response); } } </code></pre> 说明:Spring 的OncePerRequestFilter类实际上是一个实现了Filter接口的抽象类。spring对Filter进行了一些封装处理。 <h2>三.配置Filter到容器中</h2>     上面只是创建好了两个Filter实现类。在web编程中我们知道需要在web.xml中配置相应的过滤器内容,如过滤的url,过滤器名字,参数等。spring boot框架中为配置Filter进行了一些封装使得我们可以用spring风格的方式配置Filter。<br /> 首先创建一个类,添加配置注解,并将上面的两个过滤器配置到这个配置类中: <pre> <code class="language-java">package net.xqlee.project.config; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import net.xqlee.project.filter.MyFilter; import net.xqlee.project.filter.MyOncePerRequestFilter; /** * 配置拦截器 * * @author xqlee * */ @Configuration public class FilterConfig { /** * 拦截器注册 * * @return */ @Bean public FilterRegistrationBean myFilterRegistration() { FilterRegistrationBean registration = new FilterRegistrationBean(); registration.setFilter(new MyFilter()); registration.addUrlPatterns("/a/*");// 拦截路径 registration.setName("MyFilter");// 拦截器名称 registration.setOrder(1);// 顺序 return registration; } /** * 拦截器注册 * * @return */ @Bean public FilterRegistrationBean myOncePerRequestFilterRegistration() { FilterRegistrationBean registration = new FilterRegistrationBean(); registration.setFilter(new MyOncePerRequestFilter()); registration.addUrlPatterns("/*");// 拦截路径 registration.setName("MyOncePerRequestFilter");// 拦截器名称 registration.setOrder(2);// 顺序 return registration; } } </code></pre> <h2>四.Spring boot Filter测试</h2> 启动spring boot项目,观察启动日志:<br /> <img alt="" class="img-thumbnail" src="/assets/upload/blog/thumbnail/2017-11/d5157c396bc8402e87c5567c843a6701.png" />注意观察上图中红色圈出来的地方,我们可以看到两个filter都进行了初始化并且也显示出来了过滤器的过滤地址。<br /> <br /> 为了进一步的测试其是否作用,创建一个简单的controller进行测试 <pre> <code class="language-java">package net.xqlee.project.controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class TestController { @GetMapping("/a/filter.do") public Object hello() { return "Hello Filter"; } @GetMapping("/") public Object index() { return "Welcome Index"; } } </code></pre> <br /> 浏览器访问地址http://localhost:8080/<br /> <img alt="index" class="img-thumbnail" src="/assets/upload/blog/thumbnail/2017-11/73d5d31bbfa040d6a97be4945d93a49b.png" /><br /> 观察eclipse控制台日志:<br /> <img alt="访问日志" class="img-thumbnail" src="/assets/upload/blog/thumbnail/2017-11/bae81d12286e4a5a8caa859cb14ac40a.png" /><br /> 我们可以看到只有MyOncePerRequestFilter的日志,因为从启动日志图中可以看到MyOncePerRequestFilter的过滤路径是所有,而myfilter过滤路径是/a/,所以这里只能看到MyOncePerRequestFilter的日志。<br /> <br /> 接下来访问地址:http://localhost:8080/a/filter.do<br /> <img alt="访问" class="img-thumbnail" src="/assets/upload/blog/thumbnail/2017-11/0dfedd8e8c8b43e9bc54a99247c1c102.png" /><br /> 后端日志:<br /> <img alt="" class="img-thumbnail" src="/assets/upload/blog/thumbnail/2017-11/3dd625c90b0d454eba69436e04ae5c52.png" />这次可以看到两个过滤器的日志了。同时也说明配置的过滤器已经生效。<br /> <br />  
  • spring boot 2.3 hibernate validate框架未引入

    项目升级到springboot之后,参数校验的注解报错,经过与原项目对比,发现spring-boot-starter-web的依赖项已经去除了依赖原版会有如下: <dependency>l; <groupId>l;org.hiberna项目升级到springboot之后,参数校验的注解报错,经过与原项目对比,发现spring-boot-starter-web的依赖项已经去除了依赖原版会有如下: <dependency> <groupId>org.hibernate.validator</groupId> <artifactId>hibernate-validator</artifactId> <version>6.0.17.Final</version> <scope>compile</scope> </dependency>2.3中已经删除了解决方法手动引入依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency>官网说明链接:https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.3-Release-Notes#validation-starter-no-longer-included-in-web-starters
  • spring boot RabbitMQ基本集成使用

    spring boot RabbitMQ基本集成使用spring boot RabbitMQ基本集成使用 <h2>RabbitMQ介绍</h2> <p>RabbitMQ是实现AMQP(高级消息队列协议)的消息中间件的一种,最初起源于金融系统,用于在分布式系统中存储转发消息,在易用性、扩展性、高可用性等方面表现不俗。RabbitMQ主要是为了实现系统之间的双向解耦而实现的。当生产者大量产生数据时,消费者无法快速消费,那么需要一个中间层。保存这个数据。</p> <p>AMQP,即Advanced Message Queuing Protocol,高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计。消息中间件主要用于组件之间的解耦,消息的发送者无需知道消息使用者的存在,反之亦然。AMQP的主要特征是面向消息、队列、路由(包括点对点和发布/订阅)、可靠性、安全。</p> <p>RabbitMQ是一个开源的AMQP实现,服务器端用Erlang语言编写,支持多种客户端,如:Python、Ruby、.NET、Java、JMS、C、PHP、ActionScript、XMPP、STOMP等,支持AJAX。用于在分布式系统中存储转发消息,在易用性、扩展性、高可用性等方面表现不俗。</p> <h2>相关概念</h2> 通常我们谈到队列服务, 会有三个概念: 发消息者、队列、收消息者,RabbitMQ 在这个基本概念之上, 多做了一层抽象, 在发消息者和 队列之间, 加入了交换器 (Exchange). 这样发消息者和队列就没有直接联系, 转而变成发消息者把消息给交换器, 交换器根据调度策略再把消息再给队列。<br /> <img alt="" class="img-thumbnail" src="/assets/upload/blog/thumbnail/2018-03/c3db0eeb8a2d40d3a9a1437c424b9934.jpg" /> <p>左侧 P 代表 生产者,也就是往 RabbitMQ 发消息的程序。</p> <ul> <li> <p>中间即是 RabbitMQ,<em>其中包括了 交换机 和 队列。</em></p> </li> <li> <p>右侧 C 代表 消费者,也就是往 RabbitMQ 拿消息的程序。</p> </li> </ul> <p>那么,<em>其中比较重要的概念有 4 个,分别为:虚拟主机,交换机,队列,和绑定。</em></p> <ul> <li> <p>虚拟主机:一个虚拟主机持有一组交换机、队列和绑定。为什么需要多个虚拟主机呢?很简单,RabbitMQ当中,<em>用户只能在虚拟主机的粒度进行权限控制。</em> 因此,如果需要禁止A组访问B组的交换机/队列/绑定,必须为A和B分别创建一个虚拟主机。每一个RabbitMQ服务器都有一个默认的虚拟主机“/”。</p> </li> <li> <p>交换机:<em>Exchange 用于转发消息,但是它不会做存储</em> ,如果没有 Queue bind 到 Exchange 的话,它会直接丢弃掉 Producer 发送过来的消息。<br /> 这里有一个比较重要的概念:<strong>路由键</strong> 。消息到交换机的时候,交互机会转发到对应的队列中,那么究竟转发到哪个队列,就要根据该路由键。</p> </li> <li> <p>绑定:也就是交换机需要和队列相绑定,这其中如上图所示,是多对多的关系。</p> </li> </ul> <h3>交换机(Exchange)</h3> <p>交换机的功能主要是接收消息并且转发到绑定的队列,交换机不存储消息,在启用ack模式后,交换机找不到队列会返回错误。交换机有四种类型:Direct, topic, Headers and Fanout</p> <ul> <li> <p>Direct:direct 类型的行为是”先匹配, 再投送”. 即在绑定时设定一个 <strong>routing_key</strong>, 消息的<strong>routing_key</strong> 匹配时, 才会被交换器投送到绑定的队列中去.</p> </li> <li> <p>Topic:按规则转发消息(最灵活)</p> </li> <li> <p>Headers:设置header attribute参数类型的交换机</p> </li> <li> <p>Fanout:转发消息到所有绑定队列</p> </li> </ul> <p><strong>Direct Exchange</strong><br /> Direct  Exchange是RabbitMQ默认的交换机模式,也是最简单的模式,根据key全文匹配去寻找队列。<br /> <img alt="生产消费关系图" class="img-thumbnail" src="/assets/upload/blog/thumbnail/2018-03/a50800a7e550419ebed551dd4f2cd5aa.png" /><br />  </p> <p>第一个 X - Q1 就有一个 binding key,名字为 orange; X - Q2 就有 2 个 binding key,名字为 black 和 green。<em>当消息中的 路由键 和 这个 binding key 对应上的时候,那么就知道了该消息去到哪一个队列中。</em></p> <p>Ps:为什么 X 到 Q2 要有 black,green,2个 binding key呢,一个不就行了吗? - 这个主要是因为可能又有 Q3,而Q3只接受 black 的信息,而Q2不仅接受black 的信息,还接受 green 的信息。</p> <p><strong>Topic Exchange</strong>  </p> <p><em>Topic Exchange 转发消息主要是根据通配符。</em> 在这种交换机下,队列和交换机的绑定会定义一种路由模式,那么,通配符就要在这种路由模式和路由键之间匹配后交换机才能转发消息。</p> <p>在这种交换机模式下:</p> <ul> <li> <p>路由键必须是一串字符,用句号(<code>.</code>) 隔开,比如说 agreements.us,或者 agreements.eu.stockholm 等。</p> </li> <li> <p>路由模式必须包含一个 星号(<code>*</code>),主要用于匹配路由键指定位置的一个单词,比如说,一个路由模式是这样子:agreements..b.*,那么就只能匹配路由键是这样子的:第一个单词是 agreements,第四个单词是 b。 井号(#)就表示相当于一个或者多个单词,例如一个匹配模式是agreements.eu.berlin.#,那么,以agreements.eu.berlin开头的路由键都是可以的。</p> </li> </ul> <p>具体代码发送的时候还是一样,第一个参数表示交换机,第二个参数表示routing key,第三个参数即消息。如下:<br />  </p> <pre> <code class="language-html">rabbitTemplate.convertAndSend("testTopicExchange","key1.a.c.key2", " this is RabbitMQ!");</code></pre> <p>topic 和 direct 类似, 只是匹配上支持了”模式”, 在”点分”的 routing_key 形式中, 可以使用两个通配符:</p> <ul> <li> <p><code>*</code>表示一个词.</p> </li> <li> <p><code>#</code>表示零个或多个词.</p> </li> </ul> <p><strong>Headers Exchange</strong></p> <p>headers 也是根据规则匹配, 相较于 direct 和 topic 固定地使用 routing_key , headers 则是一个自定义匹配规则的类型.<br /> 在队列与交换器绑定时, 会设定一组键值对规则, 消息中也包括一组键值对( headers 属性), 当这些键值对有一对, 或全部匹配时, 消息被投送到对应队列.</p> <p><strong>Fanout Exchange</strong></p> <p>Fanout Exchange 消息广播的模式,不管路由键或者是路由模式,<em>会把消息发给绑定给它的全部队列</em>,如果配置了routing_key会被忽略。</p> <h2>springboot集成RabbitMQ</h2> springboot集成RabbitMQ非常简单,如果只是简单的使用配置非常少,springboot提供了spring-boot-starter-amqp项目对消息各种支持。 <h3>简单使用</h3> 1、配置pom包,主要是添加spring-boot-starter-amqp的支持 <pre> <code class="language-xml"><dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-amqp</artifactId> </dependency>x</code></pre> <p>2、配置文件</p> <p>配置rabbitmq的安装地址、端口以及账户信息<br />  </p> <pre> <code class="language-html">spring.application.name=spirng-boot-rabbitmq spring.rabbitmq.host=192.168.0.86 spring.rabbitmq.port=5672 spring.rabbitmq.username=admin spring.rabbitmq.password=123456</code></pre> 3、队列配置 <pre> <code class="language-java">@Configuration public class RabbitConfig { @Bean public Queue Queue() { return new Queue("hello"); } }</code></pre> <p>4、发送者</p> <p>rabbitTemplate是springboot 提供的默认实现<br />  </p> <pre> <code class="language-java">public class HelloSender { @Autowired private AmqpTemplate rabbitTemplate; public void send() { String context = "hello " + new Date(); System.out.println("Sender : " + context); this.rabbitTemplate.convertAndSend("hello", context); } }</code></pre> 5、接收者 <pre> <code class="language-java">@Component @RabbitListener(queues = "hello") public class HelloReceiver { @RabbitHandler public void process(String hello) { System.out.println("Receiver : " + hello); } }</code></pre> 6、测试 <pre> <code class="language-java">@RunWith(SpringRunner.class) @SpringBootTest public class RabbitMqHelloTest { @Autowired private HelloSender helloSender; @Test public void hello() throws Exception { helloSender.send(); } }</code></pre> <blockquote> <p>注意,发送者和接收者的queue name必须一致,不然不能接收</p> </blockquote> <h3>多对多使用</h3> <p>一个发送者,N个接收者或者N个发送者和N个接收者会出现什么情况呢?</p> <p><strong>一对多发送</strong><br /> 对上面的代码进行了小改造,接收端注册了两个Receiver,Receiver1和Receiver2,发送端加入参数计数,接收端打印接收到的参数,下面是测试代码,发送一百条消息,来观察两个接收端的执行效果<br />  </p> <pre> <code class="language-java">@Test public void oneToMany() throws Exception { for (int i=0;i<100;i++){ neoSender.send(i); } }</code></pre> 结果如下: <pre> <code class="language-html">Receiver 1: spirng boot neo queue ****** 11 Receiver 2: spirng boot neo queue ****** 12 Receiver 2: spirng boot neo queue ****** 14 Receiver 1: spirng boot neo queue ****** 13 Receiver 2: spirng boot neo queue ****** 15 Receiver 1: spirng boot neo queue ****** 16 Receiver 1: spirng boot neo queue ****** 18 Receiver 2: spirng boot neo queue ****** 17 Receiver 2: spirng boot neo queue ****** 19 Receiver 1: spirng boot neo queue ****** 20</code></pre> <br /> 根据返回结果得到以下结论 <blockquote> <p>一个发送者,N个接受者,经过测试会均匀的将消息发送到N个接收者中</p> </blockquote> <p><strong>多对多发送</strong>  </p> <p>复制了一份发送者,加入标记,在一百个循环中相互交替发送<br />  </p> <pre> <code class="language-java">@Test public void manyToMany() throws Exception { for (int i=0;i<100;i++){ neoSender.send(i); neoSender2.send(i); } }</code></pre> 结果如下: <pre> <code class="language-html">Receiver 1: spirng boot neo queue ****** 20 Receiver 2: spirng boot neo queue ****** 20 Receiver 1: spirng boot neo queue ****** 21 Receiver 2: spirng boot neo queue ****** 21 Receiver 1: spirng boot neo queue ****** 22 Receiver 2: spirng boot neo queue ****** 22 Receiver 1: spirng boot neo queue ****** 23 Receiver 2: spirng boot neo queue ****** 23 Receiver 1: spirng boot neo queue ****** 24 Receiver 2: spirng boot neo queue ****** 24 Receiver 1: spirng boot neo queue ****** 25 Receiver 2: spirng boot neo queue ****** 25</code></pre> <blockquote> <p>结论:和一对多一样,接收端仍然会均匀接收到消息</p> </blockquote> <h3>高级使用</h3> <p><strong>对象的支持</strong></p> <p>springboot以及完美的支持对象的发送和接收,不需要格外的配置。<br />  </p> <pre> <code class="language-java">//发送者 public void send(User user) { System.out.println("Sender object: " + user.toString()); this.rabbitTemplate.convertAndSend("object", user); } ... //接收者 @RabbitHandler public void process(User user) { System.out.println("Receiver object : " + user); }</code></pre> 结果如下: <pre> <code class="language-html">Sender object: User{name='neo', pass='123456'} Receiver object : User{name='neo', pass='123456'}</code></pre> <p><strong>Topic Exchange</strong></p> <p>topic 是RabbitMQ中最灵活的一种方式,可以根据routing_key自由的绑定不同的队列</p> <p>首先对topic规则配置,这里使用两个队列来测试<br />  </p> <pre> <code class="language-java">@Configuration public class TopicRabbitConfig { final static String message = "topic.message"; final static String messages = "topic.messages"; @Bean public Queue queueMessage() { return new Queue(TopicRabbitConfig.message); } @Bean public Queue queueMessages() { return new Queue(TopicRabbitConfig.messages); } @Bean TopicExchange exchange() { return new TopicExchange("exchange"); } @Bean Binding bindingExchangeMessage(Queue queueMessage, TopicExchange exchange) { return BindingBuilder.bind(queueMessage).to(exchange).with("topic.message"); } @Bean Binding bindingExchangeMessages(Queue queueMessages, TopicExchange exchange) { return BindingBuilder.bind(queueMessages).to(exchange).with("topic.#"); } }</code></pre> 使用queueMessages同时匹配两个队列,queueMessage只匹配”topic.message”队列 <pre> <code class="language-java">public void send1() { String context = "hi, i am message 1"; System.out.println("Sender : " + context); this.rabbitTemplate.convertAndSend("exchange", "topic.message", context); } public void send2() { String context = "hi, i am messages 2"; System.out.println("Sender : " + context); this.rabbitTemplate.convertAndSend("exchange", "topic.messages", context); }</code></pre> <p>发送send1会匹配到topic.#和topic.message 两个Receiver都可以收到消息,发送send2只有topic.#可以匹配所有只有Receiver2监听到消息</p> <p><strong>Fanout Exchange</strong></p> <p>Fanout 就是我们熟悉的广播模式或者订阅模式,给Fanout交换机发送消息,绑定了这个交换机的所有队列都收到这个消息。</p> <p>Fanout 相关配置<br />  </p> <pre> <code class="language-java">@Configuration public class FanoutRabbitConfig { @Bean public Queue AMessage() { return new Queue("fanout.A"); } @Bean public Queue BMessage() { return new Queue("fanout.B"); } @Bean public Queue CMessage() { return new Queue("fanout.C"); } @Bean FanoutExchange fanoutExchange() { return new FanoutExchange("fanoutExchange"); } @Bean Binding bindingExchangeA(Queue AMessage,FanoutExchange fanoutExchange) { return BindingBuilder.bind(AMessage).to(fanoutExchange); } @Bean Binding bindingExchangeB(Queue BMessage, FanoutExchange fanoutExchange) { return BindingBuilder.bind(BMessage).to(fanoutExchange); } @Bean Binding bindingExchangeC(Queue CMessage, FanoutExchange fanoutExchange) { return BindingBuilder.bind(CMessage).to(fanoutExchange); } }</code></pre> 这里使用了A、B、C三个队列绑定到Fanout交换机上面,发送端的routing_key写任何字符都会被忽略: <pre> <code class="language-java">public void send() { String context = "hi, fanout msg "; System.out.println("Sender : " + context); this.rabbitTemplate.convertAndSend("fanoutExchange","", context); }</code></pre> 结果如下: <pre> <code class="language-html">Sender : hi, fanout msg ... fanout Receiver B: hi, fanout msg fanout Receiver A : hi, fanout msg fanout Receiver C: hi, fanout msg</code></pre> <blockquote> <p>结果说明,绑定到fanout交换机上面的队列都收到了消息</p> </blockquote>
  • Spring Boot validation整合hibernate validator实现数据验证

    Spring Boot validation整合hibernate validator实现数据验证,Spring Boot validation使用说明,Spring Boot validation使用教程<h2>引言</h2>     这里主要讲解在Spring  Boot项目中整合hibernate validator框架实现Spring  Boot项目的validation 验证机制。方便后端验证前端或者接口传递过来的数据格式是否正确。 <h2>一.准备环境</h2> <ul> <li>jdk1.8+(Spring Boot项目推荐使用1.8)</li> <li>eclipse(或者你喜欢的IDE)</li> <li>maven 3+</li> </ul> <h2>二.编码实现Spring Boot validation</h2> <h3>2.1创建一个spring boot项目并添web模块和validation模块</h3> 项目结构图如下:<br /> <img srcset="" width="" size="" class="img-thumbnail" alt="spring boot项目结果图" src="/assets/upload/blog/thumbnail/2017-12/017be0b2e4e848cb9bc98279929d073a.png" /><br /> 项目的依赖文件: <pre> <code class="language-xml"><?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>net.xqlee.project.demo</groupId> <artifactId>demo-springboot-hibernate-validator</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>demo-springboot-hibernate-validator</name> <description>Demo project for Spring Boot</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.8.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter- </artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project> </code></pre> <h3> </h3> <blockquote> <h3>注意:</h3> <pre> <code class="language-xml"> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency></code></pre> 该模块会自动加载hibernate-validation依赖。不要自己手动配置谨防依赖问题</blockquote> <h3><br /> 2.2编写一个测试的简单对象POJO</h3> <pre> <code class="language-java">package net.xqlee.project.demo.pojo; import javax.validation.constraints.Pattern; import org.hibernate.validator.constraints.Length; import org.hibernate.validator.constraints.NotBlank; import org.hibernate.validator.constraints.NotEmpty; import org.hibernate.validator.constraints.Range; public class User { @NotBlank(message = "用户名称不能为空。") private String name; @Range(max = 150, min = 1, message = "年龄范围应该在1-150内。") private Integer age; @NotEmpty(message = "密码不能为空") @Length(min = 6, max = 8, message = "密码长度为6-8位。") @Pattern(regexp = "[a-zA-Z]*", message = "密码不合法") private String password; public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } } </code></pre> 细心的朋友可能已经发现一些属性上的注解。验证框架正是实现了这些注解的具体验证。hibernate在java的JSR-303标准上还添加了一些额外的验证注解实现。这些实现都为我们后端验证数据取得了巨大的方便。 <blockquote> <p>提示:<br /> 验证注解推荐使用</p> <p>javax.validation.constraints包下的注解,不要使用hibernate的<br /> 在2.0中hibernate的很多注解已经弃用,但是</p> <p>javax.validation.constraints包下的注解增多增强<br /> 一种规范性的趋势</p> </blockquote> <h3>2.3编写一个controller用于测试验证机制</h3> <pre> <code class="language-java">package net.xqlee.project.demo.controller; import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.validation.BindingResult; import org.springframework.validation.ObjectError; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RestController; import net.xqlee.project.demo.pojo.User; @RestController public class ValidatorController { private static final Logger log = LoggerFactory.getLogger(ValidatorController.class); /** * 验证框架使用测试 * * @param user * @param result */ @PostMapping("v/t1.json") public void v1(@Validated User user, BindingResult result) { StringBuilder sBuilder = new StringBuilder(); sBuilder.append("\n"); if (result.hasErrors()) { List<ObjectError> list = result.getAllErrors(); for (ObjectError error : list) { log.info(error.getCode() + "---" + error.getArguments() + "---" + error.getDefaultMessage()); sBuilder.append(error.getDefaultMessage()); sBuilder.append("\n"); } } log.info(sBuilder.toString()); } } </code></pre> <blockquote> <p>提示:<br /> @Validated (org.springframework.validation.annotation)<br /> 或者@Valid(javax.validation)<br /> 验证注解推荐写在方法的参数列表中,例如:<br /> Object   methodName(@Valid Object params){...}<br /> Object   methodName(@Validated Object params){...}<br /> <br />  </p> </blockquote> <h2><br /> 三.演示</h2> <h3>3.1启动spring boot项目</h3> <h3>3.2通过工具postmain进行提交数据验证</h3> <strong>第一组测试:</strong><br /> <br /> 提交全部为空数据:<br /> <img srcset="" width="" size="" class="img-thumbnail" alt="spring boot validation测试数据1" src="/assets/upload/blog/thumbnail/2017-12/8d1b26436c0f42b382e4aa7378e4c466.png" /><br /> 观察eclipse的控制台输出:<br /> <img srcset="" width="" size="" class="img-thumbnail" alt="Spring Boot validation 控制台输出1" src="/assets/upload/blog/thumbnail/2017-12/6b5e924b8c57446ab125be2043cec0e3.png" /><br /> 可以看到非空验证已经实现。<br /> <br /> <strong>第二组测试:</strong><br /> <br /> 接下来输入一个非空的名称和密码<br /> <img srcset="" width="" size="" class="img-thumbnail" alt="spring boot validation测试数据2" src="/assets/upload/blog/thumbnail/2017-12/d86badcd7bc44cdba5080e8f87406023.png" /><br /> 观察eclipse控制台:<br /> <img srcset="" width="" size="" class="img-thumbnail" alt="Spring Boot validation 控制台输出2" src="/assets/upload/blog/thumbnail/2017-12/87f994b3a6eb4bf9a77f3ce652471c11.png" /><br /> 可以看到名称的验证错误信息已经没有说明已经输入正确。但是密码却没有通过正则表达式的验证所以报错不合法,在对象上我们设置的密码只能是大写的字母。所以刚才输入的全数字不合法。<br /> <br /> <strong>第三组测试:</strong><br /> <br /> 接下来输入正常的密码<br /> <img srcset="" width="" size="" class="img-thumbnail" alt="spring boot validation测试数据3" src="/assets/upload/blog/thumbnail/2017-12/40be7f44b6144ade956b3453ae420766.png" />观察控制台:<br /> <br /> <img srcset="" width="" size="" class="img-thumbnail" alt="Spring Boot validation 控制台输出3" src="/assets/upload/blog/thumbnail/2017-12/2be73b7acb544f22845efacbb9c58a6f.png" /><br /> 可以看到已经没有错误信息。验证通过
  • Spring Boot Logback 输出日志慢8小时解决

    找到encoder的pattern节点找到encoder的pattern节点。        <encoder>            <charset>UTF-8</charset>            <pattern>%d{yyyy-MM-dd HH:mm:ss} [%-4relative] %-5level %logger{30} - %msg%n</pattern>        </encoder>在日期%d{yyyy-MM-dd HH:mm:ss} 里面添加时区,%d{yyyy-MM-dd HH:mm:ss,CTT} 其中(CTT代表上海)修改后如下:        <encoder>            <charset>UTF-8</charset>            <pattern>%d{yyyy-MM-dd HH:mm:ss,CTT} [%-4relative] %-5level %logger{30} - %msg%n</pattern>        </encoder> ​​​​​​​
  • spring boot 1.5.2整合mybaties日志打印出sql语句

    本文主要讲解spring boot框架整合mybaties后,如何打印出mybaties的sql语句以及参数列表本文主要讲解spring boot框架整合mybaties后,如何打印出mybaties的sql语句以及参数列表<br /> 其实办法很简单<br /> 在springboot项目中有一个application.properties属性配置文件,在该配置文件中加入以下代码即可 <pre> <code>logging.level.com.leftso.mapper=DEBUG</code></pre> <br /> <span style="color:#1abc9c">解释:<br /> 其中只需要将com.leftso.mapper改为你自己的mapper接口存放的包路径就OK</span>
  • spring boot RPC 框架 Hessian

    spring boot RPC 框架 Hessian,本文主要讲解spring boot整合hessian实现Spring boot RPC调用和Spring boot rpc框架hessian的使用。在微服务中经常遇到一个服务需要调用另外的服务这种内部调用的方式有RPC的方式。<h2>一.背景说明</h2>  该博客主要讲述的是spring boot整合hession实现Java编程中的RPC(远程调用)。实现RPC的方式还有很多例如Java的RMI,webservice等。这里主要讲解hessian的方式。 <h2>二.hessian服务端</h2> <h3>2.1创建一个spring boot项目添加web依赖和hessian依赖</h3> pom.xml如下: <pre> <code class="language-xml"><?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>net.xqlee.project</groupId> <artifactId>demo-springboot-rpc-hessian-server</artifactId> <version>2.0</version> <packaging>jar</packaging> <name>demo-springboot-rpc-hessian-server</name> <description>lee-leftso-v2.0</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.8.RELEASE</version> <relativePath /> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- https://mvnrepository.com/artifact/com.caucho/hessian --> <dependency> <groupId>com.caucho</groupId> <artifactId>hessian</artifactId> <version>4.0.51</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project> </code></pre> <h3>2.2创建一个简单的service接口并实现</h3> HelloWorldServcie.java接口: <pre> <code class="language-java">package net.xqlee.project.service; public interface HelloWorldService { String sayHello(String name); } </code></pre> HelloWorldServiceServiceImp.java <pre> <code class="language-java">package net.xqlee.project.service; import org.springframework.stereotype.Service; @Service("HelloWorldService") public class HelloWorldServiceServiceImp implements HelloWorldService { @Override public String sayHello(String name) { return "Hello, " + name; } } </code></pre> <h3>2.3注册service接口到hessian工厂</h3> <pre> <code class="language-java">package net.xqlee.project; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; import org.springframework.remoting.caucho.HessianServiceExporter; import net.xqlee.project.service.HelloWorldService; @SpringBootApplication public class DemoSpringbootRpcHessianApplication { @Autowired private HelloWorldService helloWorldService; public static void main(String[] args) { SpringApplication.run(DemoSpringbootRpcHessianApplication.class, args); } // 发布服务 @Bean(name = "/HelloWorldService") public HessianServiceExporter accountService() { HessianServiceExporter exporter = new HessianServiceExporter(); exporter.setService(helloWorldService); exporter.setServiceInterface(HelloWorldService.class); return exporter; } } </code></pre> <br /> 至此服务端编写完毕。<br /> <span style="color:#ff0000"><strong>提示:</strong></span><br /> <span style="color:#ff0000">这里需要一个操作.eclipse中进行右键导出,将接口导出到一个jar包文件,这样也是方便调用方使用。我这里导出并命令为rpc-hessian-demo-interface.jar</span> <p> </p> <h2>三.hessian客服端</h2> <h3>3.1创建一个spring boot项目添加web依赖和hessian依赖</h3> 注意,还需要引入刚才服务端导出的<strong><span style="color:#ff0000">rpc-hessian-demo-interface.jar</span></strong><br /> pom.xml文件: <pre> <code class="language-xml"><?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>net.xqlee.project</groupId> <artifactId>demo-springboot-rpc-hessian-client</artifactId> <version>2.0</version> <packaging>jar</packaging> <name>demo-springboot-rpc-hessian-client</name> <description>lee-leftso-v2.0</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.8.RELEASE</version> <relativePath /> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- https://mvnrepository.com/artifact/com.caucho/hessian --> <dependency> <groupId>com.caucho</groupId> <artifactId>hessian</artifactId> <version>4.0.51</version> </dependency> <!-- 本地引入hessian服务端的接口 --> <dependency> <groupId>net.xqlee.project</groupId> <artifactId>rpc-hessian-demo</artifactId> <version>1.0</version> <scope>system</scope> <systemPath>${project.basedir}/lib/rpc-hessian-demo-interface.jar</systemPath> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project> </code></pre> <h3>3.2将引入的服务端接口通过hessian实例化</h3> <pre> <code class="language-java">package net.xqlee.project; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; import org.springframework.remoting.caucho.HessianProxyFactoryBean; import net.xqlee.project.service.HelloWorldService; @SpringBootApplication public class DemoSpringbootRpcHessianClientApplication { public static void main(String[] args) { SpringApplication.run(DemoSpringbootRpcHessianClientApplication.class, args); } @Bean public HessianProxyFactoryBean helloClient() { HessianProxyFactoryBean factory = new HessianProxyFactoryBean(); factory.setServiceUrl("http://localhost:8083/HelloWorldService"); factory.setServiceInterface(HelloWorldService.class); return factory; } } </code></pre> <h3><br /> 3.3创建一个测试的controller进行调用测试</h3> <pre> <code class="language-java">package net.xqlee.project.controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import net.xqlee.project.service.HelloWorldService; @RestController public class DemoHessianController { @Autowired HelloWorldService helloWorldService; @GetMapping("/rpchello.do") public Object rpcSayHello(String name) { return helloWorldService.sayHello(name); } } </code></pre> <br /> 浏览器访问地址:<br /> <img alt="rpc" class="img-thumbnail" src="/assets/upload/blog/thumbnail/2017-11/539bdd2e07d847feb851a8d7513363dc.png" /><br /> <br />  
  • Spring Cloud v1 面试题及答案

    1. 什么是 spring cloud?spring cloud 是一系列框架的有序集合1. 什么是 spring cloud?spring cloud 是一系列框架的有序集合。它利用 spring boot 的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等,都可以用 spring boot 的开发风格做到一键启动和部署。2. spring cloud 断路器的作用是什么?在分布式架构中,断路器模式的作用也是类似的,当某个服务单元发生故障(类似用电器发生短路)之后,通过断路器的故障监控(类似熔断保险丝),向调用方返回一个错误响应,而不是长时间的等待。这样就不会使得线程因调用故障服务被长时间占用不释放,避免了故障在分布式系统中的蔓延。3. spring cloud 的核心组件有哪些?Eureka:服务注册于发现。Feign:基于动态代理机制,根据注解和选择的机器,拼接请求 url 地址,发起请求。Ribbon:实现负载均衡,从一个服务的多台机器中选择一台。Hystrix:提供线程池,不同的服务走不同的线程池,实现了不同服务调用的隔离,避免了服务雪崩的问题。Zuul:网关管理,由 Zuul 网关转发请求给对应的服务。​​​​​​​Ribbon和Feign的区别:​​​​​​​Ribbon添加的maven依赖是spring-starter-ribbon,使用@RibbonClient(value=“服务名称”)使用RestTemplate调用远程服务对应的方法,Feign添加的maven依赖是spring-starter-feign,服务提供方提供对外接口,调用方使用,在接口上使用FeignClient(“指定服务名”),具体区别:1、启动类使用的注解不同,Ribbon使用的是@RibbonClient,Feign使用的是@EnableFeignClients2、服务的指定位置不同,Ribbon是在@RibbonClient注解上声明,Feign则是在定义抽象方法的接口中使用@FeignClient声明3、调用方式不同,Ribbon需要自己构建http请求,模拟http请求然后使用RestTemplate发送给其他服务,步骤比较繁琐。Feign则是在Ribbon的基础上进行了一次改进,采用接口调用的方式,将需要调用的其他服务的方法定义成抽象方法即可,不需要自己构建http请求,不过要注意的是抽象方法的注解、方法签名要和提供方的完全一致。4. SpringCloud和Dubbo SpringCloud和Dubbo都是现在主流的微服务架构SpringCloud是Apache旗下的Spring体系下的微服务解决方案Dubbo是阿里系的分布式服务治理框架从技术维度上,其实SpringCloud远远的超过Dubbo,Dubbo本身只是实现了服务治理,而SpringCloud现在以及有21个子项目以后还会更多所以其实很多人都会说Dubbo和SpringCloud是不公平的但是由于RPC以及注册中心元数据等原因,在技术选型的时候我们只能二者选其一,所以我们常常为用他俩来对比服务的调用方式Dubbo使用的是RPC远程调用,而SpringCloud使用的是 Rest API,其实更符合微服务官方的定义服务的注册中心来看,Dubbo使用了第三方的ZooKeeper作为其底层的注册中心,实现服务的注册和发现,SpringCloud使用Spring Cloud Netflix Eureka实现注册中心,当然SpringCloud也可以使用ZooKeeper实现,但一般我们不会这样做服务网关,Dubbo并没有本身的实现,只能通过其他第三方技术的整合,而SpringCloud有Zuul路由网关,作为路由服务器,进行消费者的请求分发,SpringCloud还支持断路器,与git完美集成分布式配置文件支持版本控制,事务总线实现配置文件的更新与服务自动装配等等一系列的微服务架构要素从技术选型上讲~目前国内的分布式系统选型主要还是Dubbo毕竟国产,而且国内工程师的技术熟练程度高,并且Dubbo在其他维度上的缺陷可以由其他第三方框架进行集成进行弥补而SpringCloud目前是国外比较流行,当然我觉得国内的市场也会慢慢的偏向SpringCloud,就连刘军作为Dubbo重启的负责人也发表过观点,Dubbo的发展方向是积极适应SpringCloud生态,并不是起冲突Rest和RPC对比其实如果仔细阅读过微服务提出者马丁福勒的论文的话可以发现其定义的服务间通信机制就是Http RestRPC最主要的缺陷就是服务提供方和调用方式之间依赖太强,我们需要为每一个微服务进行接口的定义,并通过持续继承发布,需要严格的版本控制才不会出现服务提供和调用之间因为版本不同而产生的冲突而REST是轻量级的接口,服务的提供和调用不存在代码之间的耦合,只是通过一个约定进行规范,但也有可能出现文档和接口不一致而导致的服务集成问题,但可以通过swagger工具整合,是代码和文档一体化解决,所以REST在分布式环境下比RPC更加灵活这也是为什么当当网的DubboX在对Dubbo的增强中增加了对REST的支持的原因文档质量和社区活跃度SpringCloud社区活跃度远高于Dubbo,毕竟由于梁飞团队的原因导致Dubbo停止更新迭代五年,而中小型公司无法承担技术开发的成本导致Dubbo社区严重低落,而SpringCloud异军突起,迅速占领了微服务的市场,背靠Spring混的风生水起Dubbo经过多年的积累文档相当成熟,对于微服务的架构体系各个公司也有稳定的现状5. SpringBoot和SpringCloudSpringBoot是Spring推出用于解决传统框架配置文件冗余,装配组件繁杂的基于Maven的解决方案,旨在快速搭建单个微服务而SpringCloud专注于解决各个微服务之间的协调与配置,服务之间的通信,熔断,负载均衡等技术维度并相同,并且SpringCloud是依赖于SpringBoot的,而SpringBoot并不是依赖与SpringCloud,甚至还可以和Dubbo进行优秀的整合开发总结:SpringBoot专注于快速方便的开发单个个体的微服务SpringCloud是关注全局的微服务协调整理治理框架,整合并管理各个微服务,为各个微服务之间提供,配置管理,服务发现,断路器,路由,事件总线等集成服务SpringBoot不依赖于SpringCloud,SpringCloud依赖于SpringBoot,属于依赖关系SpringBoot专注于快速,方便的开发单个的微服务个体,SpringCloud关注全局的服务治理框架6. 微服务之间是如何独立通讯的1.远程过程调用(Remote Procedure Invocation):       也就是我们常说的服务的注册与发现       直接通过远程过程调用来访问别的service。       优点:       简单,常见,因为没有中间件代理,系统更简单       缺点:       只支持请求/响应的模式,不支持别的,比如通知、请求/异步响应、发布/订阅、发布/异步响应       降低了可用性,因为客户端和服务端在请求过程中必须都是可用的2.消息:       使用异步消息来做服务间通信。服务间通过消息管道来交换消息,从而通信。       优点:       把客户端和服务端解耦,更松耦合       提高可用性,因为消息中间件缓存了消息,直到消费者可以消费       支持很多通信机制比如通知、请求/异步响应、发布/订阅、发布/异步响应       缺点:       消息中间件有额外的复杂7. 负载均衡的意义是什么?在计算中,负载均衡可以改善跨计算机,计算机集群,网络链接,中央处理单元或磁盘驱动器等多种计算资源的工作负载分布。负载均衡旨在优化资源使用,最大吞吐量,最小响应时间并避免任何单一资源的过载。使用多个组件进行负载均衡而不是单个组件可能会通过冗余来提高可靠性和可用性。负载平衡通常涉及专用软件或硬件,例如多层交换机或域名系统服务进程。8. springcloud如何实现服务的注册?1.服务发布时,指定对应的服务名,将服务注册到 注册中心(eureka zookeeper)2.注册中心加@EnableEurekaServer,服务用@EnableDiscoveryClient,然后用ribbon或feign进行服务直接的调用发现。9. 什么是服务熔断?什么是服务降级在复杂的分布式系统中,微服务之间的相互调用,有可能出现各种各样的原因导致服务的阻塞,在高并发场景下,服务的阻塞意味着线程的阻塞,导致当前线程不可用,服务器的线程全部阻塞,导致服务器崩溃,由于服务之间的调用关系是同步的,会对整个微服务系统造成服务雪崩为了解决某个微服务的调用响应时间过长或者不可用进而占用越来越多的系统资源引起雪崩效应就需要进行服务熔断和服务降级处理。所谓的服务熔断指的是某个服务故障或异常一起类似显示世界中的“保险丝"当某个异常条件被触发就直接熔断整个服务,而不是一直等到此服务超时。服务熔断就是相当于我们电闸的保险丝,一旦发生服务雪崩的,就会熔断整个服务,通过维护一个自己的线程池,当线程达到阈值的时候就启动服务降级,如果其他请求继续访问就直接返回fallback的默认值10. 微服务的优缺点分别是什么?说下你在项目开发中碰到的坑优点每一个服务足够内聚,代码容易理解开发效率提高,一个服务只做一件事微服务能够被小团队单独开发微服务是松耦合的,是有功能意义的服务可以用不同的语言开发,面向接口编程易于与第三方集成微服务只是业务逻辑的代码,不会和HTML,CSS或者其他界面组合        开发中,两种开发模式                 前后端分离                 全栈工程师可以灵活搭配,连接公共库/连接独立库缺点分布式系统的负责性多服务运维难度,随着服务的增加,运维的压力也在增大系统部署依赖服务间通信成本数据一致性系统集成测试性能监控11. 你所知道的微服务技术栈?维度(springcloud)服务开发:springboot spring springmvc服务配置与管理:Netfix公司的Archaiusm ,阿里的Diamond服务注册与发现:Eureka,Zookeeper服务调用:Rest RPC gRpc服务熔断器:Hystrix服务负载均衡:Ribbon Nginx服务接口调用:Fegin消息队列:Kafka Rabbitmq activemq服务配置中心管理:SpringCloudConfig服务路由(API网关)Zuul事件消息总线:SpringCloud Bus12. Eureka和ZooKeeper都可以提供服务注册与发现的功能,请说说两个的区别1.ZooKeeper保证的是CP,Eureka保证的是APZooKeeper在选举期间注册服务瘫痪,虽然服务最终会恢复,但是选举期间不可用的Eureka各个节点是平等关系,只要有一台Eureka就可以保证服务可用,而查询到的数据并不是最新的自我保护机制会导致Eureka不再从注册列表移除因长时间没收到心跳而应该过期的服务Eureka仍然能够接受新服务的注册和查询请求,但是不会被同步到其他节点(高可用)当网络稳定时,当前实例新的注册信息会被同步到其他节点中(最终一致性)Eureka可以很好的应对因网络故障导致部分节点失去联系的情况,而不会像ZooKeeper一样使得整个注册系统瘫痪2.ZooKeeper有Leader和Follower角色,Eureka各个节点平等3.ZooKeeper采用过半数存活原则,Eureka采用自我保护机制解决分区问题4.Eureka本质上是一个工程,而ZooKeeper只是一个进程13. eureka自我保护机制是什么?当Eureka Server 节点在短时间内丢失了过多实例的连接时(比如网络故障或频繁启动关闭客户端)节点会进入自我保护模式,保护注册信息,不再删除注册数据,故障恢复时,自动退出自我保护模式。14. 什么是Ribbon?ribbon是一个负载均衡客户端,可以很好的控制htt和tcp的一些行为。feign默认集成了ribbon。15. 什么是feigin?它的优点是什么?1.feign采用的是基于接口的注解2.feign整合了ribbon,具有负载均衡的能力3.整合了Hystrix,具有熔断的能力使用:1.添加pom依赖。2.启动类添加@EnableFeignClients3.定义一个接口@FeignClient(name=“xxx”)指定调用哪个服务16. Ribbon和Feign的区别?1.Ribbon都是调用其他服务的,但方式不同。2.启动类注解不同,Ribbon是@RibbonClient feign的是@EnableFeignClients3.服务指定的位置不同,Ribbon是在@RibbonClient注解上声明,Feign则是在定义抽象方法的接口中使用@FeignClient声明。4.调用方式不同,Ribbon需要自己构建http请求,模拟http请求然后使用RestTemplate发送给其他服务,步骤相当繁琐。Feign需要将调用的方法定义成抽象方法即可。17. 什么是Spring Cloud Bus?spring cloud bus 将分布式的节点用轻量的消息代理连接起来,它可以用于广播配置文件的更改或者服务直接的通讯,也可用于监控。如果修改了配置文件,发送一次请求,所有的客户端便会重新读取配置文件。使用:1.添加依赖2.配置rabbimq18. 什么是Hystrix?防雪崩利器,具备服务降级,服务熔断,依赖隔离,监控(Hystrix Dashboard)服务降级:双十一 提示 哎哟喂,被挤爆了。 app秒杀 网络开小差了,请稍后再试。优先核心服务,非核心服务不可用或弱可用。通过HystrixCommand注解指定。fallbackMethod(回退函数)中具体实现降级逻辑。19. springcloud断路器作用?当一个服务调用另一个服务由于网络原因或自身原因出现问题,调用者就会等待被调用者的响应 当更多的服务请求到这些资源导致更多的请求等待,发生连锁效应(雪崩效应)断路器有完全打开状态:一段时间内 达到一定的次数无法调用 并且多次监测没有恢复的迹象 断路器完全打开 那么下次请求就不会请求到该服务半开:短时间内 有恢复迹象 断路器会将部分请求发给该服务,正常调用时 断路器关闭关闭:当服务一直处于正常状态 能正常调用20. 什么是SpringCloudConfig?在分布式系统中,由于服务数量巨多,为了方便服务配置文件统一管理,实时更新,所以需要分布式配置中心组件。在Spring Cloud中,有分布式配置中心组件spring cloud config ,它支持配置服务放在配置服务的内存中(即本地),也支持放在远程Git仓库中。在spring cloud config 组件中,分两个角色,一是config server,二是config client。使用:1、添加pom依赖2、配置文件添加相关配置3、启动类添加注解@EnableConfigServer21. 架构?在微服务架构中,需要几个基础的服务治理组件,包括服务注册与发现、服务消费、负载均衡、断路器、智能路由、配置管理等,由这几个基础组件相互协作,共同组建了一个简单的微服务系统在Spring Cloud微服务系统中,一种常见的负载均衡方式是,客户端的请求首先经过负载均衡(zuul、Ngnix),再到达服务网关(zuul集群),然后再到具体的服。,服务统一注册到高可用的服务注册中心集群,服务的所有的配置文件由配置服务管理,配置服务的配置文件放在git仓库,方便开发人员随时改配置。
  • spring boot 入门之整合spring session实现session共享

    spring boot 入门之整合spring session实现session共享。一直以来Java编程中web项目中的session共享问题都是一个很难解决的问题。接下来将讲解通过spring boot整合spring session的方案,将session存入分布式缓存Redis中实现session的共享。<h2>一.前言</h2>   spring boot 入门之整合spring session实现session共享。一直以来Javaweb项目中的session共享问题都是一个很难解决的问题。原因是session是由启动servlet的当前web容器所创建和管理。与其他地方的web容器很难通讯。导致无法共享也就很难实现集群化。这里将通过spring boot整合spring session的方案,将session存入分布式缓存Redis中实现session的共享。 <h2>二.编码</h2> <strong>pom.xml文件:</strong> <pre> <code class="language-xml"><?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>net.xqlee.project</groupId> <artifactId>demo-springboot-spring-session</artifactId> <version>1.0</version> <packaging>jar</packaging> <name>demo-springboot-spring-session</name> <description>lee-leftso-v2.0</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.8.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>org.springframework.session</groupId> <artifactId>spring-session</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project> </code></pre> 核心配置类,HttpSessionConfig: <pre> <code class="language-java">package net.xqlee.project.config; import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession; /** 启用redis session **/ /*** * * @author xqlee maxInactiveIntervalInSeconds设置session超时时间,默认1800秒 */ @EnableRedisHttpSession(maxInactiveIntervalInSeconds = 1800) public class HttpSessionConfig { // spring boot项目可以直接配置application配置文件自动注册redis connect 工厂 // 非boot项目则需要再这里配置一下redis得链接工厂 } </code></pre> <strong>编写一个测试的controller,TestController:</strong> <pre> <code class="language-java">package net.xqlee.project.controller; import java.util.Date; import java.util.HashMap; import java.util.Map; import javax.servlet.http.HttpServletRequest; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class TestController { @GetMapping("/test.do") public Object testSession(HttpServletRequest request) { Map<String, Object> obj = new HashMap<>(); obj.put("result", true); request.getSession().setAttribute("time", new Date().getTime()); obj.put("time", request.getSession().getAttribute("time")); return obj; } } </code></pre> <p><strong>application配置文件配置redis的链接信息</strong></p> <pre> <code class="language-html">spring.redis.host=10.1.1.2 spring.redis.password= spring.redis.port=6379 </code></pre> <p><span style="color:#0066cc"><strong>具体的redis配置信息根据自己的环境改变</strong></span></p> <h2>三.测试</h2> 启动spring boot项目,注意观察启动日志<br /> <img alt="启动日志" class="img-thumbnail" src="/assets/upload/blog/thumbnail/2017-11/5c72b8f1caff4859b11a60a590bbbf2c.png" /><br /> 从上方日志可以看到springSessionRepositoryFilter已经启用<br /> <br /> 在浏览器中访问测试controller的地址test.do<br /> 通过谷歌浏览器的调试模式可以看到SESSION的cookie<br /> <img alt="cookie" class="img-thumbnail" src="/assets/upload/blog/thumbnail/2017-11/a39e346b5dca4a1493812e9c15793ed4.png" /><br /> 再通过redis的可视化链接工具链接过去查看:<br /> <img alt="session redis" class="img-thumbnail" src="/assets/upload/blog/thumbnail/2017-11/6f5d096243ea4d4d85d53bd83105efa6.png" /><br /> 上图可以看到: <ul> <li>session id和浏览器中的对应;</li> <li>自己添加的time属性;</li> </ul> 到这里spring  boot 整合spring session 通过redis的方式持久化完成。<br /> <br />