田维常

田维常 查看完整档案

北京编辑华东师范大学  |  应用统计 编辑自由职业  |  自由职业 编辑 www.woaijava.cc/ 编辑
编辑

欢迎关注我的公众号“Java后端技术全栈”,每天解锁一本电子书

个人动态

田维常 发布了文章 · 1月18日

Zookeeper面试常见11个连环炮

图片

获取:10万字的面试小抄

面试的时候,面试官只要看到你简历的上写的有Zookeeper(熟悉、掌握)之类,那你至少要准备接下来的11连问。

图片

NO1:说说zookeeper是什么?

ZooKeeper是一个分布式的,开放源码的分布式应用程序协调服务,是Google的Chubby一个开源的实现(Chubby是不开源的),它是集群的管理者,监视着集群中各个节点的状态根据节点提交的反馈进行下一步合理操作。最终,将简单易用的接口和性能高效、功能稳定的系统提供给用户 。

Zookeeper一个最常用的使用场景就是用于担任服务生产者和服务消费者的注册中心,服务生产者将自己提供的服务注册到Zookeeper中心,服务的消费者在进行服务调用的时候先到Zookeeper中查找服务,获取到服务生产者的详细信息之后,再去调用服务生产者的内容与数据,简单示例图如下:

图片

NO2:了解Zookeeper的系统架构吗?

图片

ZooKeeper 的架构图中我们需要了解和掌握的主要有:

(1)ZooKeeper分为服务器端(Server) 和客户端(Client),客户端可以连接到整个 ZooKeeper服务的任意服务器上(除非 leaderServes 参数被显式设置, leader 不允许接受客户端连接)。

(2)客户端使用并维护一个 TCP 连接,通过这个连接发送请求、接受响应、获取观察的事件以及发送心跳。如果这个 TCP 连接中断,客户端将自动尝试连接到另外的 ZooKeeper服务器。客户端第一次连接到 ZooKeeper服务时,接受这个连接的 ZooKeeper服务器会为这个客户端建立一个会话。当这个客户端连接到另外的服务器时,这个会话会被新的服务器重新建立。

(3)上图中每一个Server代表一个安装Zookeeper服务的机器,即是整个提供Zookeeper服务的集群(或者是由伪集群组成);

(4)组成ZooKeeper服务的服务器必须彼此了解。它们维护一个内存中的状态图像,以及持久存储中的事务日志和快照, 只要大多数服务器可用,ZooKeeper服务就可用;

(5)ZooKeeper 启动时,将从实例中选举一个 leader,Leader 负责处理数据更新等操作,一个更新操作成功的标志是当且仅当大多数Server在内存中成功修改数据。每个Server 在内存中存储了一份数据。

(6)Zookeeper是可以集群复制的,集群间通过Zab协议(Zookeeper Atomic Broadcast)来保持数据的一致性;

(7)Zab协议包含两个阶段:leader election阶段和Atomic Brodcast阶段。

  • a) 集群中将选举出一个leader,其他的机器则称为follower,所有的写操作都被传送给leader,并通过brodcast将所有的更新告诉给follower。
  • b) 当leader崩溃或者leader失去大多数的follower时,需要重新选举出一个新的leader,让所有的服务器都恢复到一个正确的状态。
  • c) 当leader被选举出来,且大多数服务器完成了 和leader的状态同步后,leadder election 的过程就结束了,就将会进入到Atomic brodcast的过程。
  • d) Atomic Brodcast同步leader和follower之间的信息,保证leader和follower具有形同的系统状态。

NO3:能说说Zookeeper的工作原理?

Zookeeper的核心是原子广播,这个机制保证了各个Server之间的同步。实现这个机制的协议叫做Zab协议。

Zab协议有两种模式,它们 分别是恢复模式(选主)和广播模式(同步)。

Zab协议 的全称是 Zookeeper Atomic Broadcast** (Zookeeper原子广播)。Zookeeper 是通过 Zab 协议来保证分布式事务的最终一致性。Zab协议要求每个 Leader 都要经历三个阶段:发现,同步,广播。

当服务启动或者在领导者崩溃后,Zab就进入了恢复模式,当领导者被选举出来,且大多数Server完成了和 leader的状态同步以后,恢复模式就结束了。状态同步保证了leader和Server具有相同的系统状态。

为了保证事务的顺序一致性,zookeeper采用了递增的事务id号(zxid)来标识事务。所有的提议(proposal)都在被提出的时候加 上了zxid。实现中zxid是一个64位的数字,它高32位是epoch用来标识leader关系是否改变,每次一个leader被选出来,它都会有一 个新的epoch,标识当前属于那个leader的统治时期。低32位用于递增计数。

epoch:可以理解为皇帝的年号,当新的皇帝leader产生后,将有一个新的epoch年号。

每个Server在工作过程中有三种状态:

  • LOOKING:当前Server不知道leader是谁,正在搜寻。
  • LEADING:当前Server即为选举出来的leader。
  • FOLLOWING:leader已经选举出来,当前Server与之同步。

NO4:Zookeeper为什么要这么设计?

ZooKeeper设计的目的是提供高性能、高可用、顺序一致性的分布式协调服务、保证数据最终一致性。

高性能(简单的数据模型)

  1. 采用树形结构组织数据节点;
  2. 全量数据节点,都存储在内存中;
  3. Follower 和 Observer 直接处理非事务请求;

高可用(构建集群)

  1. 半数以上机器存活,服务就能正常运行
  2. 自动进行 Leader 选举

顺序一致性(事务操作的顺序)

  1. 每个事务请求,都会转发给 Leader 处理
  2. 每个事务,会分配全局唯一的递增id(zxid,64位:epoch + 自增 id)

最终一致性

  1. 通过提议投票方式,保证事务提交的可靠性
  2. 提议投票方式,只能保证 Client 收到事务提交成功后,半数以上节点能够看到最新数据

NO5:你知道Zookeeper中有哪些角色?

系统模型:

图片

领导者(leader)

Leader服务器为客户端提供读服务和写服务。负责进行投票的发起和决议,更新系统状态。

学习者(learner)

  • 跟随者(follower) Follower服务器为客户端提供读服务,参与Leader选举过程,参与写操作“过半写成功”策略。
  • 观察者(observer) Observer服务器为客户端提供读服务,不参与Leader选举过程,不参与写操作“过半写成功”策略。用于在不影响写性能的前提下提升集群的读性能。

客户端(client):服务请求发起方。

NO6:你熟悉Zookeeper节点ZNode和相关属性吗?

节点有哪些类型?

Znode两种类型

持久的(persistent):客户端和服务器端断开连接后,创建的节点不删除(默认)。

短暂的(ephemeral):客户端和服务器端断开连接后,创建的节点自己删除。

Znode有四种形式

  • 持久化目录节点(PERSISTENT):客户端与Zookeeper断开连接后,该节点依旧存在持久化顺序编号目录节点(PERSISTENT_SEQUENTIAL)
  • 客户端与Zookeeper断开连接后,该节点依旧存在,只是Zookeeper给该节点名称进行顺序编号:临时目录节点(EPHEMERAL)
  • 客户端与Zookeeper断开连接后,该节点被删除:临时顺序编号目录节点(EPHEMERAL_SEQUENTIAL)
  • 客户端与Zookeeper断开连接后,该节点被删除,只是Zookeeper给该节点名称进行顺序编号

「注意」:创建ZNode时设置顺序标识,ZNode名称后会附加一个值,顺序号是一个单调递增的计数器,由父节点维护。

节点属性有哪些

一个znode节点不仅可以存储数据,还有一些其他特别的属性。接下来我们创建一个/test节点分析一下它各个属性的含义。

 [zk: localhost:2181(CONNECTED) 6] get /test
    456
    cZxid = 0x59ac //
    ctime = Mon Mar 30 15:20:08 CST 2020
    mZxid = 0x59ad
    mtime = Mon Mar 30 15:22:25 CST 2020
    pZxid = 0x59ac
    cversion = 0
    dataVersion = 2
    aclVersion = 0
    ephemeralOwner = 0x0
    dataLength = 3
    numChildren = 0 

属性说明

NO7:请简述Zookeeper的选主流程

Zookeeper的核心是原子广播,这个机制保证了各个Server之间的同步。实现这个机制的协议叫做Zab协议。Zab协议有两种模式,它们分别是恢复模式(选主)和广播模式(同步)。当服务启动或者在领导者崩溃后,Zab就进入了恢复模式,当领导者被选举出来,且大多数Server完成了和leader的状态同步以后,恢复模式就结束了。状态同步保证了leader和Server具有相同的系统状态。leader选举是保证分布式数据一致性的关键。

出现选举主要是两种场景:初始化、leader不可用。

当zk集群中的一台服务器出现以下两种情况之一时,就会开始leader选举。

(1)服务器初始化启动。

(2)服务器运行期间无法和leader保持连接。

而当一台机器进入leader选举流程时,当前集群也可能处于以下两种状态。

(1)集群中本来就已经存在一个leader。

(2)集群中确实不存在leader。

首先第一种情况,通常是集群中某一台机器启动比较晚,在它启动之前,集群已经正常工作,即已经存在一台leader服务器。当该机器试图去选举leader时,会被告知当前服务器的leader信息,它仅仅需要和leader机器建立连接,并进行状态同步即可。

重点是leader不可用了,此时的选主制度。

投票信息中包含两个最基本的信息。

sid:即server id,用来标识该机器在集群中的机器序号。

zxid:即zookeeper事务id号。

ZooKeeper状态的每一次改变, 都对应着一个递增的Transaction id,,该id称为zxid.,由于zxid的递增性质, 如果zxid1小于zxid2,,那么zxid1肯定先于zxid2发生。创建任意节点,或者更新任意节点的数据, 或者删除任意节点,都会导致Zookeeper状态发生改变,从而导致zxid的值增加。

以(sid,zxid)的形式来标识一次投票信息。

例如:如果当前服务器要推举sid为1,zxid为8的服务器成为leader,那么投票信息可以表示为(1,8)

集群中的每台机器发出自己的投票后,也会接受来自集群中其他机器的投票。每台机器都会根据一定的规则,来处理收到的其他机器的投票,以此来决定是否需要变更自己的投票。

规则如下

(1)初始阶段,都会给自己投票。

(2)当接收到来自其他服务器的投票时,都需要将别人的投票和自己的投票进行pk,规则如下:

优先检查zxid。zxid比较大的服务器优先作为leader。如果zxid相同的话,就比较sid,sid比较大的服务器作为leader。

NO8:有了解过watch机制吗?

简单地说:client端会对某个znode 注册一个watcher事件,当该znode发生变化时,这些client会收到ZooKeeper的通知,然后client可以根据znode变化来做出业务上的改变等。

经典使用场景:zookeeper为dubbo提供服务的注册与发现,作为注册中心,但是大家有没有想过zookeeper为啥能够实现服务的注册与发现吗?

这就不得不说一下zookeeper的灵魂 Watcher(监听者)。

什么是watcher?

watcher 是zooKeeper中一个非常核心功能 ,客户端watcher 可以监控节点的数据变化以及它子节点的变化,一旦这些状态发生变化,zooKeeper服务端就会通知所有在这个节点上设置过watcher的客户端 ,从而每个客户端都很快感知,它所监听的节点状态发生变化,而做出对应的逻辑处理。

简单的介绍了一下watcher ,那么我们来分析一下,zookeeper是如何实现服务的注册与发现。zookeeper的服务注册与发现,主要应用的是zookeeper的znode节点数据模型和watcher机制,大致的流程如下:

  • 服务注册:服务提供者(Provider)启动时,会向zookeeper服务端注册服务信息,也就是创建一个节点,例如:用户注册服务com.xxx.user.register,并在节点上存储服务的相关数据(如服务提供者的ip地址、端口等)。
  • 服务发现:服务消费者(Consumer)启动时,根据自身配置的依赖服务信息,向zookeeper服务端获取注册的服务信息并设置watch监听,获取到注册的服务信息之后,将服务提供者的信息缓存在本地,并进行服务的调用。
  • 服务通知:一旦服务提供者因某种原因宕机不再提供服务之后,客户端与zookeeper服务端断开连接,zookeeper服务端上服务提供者对应服务节点会被删除(例如:用户注册服务com.xxx.user.register),随后zookeeper服务端会异步向所有消费用户注册服务com.xxx.user.register,且设置了watch监听的服务消费者发出节点被删除的通知,消费者根据收到的通知拉取最新服务列表,更新本地缓存的服务列表。

上边的过程就是zookeeper可以实现服务注册与发现的大致原理。

watcher有哪些类型?

znode节点可以设置两类watch,一种是DataWatches,基于znode节点的数据变更从而触发 watch 事件,触发条件getData()、exists()、setData()、 create()。

另一种是Child Watches,基于znode的孩子节点发生变更触发的watch事件,触发条件 getChildren()、 create()。

而在调用 delete() 方法删除znode时,则会同时触发Data Watches和Child Watches,如果被删除的节点还有父节点,则父节点会触发一个Child Watches。

watcher有什么特性?

watch对节点的监听事件是一次性的!客户端在指定的节点设置了监听watch,一旦该节点数据发生变更通知一次客户端后,客户端对该节点的监听事件就失效了。

如果还要继续监听这个节点,就需要我们在客户端的监听回调中,再次对节点的监听watch事件设置为True。否则客户端只能接收到一次该节点的变更通知。

NO9:那你说说Zookeeper有哪些应用场景?

数据发布与订阅

发布与订阅即所谓的配置管理,顾名思义就是将数据发布到ZooKeeper节点上,供订阅者动态获取数据,实现配置信息的集中式管理和动态更新。例如全局的配置信息,地址列表等就非常适合使用。

数据发布/订阅的一个常见的场景是配置中心,发布者把数据发布到 ZooKeeper 的一个或一系列的节点上,供订阅者进行数据订阅,达到动态获取数据的目的。

配置信息一般有几个特点:

  1. 数据量小的KV
  2. 数据内容在运行时会发生动态变化
  3. 集群机器共享,配置一致

图片

ZooKeeper 采用的是推拉结合的方式。

  1. 推: 服务端会推给注册了监控节点的客户端 Wathcer 事件通知
  2. 拉: 客户端获得通知后,然后主动到服务端拉取最新的数据
命名服务

作为分布式命名服务,命名服务是指通过指定的名字来获取资源或者服务的地址,利用ZooKeeper创建一个全局的路径,这个路径就可以作为一个名字,指向集群中的集群,提供的服务的地址,或者一个远程的对象等等。

统一命名服务的命名结构图如下所示:

图片

1、在分布式环境下,经常需要对应用/服务进行统一命名,便于识别不同服务。

  • 类似于域名与IP之间对应关系,IP不容易记住,而域名容易记住。
  • 通过名称来获取资源或服务的地址,提供者等信息。

2、按照层次结构组织服务/应用名称。

  • 可将服务名称以及地址信息写到ZooKeeper上,客户端通过ZooKeeper获取可用服务列表类。
配置管理

程序分布式的部署在不同的机器上,将程序的配置信息放在ZooKeeper的znode下,当有配置发生改变时,也就是znode发生变化时,可以通过改变zk中某个目录节点的内容,利用watch通知给各个客户端 从而更改配置。

ZooKeeper配置管理结构图如下所示:

1、分布式环境下,配置文件管理和同步是一个常见问题。

  • 一个集群中,所有节点的配置信息是一致的,比如 Hadoop 集群。
  • 对配置文件修改后,希望能够快速同步到各个节点上。

2、配置管理可交由ZooKeeper实现。

  • 可将配置信息写入ZooKeeper上的一个Znode。
  • 各个节点监听这个Znode。
  • 一旦Znode中的数据被修改,ZooKeeper将通知各个节点。
集群管理

所谓集群管理就是:是否有机器退出和加入、选举master。

集群管理主要指集群监控和集群控制两个方面。前者侧重于集群运行时的状态的收集,后者则是对集群进行操作与控制。开发和运维中,面对集群,经常有如下需求:

  1. 希望知道集群中究竟有多少机器在工作
  2. 对集群中的每台机器的运行时状态进行数据收集
  3. 对集群中机器进行上下线的操作

集群管理结构图如下所示:

1、分布式环境中,实时掌握每个节点的状态是必要的,可根据节点实时状态做出一些调整。

2、可交由ZooKeeper实现。

  • 可将节点信息写入ZooKeeper上的一个Znode。
  • 监听这个Znode可获取它的实时状态变化。

3、典型应用

  • Hbase中Master状态监控与选举。

利用ZooKeeper的强一致性,能够保证在分布式高并发情况下节点创建的全局唯一性,即:同时有多个客户端请求创建 /currentMaster 节点,最终一定只有一个客户端请求能够创建成功

分布式通知与协调

1、分布式环境中,经常存在一个服务需要知道它所管理的子服务的状态。

a)NameNode需知道各个Datanode的状态。

b)JobTracker需知道各个TaskTracker的状态。

2、心跳检测机制可通过ZooKeeper来实现。

3、信息推送可由ZooKeeper来实现,ZooKeeper相当于一个发布/订阅系统。

分布式锁

处于不同节点上不同的服务,它们可能需要顺序的访问一些资源,这里需要一把分布式的锁。

分布式锁具有以下特性:写锁、读锁、时序锁。

写锁:在zk上创建的一个临时的无编号的节点。由于是无序编号,在创建时不会自动编号,导致只能客户端有一个客户端得到锁,然后进行写入。

读锁:在zk上创建一个临时的有编号的节点,这样即使下次有客户端加入是同时创建相同的节点时,他也会自动编号,也可以获得锁对象,然后对其进行读取。

时序锁:在zk上创建的一个临时的有编号的节点根据编号的大小控制锁。

分布式队列

分布式队列分为两种:

1、当一个队列的成员都聚齐时,这个队列才可用,否则一直等待所有成员到达,这种是同步队列。

a)一个job由多个task组成,只有所有任务完成后,job才运行完成。

b)可为job创建一个/job目录,然后在该目录下,为每个完成的task创建一个临时的Znode,一旦临时节点数目达到task总数,则表明job运行完成。

2、队列按照FIFO方式进行入队和出队操作,例如实现生产者和消费者模型。

NO10:知道监听器的原理吗?

  1. 创建一个Main()线程。
  2. 在Main()线程中创建两个线程,一个负责网络连接通信(connect),一个负责监听(listener)。
  3. 通过connect线程将注册的监听事件发送给Zookeeper。
  4. 将注册的监听事件添加到Zookeeper的注册监听器列表中。
  5. Zookeeper监听到有数据或路径发生变化时,把这条消息发送给Listener线程。
  6. Listener线程内部调用process()方法。

NO11:为什么Zookeeper集群的数目,一般为奇数个?

首先需要明确zookeeper选举的规则:leader选举,要求可用节点数量 > 总节点数量/2

比如:标记一个写是否成功是要在超过一半节点发送写请求成功时才认为有效。同样,Zookeeper选择领导者节点也是在超过一半节点同意时才有效。最后,Zookeeper是否正常是要根据是否超过一半的节点正常才算正常。这是基于CAP的一致性原理。

zookeeper有这样一个特性:集群中只要有过半的机器是正常工作的,那么整个集群对外就是可用的。

也就是说如果有2个zookeeper,那么只要有1个死了zookeeper就不能用了,因为1没有过半,所以2个zookeeper的死亡容忍度为0;

同理,要是有3个zookeeper,一个死了,还剩下2个正常的,过半了,所以3个zookeeper的容忍度为1;

同理:

  • 2->0;两个zookeeper,最多0个zookeeper可以不可用。
  • 3->1;三个zookeeper,最多1个zookeeper可以不可用。
  • 4->1;四个zookeeper,最多1个zookeeper可以不可用。
  • 5->2;五个zookeeper,最多2个zookeeper可以不可用。
  • 6->2;两个zookeeper,最多0个zookeeper可以不可用。

....

会发现一个规律,2n和2n-1的容忍度是一样的,都是n-1,所以为了更加高效,何必增加那一个不必要的zookeeper呢。

zookeeper的选举策略也是需要半数以上的节点同意才能当选leader,如果是偶数节点可能导致票数相同的情况。

总结

很多面试官,面试套路基本就是这个,从背景到原理,到架构体系,再到Zookeeper固有特点、最后要求面试者能说出Zookeeper的实际应用场景。

「你多学一样本事,就少说一句求人的话。」

推荐阅读

掌握Mybatis动态映射,我可是下了功夫的

2020所有原创

写给大忙人看的JAVA核心技术.pdf下载

图解多线程

京东面试:说说MySQL的架构体系

查看原文

赞 0 收藏 0 评论 0

田维常 发布了文章 · 1月15日

保姆级 tomcat 快速入门

img

背景介绍

Apache Tomcat 是Java Servlet、JavaServer Pages (JSP)、Java表达式语言和Java的WebSocket技术的一个开源实现 ,通常我们将Tomcat称为Web容器或者Servlet容器 。

tomcat各版本和相应规范的映射关系:

img

下载地址

https://tomcat.apache.org/dow...

下载到本地并解压:

img

进入主目录:

img

tomcat目录介绍

bin

启动,关闭和其他脚本。这些 .sh文件(对于Unix系统)是这些.bat文件的功能副本(对于Windows系统)。由于Win32命令行缺少某些功能,因此此处包含一些其他文件。

比如说:windows下启动tomcat用的是startup.bat,另外Linux环境中使用的是startup.sh。对应还有相应的shutdown关闭脚本。

conf

tomcat的配置文件和相关的DTD。这里最重要的文件是server.xml。它是容器的主要配置文件。

catalina.policy:tomcat:安全策略文件,控制JVM相关权限,具体可以参考java.security.Permission。

catalina.properties:tomcat Catalina 行为控制配置文件,比如:Common ClassLoader。

logging.properties:tomcat日志配置文件。里面的日志采用的是JDK Logging。

server.xml:tomcat server配置文件(对于我开发人员来说是非常重要)。

context.xml:全局context配置文件,监视并加载资源文件,当监视的文件发生发生变化时,自动加载 。

tomcat-user.xml:tomcat角色配置文件。

web.xml:Servlet标准的web.xml部署文件,tomcat默认实现部分配置 入内:

  • org.apache.catalina.servlets.DefaultServlet。
  • org.apache.jasper.servlet.JspServlet

logs

日志文件默认位于此处。

localhost有用,当你们的tomcat启动不了的时候,多看这个文件。比如:

  • NoClassDefFoundError
  • ClassNotFoundException

access最没用。

catalina.{date} 主要是控制台输出,全部日志都在这里面。

webapps

这是您的webapp所在的位置。其实这里面这几个都是一个项目。

简化web部署的方式。在线上环境中我们的应用是不会放在这里的。最好的办法就是外置。

lib

tomcat存放共用的类库。比如:

  • ecj-4.17.jar: eclipse Java编译器
  • jasper.jar:JSP编译器。

work

存放tomcat运行时编译后的文件,比如JSP编译后的文件 。

temp

存放运行时产生的临时文件。

启动tomcat

启动tomcat

我们在windows下的就直接启动bin目录下的startup.bat,对应Linux环境中我们使用的是startup.sh。

双击就能启动了。控制台会输8080端口,然后我们访问:

http://localhost:8080/

页面展示:

img

这就代表着我们的tomcat启动成功了。

此时,http://localhost:8080/请求到的是ROOT目录。

比如:我们还可以http://localhost:8080/manager

Servlet项目部署到tomcat中

创建web项目

使用maven创建web项目,既然说tomcat是Servlet容器,那么在项目中创建一个Servlet类,然后打成war包,拷贝到tomcat中部署。

项目结构如下:

img

添加依赖

<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/maven...d">
<modelVersion>4.0.0</modelVersion>
<groupId>com.tian.maven</groupId>
<artifactId>my-web-maven</artifactId>
<packaging>war</packaging>
<version>1.0-SNAPSHOT</version>
<name>my-web-maven Maven Webapp</name>
<url>http://maven.apache.org</url>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
</dependency>
</dependencies>
<build>
<finalName>my-web-maven</finalName>
</build>
</project>

创建DemoServlet

package com.tian.maven;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet(urlPatterns = "/demo")
public class DemoServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String msg = req.getParameter("message");
String contextPath = req.getServletContext().getContextPath();
System.out.println("contextPath=" + contextPath);
resp.getWriter().println(msg);
}
}

web.xml什么都没有,仅仅就是为了打包。

<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>
<display-name>Archetype Created Web Application</display-name>
</web-app>

index.jsp找那个也没什么内容:

<html>
<body>
<h2>Hello World!</h2>
</body>
</html>

使用mvn命令打成war包。

img

把打成的war包拷贝(其实拷贝my-web-maven文件夹也是一样的)到tomcat中webapps目录下:

img

然后到bin目录下,双击

img

项目运行起来后,访问 http://localhost:8080/

img

证明我们的项目已经启动成功了。

接下来我们访问我们写的Servlet:

http://localhost:8080/demo?message=hello

img

报错,HTTP状态 404 - 未找到;

注意:这里访问的时候,我们需要把项目名称当做contextPath,即访问方式应该是:

http://localhost:8080/my-web-maven/demo?message=hello

页面上输出

hello

轻松搞定,这样我们的项目就成功的部署到tomcat中。

IDEA中的项目部署到tomcat中

创建一个servlet项目,项目名称my-servlet。

img

img

新建一个类MyServlet

img

进入我们刚刚安装的tomcat目录,进入到lib目录中,选中servlet-api.jar。

img

img

img

点击ok。

img

点击Apply,然后点击OK。

修改MyServlet内容:

package com.tian.servlet;

import javax.servlet.*;
import java.io.IOException;
import java.io.PrintWriter;

//实现接口Servlet,必须重写下面的这些方法
public class MyServlet implements Servlet {

private transient ServletConfig servletConfig;
@Override
public void init(ServletConfig servletConfig) throws ServletException {
this.servletConfig = servletConfig;
}

@Override
public ServletConfig getServletConfig() {
return servletConfig;
}

@Override
public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
String servletName = servletConfig.getServletName();
//网页响应类型,浏览器将其渲染为HTML格式
response.setContentType("text/html");
PrintWriter writer = response.getWriter();
writer.println("<html><head></head>" + "<body> Hello this is " + servletName + "</body></html>");
}

@Override
public String getServletInfo() {
return "my first Servlet";
}

@Override
public void destroy() {
}
}

修改web.xml文件内容:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         version="3.1">
    <servlet>
        <servlet-name>myServlet</servlet-name>
        <servlet-class>com.tian.servlet.MyServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>myServlet</servlet-name>
        <url-pattern>/demo</url-pattern>
    </servlet-mapping>
</web-app>

另外我们把index.jsp内容修改一下,主要是为了更好的演示:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<body>
<h1> hello world </h1>
</body>
</html>

IDEA集成tomcat

在我们的IDEA中添加我们安装的tomcat:

img

img

来到tomcat配置 的界面:

img

配置tomcat:

img

进入安装目录:

img

点击OK,

img

然后进入deployment栏目:

img

添加我们创建的servlet项目:

img

自动就添加了我们的项目:

img

然后点击Apply,再点击OK。

IDEA集成tomcat,以及把我们项目给关联起来了。下面我们就来运行一下:

img

启动tomcat

点击绿色三角:

img

证明我们的项目在tomcat中已经启动成功。

img

访问我们的servlet

此时,我们就可以来访问我们的servlet了。

访问:http://localhost:8080/ 页面显示就是我们前面修改的index.jsp的内容。

再访问我们自己写的Servlet:

http://localhost:8080/demo

img

成功的把我们的内容给输出来了。好了本文内容就这些,简单式tomcat入门。

总结

tomcat是什么,tomcat如何安装,如何启动tomcat,如何部署我们自己的Servlet项目,我们在IDEA如何集成tomcat以及如何启动tomcat。

人只要不失去方向,就不会失去自己

查看原文

赞 0 收藏 0 评论 0

田维常 发布了文章 · 1月14日

如何使用maven 轻松重构项目

图片

现在是微服务盛行时代,说不准哪一天领导就会让你对一个大项目进行重构。大项目的痛点:编译慢、发布繁琐等。就像下面这张图:

图片

真的不敢动呀,一不小心就坍塌了。

比如说我们用户系统,我们可以这么重构(这里只是举例,每个项目拆分目的可能不同)。

user-system拆分成:

  • user-web
  • user-service
  • user-dao
  • user-common

我们对其拆分后很有可能存在多个子项目中同时依赖某个jar包,如果使用不当可能会导致每个模块使用的版本不一样,所以想想能不能有什么方式来解决这类问题呢?

答案:能。但是得先了解两个概念:聚合和继承。

聚合

所谓的聚合就是我们如果想一次性构建多个项目模块,那我们就需要把这些项目模块进行聚合。

配置模板

<modules>
    <module>项目模块一</module>
    <module>项目模块二</module>
    <module>项目模块三</module>
    <module>项目模块...</module>
</modules>

使用方式

比如我们对user-web、user-service、user-dao、user-common四个项目模块进行聚合。

<modules>
    <module>user-web</module>
    <module>user-service</module>
    <module>user-dao</module>
    <module>user-common</module>
</modules>

注意:其中module的路径为相对路径。

这样就解决了我们项目模块的聚合,我们再来看看maven是如何来解决由于重复依赖导致版本不一样的问题。

继承

继承为了消除重复,我们把很多相同的配置提取出来,例如:grouptId,version等 。

继承的配置

<parent>
    <artifactId>maven-demo</artifactId>
    <groupId>com.tian</groupId>
    <version>1.0-SNAPSHOT</version>
</parent>

继承代码中定义属性

继承代码过程中,可以定义属性,例如:

<properties>
   <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
   <junit.version>4.13.1</junit.version>
   <maven.version>0.0.1-SNAPSHOT</maven.version>
</properties>

访问属性的方式为${junit.version},例如:

<?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">
    <parent>
        <artifactId>maven-demo</artifactId>
        <groupId>com.tian</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>user-web</artifactId>
    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <!-- 使用了parent中 junit.version -->
            <version>${junit.version}</version>
        </dependency>
    </dependencies>
</project>

这样我们就可以使用到上面定义的属性junit.version=4.13.1。

父模块用dependencyManagement进行管理

由于项目模块较多,所以我们又抽象了一层,抽出一个parent来管理子项目的公共的依赖。为了项目的正确运行,必须让所有的子项目使用依赖项的统一版本,必须确保应用的各个项目的依赖项和版本一致,才能保证测试的和发布的是相同的结果。

<dependencyManagement>
    <dependencies>
     <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>${junit.version}</version>
        <scope>test</scope>
      </dependency>
     </dependencies>
</dependencyManagement>

这样的好处是子模块可以有选择性的继承,而不需要全部继承。

图片

这样做的好处:统一管理项目的版本号,确保应用的各个项目的依赖和版本一致,才能保证测试的和发布的是相同的成果,因此,在顶层pom中定义共同的依赖关系。同时可以避免在每个使用的子项目中都声明一个版本号,这样想升级或者切换到另一个版本时,只需要在父类容器里更新,不需要任何一个子项目的修改;如果某个子项目需要另外一个版本号时,只需要在dependencies中声明一个版本号即可。子类就会使用子类声明的版本号,不继承于父类版本号。

聚合和继承的关系

在前面我们构建多模块项目中,关系如下

mavendemo就是user-web、user-service等几个模块的父项目。

父项目主要是用来配置一些公共的配置,其它三个项目再通过继承的方式拥有父项目中的配置,首先配置父项目的pom.xml,添加对项目的user-webuser-serviceuser-daouser-common这四个模块进行聚合以及jar包依赖。

相对于dependencyManagement,所有生命在dependencies里的依赖都会自动引入,并默认被所有的子项目继承。

面试题:dependencyManagement和dependencies的区别

  • dependencyManagement里只是声明依赖,并不实现引入,因此子项目需要显示的声明需要用的依赖。如果不在子项目中声明依赖,是不会从父项目中继承下来的;只有在子项目中写了该依赖项,并且没有指定具体版本,才会从父项目中继承该项,并且version和scope都读取自父pom;另外如果子项目中指定了版本号,那么会使用子项目中指定的jar版本。
  • dependencies即使在子项目中不写该依赖项,那么子项目仍然会从父项目中继承该依赖项(全部继承)。

IDEA中配置Maven

在使用IDEA开发时,如何将Maven配置呢?

打开IDEA,在File--->Settings。输入栏搜索:maven

图片

配置我们之前安装好的maven目录、maven下面的setting.xml以及本地仓库目录。然后Apply--->OK。

图片

我们项目对某个项目进行clean 等操作的时候,就可以直接双击Lifcycle下面的。

这样我们就把Maven集成到我们的IDEA中来了。

编译运行项目

我们可以在父项目中对所有子项目进行编译、打包等。我们就来对所有子模块进行打包。

然后在对应子项目中可以找到target目录和对应的jar包。

图片

也可单独对某个子项目进行打包等操作。比如我们对user-common进行clean。

图片

于是,此时的user-common下的target已经被删掉,其他子项目中并没有收到影响。

IDEA中如何创建多项目模块

先创建一个user-parent的maven项目:

然后把src目录删掉,创建子项目:

选中user-parent项目,右键,选中New-->Module

来到New Module界面,选择Maven,选好自己的本地安装的JDK。

图片

next。填写子项目名称,我们这里创建一个user-web的子项目:

图片

注意这里user-web,默认是userweb,需要我们手工在两个单词之间添加横线:

图片

点击Finish:

然后在看看user-parent的pom中的内容:

已经添加了modules,同时把我们的user-web添加进去了。

同样方法,我们就可以创建更多字项目了:

图片

再看看user-parent中的pom.xml文件内容:

图片

到这里,我们就轻轻松松的构建了我们这个大项目user-parent,该项目包括了user-web,user-service,user-dao,-user-commmon子项目。

若是新建多模块项目,安装上面的步骤就搞定了。

若是大项目拆分,我们就可以使用上面的方式先创建好自己的多模块项目,然后对应把大项目里的内容拷贝到对应模块中(需要注意此时子模块直接肯定会有相互依赖的)。

总结

通过继承,我们可以定义项目的父类项目,用于继承父项目的依赖、插件、属性等信息。聚合就是我们如果想一次性构建多个项目模块,那我们就需要对这些项目模块进行聚合。

构建多模块项目,在IDEA中使用创建Module的方式很轻松就可以创建了。在父项目中使用来管理子模块的依赖相关依赖。

对大项目进行重构时,只需要把各模块拆分出来的代码拷贝到对应子模块就可以了。

「没有天生的信心,只有不断培养的信心。」

推荐阅读

搞定这24道JVM面试题,要价30k都有底气~

京东面试:说说MySQL的架构体系

6000多字 | 秒杀系统设计注意点

查看原文

赞 0 收藏 0 评论 0

田维常 赞了文章 · 1月14日

小程序本地测试:开发者工具能请求后台数据,手机预览却不行

在微信小程序本地开发测试过程中(这里指的是本地测试本地测试本地测试,重要的事说三遍),会遇到一个坑:在微信开发者工具中能正常请求本地后台数据,但在手机预览中却请求不到,如下图所示:

1.在微信开发者工具中数据正常显示

2.在手机预览中无数据

解决此问题需要有以下4点设置:

1、在微信开发者工具中设置:不校验合法域名、web-view(业务域名)、TLS 版本以及 HTTPS 证书

2、wx.request请求的地址不得使用localhost,而应改成本地服务器所在的电脑IP

假设电脑的IP为192.168.0.110;要请求的地址为:index/list/getdata;代码书写如下:

wx.request({  
  //url: 'http://localhost/index/list/getdata',错误请求地址  
  url: 'http://192.168.0.110/index/list/getdata',//正确请求地址  
  data: {},  
  header: {    
   'content-type': 'application/json'  
  },  
  success (res) {    
   console.log(res.data)  
  }
})

3、手机和电脑(本地服务器)需要连接同一局域网(WIFI网络)

4、手机扫码进入小程序后,需要打开调试模式才能请求到数据

进入小程序后,此时页面的数据依旧是空的;点击右上角三个点,打开调试

打开调试后会自动关闭当前小程序,需要重新进入,调试模式才生效

重新进入小程序,可以看到页面数据已经出来了,并且右下角有个绿色方形的调试工具按钮

点击右下角的调试工具按钮,可以看到页面的所有数据,方便开发者在手机端更好的测试

总结

1.本地测试时,微信开发者工具务必勾选(不校验合法域名)此设置,否则微信开发者工具请求不到数据;待上线时,再取消此设置。有关为何要设置(不校验合法域名)的文档地址如下:
https://developers.weixin.qq....

2.在使用wx.request请求地址,如果url是localhost格式的话,虽然在微信开发者工具中是可以请求到后台数据,但是手机预览时,数据无法请求。因为localhost是指本地服务器所在的那台电脑,手机访问localhost并不知道localhost是什么,所以需要使用本机的IP+请求地址

3.疑问:为何手机开启了调试模式就可以请求到数据,未开启却请求不到?这其实跟前面微信开发者工具设置不校验合法域名的道理是一样的

最后

觉得文章不错的,给我点个赞哇,关注一下呗!
技术交流可关注微信公众号【GitWeb】,加我好友一起探讨

查看原文

赞 37 收藏 2 评论 3

田维常 赞了文章 · 1月14日

phpspider简单快速上手的php爬虫框架

前言

前段时间接到一个开发采集网站数据的项目,从事php开发的我立刻想到使用php做爬虫。虽然python爬虫方便,但是php在这方面也不弱,谁让php是世界上最好的语言!这里推荐一款php的爬虫框架phpspider。不建议自己写爬虫,因为效率太低。使用框架爬虫真的要高效许多

官方文档:
https://doc.phpspider.org/

1、下载

官方github下载地址:
https://github.com/owner888/p...

下载地址可能无法访问,这里提供一个网盘下载地址:
https://pan.baidu.com/s/10n9Z...
提取码:b2zc

2、文件结构

下载解压后,phpspider的文件结构如图所示:


其中demo文件夹放的是phpspider的一些案例,如图所示:

3、创建爬虫并且运行

在demo文件夹下创建爬虫文件。需要注意的是,phpspider有两种运行爬虫文件的方式,一种是在命令行下运行;另外一种是可视化操作(在浏览器下运行)

3.1 在命令行下运行爬虫文件

要爬取的对象链接:
https://www.douban.com/photos...

要爬取的内容如图所示:

爬取id为wrapper的div所包含的内容

3.1.1 在demo文件夹下新建文件spider.php,代码如下:

<?php  
require_once__DIR__ . '/../autoloader.php';  
usephpspider\core\phpspider;  
  
/* Do NOT delete this comment */  
/* 不要删除这段注释 */  
  
$configs = array(  
 'name' => '豆瓣',//定义当前爬虫名称  
 'log_show' => true, //显示日志调试信息  
 'input_encoding' => 'UTF-8',//输入编码  
  
//定义爬虫爬取哪些域名下的网页, 非域名下的url会被忽略以提高爬取速度  
'domains' => array(  
  'www.douban.com'  
 ),  
  
//定义爬虫的入口链接, 爬虫从这些链接开始爬取,同时这些链接也是监控爬虫所要监控的链接  
'scan_urls' => array(  
   'https://www.douban.com/photos/album/1616649448/'  
 ),  
  
//爬虫爬取数据导出  
'export' => array(   
   'type' => 'csv', //type:导出类型 csv、sql、db  
   'file' => '../data/abc.csv', //file:导出 csv、sql 文件地址,如果不存在文件自动创建  
  ),  
  
  
//定义内容页的抽取规则  
'fields' => array(  
   array(  
    'name' => "wrapper",  
    'selector' => "//div[@id='wrapper']",  
     )  
   )  
);  
  
$spider = new phpspider($configs);  
$spider->start();

3.1.2 在demo文件夹中直接打开cmd命令面板,输入命令行 php -f spider.php 回车,代码跑起来,如图所示:

3.1.3 查看爬取下来的数据

在phpspider文件结构中找到data文件夹下的abc.csv文件,打开文件可看到爬取下来的数据,如图所示:

3.2 可视化操作(在浏览器下运行爬虫文件)

要爬取的对象链接:
https://movie.douban.com/subj...

要爬取的内容如图所示:

爬取class为nav-items的div所包含的内容

3.2.1 在demo文件夹下新建另外一个文件test.php,代码如下:

<?php  
  
header("Content-Type: text/html;charset=utf-8");  
date_default_timezone_set("Asia/Shanghai");  
ini_set("memory_limit", "10240M");  
  
require_once__DIR__ . '/../autoloader.php';  
usephpspider\core\phpspider;  
usephpspider\core\requests;  
usephpspider\core\selector;  
  
/* Do NOT delete this comment */  
/* 不要删除这段注释 */  
  
$html = requests::get('https://movie.douban.com/subject/26588308/?from=showing');  
$data = selector::select($html, "//div[@class='nav-items']");  
echo $data;

3.2.2 打开浏览器输入文件地址

结语

以上只是简单的爬虫例子,还可以进行多进程爬取,代理爬虫,很多好玩的,更多操作参考官方文档
https://doc.phpspider.org/

最后

觉得文章不错的,给我点个赞哇,关注一下呗!
技术交流可关注微信公众号【GitWeb】,加我好友一起探讨
微信交流群:加好友(备注思否)邀你入群,抱团学习共进步

查看原文

赞 41 收藏 4 评论 2

田维常 赞了文章 · 1月14日

swiper.js简单快速实现轮播滑动(兼容PC端、移动端)

swiper是一款免费以及轻量级轮播滑动的js框架,适用于PC端跟移动端,官方地址:(https://www.swiper.com.cn/)

效果演示:

PC端
PC端

移动端
移动端(在浏览器将设备切换为手机,这里切换为iphone),swiper支持移动端触控左右滑动

代码部分(复制粘贴可直接使用):

1 <!DOCTYPE html>  
2 <html>  
3 <head>  
4 <title>Swiper.js简单快速实现轮播滑动(兼容PC端、移动端)</title>  
5 <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0,minimum-scale=1.0,user-scalable=0" />  
6   
7 <!-- 下面加载Swiper的css跟js文件。可以将文件下载到项目中加载,也可以通过cdn远程加载,这里使用的是cdn远程加载 -->  
8 <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/Swiper/4.4.2/css/swiper.css"><!-- 加载Swiper的css文件 -->  
9 <script data-original="https://cdnjs.cloudflare.com/ajax/libs/Swiper/4.4.2/js/swiper.js"></script><!-- 加载Swiper的js文件 -->  
10 </head>  
11 <body>  
12 <div style="height: 200px;" class="swiper-container">  
13 <div class="swiper-wrapper">  
14 <!-- 下面是4个不同颜色的轮播滑块 -->  
15 <div style="background-color: red;" class="swiper-slide">slider1</div>  
16 <div style="background-color: green;" class="swiper-slide">slider2</div>  
17 <div style="background-color: yellow;" class="swiper-slide">slider3</div>  
18 <div style="background-color: blue;" class="swiper-slide">slider4</div>  
19 </div>  
20 <div class="swiper-button-prev"></div><!--左箭头-->  
21 <div class="swiper-button-next"></div><!--右箭头-->  
22 <div class="swiper-pagination"></div><!--分页器(就是4个点点)-->  
23 </div>  
24 </body>  
25 </html>  
26 <script>  
27 var mySwiper = new Swiper('.swiper-container', {//初始化Swiper  
28     autoplay: {//自动切换  
29        delay: 3000,  
30        stopOnLastSlide: false,  
31        disableOnInteraction: false,  
32      },  
33     navigation: {//前进后退  
34        nextEl: '.swiper-button-next',  
35        prevEl: '.swiper-button-prev',  
36      },  
37     pagination: {//分页器  
38        el: '.swiper-pagination',  
39        clickable :true,  
40     },  
41     loop : true,//循环  
42 })  
43 </script>

使用方法:

1.首先需要在head头部加载插件,需要用到的文件有swiper.css和swiper.js文件。可下载swiper文件或使用CDN远程加载
<head>  
<!-- 这里使用的是cdn远程加载Swiper4的版本 -->  
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/Swiper/4.4.2/css/swiper.css">  
<script data-original="https://cdnjs.cloudflare.com/ajax/libs/Swiper/4.4.2/js/swiper.js"></script>  
</head>

swiper4文件下载地址:
https://www.swiper.com.cn/dow...
swiper4远程CDN加载地址:
https://www.swiper.com.cn/cdn...

2.HTML内容
<div class="swiper-container">  
    <div class="swiper-wrapper">  
        <div class="swiper-slide">slider1</div>  
        <div class="swiper-slide">slider2</div>  
        <div class="swiper-slide">slider3</div>  
        <div class="swiper-slide">slider4</div>  
    </div>  
</div>

上面的HTML内容是固定模板,如果需要添加左右箭头或者分页器等其他组件可以参考swiper4的官方文档:https://www.swiper.com.cn/api...

3.初始化Swiper
<script>  
var mySwiper = new Swiper('.swiper-container', {  
autoplay:true,//等同于以下设置  
/*autoplay: {  
        delay: 3000,  
        stopOnLastSlide: false,  
        disableOnInteraction: true,  
        },*/  
    });  
</script>

在js中如果需要添加其他配置选项可以参考swiper4的官方文档:
https://www.swiper.com.cn/api...

结语:

在实际的web开发中,总是需要兼容PC跟移动端,如果使用原生js自己写的话,不仅耗时,可能兼容性、稳定性也不好,特别是移动端的touch触控事件。使用swiper便不用考虑到兼容这些事,只要熟悉它的API,便可快速实现手机,电脑网页大部分滑动,焦点图、tab、触摸导航等功能

最后

觉得文章不错的,给我点个赞哇,关注一下呗!
技术交流可关注微信公众号【GitWeb】,加我好友一起探讨

查看原文

赞 47 收藏 4 评论 2

田维常 赞了文章 · 1月14日

jquery实现tab菜单切换内容(精简版)

效果预览:

3.gif

完整代码:

1 <!DOCTYPE html>  
2 <html>  
3 <head>  
4 <title>jquery实现tab菜单切换内容(精简版)</title>  
5 <script data-original="https://cdn.staticfile.org/jquery/1.10.2/jquery.min.js"></script>  
6 </head>  
7 <body>  
8 <!-- 这是菜单 -->  
9 <div>  
10 <span style="background-color: red;cursor: pointer;" list="0" onclick="tab(this)" >我是A</span>  
11 <span style="background-color: blue;cursor: pointer;" list="1" onclick="tab(this)" >我是B</span>  
12 <span style="background-color: orange;cursor: pointer;" list="2" onclick="tab(this)" >我是C</span>  
13 <span style="background-color: green;cursor: pointer;" list="3" onclick="tab(this)" >我是D</span>  
14 </div>  
15 <!-- 这是菜单对应的内容 -->  
16 <div class="content">  
17 <div style="background-color: red" onclick="cont(this)">我是A的内容</div>  
18 <div style="background-color: blue;display: none"   onclick="cont(this)" >我是B的内容</div>  
19 <div style="background-color: orange;display: none" onclick="cont(this)" >我是C的内容</div>  
20 <div style="background-color: green;display: none"  onclick="cont(this)" >我是D的内容</div>  
21 </div>  
22   
23 </body>  
24 </html>  
25 <script type="text/javascript">  
26   
27     //点击菜单执行函数  
28     function tab(param) {  
29         var sp_an=$(param).attr('list');//获取被点击菜单的list属性值(0,1,2,3)  
30         $('.content').children('div').eq(sp_an).click();//点击菜单后,对应的内容被点击,从而实现展示  
31         //使用click()方法模拟点击事件,触发下面的cont函数  
32       }  
33   
34     //这个函数的触发是通过点击菜单的时候触发的  
35     function cont(param){  
36         $(param).show();//被选中的内容显示  
37         $(param).siblings().hide();//没有被选中的内容隐藏  
38     }  
39   
40 </script>

重点总结:

  1. span标签中list属性值(0,1,2,3)用来与四个div内容一一对应
  2. siblings()。在cont函数中使用siblings()方法来获取除了被选中元素的其他兄弟元素。siblings()是jquery的方法

最后

觉得文章不错的,给我点个赞哇,关注一下呗!
技术交流可关注微信公众号【GitWeb】,加我好友一起探讨

logo.png

查看原文

赞 59 收藏 2 评论 2

田维常 发布了文章 · 1月13日

三分钟快速掌握 maven插件

图片

老铁昨天下午问我什么时候讲讲Maven插件:

图片

于是老田搞到大半夜终于写了一篇maven的插件,今天分享给大家。

想进一步详聊了请加我微信tj20120622,进群和大家一起聊技术。

Maven 是一个执行插件的框架,每一个任务实际上是由插件完成的。那么我们今天就来聊聊Maven插件。

图片

什么是Maven插件?

Maven 实际上只是Maven插件集合的核心框架。换句话说,插件是执行大部分实际操作的地方。

插件用于:

  • 创建jar文件,
  • 创建war文件,
  • 编译代码,
  • 单元测试代码,
  • 创建项目文档等。

插件是Maven的核心功能,它允许在多个项目中重用通用的构建逻辑。他们通过在项目描述(项目对象模型(POM))的上下文中执行“操作”(即创建WAR文件或编译单元测试)来实现此目的。可以通过一组唯一的参数来自定义插件的行为,这些参数通过每个插件目标(或Mojo)的描述公开。

一个插件通常提供了一组目标,可使用以下语法来执行:

`mvn [plugin-name]:[goal-name]
`

例如:一个 Java 项目可以使用 Maven 编译器插件来编译目标,通过运行以下命令编译

`mvn compiler:compile
`

插件有哪些类型

Maven 提供以下两种类型插件:

以下是一些常见的插件列表:

例如

我们使用 maven-antrun-plugin 插件在例子中来在控制台打印数据。现在在 C:MVNproject 文件夹 创建一个 pom.xml 文件,内容如下:

`<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/m...d">
<modelVersion>4.0.0</modelVersion>
<groupId>com.companyname.projectgroup</groupId>
<artifactId>project</artifactId>
<version>1.0</version>
<build>
<plugins>
   <plugin>
   <groupId>org.apache.maven.plugins</groupId>
   <artifactId>maven-antrun-plugin</artifactId>
   <version>1.1</version>
   <executions>
      <execution>
         <id>id.clean</id>
         <phase>clean</phase>
         <goals>
            <goal>run</goal>
         </goals>
         <configuration>
            <tasks>
               <echo>clean phase</echo>
            </tasks>
         </configuration>
      </execution>     
   </executions>
   </plugin>
</plugins>
</build>
</project>
`

接下来,打开命令终端跳转到 pom.xml 所在的目录,并执行下面的 mvn 命令。

`mvn clean
`

Maven 将开始处理并显示 clean 生命周期的 clean 阶段。

`[INFO] Scanning for projects...
[INFO] ------------------------------------------------------------------
[INFO] Building Unnamed - com.companyname.projectgroup:project:jar:1.0
[INFO]    task-segment: [post-clean]
[INFO] ------------------------------------------------------------------
[INFO] [clean:clean {execution: default-clean}]
[INFO] [antrun:run {execution: id.clean}]
[INFO] Executing tasks [echo] clean phase
[INFO] Executed tasks
[INFO] ------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------
[INFO] Total time: < 1 second
[INFO] Finished at: Sat Jul 07 13:38:59 IST 2020
[INFO] Final Memory: 4M/44M
[INFO] --------
`

上面的例子展示了以下关键概念:

  • 插件是在 pom.xml 中使用 plugins 元素定义的。
  • 每个插件可以有多个目标。
  • 你可以定义阶段,插件会使用它的 phase 元素开始处理。我们已经使用了 clean 阶段。
  • 你可以通过绑定到插件的目标的方式来配置要执行的任务。我们已经绑定了 echo 任务到 maven-antrun-plugin 的 run 目标。
  • 就是这样,Maven 将处理剩下的事情。它将下载本地仓库中获取不到的插件,并开始处理。

插件与目标

一个插件通常可以完成多个任务,每一个任务就叫做插件的一个目标。如执行mvn install命令时,调用的插件和执行的插件目标如下 :

将插件绑定到生命周期

Maven的生命周期是抽象的,实际需要插件来完成任务,这一过程是通过将插件的目标(goal)绑定到生命周期的具体阶段(phase)来完成的。如:将maven-compiler-plugin插件的compile目标绑定到default生命周期的compile阶段,完成项目的源代码编译:

图片

内置绑定

Maven对一些生命周期的阶段(phase)默认绑定了插件目标,因为不同的项目有jar、war、pom等不同的打包方式,因此对应的有不同的绑定关系,其中针对default生命周期的jar包打包方式的绑定关系如下:

图片

在第二列中,冒号前面是插件的前缀(prefix),是配置和使用插件的一种简化方式;冒号后面即是绑定的插件目标。

你的仓库中有哪些maven插件?

图片

`存放目录=%本地仓库%orgapachemavenplugins
`

Maven官网上有更详细的官方插件列表:

自定义插件

在前面我们提到了一个Mojo,Mojo实际上是一个Maven的目标,插件包含任意数量的目标(Mojos)。Mojos可以定义为带注释的java类或Beanshell脚本。Mojo指定有关目标的元数据:目标名称,它适合生命周期的哪个阶段,以及它期望的参数。

Mojo术语是在maven2中引入,它是对如何编写插件的完整重写。Mojo是对Pojo(plain-old-java-object)的一种改进,它将maven替换为plain。

一个 Mojo 包含一个简单的 Java 类。插件中多个类似 Mojo 的通用之处可以使用抽象父类来封装。Maven插件项目的打包方式packaging必须为maven-plugin。

实现自定义插件

创建maven项目,添加依赖:

`<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/maven...d">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.tian.maven</groupId>
    <artifactId>my-maven-plugin</artifactId>
    <packaging>maven-plugin</packaging>
    <version>1.0-SNAPSHOT</version>
    <name>my-maven-plugin</name>
    <url>http://maven.apache.org</url>
    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>3.8.1</version>
            <scope>test</scope>
        </dependency>
        <!--api依赖-->
        <dependency>
            <groupId>org.apache.maven</groupId>
            <artifactId>maven-plugin-api</artifactId>
            <version>3.0</version>
        </dependency>
        <!--注解支持-->
        <dependency>
            <groupId>org.apache.maven.plugin-tools</groupId>
            <artifactId>maven-plugin-annotations</artifactId>
            <version>3.4</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>
</project>
`

TianMojo继承了 AbstractMojo 这个抽象类,并实现了 execute() 方法,该方法就是用来定义这个 Mojo 具体操作内容,我们只需要根据自己的需要来编写自己的实现即可。

`//自定义插件类
//name就是后面使用该插件的时候excuation里面的
@Mojo(name = "tian")
public class TianMojo extends AbstractMojo
{
    // 配置的是本maven插件的配置,在pom使用configration标签进行配置 property就是名字,
    // 在配置里面的标签名字。在调用该插件的时候会看到,还可以设置默认值
    @Parameter(property = "userName",defaultValue = "田哥你好")
    private String userName;
    @Parameter(property = "pwd",defaultValue = "000000")
    private String pwd;
    @Override
    public void execute() throws MojoExecutionException, MojoFailureException {
        System.out.println("userm=" + userName + " pwd=" + pwd);
        System.out.println("my plugin is running");
    }
}
`

然后在执行mvn clean install命令。

使用自定义插件

在我们的maven项目添加我们自定义的插件:

`<build>
    <plugins>
      <plugin>
        <groupId>com.tian.maven</groupId>
        <artifactId>my-maven-plugin</artifactId>
        <version>1.0-SNAPSHOT</version>
        <executions>
          <execution>
            <!-- 表示我们在执行mvn install时候就会执行我们自定义的插件tian-->  
            <phase>install</phase>
            <goals>
              <!-- 目标 -->
              <goal>tian</goal>
            </goals>
          </execution>
        </executions>
        <!-- 我们自定义的Mojo中定义的属性 -->
        <configuration>
          <userName>田维常</userName>
          <pwd>123456</pwd>
        </configuration>
      </plugin>
    </plugins>
</build>
`

然后就可以在我们的IDEA中看到:

图片

双击my:tian

图片

还可以使用命令的方式:mvn my:tian

图片

my是前缀,是my-maven的缩写。后缀tian就是插件绑定的目标。

到此,我们的自定义Maven插件就搞定了。

总结

Maven插件是Maven的核心功能,插件类型有构建类型和报告类型,插件可以有多个目标也就是可以理解为多个功能。自定义插件主要两步:依赖相关jar包和重写Mojo。自定义的插件的使用和我们用其他插件使用一样,只要在pom中配置相关<plugin>配置即可。

只有真正理解了插件实现原理后,才能慢慢去体会猜测我们平时使用的那些mvn...命令背后是如何实现的。

[ 学习是一个人对这个神奇世界不断认识的一个过程]

推荐阅读

掌握Mybatis动态映射,我可是下了功夫的

2020所有原创

写给大忙人看的JAVA核心技术.pdf下载

图解多线程

京东面试:说说MySQL的架构体系

查看原文

赞 0 收藏 0 评论 0

田维常 赞了文章 · 1月13日

使用FileZilla Server快速搭建本地FTP服务器

最近开发一套仅限公司内部员工使用的系统,因此需要在本地搭建一台FTP服务器进行项目代码文件传输。服务器主机是一台配置较好的办公电脑

Wind系统自带的FTP服务器是个坑

原本打算使用Wind系统的Internet信息服务配置FTP服务器,后面配置到怀疑人生,果断放弃。强烈不建议使用Wind系统的Internet信息服务配置FTP服务器,因为繁琐,又各种问题,容易出错

使用专门搭建FTP服务器软件

自己配置不行,那就使用现有的工具。于是找到FileZilla Server这个工具。FileZilla Server是一款免费开源的FTP服务器端架设程序,使用FileZilla Server你可以轻轻松松在你的服务器开设FTP

操作步骤(在服务器主机的那台电脑上操作)

一、下载Filezilla  Server

官方下载地址:https://filezilla-project.org/


注意:下载红色箭头指向的Filezilla  Server安装包,这个才是搭建FTP服务器。如果是要连接FTP服务器的话,下载FileZilla Client客户端

二、安装Filezilla  Server

Filezilla  Server的安装过程按照默认提示一路点击“next”到底

三、配置FTP服务器

3.1 打开Filezilla  Server(安装完成后会自动打开Filezilla  Server)


Host默认为localhost(或 127.0.0.1),即默认将本机作为FTP服务器。点击Connect连接



连接成功后(Logged on),日志面板会出现问题跟警告两行红字,这里的问题跟警告并不会对FTP服务器搭建以及连接FTP服务器有影响,因此不对其处理


3.2 设置用户名和密码(用户名为:test 密码为:123456)


点击Edit,选择Users



根据共色箭头顺序来操作


3.3 设置共享文件夹


到这里,使用FileZilla Server搭建FTP服务器就完成了


四、将FileZilla Server添加为防火墙允许的应用

由于防火墙的原因,FileZilla Server搭建FTP服务器完成后,需要将其添加为防火墙允许的应用,否则其他电脑没办法连接访问FTP服务器主机

具体操作如下:

在FTP服务器的主机中打开防火墙>允许的应用,点击允许其他应用按钮


在弹出的窗口,点击预览


在Filazilla server安装目录中,选择Filazilla server interface.exe跟Filezilla server.exe两个应用程序文件


点击添加,将选中的两个程序添加到允许的应用中


将添加的两个应用的通信专用跟公用都勾选,此时就可以通过另外一台电脑连接FTP服务器

连接FTP服务器(在另外一台电脑上操作)

一、下载Filezilla  Client客户端

官方下载地址:https://filezilla-project.org/


注意:此时下载的是红色箭头指向的FileZilla Client客户端

二、安装FileZilla Client客户端

FileZilla Client客户端的安装过程按照默认提示一路点击“next”到底

三、打开FileZilla Client客户端,新建站点

点击文件,选择站点管理器


按照红色箭头顺序操作

主机:填写FTP服务器主机的IP地址
加密:需要选择只使用普通FTP(不安全)
用户名跟密码:填写FTP服务器设置的用户名跟密码


成功连接后,列出FTP服务器共享的文件目录

此时就可以对FTP服务器进行文件的上传跟下载(两台电脑需要在同一网络下)

最后

觉得文章不错的,给我点个赞哇,关注一下呗!
技术交流可关注微信公众号【GitWeb】,加我好友一起探讨
微信交流群:加好友(备注思否)邀你入群,抱团学习共进步

查看原文

赞 35 收藏 2 评论 0

田维常 赞了文章 · 1月13日

微信小程序wx.request请求数据报错:不在以下 request 合法域名列表中

首先写一个后台的数据接口,地址是:http://localhost/weicms/index...

然后使用wx.request调用后台数据接口的地址

示例代码

1 wx.request({  
2   url: 'http://localhost/weicms/index.php?s =/addon/Cms/Cms/getList',  
3   data: {  
4     x: '',  
5     y: ''  
6   },  
7   header: {  
8     'content-type': 'application/json'// 默认值  
9   },  
10  success (res) {  
11     console.log(res.data)  
12    }  
13  })

运行代码,效果如下图:

1.jpg

从上图中看到页面一片空白,没有获取到数据,并且控制台报错(request 合法域名校验出错;http://localhost 不在以下 request 合法域名列表中)

为何出现这种错误?

打开wx.request网络请求的开发文档可以看到

2.jpg

上面截图中红色框就是问题所在(小程序服务器域名配置中是不能使用IP地址跟localhost),示例代码中wx.request请求的url地址包含localhost,因此出错。

但是一般开发过程中都要先在本地开发调试。如果没法使用ip地址跟localhos,本地开发调试过程中如何获取数据呢,有没有办法在本地开发调试的时候屏蔽这个错误呢?

答案是有的。开发文档中指出了可以跳过域名校验,如下图:

3.png

具体在哪里开启不检验域名的选项呢?在微信开发者工具中,点击详情后,选中不检验合法域名,如下图所示:

4.jpg

此时,再次运行代码后,效果如下图:

5.jpg

从上图看到数据已经成功获取到了,且控制也没有报错,只是提示:配置中关闭合法域名、web-view(业务域名)、TLS 版本以及 HTTPS 证书检查

查看原文

赞 70 收藏 2 评论 0

认证与成就

  • 获得 38 次点赞
  • 获得 2 枚徽章 获得 0 枚金徽章, 获得 0 枚银徽章, 获得 2 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2020-10-24
个人主页被 2.9k 人浏览