• [技术干货] JVM监控及诊断工具-转载
     命令行 jps 查看正在运行的Java进程 ps(Java Process Status):显示指定系统内所有的HotSpot虚拟机进程(查看虚拟机进程信息),可用于查询正在运行的虚拟机进程。  说明:对于本地虚拟机进程来说,进程的本地虚拟机ID与操作系统的进程ID是一致的,是唯一的。  基本使用语法为:jps [options] [hostid]  我们还可以通过追加参数,来打印额外的信息  options参数  -q:仅仅显示LVMID(local virtual machine id),即本地虚拟机唯一id。不显示主类的名称等 -l:输出应用程序主类的全类名 或 如果进程执行的是jar包,则输出jar完整路径 -m:输出虚拟机进程启动时传递给主类main()的参数 -v:列出虚拟机进程启动时的JVM参数。比如:-Xms20m -Xmx50m是启动程序指定的jvm参数。 说明:以上参数可以综合使用。  补充:如果某 Java 进程关闭了默认开启的UsePerfData参数(即使用参数-XX:-UsePerfData),那么jps命令(以及下面介绍的jstat)将无法探知该Java 进程。  hostid参数  RMI注册表中注册的主机名。如果想要远程监控主机上的 java 程序,需要安装 jstatd。  对于具有更严格的安全实践的网络场所而言,可能使用一个自定义的策略文件来显示对特定的可信主机或网络的访问,尽管这种技术容易受到IP地址欺诈攻击。  如果安全问题无法使用一个定制的策略文件来处理,那么最安全的操作是不运行jstatd服务器,而是在本地使用jstat和jps工具。   jstat:查看JVM统计信息 jstat(JVM Statistics Monitoring Tool):用于监视虚拟机各种运行状态信息的命令行工具。它可以显示本地或者远程虚拟机进程中的类装载、内存、垃圾收集、JIT编译等运行数据。在没有GUI图形界面,只提供了纯文本控制台环境的服务器上,它将是运行期定位虚拟机性能问题的首选工具。常用于检测垃圾回收问题以及内存泄漏问题。  基本使用语法为:jstat - [-t] [-h] [ []]  查看命令相关参数:jstat-h 或 jstat-help  其中vmid是进程id号,也就是jps之后看到的前面的号码  option参数  选项option可以由以下值构成。  类装载相关的:  -class:显示ClassLoader的相关信息:类的装载、卸载数量、总空间、类装载所消耗的时间等 垃圾回收相关的:  -gc:显示与GC相关的堆信息。包括Eden区、两个Survivor区、老年代、永久代等的容量、已用空间、GC时间合计等信息。 -gccapacity:显示内容与-gc基本相同,但输出主要关注Java堆各个区域使用到的最大、最小空间。 -gcutil:显示内容与-gc基本相同,但输出主要关注已使用空间占总空间的百分比。 -gccause:与-gcutil功能一样,但是会额外输出导致最后一次或当前正在发生的GC产生的原因。 -gcnew:显示新生代GC状况 -gcnewcapacity:显示内容与-gcnew基本相同,输出主要关注使用到的最大、最小空间 -geold:显示老年代GC状况 -gcoldcapacity:显示内容与-gcold基本相同,输出主要关注使用到的最大、最小空间 -gcpermcapacity:显示永久代使用到的最大、最小空间。 JIT相关的:  -compiler:显示JIT编译器编译过的方法、耗时等信息 -printcompilation:输出已经被JIT编译的方法  jinfo:实时查看和修改JVM配置参数 jinfo(Configuration Info for Java):查看虚拟机配置参数信息,也可用于调整虚拟机的配置参数。在很多情况卡,Java应用程序不会指定所有的Java虚拟机参数。而此时,开发人员可能不知道某一个具体的Java虚拟机参数的默认值。在这种情况下,可能需要通过查找文档获取某个参数的默认值。这个查找过程可能是非常艰难的。但有了jinfo工具,开发人员可以很方便地找到Java虚拟机参数的当前值  基本使用语法为:jinfo [options] pid  说明:java 进程ID必须要加上   jmap:导出内存映像文件&内存使用情况 map(JVM Memory Map):作用一方面是获取dump文件(堆转储快照文件,二进制文件),它还可以获取目标Java进程的内存相关信息,包括Java堆各区域的使用情况、堆中对象的统计信息、类加载信息等。开发人员可以在控制台中输入命令“jmap -help”查阅jmap工具的具体使用方式和一些标准选项配置。  基本使用语法为:  jmap [option] jmap [option] <executable jmap [option] [server_id@]  jhat:JDK自带堆分析工具 jhat(JVM Heap Analysis Tool):Sun JDK提供的jhat命令与jmap命令搭配使用,用于分析jmap生成的heap dump文件(堆转储快照)。jhat内置了一个微型的HTTP/HTML服务器,生成dump文件的分析结果后,用户可以在浏览器中查看分析结果(分析虚拟机转储快照信息)。  使用了jhat命令,就启动了一个http服务,端口是7000,即http://localhost:7000/,就可以在浏览器里分析。  说明:jhat命令在JDK9、JDK10中已经被删除,官方建议用VisualVM代替。  基本适用语法:jhat  jstack:打印JVM中线程快照 jstack(JVM Stack Trace):用于生成虚拟机指定进程当前时刻的线程快照(虚拟机堆栈跟踪)。线程快照就是当前虚拟机内指定进程的每一条线程正在执行的方法堆栈的集合。  生成线程快照的作用:可用于定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致的长时间等待等问题。这些都是导致线程长时间停顿的常见原因。当线程出现停顿时,就可以用jstack显示各个线程调用的堆栈情况。  在thread dump中,要留意下面几种状态  死锁,Deadlock(重点关注) 等待资源,Waiting on condition(重点关注) 等待获取监视器,Waiting on monitor entry(重点关注) 阻塞,Blocked(重点关注) 执行中,Runnable 暂停,Suspended 对象等待中,Object.wait() 或 TIMED_WAITING 停止,Parked  jcmd:多功能命令行 在JDK 1.7以后,新增了一个命令行工具jcmd。它是一个多功能的工具,可以用来实现前面除了jstat之外所有命令的功能。比如:用它来导出堆、内存使用、查看Java进程、导出线程信息、执行GC、JVM运行时间等。  cmd拥有jmap的大部分功能,并且在Oracle的官方网站上也推荐使用jcmd命令代jmap命令  jcmd 进程号 具体命令:显示指定进程的指令命令的数据  Thread.print 可以替换 jstack指令 GC.class_histogram 可以替换 jmap中的-histo操作 GC.heap_dump 可以替换 jmap中的-dump操作 GC.run 可以查看GC的执行情况 VM.uptime 可以查看程序的总执行时间,可以替换jstat指令中的-t操作 VM.system_properties 可以替换 jinfo -sysprops 进程id VM.flags 可以获取JVM的配置参数信息 GUI JDK自带的工具 jconsole:JDK自带的可视化监控工具。查看Java应用程序的运行概况、监控堆信息、永久区(或元空间)使用情况、类加载情况等 Visual VM:Visual VM是一个工具,它提供了一个可视界面,用于查看Java虚拟机上运行的基于Java技术的应用程序的详细信息。 JMC:Java Mission Control,内置Java Flight Recorder。能够以极低的性能开销收集Java虚拟机的性能数据。 第三方工具 MAT:MAT(Memory Analyzer Tool)是基于Eclipse的内存分析工具,是一个快速、功能丰富的Java heap分析工具,它可以帮助我们查找内存泄漏和减少内存消耗 JProfiler:商业软件,需要付费。功能强大。 Jconsole jconsole:从Java5开始,在JDK中自带的java监控和管理控制台。用于对JVM中内存、线程和类等的监控,是一个基于JMX(java management extensions)的GUI性能监控工具。   Visual VM Visual VM是一个功能强大的多合一故障诊断和性能监控的可视化工具。它集成了多个JDK命令行工具,使用Visual VM可用于显示虚拟机进程及进程的配置和环境信息(jps,jinfo),监视应用程序的CPU、GC、堆、方法区及线程的信息(jstat、jstack)等,甚至代替JConsole。在JDK 6 Update 7以后,Visual VM便作为JDK的一部分发布(VisualVM 在JDK/bin目录下)即:它完全免费。  主要功能:  1.生成/读取堆内存/线程快照 2.查看JVM参数和系统属性 3.查看运行中的虚拟机进程 4.程序资源的实时监控 5.JMX代理连接、远程环境监控、CPU分析和内存分析 官方地址:https://visualvm.github.io/index.html  Eclipse MAT MAT(Memory Analyzer Tool)工具是一款功能强大的Java堆内存分析器。可以用于查找内存泄漏以及查看内存消耗情况。MAT是基于Eclipse开发的,不仅可以单独使用,还可以作为插件的形式嵌入在Eclipse中使用。是一款免费的性能分析工具,使用起来非常方便。  MAT可以分析heap dump文件。在进行内存分析时,只要获得了反映当前设备内存映像的hprof文件,通过MAT打开就可以直观地看到当前的内存信息。一般说来,这些内存信息包含:  所有的对象信息,包括对象实例、成员变量、存储于栈中的基本类型值和存储于堆中的其他对象的引用值。 所有的类信息,包括classloader、类名称、父类、静态变量等 GCRoot到所有的这些对象的引用路径 线程信息,包括线程的调用栈及此线程的线程局部变量(TLS) MAT 不是一个万能工具,它并不能处理所有类型的堆存储文件。但是比较主流的厂家和格式,例如Sun,HP,SAP 所采用的 HPROF 二进制堆存储文件,以及 IBM的 PHD 堆存储文件等都能被很好的解析。  最吸引人的还是能够快速为开发人员生成内存泄漏报表,方便定位问题和分析问题。虽然MAT有如此强大的功能,但是内存分析也没有简单到一键完成的程度,很多内存问题还是需要我们从MAT展现给我们的信息当中通过经验和直觉来判断才能发现  官方地址: https://www.eclipse.org/mat/downloads.php  JProfiler 在运行Java的时候有时候想测试运行时占用内存情况,这时候就需要使用测试工具查看了。在eclipse里面有 Eclipse Memory Analyzer tool(MAT)插件可以测试,而在IDEA中也有这么一个插件,就是JProfiler。JProfiler 是由 ej-technologies 公司开发的一款 Java 应用性能诊断工具。功能强大,但是收费。  特点:  使用方便、界面操作友好(简单且强大) 对被分析的应用影响小(提供模板) CPU,Thread,Memory分析功能尤其强大 支持对jdbc,noSql,jsp,servlet,socket等进行分析 支持多种模式(离线,在线)的分析 支持监控本地、远程的JVM 跨平台,拥有多种操作系统的安装版本 主要功能:  1-方法调用:对方法调用的分析可以帮助您了解应用程序正在做什么,并找到提高其性能的方法 2-内存分配:通过分析堆上对象、引用链和垃圾收集能帮您修复内存泄露问题,优化内存使用 3-线程和锁:JProfiler提供多种针对线程和锁的分析视图助您发现多线程问题 4-高级子系统:许多性能问题都发生在更高的语义级别上。例如,对于JDBC调用,您可能希望找出执行最慢的SQL语句。JProfiler支持对这些子系统进行集成分析 官网地址:https://www.ej-technologies.com/products/jprofiler/overview.html  Arthas 上述工具都必须在服务端项目进程中配置相关的监控参数,然后工具通过远程连接到项目进程,获取相关的数据。这样就会带来一些不便,比如线上环境的网络是隔离的,本地的监控工具根本连不上线上环境。并且类似于Jprofiler这样的商业工具,是需要付费的。  那么有没有一款工具不需要远程连接,也不需要配置监控参数,同时也提供了丰富的性能监控数据呢?  阿里巴巴开源的性能分析神器Arthas应运而生。  Arthas是Alibaba开源的Java诊断工具,深受开发者喜爱。在线排查问题,无需重启;动态跟踪Java代码;实时监控JVM状态。Arthas 支持JDK 6+,支持Linux/Mac/Windows,采用命令行交互模式,同时提供丰富的 Tab 自动补全功能,进一步方便进行问题的定位和诊断。当你遇到以下类似问题而束手无策时,Arthas可以帮助你解决:  这个类从哪个 jar 包加载的?为什么会报各种类相关的 Exception? 我改的代码为什么没有执行到?难道是我没 commit?分支搞错了? 遇到问题无法在线上 debug,难道只能通过加日志再重新发布吗? 线上遇到某个用户的数据处理有问题,但线上同样无法 debug,线下无法重现! 是否有一个全局视角来查看系统的运行状况? 有什么办法可以监控到JVM的实时运行状态? 怎么快速定位应用的热点,生成火焰图? 官方地址:https://arthas.aliyun.com/doc/quick-start.html ———————————————— 版权声明:本文为CSDN博主「白马少年度春风」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/m0_74787523/article/details/128274470 
  • [技术干货] Java实现五子棋对战小游戏【完整版】-转载
    效果展示 ​一、游戏界面 ​二、悔棋与重开功能 ​项目介绍 五子棋是一种两人对弈的纯策略型棋类游戏,亦称串珠连,五子是中国民间非常熟知的一个古老棋种,它因操作简单、逻辑性强,深受喜爱,本项目基于Java技术,开发了一个操作简单、界面简洁、功能较完整的“五子棋”游戏。通过本游戏的开发,达到初学者学习和熟悉软件开发流程的目的。  总体需求 本程序主要完成五子棋游戏的简单操作,用户通过鼠标完成游戏过程。需要满足以下几点要求:  1.实现五子棋简易窗口界面。 2.实现黑白棋轮流下棋功能。 3.实现自动判断获胜方功能。 4.实现悔棋与重开游戏功能。  实现过程 1.绘制窗体对象。  2.UI设计(包括游戏区域、黑白棋子、按钮和标题区域)。  3.使用鼠标监听事件实现下棋。  4.连成五子判定获胜的实现。  代码展示 主框架 在主框架里有面板对象和两个按钮对象,分别是重新开始游戏按钮,悔棋按钮。还设置一个按钮事件类,用来监听两个按钮,并作出相应的动作,代码如下:  import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import javax.swing.*; public class ChessJFrame extends JFrame {     private ChessBord chessbord;//声明一个棋盘对象     private Panel tool;            //声明一个面板对象     private Button StartButton;//声明开始按钮     private Button BackButton;//声明悔棋按钮     private Button exitButton;//声明退出按钮     public ChessJFrame() {//构造函数         setTitle("单机版五子棋");//设置标题         MyButtonLister mb=new MyButtonLister();//按钮事件处理对象         tool=new Panel();//面板对象         chessbord=new ChessBord();//棋盘对象         StartButton=new Button("Reopen");//设置开始按钮         BackButton=new Button("Regret");//设置悔棋按钮         //exitButton=new Button("EXIT");//设置退出游戏按钮         tool.setLayout(new FlowLayout(FlowLayout.CENTER ));//流式布局         tool.add(StartButton);         tool.add(BackButton);        // tool.add(exitButton);//将三个按钮添加到面板对象         StartButton.addActionListener(mb);         BackButton.addActionListener(mb);        // exitButton.addActionListener(mb);//将三个按钮添加到事件监听         add(tool,BorderLayout.SOUTH);//按钮所在的位置         add(chessbord);//添加棋盘对象         setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//设置关闭         pack();//自适应     }     private class MyButtonLister implements ActionListener{         //按钮处理事件类         @Override         public void actionPerformed(ActionEvent e) {             // TODO Auto-generated method stub             Object obj=e.getSource();//获取事件源             if(obj==StartButton) {//事件源是重新开始按钮                 System.out.println("重新开始");                 chessbord.restartGame();             }             else if(obj==BackButton) {//事件源是悔棋按钮                 System.out.println("悔棋!");                 chessbord.goback();             }             else if(obj==exitButton) {//事件源是退出按钮                 System.exit(0);             }         }     }     public static void main(String[] args) {         ChessJFrame jf=new ChessJFrame();//声明框架对象         jf.setLocationRelativeTo(null);//居中显示         jf.setVisible(true);//设置为可见     } }  棋子类 棋子类里有棋子的坐标,半径和颜色;还有棋子的构造函数。代码如下:  import java.awt.Color; public class Chess {     private int x;//棋子的x坐标索引     private int y;//棋子的y坐标索引     private Color color;//棋子颜色     public static   int DIAMETER=30;//直径     public Chess(int x,int y,Color color){//棋子构造函数         this.x=x;         this.y=y;         this.color=color;     }     public int getX() {         return x;     }     public int getY() {         return y;     }     public Color getColor() {         return color;     }      }  棋盘类 我们要对棋盘的边距,网格的距离进行赋值,设定棋盘的初始状态,如默认黑子先下,定义一个棋子类数组,来保存棋子对象,再定义一个字符串二维数组函数来保存颜色,用来判断输赢,用匿名内部类来处理当棋子在面板上移动时的一些状态。  再对下棋时鼠标按下时的动作事件类进行解析,当在网格上下棋时,判断是否在棋盘内和判断网格上有没有棋子,当棋子在棋盘内并且网格上没有棋子,我们就开始画棋子,将棋子对象和颜色保存,接下来判断是否胜利,判断棋盘是否下满,如果没有继续下棋。  使用函数paintComponent来对棋盘和棋子进行画图,对棋子进行绘图时,将最后一个棋子设为红色。  最后就是判断棋子输赢的函数win,用字符串数组保存的棋子的颜色来进行判断所在行和列有没有五个棋子相连,判断撇和捺行的棋子有没有五个相连的,如果有则某位棋子胜利。代码如下:  import java.awt.*; import java.awt.event.*; import javax.swing.*;   public class ChessBord extends JPanel implements MouseListener{//继承面板类和鼠标事件接口     public static int MARGIN=30;//定义边距     public static int ROWS=15;//定义行数     public static int COLS=15;//定义列数     public static int GRID_SPAN=35;//网格间距     Chess[] chessList=new Chess[(ROWS+1)*(COLS+1)];//定义一个棋子数组     String[][] board=new String[MARGIN*2+GRID_SPAN*COLS][MARGIN*2+GRID_SPAN*COLS];//声明一个字符串数组,用来判断输赢     int chessCount;//棋子数目     int xindex,yindex;//棋子的坐标索引     boolean start=true;//开始默认黑子先下     boolean GameOver=false;//定义是否游戏结束     public ChessBord() {//棋盘类构造函数         setBackground(Color.LIGHT_GRAY);//设置背景颜色         addMouseListener(this);//将棋盘类添加到鼠标事件监听器         addMouseMotionListener(new MouseMotionListener() {//匿名内部类             @Override             public void mouseMoved(MouseEvent e) {//根据鼠标的移动所在的坐标来设置鼠标光标形状                 int x1=(e.getX()-MARGIN+GRID_SPAN/2)/GRID_SPAN;//对鼠标光标的x坐标进行转换                 int y1=(e.getY()-MARGIN+GRID_SPAN/2)/GRID_SPAN;//对鼠标光标的y坐标进行转换                 if(x1<0||x1>ROWS||y1<0||y1>COLS||GameOver||findchess(x1, y1)) {                     setCursor(new Cursor(Cursor.DEFAULT_CURSOR));//设置鼠标光标为默认形状                 }else {                     setCursor(new Cursor(Cursor.HAND_CURSOR));//设置鼠标光标为手型                 }             }             @Override             public void mouseDragged(MouseEvent e) {             }         });         for(int i=0;i<MARGIN*2+GRID_SPAN*COLS;i++) {//对board[][]赋初值             for (int j = 0; j < MARGIN*2+GRID_SPAN*COLS; j++) {                 board[i][j]="0";             }         }     }     @Override     public void mouseClicked(MouseEvent e) {         // TODO Auto-generated method stub     }     @Override     public void mousePressed(MouseEvent e) {//鼠标点击事件         if(GameOver)//游戏结束,不能按             return ;         String colorName=start?"黑棋":"白棋";//判断是什么颜色的棋子         xindex=(e.getX()-MARGIN+GRID_SPAN/2)/GRID_SPAN;//得到棋子x坐标         yindex=(e.getY()-MARGIN+GRID_SPAN/2)/GRID_SPAN;//得到棋子y坐标         board[xindex][yindex]=colorName;//以棋子x坐标y坐标做索引将棋子的颜色添加到board中         if(xindex<0||xindex>ROWS||yindex<0||yindex>COLS) {//棋子在棋盘外不能下,             return ;         }else if(findchess( xindex, yindex)) {//所下位置已有棋子,不能下             return ;         }         Chess po=new Chess(xindex,yindex,start?Color.black:Color.WHITE);//对棋子对象进行初始化         chessList[chessCount++]=po;//将棋子对象添加到棋子数组中         repaint();//重画图型         if(win( xindex,yindex,start)) {//判断是否胜利             String msg=String.format("恭喜 %s赢了",colorName);             JOptionPane.showMessageDialog(this, msg);             //gameOver=true;             GameOver=true;         }else if(chessCount==(COLS+1)*(ROWS+1)) {//判断是否全部下满             String msg=String.format("恭喜 %s赢了",colorName);             JOptionPane.showMessageDialog(this, msg);             GameOver=true;         }         start=!start;//改变棋子先下棋状态     }     @Override     public void mouseReleased(MouseEvent e) {     }     @Override     public void mouseEntered(MouseEvent e) {     }     @Override     public void mouseExited(MouseEvent e) {     }     @Override     protected void paintComponent(Graphics g) {//画棋盘和棋子         super.paintComponent(g);         for(int i=0;i<=ROWS;i++) {//画横线             g.drawLine(MARGIN, MARGIN+i*GRID_SPAN, MARGIN+COLS*GRID_SPAN, MARGIN+i*GRID_SPAN);         }         for(int j=0;j<=COLS;j++) {//画竖线             g.drawLine(MARGIN+j*GRID_SPAN, MARGIN, MARGIN+j*GRID_SPAN, MARGIN+ROWS*GRID_SPAN);         }         for(int i=0;i<chessCount;i++) {//画棋子             int xpos=chessList[i].getX()*GRID_SPAN+MARGIN;//得到棋子x坐标             int ypos=chessList[i].getY()*GRID_SPAN+MARGIN;//得到棋子y坐标             g.setColor(chessList[i].getColor());//设置棋子颜色             g.fillOval(xpos-Chess.DIAMETER/2, ypos-Chess.DIAMETER/2, Chess.DIAMETER, Chess.DIAMETER);//画棋子             if(i==chessCount-1){                 g.setColor(Color.red);//标记最后一个棋子为红色                 g.drawRect(xpos-Chess.DIAMETER/2, ypos-Chess.DIAMETER/2, Chess.DIAMETER, Chess.DIAMETER);             }         }     }     private    boolean findchess(int index,int yindex) {//查找所在位置是否有棋子         for (Chess c : chessList) {             if(c!=null&&c.getX()==xindex&&c.getY()==yindex)                 return true;         }         return false;     }     private    boolean win(int x,int y,boolean start) {//对棋子输赢的判断         String str=start?"黑棋":"白棋";         //棋子所在行和列是否有五子相连的情况         for(int i=0;i<16;i++){             if((board[x][i].equals(str)&&board[x][i+1].equals(str)&&board[x][i+2].equals(str)&&board[x][i+3].equals(str)&&board[x][i+4].equals(str))||(board[i][y].equals(str)&&board[i+1][y].equals(str)&&board[i+2][y].equals(str)&&board[i+3][y].equals(str)&&board[i+4][y].equals(str)))                 return true;         }         //棋子所在撇行是否有五子相连的情况         if(x+y>=4&&x+y<=30){             int i=(x+y<=19)?x+y:x+y-20;             if(x+y<=19){                 for(int k=0;k<=i-4;k++){                     if(board[k][i-k].equals(str)&&board[k+1][i-k-1].equals(str)&&board[k+2][i-k-2].equals(str)&&board[k+3][i-k-3].equals(str)&&board[k+4][i-k-4].equals(str))                         return true;                 }             }else{                 for(int k=i;k<=15;k++){                     if(board[k][20-k].equals(str)&&board[k+1][20-k-1].equals(str)&&board[k+2][20-k-2].equals(str)&&board[k+3][20-k-3].equals(str)&&board[k+4][20-k-4].equals(str))                         return true;                 }             }         }         //棋子所在捺行是否有五子相连的情况         if(y-x<=15&&x-y<=15){             int i=(x<y)?y-x:x-y;             if(x<y){                 for(int k=0;k<=19-4-i;k++){                     if(board[k][i+k].equals(str)&&board[k+1][i+k+1].equals(str)&&board[k+2][i+k+2].equals(str)&&board[k+3][i+k+3].equals(str)&&board[k+4][i+k+4].equals(str))                         return true;                 }             }else{                 for(int k=i;k<=15;k++){                     if(board[k][i+k].equals(str)&&board[k+1][i+k+1].equals(str)&&board[k+2][i+k+2].equals(str)&&board[k+3][i+k+3].equals(str)&&board[k+4][i+k+4].equals(str))                         return true;                 }             }         }         return false;     }     public void goback() {//悔棋函数         if(chessCount==0) {             return ;         }         chessList[chessCount-1]=null;         chessCount--;         if(chessCount>0) {             xindex=chessList[chessCount-1].getX();             yindex=chessList[chessCount-1].getY();         }         start=!start;         repaint();     }     public void restartGame() {//重新开始函数         for(int i=0;i<chessList.length;i++)//设置为初始状态             chessList[i]=null;         for(int i=0;i<MARGIN*2+GRID_SPAN*COLS;i++) {             for (int j = 0; j < MARGIN*2+GRID_SPAN*COLS; j++) {                 board[i][j]="0";             }         }         start=true;         GameOver=false;         chessCount=0;         repaint();     }     public Dimension getPreferredSize(){//画矩形         return new Dimension(MARGIN*2+GRID_SPAN*COLS,MARGIN*2+GRID_SPAN*ROWS);     }   }  项目结构  ​本程序用于Java初学者学习,共封装三个类,分别是主框架ChessJFrame类、棋子Chess类、棋盘ChessBord类,结构简单,框架明了。  总结 本项目的设计思路与“五子棋”游戏一致,程序在界面设计与功能实现上还有许多不足,但该项目的实现对于Java编程新手实战能力的提升有很大作用,在编写该项目的过程中,对编程者算法的设计能力与面向对象编程的理解都有深刻的考验。  程序在一定程序上还有还有缺陷,仅用于初学者学习,欢迎各位指正。 ———————————————— 版权声明:本文为CSDN博主「陈橘又青」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/m0_63947499/article/details/125417438 
  • [技术干货] 智能生成并盘活API研发资产-转载
     一、基于代码智能生成API文档 众所周知,Java程序员在写完后端API接口后,都要和前端程序员进行接口调试,这个时候,就要手写API文档了,包括API名称、请求方式、入参、出参、一些注意事项等。 如果调试出问题了,需要调整代码,再调整API文档,苦不堪言。后来一个同事向我推荐了Eolink,可以直接基于代码生成API文档,这,太香了。  体验链接 ➢ ➢ ➢ https://www.eolink.com/   1、点击其他,选择API文档生成  2、点击添加来源,在弹窗中选择通过 Swagger URL 生成 API 文档,然后点击下一步:   配置完成后,界面会提示配置完成。此时您可以通过在当前页面页点击同步按钮,或者通过 Open API 触发同步操作。  3、从代码仓库中扫描 API 文档 目前支持GitHub、Gitlab、码云。 进入Eolink主页,点击左下角其他,点击API文档生成,点击添加来源,即可看到代码仓库。  4、以GitHub为例,配置项介绍 •来源名称 •代码注释方式(swagger、apiDoc) •代码仓库地址 •用户名:GitHub账户名称 •仓库名:GitHub Repository 仓库名称 •访问私钥:仓库私人令牌,可在Personal Access Tokens中生成 •需要扫描的分支 •需要扫描的API目录路径:API 层相关代码的存放路径 •需要扫描的数据结构目录路径 •目标语言:java、php •数据同步方式:目前可选增量更新、全量更新、仅添加新的API三种形式,推荐采用增量更新的方式。每次同步之后,系统都会自动生成API历史版本方便回滚文档,因此不用担心操作失误问题。   可以根据不同的需求,选择适合自己的方式,直接生成API文档,解放了我们的双手,爽歪歪。  二、基于API文档智能生成测试用例 体验链接 ➢ ➢ ➢ https://www.eolink.com/  通过Eolink的 自动生成测试用例 的功能,可以帮助测试人员根据预先定义的规则快速生成对应接口的测试用例,并可以在次基础上进行二次编辑调整,大大减少测试人员用例编写时间。   1、新建API 点击+API按钮,填写 API 的请求地址、请求参数、校验规则等信息,然后保存即可。   2、校验返回结果 测试用例支持对返回结果进行校验,以下是几种结果校验规则:    很多时候,测试需要经过一个漫长的过程,单元测试、前后端联调、系统测试、性能测试等等,一套API接口可能需要很多人、在不同的电脑上进行各种各样的测试,测试一次编写一次API路径、入参……麻烦至极!此时,Eolink又给大家提供了一个很便捷的功能,导出API文档、再到其它电脑导入API文档生成测试用例,让我们一起来体验一下吧。  3、导出API文档  4、导入api文档,生成测试用例  5、API文档导入成功,测试十分便捷  目前的项目基本都是前后端分离,后端为前端提供接口的同时,还需同时提供接口的说明文档。但我们的代码总是会根据实际情况来实时更新,这个时候有可能会忘记更新接口的说明文档,造成一些不必要的问题。 通过导出、导入功能,生成可视化 RESTful 风格的 Web 服务的接口文档,前后端工程师沟通起来更加便捷。  三、基于API文档智能生成请求代码和业务代码 1、单点测试功能齐全 •编辑 •查看、对比API的修改历史 •当API改变时通知相关成员 •对API文档发表评论并通知相关人员 •代码示例 •复制、删除   2、API请求代码支持多种语言  3、生成API请求代码  基于API文档智能生成请求代码和业务代码,可以查看API的修改历史,大大提高了代码的容错率和健壮性;还支持生成各种编程语言的代码示例,节省了大量的代码交接、讲解时间。  4、生成业务代码 登录私有云,点击API代码生成,根据需求,生成业务代码。    四、生成请求参数,前后置脚本,Mock规则等业务数据 1、快速生成随机参数值 点击构造器按钮,在弹窗中选择 动态生成数据,在下拉菜单中可以选择需要生成的随机值,每次请求的时候都可以动态生成不同的请求参数。   2、对参数进行快速加解密处理 例如需要对 user_password 字段值:123456 进行 MD5 加密。点击相应参数的 构造器 按钮:   在弹窗中选择需要处理的数据以及要进行的操作。选择 user_password 的参数值,并且选择 MD5 加密操作,然后点击替换即可:   构造完成之后会得到 user_password 经过 MD5 加密之后的结果:   3、编辑前置脚本 前置脚本都包括什么呢?  体验链接 ➢ ➢ ➢ https://www.eolink.com/    4、编辑后置脚本 后置脚本都包括什么呢?    5、编辑 Mock API 期望 在编写API文档返回结果时,若参数字段和类型匹配智能Mock规则,系统则会自动填入对应的Mock值。该功能提供无感的快速mock值配置,减轻mock规则配置的工作负担。 智能Mock的匹配规则分两种类型:内置规则和自定义规则。 内置规则是一套完整的预置智能Mock规则,用户不可编辑具体的匹配规则,仅可整体启用或停用。内置规则有助于用户快速使用智能Mock能力。 自定义规则是用户自定义自由配置的智能Mock匹配规则,用户可以根据个性化需求创建、编辑和删除自定义规则。支持使用通配符、正则表达式、完全匹配的方式来匹配字段名。  (1)进入空间级公共资源内的智能mock设置,可看到自定义规则模块和内置规则模块。   (2)在内置规则模块右上角,有开启/停用内置规则的切换按钮。该按钮默认开启,点击按钮则工作空间内停用内置智能Mock规则。   (3)点击页面标题下的保存按钮,变更方可生效。   添加/编辑自定义规则  进入空间级公共资源内的智能mock设置页。 在自定义规则模块中,点击底部最后一行中输入对应的数据即可添加新的自定义规则:  (4)字段规则说明  1.数据类型:匹配的字段数据类型,必填。 2.匹配方式:用于确认字段名称的匹配方式。默认为通配符,可选择通配符、正则表达式、完全匹配三种方式。 3.匹配规则:可输入字段名称匹配表达式,必填。根据所选匹配方式输入对应的名称匹配表达式。 4.匹配大小写:控制字段名称匹配时是否大小写敏感,默认为否。 5.Mock 规则:支持输入任意固定值或mockjs的占位符。当字段匹配智能Mock规则的数据类型和字段名称后,系统自动填入的mock值。 6.备注:用于标注说明该智能匹配规则,仅展示于智能mock设置页。 7.操作:当前仅提供删除操作。  完成自定义规则添加或编辑后,需点击页面标题下的保存按钮才会生效。否则刷新页面后当次编辑的数据会清空。  (5)删除自定义设置  进入空间级公共资源内的智能mock设置页。 点击对应自定义规则右侧的删除操作按钮,即可进行删除。 点击页面标题下的保存按钮,变更方可生效。  (6)规则优先级  智能Mock中,自定义规则的优先级大于内置规则。若某个字段同时满足自定义规则和内置规则的匹配条件,则该字段的mock规则优先填入自定义规则的mock内容。(即自定义规则>内置规则) 新创建的自定义规则优先级更高。若某个字段同时满足两个自定义规则的匹配条件,则该字段的mock规则优先填入较新创建的自定义规则的mock内容。 随着测试API的全面智能化,可以大大的提升测试阶段的工作效率;降低测试岗位的工作难度,进一步拓展职场人的能力边界,使得Java工程师也可以兼职测试工作,成就编程“多面手”。  五、总结 智能化测试是近年来最热门的软件测试话题,它已成为许多高级开发和部署实践的关键推动因素。本文通过简单的实例,介绍了基于代码智能生成API文档、基于API文档智能生成测试用例、基于API文档智能生成请求代码和业务代码、生成请求参数、前后置脚本、Mock规则等业务数据,智能化测试相比手动测试成本更低、效率更高、更准确的测试、使应用程序质量和性能更高,让我们一起开启智能化测试的新篇章。  体验链接 ➢ ➢ ➢ https://www.eolink.com/ ———————————————— 版权声明:本文为CSDN博主「哪 吒」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/guorui_java/article/details/128193662 
  • [技术干货] Java中泛型的深入理解-转载
     泛型深入 泛型基本介绍 泛型的概述:  泛型:是JDK5中引入的特性,可以在编译阶段约束操作的数据类型,并进行检查。  泛型的格式:<数据类型>; 注意:泛型只能支持引用数据类型。  集合体系的全部接口和实现类都是支持泛型的使用的。  泛型的好处:  统一数据类型。  把运行时期的问 题提前到了编译期间,避免了强制类型转换可能出现的异常,因为编译阶段类型就能确定下来。  泛型可以在很多地方进行定义:  定义在类后面 —> 泛型类  定义在方法上 —> 泛型方法  定义在接口后面 —> 泛型接口  如果需要存任意类型的数据, 我们可以不添加泛型或者规定泛型为Object(推荐使用泛型更加规范):  public static void main(String[] args) {     List<Object> list = new ArrayList<>();     list.add("aaa");     list.add(123);     list.add(12.34);     list.add(false); } 注意: 泛型必须是引用类型, 基础类型需要使用包装类  public static void main(String[] args) {     // 例如int类型需要使用Integer包装类     List<Integer> lists = new ArrayList<>();     lists.add(123); } 自定义泛型类 泛型类的概述:  定义类时同时定义了泛型的类就是泛型类。  泛型类的格式:修饰符 class 类名<泛型变量> { }  例如:public class MyArrayList<T> { }  此处泛型变量 T 可以随便写为任意标识,常见的如E、T、K、V等。  作用:编译阶段可以指定数据类型,类似于集合的作用。  用下面的案例来举例:  模拟ArrayList集合自定义一个集合MyArrayList集合, 模拟添加和删除功能的泛型设计。  定义一个泛型类 public class MyArrayList<E> {     public void add(E a) {         System.out.println(a);     }      public void remove(E b) {         System.out.println(b);     } } 测试使用泛型类 public static void main(String[] args) {     // 规定泛型     MyArrayList<String> list = new MyArrayList<>();     // 规定了泛型后, 只能使用规定的类型     list.add("aaa"); // aaa     list.remove("bbb"); // bbb } 泛型类的原理:  把出现泛型变量(E、T、K、V)的地方全部替换成传输的真实数据类型, 做到约束数据类型的作用。  自定义泛型方法 泛型方法的概述:  定义方法时同时定义了泛型的方法就是泛型方法。  泛型方法的格式:修饰符 <泛型变量> 方法返回值 方法名称(形参列表) {}  例如: public <T> void show(T t) {}  作用:让方法中可以使用泛型接收一切实际类型的参数,使方法更具备通用性。  用下面一个例子进行讲解:  给你任何一个类型的数组,都能返回它的内容。也就是实现Arrays.toString(数组)的功能!  定义一个接收任意类型数组的泛型方法 // 定义泛型方法 public static <T> void printArray(T[] arr) {     StringBuilder stb = new StringBuilder("[");     for (int i = 0; i < arr.length; i++) {         stb.append(arr[i]).append(i == arr.length - 1 ? "": ", ");     }     stb.append("]");     System.out.println(stb); } 测试: 使用泛型方法 public static void main(String[] args) {     Integer[] arr1 = {10, 20, 30};     printArray(arr1); // [10, 20, 30]      String[] arr2 = {"aaa", "bbb", "ccc"};     printArray(arr2); // [aaa, bbb, ccc] } 泛型方法的原理:  把出现泛型变量的地方全部替换成传输的真实数据类型。  自定义泛型接口 泛型接口的概述:  使用了泛型定义的接口就是泛型接口。  泛型接口的格式:修饰符 interface 接口名称<泛型变量>{}  例如: public interface Data<E>{}  作用:泛型接口可以让实现类选择当前功能需要操作的数据类型  我们还是通过一个例子讲解:  需求: 教务系统,提供一个接口可约束一定要完成数据(学生,老师)的增删改查操作  由于在接口中方法的类型定义为学生类型和老师类型都是不合适的, 所以需要使用泛型, 创建一个泛型接口 public interface Date<E> {     void add(E e);     void delete(E e);     void update(E e); } 再在创建实现类时, 传入真实的类型即可, 类似如下: class Teacher {  }  class TeacherData implements Date<Teacher> {     @Override     public void add(Teacher teacher) {      }      @Override     public void delete(Teacher teacher) {      }      @Override     public void update(Teacher teacher) {      } } 泛型接口的原理:  实现类可以在实现接口的时候传入自己操作的数据类型,这样重写的方法都将是针对于该类型的操作。  泛型通配符和上下限 通配符: ?  ? 可以在“使用泛型”的时候代表一切类型。  E T K V 是在定义泛型的时候使用的。  演示代码:  // 表示接收任意类型的列表 public static void go(ArrayList<?> list) {  } 泛型的上下限, 格式如下:  ? extends Car: 表示 ? 必须是Car或者其子类 泛型上限  // 传入的类型必须是Car或者Car的子类 public static void go(ArrayList<? extends Car> list) {  } 1 2 3 4 ? super Car : 表示 ? 必须是Car或者其父类 泛型下限(了解)  // 传入的类型必须是Car或者Car的父类 public static void go(ArrayList<? super Car> list) {  } ———————————————— 版权声明:本文为CSDN博主「学全栈的灌汤包」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/m0_71485750/article/details/127379466 
  • Spring(Bean 作用域和生命周期)-转载
     1. 案例1: Bean作用域的问题 现在有一个公共的 Bean,通过给 A 用户 和 B 用户使用, 然后在使用的过程中 A 偷偷的修改了公共 Bean 的数据, 导致 B 在使用时发生了预期之外的逻辑错误  (1) 公共 Bean:   [名称是 java]  @Component public class Users {     @Bean     public User user1() {         User user = new User();         user.setId(1);         user.setName("java");         return user;     } } (2) A 用户使用时,进行了修改操作   [名称是 悟空, 进行了修改操作]  @Controller public class BeanScopesController {       @Autowired     private User user1;       public User getUser1() {         User user = user1;         System.out.println("Bean 原 name: " + user.getName());         user.setName("悟空");         return user;     } } (3) B 用户再去使用公共 Bean 的时候  @Controller public class BeanScopesController2 {       @Autowired     private User user1;       public User getUser1() {         User user = user1;         return user;     } } (4) 打印 A 用户和 B 用户公共 Bean 的值  public class BeanScopesTest {     public static void main(String[] args) {         ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");           BeanScopesController beanScopesController = context.getBean(BeanScopesController.class);         System.out.println("A 对象修改之后 Name: " + beanScopesController.getUser1().toString());           BeanScopesController2 beanScopesController2 = context.getBean(BeanScopesController2.class);         System.out.println("B 对象读取到是 Name: " + beanScopesController2.getUser1().toString());     } } (5) 运行程序   (6) 原因分析   存在这个问题的原因就是 Bean 默认情况下是 单例模式 (singleton), 也就是所有类使用的都是同一个对象, 使用同一个对象,那就是没有过多的生命周期的创建和销毁,从而可以提高性能,所以在 Spring 中 Bean 的作用域也是 单例模式的  2. 作用域 限定程序中变量的可用范围叫做作用域.  Bean的作用域是指, Bean 在 Spring 整个框架中的某种行为,比如 单例模式的作用域, 意思就是 Bean 在整个 Spring 中只有一份, 它是全局共享的, 当有人修改了这个值后, 其他人读到的就是被修改的值  Spring 容器在初始化一个 Bean 的实例时,会同时指定该实例的作用域,Spring中有 6 种作用域  singleton: 单例模式 (默认,每次请求取的是同一个对象) prototype: 原型模式 (多例模式,每次请求创建一个新的对象) request: 请求作用域 (Spring MVC,每次请求创建一个对象) session: 会话作用域 (Spring MVC,在一个会话中共享一个对象) application: 全局作用域 (Spring MVC,所有人共用一个) websocket: HTTP WebSocket 作用域 (Spring WebSocket 项目中存在的一种) 3. 设置 Bean 的作用域 使用 @Scope 标签来声明 Bean 的作用域,  @Scope 标签既可以修饰方法,也可以修饰类, @Scope 有两种设置方式  直接设置值: @Scope("prototype") 类似于枚举的常量方式: @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) @Component public class Users {     @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)     @Bean     public User user1() {         User user = new User();         user.setId(1);         user.setName("java");         return user;     } } 还是 案例1中的代码,只不过加上 Bean的作用域 原型模式后,运行程序,可以看到A修改后,B没有受到影响   4. Spring 的执行流程 Spring 执行流程:  启动 Spring 容器 实例化 Bean (分配内存空间) 将 Bean 注册到 Spring 中 (存操作) 将 Bean 装配到需要的类中 (取操作)  5. Bean 的生命周期 Bean 的生命周期:  实例化 Bean (给 Bean 分配内存空间)          买房子 设置属性 (Bean 对象注入)                            装修 Bean 初始化                                                 买家电 执行各种通知 (执行各种 Aware) 执行初始化的前置方法 执行构造方法,两种执行方式,一种是执行 @PostConstruct,另一种是执行 init-method 执行初始化的后置方法      4. 使用 Bean                                                    入住       5. 销毁 Bean                                                    卖房  @PreDestroy (销毁前方法) 重写 DisposableBean 接口方法 destroy-method public class BeanLifeComponent implements BeanNameAware {     @PostConstruct     public void postConstruct() {         System.out.println("执行 PostConstruct()");     }     public void init() {         System.out.println("执行 init-method");     }     public void use() {         System.out.println("使用 bean");     }     @PreDestroy     public void preDestroy() {         System.out.println("执行: preDestroy()");     }     public void setBeanName(String s) {         System.out.println("执行了 Aware 通知");     } }  <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans"        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"        xmlns:content="http://www.springframework.org/schema/context"        xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">     <content:component-scan base-package="com.beans"></content:component-scan>     <beans>         <bean id="beanLifeComponent" class="com.beans.BeanLifeComponent" init-method="init"></bean>     </beans> </beans> public class BeanLifeTest {     public static void main(String[] args) {         ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring-config2.xml");         BeanLifeComponent lifeComponent = context.getBean(BeanLifeComponent.class);         lifeComponent.use();         //执行销毁方法         context.destroy();     } }  ———————————————— 版权声明:本文为CSDN博主「快到锅里来呀」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/m0_58761900/article/details/128091598 
  • [技术干货] 微服务Spring Boot 整合 Redis 实现 好友关注-转载
    一、Redis 实现好友关注 – 关注与取消关注需求:针对用户的操作,可以对用户进行关注和取消关注功能。在探店图文的详情页面中,可以关注发布笔记的作者具体实现思路:基于该表数据结构,实现2个接口关注和取关接口判断是否关注的接口关注是用户之间的关系,是博主与粉丝的关系,数据表如下:tb_followCREATE TABLE `tb_follow` (`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',`user_id` bigint(20) unsigned NOT NULL COMMENT '用户id',`follow_user_id` bigint(20) unsigned NOT NULL COMMENT '关联的用户id',`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',PRIMARY KEY (`id`) USING BTREE) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8mb4 ROW_FORMAT=COMPACT;id为自增长,简化开发核心代码FollowController//关注@PutMapping("/{id}/{isFollow}")public Result follow(@PathVariable("id") Long followUserId, @PathVariable("isFollow") Boolean isFollow) {return followService.follow(followUserId, isFollow);}//取消关注@GetMapping("/or/not/{id}")public Result isFollow(@PathVariable("id") Long followUserId) {return followService.isFollow(followUserId);}FollowService@Overridepublic Result follow(Long followUserId, Boolean isFollow) {// 1.获取登录用户Long userId = UserHolder.getUser().getId();String key = "follows:" + userId;// 1.判断到底是关注还是取关if (isFollow) {// 2.关注,新增数据Follow follow = new Follow();follow.setUserId(userId);follow.setFollowUserId(followUserId);boolean isSuccess = save(follow);if (isSuccess) {stringRedisTemplate.opsForSet().add(key, followUserId.toString());}} else {// 3.取关,删除 delete from tb_follow where user_id = ? and follow_user_id = ?boolean isSuccess = remove(new QueryWrapper().eq("user_id", userId).eq("follow_user_id", followUserId));if (isSuccess) {// 把关注用户的id从Redis集合中移除stringRedisTemplate.opsForSet().remove(key, followUserId.toString());}}return Result.ok();}@Overridepublic Result isFollow(Long followUserId) {// 1.获取登录用户Long userId = UserHolder.getUser().getId();// 2.查询是否关注 select count(*) from tb_follow where user_id = ? and follow_user_id = ?Integer count = query().eq("user_id", userId).eq("follow_user_id", followUserId).count();// 3.判断return Result.ok(count > 0);}代码编写完毕,进行测试代码测试点击进行关注用户关注成功取消关注测试成功二、Redis 实现好友关注 – 共同关注功能实现共同关注好友功能,首先,需要进入博主发布的指定笔记页,然后点击博主的头像去查看详细信息核心代码如下UserController// UserController 根据id查询用户@GetMapping("/{id}")public Result queryUserById(@PathVariable("id") Long userId){// 查询详情User user = userService.getById(userId);if (user == null) {return Result.ok();}UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);// 返回return Result.ok(userDTO);}BlogController@GetMapping("/of/user")public Result queryBlogByUserId(@RequestParam(value = "current", defaultValue = "1") Integer current,@RequestParam("id") Long id) {// 根据用户查询Page page = blogService.query().eq("user_id", id).page(new Page<>(current, SystemConstants.MAX_PAGE_SIZE));// 获取当前页数据List records = page.getRecords();return Result.ok(records);}那么如何实现共同好友关注功能呢?需求:利用Redis中的数据结构,实现共同关注功能。 在博主个人页面展示出当前用户与博主的共同关注思路分析: 使用Redis的Set集合实现,我们把两人关注的人分别放入到一个Set集合中,然后再通过API去查看两个Set集合中的交集数据改造核心代码:当用户关注某位用户后,需要将数据存入Redis集合中,方便后续进行共同关注的实现,同时取消关注时,需要删除Redis中的集合FlowServiceImpl@Overridepublic Result follow(Long followUserId, Boolean isFollow) {// 1.获取登录用户Long userId = UserHolder.getUser().getId();String key = "follows:" + userId;// 1.判断到底是关注还是取关if (isFollow) {// 2.关注,新增数据Follow follow = new Follow();follow.setUserId(userId);follow.setFollowUserId(followUserId);boolean isSuccess = save(follow);if (isSuccess) {stringRedisTemplate.opsForSet().add(key, followUserId.toString());}} else {// 3.取关,删除 delete from tb_follow where user_id = ? and follow_user_id = ?boolean isSuccess = remove(new QueryWrapper().eq("user_id", userId).eq("follow_user_id", followUserId));if (isSuccess) {// 把关注用户的id从Redis集合中移除stringRedisTemplate.opsForSet().remove(key, followUserId.toString());}}return Result.ok();}// 具体获取好友共同关注代码@Overridepublic Result followCommons(Long id) {// 1.获取当前用户Long userId = UserHolder.getUser().getId();String key = "follows:" + userId;// 2.求交集String key2 = "follows:" + id;Set intersect = stringRedisTemplate.opsForSet().intersect(key, key2);if (intersect == null || intersect.isEmpty()) {// 无交集return Result.ok(Collections.emptyList());}// 3.解析id集合List ids = intersect.stream().map(Long::valueOf).collect(Collectors.toList());// 4.查询用户List users = userService.listByIds(ids).stream().map(user -> BeanUtil.copyProperties(user, UserDTO.class)).collect(Collectors.toList());return Result.ok(users);}进行测试⛵小结以上就是【Bug 终结者】对 微服务Spring Boot 整合 Redis 实现 好友关注 的简单介绍,Redis 实现好友关注功能也是 利用Set集合、ZSet集合实现这样一个需求,同时,采用Redis来实现更加的快速,减少系统的消耗,更加快速的实现数据展示! 下篇博文我们继续 关注 如何 使用Redis 实现推送消息到粉丝收件箱以及滚动分页查询!————————————————版权声明:本文为CSDN博主「Bug 终结者」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。原文链接:https://blog.csdn.net/weixin_45526437/article/details/128276229
  • [技术干货] java 定时任务 quartz 时间表达式Cron总结
     Cron 表达式的介绍:  cron 的表达式是被用来配置 CronTrigger 实例的。  cron 的表达式是字符串,实际上是有七个子表达式,描述个别细节的时间表。  这些子表达式是由空格分开的,分别代表:  Seconds Minutes Hours Day-of-Month Month Day-of-Week Year (可选字段) 例如 “0 0 12 ? * WED” 表示在每星期三下午 12:00 执行,个别子表达式可以包含范围;  例如,在前面的例子里 “WED” 可以替换为 “MON-FRI”,”MON,WED,FRI” 甚至 “MON-WED,SAT”。”*” 代表整个时间段。  每一个字段都有一套可以指定有效值,如  Seconds (秒):可以用数字 0-59 表示  Minutes (分):可以用数字 0-59 表示  Hours (时):可以用数字 0-23 表示  Day-of-Month (天):可以用数字 1-31 中的任意一个值,但要注意一些特别的月份  Month (月):可以用 0-11 或用字符串 “JAN,FEB,MAR,APR,MAY,JUN,JUL,AUG,SEP,OCT,NOV and DEC” 表示  Day-of-Week (每周):可以用数字 1-7 表示(1=星期日)或用字符串 “SUN,MON,TUE,WED,THU,FRI and SAT” 表示  “*”:指定所有的值,比如,Minutes 设置为 *,表示每分钟  “/“:为特别单位,表示为 “每”,例如 “0/15” 表示每隔 15 分钟执行一次,”0” 表示为从 0 分开始,”3/20” 表示每隔 20 分钟执行一次,”3” 表示从第 3 分钟开始执行  “?”:表示每月的某一天,或每周的某一天  “L”:用于每月或每周,表示为每月的最后一天,或每个月的最后一个星期几如 “6L” 表示 “每月的最后一个星期五”  “W”:表示为最近的工作日,如 “15W” 放在每月(day-of-month)字段上表示为 “到本月 15 日最近的工作日”  “#”:是用来指定每月第 n 个工作日,例如在每周(day-of-week)这个字段中内容为 “6#3” or “FRI#3” 则表示 “每月第三个星期五”  Cron 表达式的格式:  秒 分 时 日 月 周 年 (可选)  只有日和周可以为 “?”,并且日和周不能同时为 “?”、”*”;否则会报无效错误;  字段名    允许的值    允许的特殊字符 秒    0-59    , - * / 分    0-59    , - * / 小时    0-23    , - * / 日    1-31    , - * ? / L W C 月    1-12 or JAN-DEC    , - * / 周几    1-7 or SUN-SAT    , - * ? / L C # 年 (可选字段)    empty, 1970-2099    , - * / java 项目 from fhadmin.cn  “?” 字符:表示不确定的值  “,” 字符:指定数个值  “-” 字符:指定一个值的范围  “/” 字符:指定一个值的增加幅度。n/m 表示从 n 开始,每次增加 m  “L” 字符:用在日表示一个月中的最后一天,用在周表示该月最后一个星期 X  “W” 字符:指定离给定日期最近的工作日 (周一到周五)  “#” 字符:表示该月第几个周 X。6#3 表示该月第 3 个周五  Cron 表达式的范例:  每隔 5 秒执行一次:*/5 * * * * ?  每隔 1 分钟执行一次:0 */1 * * * ?  每天 23 点执行一次:0 0 23 * * ?  每天凌晨 1 点执行一次:0 0 1 * * ?  每月 1 号凌晨 1 点执行一次:0 0 1 1 * ?  每月最后一天 23 点执行一次:0 0 23 L * ?  每周星期天凌晨 1 点实行一次:0 0 1 ? * L  在 26 分、29 分、33 分执行一次:0 26,29,33 * * * ?  每天的 0 点、13 点、18 点、21 点都执行一次:0 0 0,13,18,21 * * ?  每天的 7 点到 21 点都执行一次:0 0 7-21 * * ?  
  • [技术干货] springboot 整合国产达梦数据库配置代码
    1. pom <!-- 达梦数据库驱动 --> <dependency> <groupId>com.dm</groupId> <artifactId>DmJdbcDriver18</artifactId> <version>1.8</version> </dependency>maven 中央仓库里面没有,需要手动安装到 maven 本地仓库2. druid 配置 application.properties#数据源1datasource.no1.driver-class-name: dm.jdbc.driver.DmDriverdatasource.no1.url=jdbc:dm://192.168.0.11:5238/SYSDBA?serverTimezone=UTC&useSSL=false&useUnicode=true&characterEncoding=utf-8datasource.no1.username=SYSDBAdatasource.no1.password=root1234567893. 配置类package org.fh.config;import java.sql.SQLException;import javax.sql.DataSource;import org.apache.ibatis.session.SqlSessionFactory;import org.mybatis.spring.SqlSessionFactoryBean;import org.mybatis.spring.annotation.MapperScan;import org.springframework.beans.factory.annotation.Qualifier;import org.springframework.beans.factory.annotation.Value;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.context.annotation.Primary;import org.springframework.core.io.DefaultResourceLoader;import org.springframework.core.io.support.PathMatchingResourcePatternResolver;import org.springframework.jdbc.datasource.DataSourceTransactionManager;import com.alibaba.druid.pool.DruidDataSource;/** * 说明:第一数据源配置 * 作者:FH Admin * from:fhadmin.cn */@Configuration@MapperScan(basePackages = MasterDataSourceConfig.PACKAGE, sqlSessionFactoryRef = "masterSqlSessionFactory") //扫描 Mapper 接口并容器管理public class MasterDataSourceConfig {…… //其中的 dataSource.addFilters("wall"); 去掉,否则访问不了数据库 @Bean(name = "masterDataSource") @Primary public DataSource masterDataSource() throws SQLException { DruidDataSource dataSource = new DruidDataSource(); dataSource.setDriverClassName(driverClass); dataSource.setUrl(url); dataSource.setUsername(user); dataSource.setPassword(password); dataSource.addFilters("stat"); //dataSource.addFilters("wall"); return dataSource; } ……}
  • [技术干货] Java 开发者 必备的工具 和 框架
    最近几年,Java 的技术栈发展的非常快,成百上千的技术工具正不断地涌出来,这也造成了一个问题:我们作为开发者,到底应该选哪些工具搭建出最合适的技术栈呢?今天我就推荐一波我常用的、我了解的工具和框架。一、项目工具1.1 IDE主流的 Java 开发工具现在非 IntelliJ IDEA 莫属。前几年,可能 Eclipse 还能和 IDEA 一争高下,到了现在已经基本是 IDEA 的天下了。就拿我自己来说吧,我最早用 IDEA,后来用了几年 Eclipse,再后来又用回了 IDEA。包括我身边的程序员,之前用 Eclipse 的人,这几年不少人都换成用 IDEA 了。如果你问我用 IDEA 到底哪最爽,我觉得有 3 点:代码智能提示,爽!代码自动生成,爽!代码调试,爽!而这 3 点,恰恰就是能极大提高开发人员开发效率的 3 点。所以建议做 Java 后端开发的,可以优先考虑 IDEA 作为开发工具。eclipse 也有有点,免安装,不花钱,打开多个项目方便,配置上简单轻便1.2 版本管理工具对于项目中的代码版本管理工具,Git 已经处于垄断地位了,新项目的话不需要再考虑 SVN、CVS 了。之所以 Git 现在处于垄断地位,主要胜在 2 点:Git 是分布式的,不会因为版本管理服务器崩溃导致完整的代码历史版本丢失。Git 创建分支是非常廉价的操作,可以随意创建分支,从而使并行开发很容易落地。而 SVN、CVS 这些版本管理工具创建分支则非常笨拙,并行开发非常麻烦。上述第 1 点大大提升了代码资产的安全可靠程度;第 2 点则完美适应当代的敏捷开发需求。也因此,Git 大行其道就不足为怪了。1.3 构建工具Java 项目的构建工具现在是龙争虎斗,业内一般有两个选择:Maven 和 Gradle。如果是后端的 Java 项目,那绝大部分用的还是 Maven 去构建项目。如果是前端的 Android 项目,则选择 Gradle。Gradle 本身要比 Maven 先进很多:它配置灵活,性能优秀,真的是个非常优秀的构建工具。那为什么在后端 Java 项目构建的时候,大部分用的还是 Maven 呢?因为 Gradle 本身太过灵活了,这种灵活带来了两个和后端项目构建特性不太匹配的问题:Gradle 因为灵活,所以用法规则多变,导致学习门槛过高 —— 后端项目本身的构建流程,套路比较死板,变化非常少,所以不需要太多的构建特性、构建规则。也就是说,灵活本身引入的多种用法、规则、特性对后端项目意义不大,为了构建工具本身的使用,去投入时间学习,本身性价比不高。上面说了,后端项目本身的构建流程是比较套路化的,需要进行一些强约束,去保证这种套路的可靠与稳定。而 Gradle 因为本身比较灵活的配置规则,反而失去了 Maven 的那种强约束,这就很可能因为失去了约束,从而造成团队在使用 Gradle 的时候,出现各种冲突和潜在的错误,造成项目构建的不稳定,这对后端项目来说是得不偿失的。二、开发框架2.1 Web 框架现在的 Web 项目开发,大部分都转向了 SpringBoot 了。使用 SpringBoot 有三个最大的好处:配置非常少,可以说是即插即用基于 Spring 构建,入手门槛非常低直接运行,不需要再考虑 Web 容器的问题SpringBoot 大部分人都很熟了,不再赘述了。springboot springcloud 项目大全 fhadmin.cn2.2 持久层框架项目开发中用到的持久层框架,基本有两类:Mybatis 系列衍生框架JPA 系列衍生框架在国内来讲,大部分持久层框架还是首选 Mybatis,貌似在国外大部分项目是用的 JPA 框架。在我看来,互联网项目、toC 的项目更适合 Mybatis,toB 的项目更适合 JPA。toC 项目的业务需求经常是灵活多变的,所以,往往它需要项目的技术也要跟着灵活多变,而 Mybatis 本身就是 SQL 的简单封装,很容易加表加字段、改 SQL。而 toB 项目则不一样,需求基本比较稳定,设计好的数据模型不会频繁变化,所以不太需要 Mybatis 的灵活性的,反而更需要对随意修改模型进行一系列的强约束。而这也是 JPA 自身的特性:非常规范,且有众多约束,要改 JPA 的数据模型成本比较大。因此,大家选持久层框架的时候,要看清项目的特性,根据实际情况选择用 Mybatis 还是 JPA。2.3 RPC 框架现在 Java 项目的架构,基本都在转向分布式架构。分布式系统的整合,核心就是 RPC,因此很多项目中都引入了 RPC 框架。RPC 框架,现在用的比较多的是 Dubbo 框架。Dubbo 性能非常好:很多 RPC 框架底层使用的通信协议是 HTTP,而 Dubbo 则选择了 TCP 协议作为通信协议。仅从性能上来说,TCP 的性能肯定要比 HTTP 好上许多。而且 Dubbo 自身还大量使用了 NIO 异步编程去进一步做了性能优化。所以,如果项目中需要使用 RPC,可以首先考虑 Dubbo 框架。三、中间件3.1 Web 服务器现在的 Java 开发,由于大部分使用了 SpringBoot,所以以前大家常用的什么 Tomcat、Jetty、Resin 等 Web 容器都不怎么单独部署使用了。但是,有一个 Web 容器反而还愈加兴旺起来,这就是 Nginx。Nginx 在 Java 项目开发里,地位是非常特殊的。它在 Java 项目架构里起到了两个作用:处理静态资源请求的 web 容器 ——Nginx 在 Java 项目中,专门负责处理对图片、html、js、css 等这类静态资源的 Http 请求。反向代理做分发 —— 除了做专门处理静态资源请求的 Web 容器之外,Nginx 同时还会把对 servlet、controller 等这些动态资源的请求,转发给后面的 SpringBoot 中内置的 Tomcat 容器。多说一句,因为反向代理这个特性,Nginx 后面会被部署上集群,Nginx 在转发请求的时候,同时也会做负载均衡的请求分发的反向代理。3.2 消息队列如今,大家做架构越来越趋向分布式架构。分布式架构里,常用的通信手段,除了网络请求,就是消息队列了。现在主流的消息队列框架有 RabbitMQ、RocketMQ、Kafka 等。我之前写过一篇 RabbitMQ 和 Kafka 对比的文章,RabbitMQ 性能虽然低一些,但是容易上手,更适合用在中小项目。另外,做金融领域相关项目,用消息队列的话可以优先考虑 RabbitMQ,原因有以下两点:RabbitMQ 是 AMQP 协议的实现,而 AMQP 协议本身就是来自于金融行业的软件专家们联手制定的,非常成熟和全面,已经成了工业标准。RabbitMQ 是 Erlang 写的,Erlang 的虚拟机对内存和 CPU 过载的保护非常成熟,也因此塑造了 Erlang 应用本身的可靠和健壮。大项目、非金融项目,大家可以在 RocketMQ、Kafka 这两者之间选择。RocketMQ 和 Kafka 差不多 90% 的功能和概念都是相通的,只是 RocketMQ 在 Kafka 理念的基础上做了一些改进,更适用的业务场景也更广泛。在流数据处理上,大家应该优先考虑 Kafka,原因是 Kafka 的流数据处理生态更加的完善周全。3.3 数据库互联网领域,主流数据库就是 MySQL。在一些传统行业,比如银行,Oracle 用的不少。Oracle 贵,互联网项目的一个特点就是数据库服务器数量贼多,如果用 Oracle 的话,成本太高了。而且大家越来越有版权意识,国家对这方面也抓的越来越紧,所以,在互联网领域几乎都在用 MySQL。使用 MySQL,常见的有 MHA 方案 ——MySQL 的高可用方案,基本架构就是一主两从。当主机出故障了,从机就会被提升为主机。3.4 外置缓存对于高并发的架构,外置缓存不可或缺,其中最最最常见的就是 Redis。之所以大家都采用 Redis 做外置缓存,原因有三点:Redis 本身性能非常好。Redis 有很多数据结构去适配不同的业务缓存需求。Redis 的集群高可用方案和分片存储的高性能方案相对成熟。以上,就是 Java 开发中经常遇到的主流技术工具了。由于篇幅所限,我也只列出了一些最核心(或者说每个人都会用到的)工具和中间件。有一些重要的中间件,我觉得不是所有人都会用到,就没有提及,比如 ElasticSearch、MongoDB、Zookeeper 等————————————————原文作者:FH-Admin转自链接:https://learnku.com/articles/66647版权声明:著作权归作者所有。商业转载请联系作者获得授权,非商业转载请保留以上作者信息和原文链接。
  • [技术干货] java 启动命令 java -jar 如何追加参数
    jar 包启动 后面覆盖或追加参数1. D= : set a system property 设置系统属性。2. 官方解释:Set a system property value. If value is a string that contains spaces, you must enclose the string in double quotes:* 首先理解参数的意义,java 命令引入 jar 时可以 - cp 参数,但时 - cp 不能用通配符 (多个 jar 时什么烦要一个个写,不能.jar),面通常的 jar 都在同一目录,且多于 1 个。)-Djava.ext.dirs 就可以解决 多个 jar 的问题如:java -jar -Dserver.port=8555 -Dxxl.job.executor.port=9998 democlient-0.0.1-SNAPSHOT.jar实战案例:java -jar -Dserver.port=8555 -Dxxl.job.executor.port=9998 democlient-0.0.1-SNAPSHOT.jar
  • [技术干货] Java stream().sorted()实现排序(升序、降序、多字段排序)
    1 自然排序sorted ():自然排序,流中元素需实现 Comparable 接口 package com.entity;import lombok.*;@Data@ToString@AllArgsConstructor@NoArgsConstructorpublic class Student implements Comparable<Student> { private int id; private String name; private int age; @Override public int compareTo(Student ob) { return name.compareTo(ob.getName()); } @Override public boolean equals(final Object obj) { if (obj == null) { return false; } final Student std = (Student) obj; if (this == std) { return true; } else { return (this.name.equals(std.name) && (this.age == std.age)); } } @Override public int hashCode() { int hashno = 7; hashno = 13 * hashno + (name == null ? 0 : name.hashCode()); return hashno; } } 2 定制排序 sorted (Comparator com):定制排序,自定义 Comparator 排序器 3 升序 3.1 自然排序 list = list.stream().sorted().collect(Collectors.toList());  3.2 定制排序 根据年龄降序排序。  list = list.stream().sorted(Comparator.comparing(Student::getAge)).collect(Collectors.toList());4 降序4.1 自然排序使用 Comparator 提供的 reverseOrder () 方法#list = list.stream().sorted(Comparator.reverseOrder()).collect(Collectors.toList());4.2 定制排序根据年龄降序排序。list = list.stream().sorted(Comparator.comparing(Student::getAge).reversed()).collect(Collectors.toList());5 多字段排序先按姓名升序,姓名相同则按年龄升序。list = list.sorted(Comparator.comparing(Student::getName).thenComparing(Student::getAge)).collect(Collectors.toList转载自https://learnku.com/articles/69631
  • [技术干货] SpringBoot static 静态方法获取 yml 配置文件
     在开发中有些业务代码中需要判断当前是测试环境还是正式环境来做出不同的逻辑处理。  今天说一下如何在 static 静态方法中获取到 yml 配置文件中的配置值,从而获得当前的环境是测试环境还是正式环境。  首先创建一个类,编写所需方法  @Getterpublic class Result<T> { private static String env; public static void setEnv(String env) { Result.env = env; } public static String getEnv() { return Result.env; }}接着创建读取配置文件的类package com.sktk.keepAccount.config;import com.sktk.keepAccount.common.core.vo.Result;import org.springframework.beans.factory.annotation.Value;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;/** * 该类使用场景在:当需要在静态方法中获取配置时,因静态方法无法直接获取配置 * https://jingyan.baidu.com/article/215817f7c907835fdb142348.html */@Configurationpublic class EnvConfig { // 定义要读取的配置key @Value("${spring.profiles.active}") private String env; @Bean public int initStatic() { Result.setEnv(env); return 0; }}接下来,就可以直接在任意地方使用 Result.getEnv 来获取当前的环境了。
  • springboot @Configuration 注解详解【转】
     @Configuration 详解 场景 新建两个 bean:user 和 pet。  若要将这两个 bean 的实例注入到容器之中,在曾经 spring 阶段我们的做法是使用 xml 进行配置。  在项目的 resource 文件夹下新建 bean.xml 并进行配置,配置内容如下: <bean id="user01" class="xt.naru.learn.bean.User"> <property name="name" value="naru"></property> <property name="age" value="21"></property> </bean> <bean id="cat" class="xt.naru.learn.bean.Pet"> <property name="name" value="tomcat"></property> </bean>当项目需求越来越大时,我们时常会陷入 xml 配置地狱,因此为了解决这一问题, springboot 可以通过注解的方式进行实例注入而无需编写 xml。  使用 在 springboot 中我们不再使用 xml 进行配置。 修改为 新建配置类,并将配置类加上 Configuration 注解,在配置类中进行 bean 实例的注入。  代码如下:  @Configuration public class myconfig { @Bean public User user01(){ return new User("naru",21); } @Bean public Pet tomcat(){ return new Pet("tomcat"); } }相比起 xml 的变化对比:  其实就是将 xml 中的配置项改为了注解,还是蛮好理解的。   检测注入结果 在 main 启动器中进行 beanname 的打印即可。  @SpringBootApplication public class MainApplication { public static void main(String[] args) { ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args); String[] beannames = run.getBeanDefinitionNames(); for (String name : beannames){ System.out.println(name); } } }可以看到我们的 user01 以及 cat 成功注入。   一些特性 bean 实例名称自定义 直接在 bean 注解中加入想要自定义的 bean 的名称就可以了。  通过阅读上面的代码可发现,默认 bean 的名称和方法名相同。  看!原来叫做 cat 的 bean,现在名字变成了 tom-cat!   从容器中获取的实例默认为单实例 来试试吧,在 main 启动器中获取组件并打印其 boolean 结果: Pet Tom01 = run.getBean("tom-cat",Pet.class); Pet Tom02 = run.getBean("tom-cat",Pet.class); System.out.println("组件实例是否是同一个:"+ (Tom01 == Tom02));你将神奇的发现,是同一个实例:  配置类本身也是组件 直接来实践一下吧,通过以下代码进行打印:  System.out.println(run.getBean(myconfig.class)); 结果显而易见:  xt.naru.learn.config.myconfig$$EnhancerBySpringCGLIB$$f891ec73@15f193b8 proxyBeanMethods springboot2 的版本中,configration 注解中有这样一个方法: proxyBeanMethods  它的默认值为 true。 它是干嘛用的呢? 直译过来是代理 bean 方法的意思。  或许这句话有些难以理解,这样去想吧,我们在 myconfig 中进行了两个 bean 的实例化。  而有参、无参构造,每调用一次我们都可以生成新的实例化对象。那这是否意味着,我从容器中或许到的组件,每次都是不同的呢?  我们来进行测试:  调用两次无参构造,生成新对象。看看二者是否相同。  myconfig bean = run.getBean(myconfig.class); System.out.println(run.getBean(myconfig.class)); User user = bean.user01(); User user1= bean.user01(); System.out.println("user和user1是否为同一个实例:"+(user1 == user));会发现, 是相同的。  那么,proxyBeanMethods 到底做了什么呢?  先卖个关子,我们将它改为 false,再进行一次打印。看看结果。   会发现,myconfig 的 bean 名变了,实例也不再是同一个实例。  那么我们可以总结一下 proxyBeanMethods 所做的事情:  **** 当 proxyBeanMethods 为 true 时:  将配置类变成增强型的代理类。保证 @bean 的方法被调用多少次,返回的组件均为单实例。  此模式被称为 Full 模式。即每次启动时,SpringBoot 总会检查这个组件是否在容器中存在,保持组件单实例。  保持组件单实例有什么好处呢?  保证组件之间的依赖关系不被破坏。  设想一下吧,如果不是单实例的组件,当我们的 User 小明,想要养一只叫 tom 的猫时,有很多只叫做 tom 的猫,他将分不清到底哪只属于他。 而单实例情况下,将不会出现这种问题,因为我们每次调用构造方法生成的实例对象一直都是同一只叫做 tom 的猫。  配置类组件之间有依赖关系,方法会被调用得到之前单实例组件,用 Full 模式  当 proxyBeanMethods 为 false 时:  配置类为普通的类,此时每个 @Bean 方法被调用多少次返回的组件都是新创建的。  此模式被称为 Lite 模式,即 springboot 将不再会去判断组件是否已经在容器中存在,而是直接新建组件。(省去了检查流程,此模式下 springboot 启动会非常的快。)  总结 好了,现在让我们来总结一下关于 @Configuration 注解的要点。  存在为了解决怎样的问题? 方便 bean 实例的注入,我们不再需要使用 xml 进行配置了!只需要使用 java 代码就可以完成 bean 实例的注入了。 特性有哪些呢? 1、配置类里面使用 @Bean 标注在方法上给容器注册组件,默认也是单实例的 2、配置类本身也是组件 3、proxyBeanMethods:代理 bean 的方法 Full (proxyBeanMethods = true)、【保证每个 @Bean 方法被调用多少次返回的组件都是单实例的】 Lite (proxyBeanMethods = false)【每个 @Bean 方法被调用多少次返回的组件都是新创建的】 组件依赖必须使用 Full 模式默认。其他默认是否 Lite 模式  ———————————————— 原文作者:kudoForever 转自链接:https://learnku.com/articles/71782 版权声明:著作权归作者所有。商业转载请联系作者获得授权,非商业转载请保留以上作者信息和原文链接。 
  • iText 导出 pdf、word、图片【转】
     一、前言 在企业的信息系统中,报表处理一直占比较重要的作用,本文将介绍一种生成 PDF 报表的 Java 组件–iText。通过在服务器端使用 Jsp 或 JavaBean 生成 PDF 报表,客户端采用超级连接显示或下载得到生成的报表,这样就很好的解决了 B/S 系统的报表处理问题。  二、iText 简介 iText 是著名的开放源码的站点 sourceforge 一个项目,是用于生成 PDF 文档的一个 java 类库。通过 iText 不仅可以生成 PDF 或 rtf 的文档,而且可以将 XML、Html 文件转化为 PDF 文件。  iText 的安装非常方便,在 www.lowagie.com/iText/download.html 网站上下载 iText.jar 文件后,只需要在系统的 CLASSPATH 中加入 iText.jar 的路径,在程序中就可以使用 iText 类库了。  * 三、建立第一个 PDF 文档 * 用 iText 生成 PDF 文档需要 5 个步骤: ①建立com.lowagie.text.Document对象的实例。 Document document = new Document(); ②建立一个书写器(Writer)与document对象关联,通过书写器(Writer)可以将文档写入到磁盘中。 PDFWriter.getInstance(document, new FileOutputStream("Helloworld.PDF")); ③打开文档。 document.open(); ④向文档中添加内容。 document.add(new Paragraph("Hello World")); ⑤关闭文档。 document.close(); 通过上面的 5 个步骤,就能产生一个 Helloworld.PDF 的文件,文件内容为”Hello World”。  建立 com.lowagie.text.Document 对象的实例 com.lowagie.text.Document 对象的构建函数有三个,分别是: public Document(); public Document(Rectangle pageSize); public Document(Rectangle pageSize, int marginLeft, int marginRight, int marginTop, int marginBottom); 构建函数的参数 pageSize 是文档页面的大小,对于第一个构建函数,页面的大小为 A4,同 Document (PageSize.A4) 的效果一样;对于第三个构建函数,参数 marginLeft、marginRight、marginTop、marginBottom 分别为左、右、上、下的页边距。  通过参数 pageSize 可以设定页面大小、面背景色、以及页面横向 / 纵向等属性。iText 定义了 A0-A10、AL、LETTER、HALFLETTER、_11x17、LEDGER、NOTE、B0-B5、ARCH_A-ARCH_E、FLSA 和 FLSE 等纸张类型,也可以通过 Rectangle pageSize = new Rectangle (144, 720); 自定义纸张。通过 Rectangle 方法 rotate () 可以将页面设置成横向。  书写器(Writer)对象 一旦文档 (document) 对象建立好之后,需要建立一个或多个书写器 (Writer) 对象与之关联。通过书写器 (Writer) 对象可以将具体文档存盘成需要的格式,如 com.lowagie.text.PDF.PDFWriter 可以将文档存成 PDF 文件,com.lowagie.text.html.HtmlWriter 可以将文档存成 html 文件。  设定文档属性 在文档打开之前,可以设定文档的标题、主题、作者、关键字、装订方式、创建者、生产者、创建日期等属性,调用的方法分别是: public boolean addTitle(String title) public boolean addSubject(String subject) public boolean addKeywords(String keywords) public boolean addAuthor(String author) public boolean addCreator(String creator) public boolean addProducer() public boolean addCreationDate() public boolean addHeader(String name, String content)public boolean setPageSize(Rectangle pageSize) public boolean add(Watermark watermark) public void removeWatermark() public void setHeader(HeaderFooter header) public void resetHeader() public void setFooter(HeaderFooter footer) public void resetFooter() public void resetPageCount() public void setPageCount(int pageN)package junit.test;import java.io.FileNotFoundException;import java.io.FileOutputStream;import java.io.IOException;import java.net.MalformedURLException;import org.junit.Test;import com.lowagie.text.Document;import com.lowagie.text.DocumentException;import com.lowagie.text.Font;import com.lowagie.text.Image;import com.lowagie.text.Paragraph;import com.lowagie.text.pdf.BaseFont;import com.lowagie.text.pdf.PdfWriter;import com.lowagie.text.rtf.RtfWriter2;/** * 导出图片、word、pdf * * @author 林计钦 * @version 1.0 Feb 7, 2014 9:02:38 AM */public class PdfAndWordTest { /** * 导出pdf */ @Test public void exportPdf() { Document document=null; try { BaseFont bfChinese = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);// 设置中文字体 Font headFont = new Font(bfChinese, 10, Font.NORMAL);// 设置字体大小 //第一步:创建一个document对象。 document = new Document(); //第二步:创建一个PdfWriter实例,将文件输出流指向一个文件。 PdfWriter.getInstance(document, new FileOutputStream("D:/test/123.pdf")); //第三步:打开文档。 document.open(); Paragraph title = new Paragraph("你好,Pdf!", headFont); //第四步:在文档中增加一个段落。 document.add(title); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (DocumentException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }finally{ if(document!=null){ //第五步:关闭文档。 document.close(); } } } /** * 导出word */ @Test public void exportWord() { Document document=null; try { document = new Document(); RtfWriter2.getInstance(document, new FileOutputStream("D:/test/word.doc")); document.open(); Paragraph title = new Paragraph("你好,Word!"); document.add(title); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (DocumentException e) { e.printStackTrace(); }finally{ if(document!=null){ document.close(); } } } /** * 导出图片 */ @Test public void exportImg() { Document document=null; try { BaseFont bfChinese = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);// 设置中文字体 Font headFont = new Font(bfChinese, 10, Font.NORMAL);// 设置字体大小 document = new Document(); PdfWriter.getInstance(document, new FileOutputStream("D:/test/img.pdf")); //设定文档的作者 document.addAuthor("林计钦"); //测试无效 document.open(); document.add(new Paragraph("你好,Img!", headFont)); //读取一个图片 Image image = Image.getInstance("D:/test/1.gif"); //插入一个图片 document.add(image); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (DocumentException e) { e.printStackTrace(); } catch (MalformedURLException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }finally{ if(document!=null){ document.close(); } } } }导出例子:转载自https://learnku.com/articles/73823
  • [技术干货] itext实现图片等比缩小放大转pdf
    itext 工具 jar 包实现图片转 pdf,可以根据不同的方案来等比伸缩图片的大小,保证正常情况下图片不失真。以下是个人写的工具类:记得导入 itext.jar 包package com.bestone.lawaid.opm.controller;import com.lowagie.text.Document;import java.io.File;import java.io.FileNotFoundException;import java.io.FileOutputStream;import java.io.IOException;import com.lowagie.text.DocumentException;import com.lowagie.text.Image;import com.lowagie.text.pdf.PdfWriter;public class TestCreatePDF { /** * 591227491@qq.com */ public void creatPDF(String imgName) { //创建一个文档对象 Document doc = new Document(); try { //定义输出文件的位置 PdfWriter.getInstance(doc, new FileOutputStream("D:/Test/"+imgName+".pdf")); //开启文档 doc.open(); //设定字体 为的是支持中文 //BaseFont bfChinese = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED); // Font FontChinese = new Font(bfChinese, 12, Font.NORMAL); //向文档中加入图片 String path = "D:/EclipseSpace/ImagFile"; //遍历文件下的文件 File file = new File(path); File [] files = file.listFiles(); for(int i=0;i<files.length;i++) { File file1 = files[i]; //根据后缀判断是否是图片 String[] imgTrue = file1.getName().split("\\."); if("png".equals(imgTrue[1])){ //取得图片~~~图片格式: System.out.println("---"+file1.getName()); Image jpg1 = Image.getInstance(path+"/"+imgTrue[0]+".png"); //原来的图片的路径 //获得图片的高度 float heigth=jpg1.getHeight(); float width=jpg1.getWidth(); System.out.println("heigth:"+imgTrue[0]+"----"+heigth); System.out.println("width:"+imgTrue[0]+"-----"+width); //合理压缩,h>w,按w压缩,否则按w压缩 //int percent=getPercent(heigth, width); //统一按照宽度压缩 int percent=getPercent2(heigth, width); System.out.println("--"+percent); //设置图片居中显示 jpg1.setAlignment(Image.MIDDLE); //直接设置图片的大小~~~~~~~第三种解决方案,按固定比例压缩 //jpg1.scaleAbsolute(210.0f, 297.0f); //按百分比显示图片的比例 jpg1.scalePercent(percent);//表示是原来图像的比例; //可设置图像高和宽的比例 //jpg1.scalePercent(50, 100); doc.add(jpg1); } } //关闭文档并释放资源 doc.close(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (DocumentException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } /** * 第一种解决方案 * 在不改变图片形状的同时,判断,如果h>w,则按h压缩,否则在w>h或w=h的情况下,按宽度压缩 * @param h * @param w * @return */ public int getPercent(float h,float w) { int p=0; float p2=0.0f; if(h>w) { p2=297/h*100; } else { p2=210/w*100; } p=Math.round(p2); return p; } /** * 第二种解决方案,统一按照宽度压缩 * 这样来的效果是,所有图片的宽度是相等的,自我认为给客户的效果是最好的 * @param args */ public int getPercent2(float h,float w) { int p=0; float p2=0.0f; p2=530/w*100; System.out.println("--"+p2); p=Math.round(p2); return p; } /** * 第三种解决方案,就是直接压缩,不安像素比例,全部压缩到固定值,如210*297 * * @param args */ public static void main(String[] args) { TestCreatePDF pt=new TestCreatePDF(); pt.creatPDF("imgCreatPdf"); }}
总条数:2294 到第 页
上滑加载中