@@ -80,11 +80,19 @@ | |||
</dependency> | |||
<!-- websocket 结束 --> | |||
<!-- retry 结束 --> | |||
<dependency> | |||
<groupId>org.springframework.retry</groupId> | |||
<artifactId>spring-retry</artifactId> | |||
<version>1.2.2.RELEASE</version> | |||
</dependency> | |||
<!-- retry 结束 --> | |||
<!-- fastjson 开始 --> | |||
<dependency> | |||
<groupId>com.alibaba</groupId> | |||
<artifactId>fastjson</artifactId> | |||
<version>1.2.78</version> | |||
<version>2.0.21</version> | |||
</dependency> | |||
<!-- fastjson 结束 --> | |||
<!-- hutool 开始 --> | |||
@@ -201,6 +209,10 @@ | |||
<artifactId>influxdb-client-java</artifactId> | |||
<version>6.7.0</version> | |||
</dependency> | |||
<dependency> | |||
<groupId>junit</groupId> | |||
<artifactId>junit</artifactId> | |||
</dependency> | |||
<!-- influx end --> | |||
@@ -30,7 +30,7 @@ public interface S7Connector extends Closeable { | |||
* @param offset | |||
* @return | |||
*/ | |||
public byte[] read(DaveArea area, int areaNumber, int bytes, int offset) throws Exception; | |||
public byte[] read(DaveArea area, int areaNumber, int bytes, int offset); | |||
/** | |||
* Reads an area 读需要bit 位置的 变量(其实就是 read bool变量的时候调用这个方法。) | |||
@@ -41,7 +41,7 @@ public interface S7Connector extends Closeable { | |||
* @param offset | |||
* @return | |||
*/ | |||
public byte[] read(DaveArea area, int areaNumber, int bytes, int offset, int bitOffset, TransportSize transportSize) throws Exception; | |||
public byte[] read(DaveArea area, int areaNumber, int bytes, int offset, int bitOffset, TransportSize transportSize); | |||
/** | |||
* Writes an area | |||
* desc : 只要未抛出异常,都是 操作成功的 | |||
@@ -50,9 +50,9 @@ public interface S7Connector extends Closeable { | |||
* @param offset | |||
* @param buffer | |||
*/ | |||
public void write(DaveArea area, int areaNumber, int offset, byte[] buffer) throws Exception; | |||
public void write(DaveArea area, int areaNumber, int offset, byte[] buffer); | |||
//如果 bitOffset 没有 那么就填0 | |||
public void write(DaveArea area, int areaNumber, int byteOffset, int bitOffset, byte[] buffer, PlcVar var) throws Exception; | |||
public void write(DaveArea area, int areaNumber, int byteOffset, int bitOffset, byte[] buffer, PlcVar var); | |||
} |
@@ -6,6 +6,7 @@ package com.qgs.dc.s7.my.s7connector.api.utils; | |||
* @DATE: 2021/12/16 9:08 | |||
*/ | |||
import com.qgs.dc.s7.my.s7connector.exception.S7ParseDataException; | |||
import com.qgs.dc.s7.my.s7connector.utils.CommonFunctions; | |||
import java.io.UnsupportedEncodingException; | |||
@@ -47,9 +48,14 @@ public class ByteUtils { | |||
} | |||
} | |||
public static String addDate(String timeParam, Long day) throws ParseException { | |||
public static String addDate(String timeParam, Long day) { | |||
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); // 日期格式 | |||
Date date = dateFormat.parse(timeParam); // 指定日期 | |||
Date date = null; | |||
try { | |||
date = dateFormat.parse(timeParam); // 指定日期 | |||
}catch (Exception e){ | |||
throw new S7ParseDataException("ByteUtils.addDate error:", e); | |||
} | |||
long time = date.getTime(); // 得到指定日期的毫秒数 | |||
day = day * 24 * 60 * 60 * 1000; // 要加上的天数转换成毫秒数 | |||
@@ -175,13 +181,13 @@ public class ByteUtils { | |||
// String ascii = new String(b, Charset.forName("UTF-8")); | |||
// return ascii; | |||
// } | |||
public static Character toChar(byte[] b) throws UnsupportedEncodingException { | |||
public static Character toChar(byte[] b) { | |||
if(b.length==1){ | |||
return toChar(b[0]); | |||
} | |||
return byteToChar(b); | |||
} | |||
public static Character toChar(byte b) throws UnsupportedEncodingException { | |||
public static Character toChar(byte b){ | |||
return byteToChar(b); | |||
} | |||
// public static String toChar(byte b) throws UnsupportedEncodingException { | |||
@@ -194,7 +200,7 @@ public class ByteUtils { | |||
/** | |||
* return null 代表返回传入参数不正确str 取的length太小 | |||
* */ | |||
public static String toStr(byte[] b) throws UnsupportedEncodingException { | |||
public static String toStr(byte[] b) { | |||
Integer length = Byte.toUnsignedInt(b[1]); | |||
if(length>(b.length-2)){ | |||
return null; | |||
@@ -207,7 +213,7 @@ public class ByteUtils { | |||
// String s = new String(content); | |||
return ascii; | |||
} | |||
public static String[] toStrArray(byte[] b,Integer length,Integer strSize) throws UnsupportedEncodingException { | |||
public static String[] toStrArray(byte[] b,Integer length,Integer strSize) { | |||
String[] res = new String[length]; | |||
strSize+=2; | |||
for(int i=0;i<length;i++){ | |||
@@ -275,7 +281,7 @@ public class ByteUtils { | |||
return b; | |||
} | |||
public static List<Boolean> toBoolArray(byte[] b) throws UnsupportedEncodingException { | |||
public static List<Boolean> toBoolArray(byte[] b) { | |||
List<Boolean> res = new ArrayList<>(); | |||
for(int i=0;i<b.length;i++){ | |||
boolean[] booleanArray = getBooleanArray(b[i], true); | |||
@@ -301,7 +307,7 @@ public class ByteUtils { | |||
//bool array to byte array | |||
public static byte[] toByteArray(boolean[] b) throws UnsupportedEncodingException { | |||
public static byte[] toByteArray(boolean[] b){ | |||
Integer byteLength = CommonFunctions.exactDivision(b.length, 8); | |||
byte[] res = new byte[byteLength]; | |||
Queue<Boolean> queue = new LinkedList<Boolean>(); | |||
@@ -347,7 +353,7 @@ public class ByteUtils { | |||
// return res; | |||
// } | |||
public static List<Byte> toByteArray(byte[] b) throws UnsupportedEncodingException { | |||
public static List<Byte> toByteArray(byte[] b) { | |||
List<Byte> res = new ArrayList<>(); | |||
for(int i=0;i<b.length;i++){ | |||
res.add((b[i])); | |||
@@ -355,7 +361,7 @@ public class ByteUtils { | |||
return res; | |||
} | |||
public static List<Character> toCharArray(byte[] b) throws UnsupportedEncodingException { | |||
public static List<Character> toCharArray(byte[] b) { | |||
List<Character> res = new ArrayList<>(); | |||
for(int i=0;i<b.length;i++){ | |||
res.add(toChar(b[i])); | |||
@@ -367,7 +373,7 @@ public class ByteUtils { | |||
/** | |||
* 默认:word => 有符号的整形 | |||
* */ | |||
public static List<Integer> toWordArray(byte[] b) throws UnsupportedEncodingException { | |||
public static List<Integer> toWordArray(byte[] b) { | |||
List<Integer> res = new ArrayList<>(); | |||
int i=0; | |||
while ((i+2)<=b.length){ | |||
@@ -383,7 +389,7 @@ public class ByteUtils { | |||
/** | |||
* 默认:dword => 有符号的整形 | |||
* */ | |||
public static List<Integer> toDWordArray(byte[] b) throws UnsupportedEncodingException { | |||
public static List<Integer> toDWordArray(byte[] b) { | |||
List<Integer> res = new ArrayList<>(); | |||
int i=0; | |||
while ((i+4)<=b.length){ | |||
@@ -444,7 +450,7 @@ public class ByteUtils { | |||
/** | |||
* USInt 无符号整形 1个字节 =》 Integer | |||
* */ | |||
public static List<Integer> toUSIntArray(byte[] b) throws UnsupportedEncodingException { | |||
public static List<Integer> toUSIntArray(byte[] b) { | |||
List<Integer> res = new ArrayList<>(); | |||
for(int i=0;i<b.length;i++){ | |||
res.add(toUInt(b[i])); | |||
@@ -454,7 +460,7 @@ public class ByteUtils { | |||
/** | |||
* UInt 无符号整形 2个字节 =》 Integer | |||
* */ | |||
public static List<Integer> toUIntArray(byte[] b) throws UnsupportedEncodingException { | |||
public static List<Integer> toUIntArray(byte[] b) { | |||
List<Integer> res = new ArrayList<>(); | |||
int i=0; | |||
while ((i+2)<=b.length){ | |||
@@ -468,7 +474,7 @@ public class ByteUtils { | |||
/** | |||
* UDInt 无符号整形 4个字节 =》 Long | |||
* */ | |||
public static List<Long> toUDIntArray(byte[] b) throws UnsupportedEncodingException { | |||
public static List<Long> toUDIntArray(byte[] b) { | |||
List<Long> res = new ArrayList<>(); | |||
int i=0; | |||
while ((i+4)<=b.length){ | |||
@@ -483,7 +489,7 @@ public class ByteUtils { | |||
/** | |||
* SInt 无符号整形 1个字节 =》 Integer | |||
* */ | |||
public static List<Integer> toSIntArray(byte[] b) throws UnsupportedEncodingException { | |||
public static List<Integer> toSIntArray(byte[] b) { | |||
List<Integer> res = new ArrayList<>(); | |||
for(int i=0;i<b.length;i++){ | |||
res.add(toInt(b[i])); | |||
@@ -493,7 +499,7 @@ public class ByteUtils { | |||
/** | |||
* Int 无符号整形 2个字节 =》 Integer | |||
* */ | |||
public static List<Integer> toIntArray(byte[] b) throws UnsupportedEncodingException { | |||
public static List<Integer> toIntArray(byte[] b) { | |||
List<Integer> res = new ArrayList<>(); | |||
int i=0; | |||
while ((i+2)<=b.length){ | |||
@@ -507,7 +513,7 @@ public class ByteUtils { | |||
/** | |||
* DInt 无符号整形 4个字节 =》 Integer | |||
* */ | |||
public static List<Integer> toDIntArray(byte[] b) throws UnsupportedEncodingException { | |||
public static List<Integer> toDIntArray(byte[] b) { | |||
List<Integer> res = new ArrayList<>(); | |||
int i=0; | |||
while ((i+4)<=b.length){ | |||
@@ -0,0 +1,355 @@ | |||
package com.qgs.dc.s7.my.s7connector.enmuc; | |||
import com.qgs.dc.s7.my.s7connector.api.DaveArea; | |||
import com.qgs.dc.s7.my.s7connector.api.S7Connector; | |||
import com.qgs.dc.s7.my.s7connector.api.factory.S7ConnectorFactory; | |||
import com.qgs.dc.s7.my.s7connector.api.utils.ByteUtils; | |||
import com.qgs.dc.s7.my.s7connector.exception.S7Exception; | |||
import com.qgs.dc.s7.my.s7connector.type.PlcVar; | |||
import com.qgs.dc.s7.my.s7connector.utils.CommonFunctions; | |||
import com.qgs.dc.s7.retry.S7RetryTemplate; | |||
import org.slf4j.Logger; | |||
import org.slf4j.LoggerFactory; | |||
import java.util.ArrayList; | |||
import java.util.List; | |||
/** | |||
* @Desc: "" | |||
* @Author: caixiang | |||
* @DATE: 2022/1/15 13:01 | |||
*/ | |||
public enum S7ClientNew { | |||
//TODO 步骤1 这里是配置多PLC 的,,,有多个plc 就在这里配置一个枚举类 | |||
//1500 西门子200smart、1200、1500默认的 机架号=0 槽位号=1; 300/400 默认的 机架-0 插槽-2 | |||
S7_1200("192.168.0.52",0,1,1,PlcVarActual.HeartBeatFor1200), | |||
S7_1500("192.168.0.51",0,1,1,PlcVarActual.HeartBeat), | |||
//1500 机架-0 插槽-1 | |||
//后续 在这里扩展 多PLC应用。 | |||
; | |||
private String host; | |||
//默认 0 机架号 | |||
private Integer rack; | |||
//默认 0 | |||
private Integer slot; | |||
//心跳变量,如果plc没有让电控的人加一个,这个是必填的 | |||
private PlcVarActual heartBeat; | |||
private List<S7Connector> connections; | |||
//coreSize 是线程池的大小 | |||
private Integer coreSize; | |||
//pickOne 就是一个初始化 的轮询取余值 | |||
private int pickOne; | |||
private static final Logger logger = LoggerFactory.getLogger(S7ClientNew.class); | |||
//coreSize 是线程池的数量 | |||
S7ClientNew(String host, Integer rack, Integer slot, Integer coreSize, PlcVarActual heartBeat){ | |||
this.host = host; | |||
this.rack = rack; | |||
this.slot = slot; | |||
this.pickOne = 0; | |||
this.coreSize = coreSize; | |||
connections = new ArrayList<>(); | |||
connectionPool(); | |||
} | |||
public String getHost(){ | |||
return this.host; | |||
} | |||
/** | |||
* PlcVar(byte[]) 转 java对象 对照表 | |||
* 单体变量 | |||
* Bool ===> Boolean | |||
* LREAL ===> Double | |||
* REAL ===> Float | |||
* DATE ===> String(yyyy-MM-dd 这种形式的类型) | |||
* DTL ===> String("年-月-日-工作日-时-分-秒" 这种格式) | |||
* TIME ===> Integer(单位 ms) | |||
* USINT ===> Integer | |||
* SINT ===> Integer | |||
* UINT ===> Integer | |||
* INT ===> Integer | |||
* DINT ===> Integer | |||
* UINT ===> Long | |||
* Byte ===> Integer(有符号)(默认) | |||
* Integer(无符号)(后续扩展) | |||
* Char ===> Character | |||
* WChar ===> Character | |||
* String ===> String (特殊) | |||
* 数组变量 | |||
* BoolArray ===> List<Boolean> | |||
* ByteArray ===> List<Byte> | |||
* WordArray ===> List<Integer> | |||
* DWordArray ===> List<Integer> | |||
* CharArray ===> List<Character> | |||
* SIntArray ===> List<Integer> | |||
* IntArray ===> List<Integer> | |||
* DIntArray ===> List<Integer> | |||
* UIntArray ===> List<Integer> | |||
* USIntArray ===> List<Integer> | |||
* UDIntArray ===> List<Long> | |||
* StringArray ===> String[] (特殊) | |||
* | |||
* 如果返回null,就代表出现了异常,并且尝试了 retryMax 次数,并且尝试重置连接 | |||
* */ | |||
public Object read(DaveArea area, Integer areaNumber, Integer byteOffset, Integer bitOffset, Integer length, Integer strSizes, PlcVar type) { | |||
S7Connector connector = getConnector(); | |||
return S7RetryTemplate.getInstance().execute( | |||
context -> { | |||
//String 类型比较特殊。 String[] 也是同理,Sring数组里面的子项 也是有两个字节的 readBytes。 | |||
if(type.equals(PlcVar.STRING)){ | |||
Integer readBytes = 2; | |||
byte[] read = connector.read( | |||
area, | |||
areaNumber, | |||
readBytes, | |||
byteOffset, | |||
bitOffset, | |||
type.getTransportSize() | |||
); | |||
Integer allLength = Integer.valueOf(read[1])+2; | |||
byte[] readF = connector.read( | |||
area, | |||
areaNumber, | |||
allLength, | |||
byteOffset, | |||
bitOffset, | |||
type.getTransportSize() | |||
); | |||
return type.toObject(readF); | |||
}else if(type.equals(PlcVar.BOOL_Array)){ | |||
byte[] read = connector.read( | |||
area, | |||
areaNumber, | |||
CommonFunctions.exactDivision(length,8), | |||
byteOffset, | |||
bitOffset, | |||
type.getTransportSize() | |||
); | |||
List<Boolean> booleans = ByteUtils.toBoolArray(read); | |||
List<Boolean> res = new ArrayList<Boolean>(); | |||
for(int i=0;i<length;i++){ | |||
res.add(i,booleans.get(i)); | |||
} | |||
return res; | |||
}else if(type.equals(PlcVar.STRING_Array)){ | |||
Integer arrayLength = length; | |||
Integer strSize = strSizes; | |||
byte[] read = connector.read( | |||
area, | |||
areaNumber, | |||
arrayLength*(strSize+2), | |||
byteOffset, | |||
bitOffset, | |||
type.getTransportSize() | |||
); | |||
return ByteUtils.toStrArray(read, arrayLength, strSize); | |||
}else { | |||
Integer readBytes = type.getTransportSize().getSizeInBytes() * length; | |||
byte[] read = connector.read( | |||
area, | |||
areaNumber, | |||
readBytes, | |||
byteOffset, | |||
bitOffset, | |||
type.getTransportSize() | |||
); | |||
return type.toObject(read); | |||
} | |||
}, | |||
context -> { | |||
logger.info("S7-Retry : 已达到最大重试次数: "+S7RetryTemplate.getMaxRetryTimes()+", 现在尝试重新连接"); | |||
if(replaceConnector(connector)==1){ | |||
logger.info("S7-Retry-Read 现在恢复成功,创建新connection成功"); | |||
return null; | |||
}else { | |||
logger.info("S7-Retry-Read 现在恢复失败,创建新connection失败"); | |||
return null; | |||
} | |||
} | |||
); | |||
} | |||
/** | |||
* | |||
* eg : | |||
* Object newValue = Boolean.FALSE | |||
* s7Service.write(PlcVarActual.HeartBeat, newValue, S7Client.S7_1200); | |||
* | |||
* PlcVar(byte[]) 转 java对象 对照表 | |||
* 单体变量 | |||
* Bool ===> Object newValue = Boolean.FALSE | |||
* LREAL ===> Object newValue = Boolean.FALSE | |||
* REAL ===> Object newValue = Boolean.FALSE | |||
* DATE ===> 暂时没需求(有问题找我) | |||
* DTL ===> 暂时没需求(有问题找我) | |||
* TIME ===> 暂时没需求(有问题找我) | |||
* USINT ===> Object newValue = new Integer(1) | |||
* SINT ===> Object newValue = new Integer(1) | |||
* UINT ===> Object newValue = new Integer(1) | |||
* INT ===> Object newValue = new Integer(1) | |||
* DINT ===> Object newValue = new Integer(1) | |||
* UINT ===> Object newValue = new Integer(1) | |||
* Byte ===> Object newValue = 0x11 | |||
* | |||
* Char ===> Object newValue = 'a' | |||
* WChar ===> Object newValue = '菜' | |||
* String ===> Object newValue = '你好啊' (特殊) | |||
* 数组变量 | |||
* 注意:在write的时候,你write的数量 一定要和 plc中存在的数量一一对应 | |||
* BoolArray ===> boolean[] booleanArray = new boolean[2]; .... 赋予值 | |||
* ByteArray ===> byte[] write_byteArrays = new byte[2]; | |||
* WordArray ===> short[] shortArrays_content = new short[2]; | |||
* DWordArray ===> int[] intArrays_content = new int[2]; | |||
* CharArray ===> char[] charArrays_content = new char[2]; | |||
* SIntArray ===> int[] sintArrays_content = new int[2]; | |||
* IntArray ===> int[] iintArrays_content = new int[2]; | |||
* DIntArray ===> int[] dintArrays_content = new int[2]; | |||
* UIntArray ===> int[] uintArrays_content = new int[3]; | |||
* USIntArray ===> int[] usintArrays_content = new int[3]; | |||
* UDIntArray ===> int[] udintArrays_content = new int[3]; | |||
* StringArray ===> String[] stringArrays_content = new String[3]; | |||
* //如果有其他数据类型 这里没有找我扩展 | |||
* | |||
* | |||
* */ | |||
public void write(DaveArea area, Integer areaNumber, Integer byteOffset, Integer bitOffset,Integer strSize, PlcVar type, Object newValue) throws Exception { | |||
S7Connector connector = getConnector(); | |||
S7RetryTemplate.getInstance().execute( | |||
context -> { | |||
//String 类型比较特殊。 String[] 也是同理,Sring数组里面的子项 也是有两个字节的 readBytes。 | |||
if(type.equals(PlcVar.STRING)){ | |||
connector.write( | |||
area, | |||
areaNumber, | |||
byteOffset, | |||
bitOffset, | |||
ByteUtils.strToBytes(newValue.toString(), strSize), | |||
type | |||
); | |||
}else if(type.equals(PlcVar.BOOL_Array)){ | |||
connector.write( | |||
area, | |||
areaNumber, | |||
byteOffset, | |||
bitOffset, | |||
ByteUtils.toByteArray((boolean[])newValue), | |||
type | |||
); | |||
}else if(type.equals(PlcVar.STRING_Array)){ | |||
//todo here 检查 read write service | |||
connector.write( | |||
area, | |||
areaNumber, | |||
byteOffset, | |||
bitOffset, | |||
ByteUtils.strArrayToBytes((String[])newValue, strSize), | |||
type | |||
); | |||
}else { | |||
byte[] bytes = type.toBytes(newValue); | |||
connector.write( | |||
area, | |||
areaNumber, | |||
byteOffset, | |||
bitOffset, | |||
bytes, | |||
type | |||
); | |||
} | |||
return null; | |||
}, | |||
context -> { | |||
logger.info("S7-Retry-Write : 已达到最大重试次数: "+S7RetryTemplate.getMaxRetryTimes()+", 现在尝试重新连接"); | |||
if( replaceConnector(connector) == 1 ){ | |||
logger.info("S7-Retry-Write 现在恢复成功,创建新connection成功"); | |||
return null; | |||
}else { | |||
logger.info("S7-Retry-Write 现在恢复失败,创建新connection失败"); | |||
return null; | |||
} | |||
} | |||
); | |||
} | |||
/** | |||
* desc: 传入的connection 是需要被替换的 | |||
* return: | |||
* 1 代表替换成功 | |||
* -1 代表替换失败(创建connection失败,原因:出现异常.原来的connection也被舍弃掉了) | |||
* */ | |||
private Integer replaceConnector(S7Connector oldOne){ | |||
connections.remove(oldOne); | |||
S7Connector connect = connect(host, rack, slot); | |||
if(connect == null){ | |||
return -1; | |||
}else { | |||
connections.add(connect); | |||
return 1; | |||
} | |||
} | |||
public S7Connector getConnector() { | |||
int size = connections.size(); | |||
S7Connector s7Connector = connections.get((pickOne + size) % size); | |||
pickOne+=1; | |||
pickOne = (pickOne)%size; | |||
return s7Connector; | |||
} | |||
private S7Connector connect(String host,Integer rack,Integer slot ){ | |||
try { | |||
S7Connector connector = S7ConnectorFactory | |||
.buildTCPConnector() | |||
.withHost(host) | |||
.withRack(rack) //optional rack 是机架号 | |||
.withSlot(slot) //optional slot 是插槽号 | |||
.build(); | |||
return connector; | |||
}catch (S7Exception e){ | |||
// logger.info("创建S7Connector 连接失败,原因:"+e.getMessage()); | |||
return null; | |||
} | |||
} | |||
private void resetConnetctions(){ | |||
this.connections = new ArrayList<S7Connector>(); | |||
connectionPool(); | |||
} | |||
private void connectionPool(){ | |||
for(int i=0;i<coreSize;i++){ | |||
S7Connector connect = connect(host, rack, slot); | |||
if(connect!=null){ | |||
connections.add(connect); | |||
} | |||
} | |||
//todo 在plc上新增一个 变量来解决 心跳问题 okok | |||
} | |||
} |
@@ -0,0 +1,64 @@ | |||
/* | |||
Copyright 2016 S7connector members (github.com/s7connector) | |||
Licensed under the Apache License, Version 2.0 (the "License"); | |||
you may not use this file except in compliance with the License. | |||
You may obtain a copy of the License at | |||
http://www.apache.org/licenses/LICENSE-2.0 | |||
Unless required by applicable law or agreed to in writing, software | |||
distributed under the License is distributed on an "AS IS" BASIS, | |||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
See the License for the specific language governing permissions and | |||
limitations under the License. | |||
*/ | |||
package com.qgs.dc.s7.my.s7connector.exception; | |||
/** | |||
* The Class S7Exception is an exception related to S7 Communication | |||
*/ | |||
public final class S7CheckResultException extends RuntimeException { | |||
/** The Constant serialVersionUID. */ | |||
private static final long serialVersionUID = -4761415733559374116L; | |||
/** | |||
* Instantiates a new s7 exception. | |||
*/ | |||
public S7CheckResultException() { | |||
} | |||
/** | |||
* Instantiates a new s7 exception. | |||
* | |||
* @param message | |||
* the message | |||
*/ | |||
public S7CheckResultException(final String message) { | |||
super(message); | |||
} | |||
/** | |||
* Instantiates a new s7 exception. | |||
* | |||
* @param message | |||
* the message | |||
* @param cause | |||
* the cause | |||
*/ | |||
public S7CheckResultException(final String message, final Throwable cause) { | |||
super(message, cause); | |||
} | |||
/** | |||
* Instantiates a new s7 exception. | |||
* | |||
* @param cause | |||
* the cause | |||
*/ | |||
public S7CheckResultException(final Throwable cause) { | |||
super(cause); | |||
} | |||
} |
@@ -0,0 +1,66 @@ | |||
/* | |||
Copyright 2016 S7connector members (github.com/s7connector) | |||
Licensed under the Apache License, Version 2.0 (the "License"); | |||
you may not use this file except in compliance with the License. | |||
You may obtain a copy of the License at | |||
http://www.apache.org/licenses/LICENSE-2.0 | |||
Unless required by applicable law or agreed to in writing, software | |||
distributed under the License is distributed on an "AS IS" BASIS, | |||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
See the License for the specific language governing permissions and | |||
limitations under the License. | |||
*/ | |||
package com.qgs.dc.s7.my.s7connector.exception; | |||
import java.io.IOException; | |||
/** | |||
* The Class S7IOException is an exception related to S7 Communication | |||
*/ | |||
public final class S7IOException extends RuntimeException { | |||
/** The Constant serialVersionUID. */ | |||
private static final long serialVersionUID = -4761415733559374116L; | |||
/** | |||
* Instantiates a new s7 exception. | |||
*/ | |||
public S7IOException() { | |||
} | |||
/** | |||
* Instantiates a new s7 exception. | |||
* | |||
* @param message | |||
* the message | |||
*/ | |||
public S7IOException(final String message) { | |||
super(message); | |||
} | |||
/** | |||
* Instantiates a new s7 exception. | |||
* | |||
* @param message | |||
* the message | |||
* @param cause | |||
* the cause | |||
*/ | |||
public S7IOException(final String message, final Throwable cause) { | |||
super(message, cause); | |||
} | |||
/** | |||
* Instantiates a new s7 exception. | |||
* | |||
* @param cause | |||
* the cause | |||
*/ | |||
public S7IOException(final Throwable cause) { | |||
super(cause); | |||
} | |||
} |
@@ -0,0 +1,64 @@ | |||
/* | |||
Copyright 2016 S7connector members (github.com/s7connector) | |||
Licensed under the Apache License, Version 2.0 (the "License"); | |||
you may not use this file except in compliance with the License. | |||
You may obtain a copy of the License at | |||
http://www.apache.org/licenses/LICENSE-2.0 | |||
Unless required by applicable law or agreed to in writing, software | |||
distributed under the License is distributed on an "AS IS" BASIS, | |||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
See the License for the specific language governing permissions and | |||
limitations under the License. | |||
*/ | |||
package com.qgs.dc.s7.my.s7connector.exception; | |||
/** | |||
* The Class S7Exception is an exception related to S7 Communication | |||
*/ | |||
public final class S7ParseDataException extends RuntimeException { | |||
/** The Constant serialVersionUID. */ | |||
private static final long serialVersionUID = -4761415733559374116L; | |||
/** | |||
* Instantiates a new s7 exception. | |||
*/ | |||
public S7ParseDataException() { | |||
} | |||
/** | |||
* Instantiates a new s7 exception. | |||
* | |||
* @param message | |||
* the message | |||
*/ | |||
public S7ParseDataException(final String message) { | |||
super(message); | |||
} | |||
/** | |||
* Instantiates a new s7 exception. | |||
* | |||
* @param message | |||
* the message | |||
* @param cause | |||
* the cause | |||
*/ | |||
public S7ParseDataException(final String message, final Throwable cause) { | |||
super(message, cause); | |||
} | |||
/** | |||
* Instantiates a new s7 exception. | |||
* | |||
* @param cause | |||
* the cause | |||
*/ | |||
public S7ParseDataException(final Throwable cause) { | |||
super(cause); | |||
} | |||
} |
@@ -17,6 +17,8 @@ package com.qgs.dc.s7.my.s7connector.impl; | |||
import com.qgs.dc.s7.my.s7connector.api.DaveArea; | |||
import com.qgs.dc.s7.my.s7connector.api.S7Connector; | |||
import com.qgs.dc.s7.my.s7connector.exception.S7CheckResultException; | |||
import com.qgs.dc.s7.my.s7connector.exception.S7Exception; | |||
import com.qgs.dc.s7.my.s7connector.impl.nodave.Nodave; | |||
import com.qgs.dc.s7.my.s7connector.impl.nodave.S7Connection; | |||
import com.qgs.dc.s7.my.s7connector.type.PlcVar; | |||
@@ -32,7 +34,7 @@ public abstract class S7BaseConnection implements S7Connector { | |||
/** The Constant MAX_SIZE. */ | |||
//private static final int MAX_SIZE = 96; //原版96 ;; ioserver 127 ;; | |||
private static final int MAX_SIZE = 212; //S7-1200-maxReadBytes = 212 ;; S7-1200-maxReadBytes = 452 | |||
private static final int MAX_SIZE = 212; //S7-1200-maxReadBytes = 212 ;; S7-1500-maxReadBytes = 452 | |||
/** The Constant PROPERTY_AREA. */ | |||
public static final String PROPERTY_AREA = "area"; | |||
@@ -52,11 +54,11 @@ public abstract class S7BaseConnection implements S7Connector { | |||
* @param libnodaveResult | |||
* the libnodave result | |||
*/ | |||
public static void checkResult(final int libnodaveResult) throws Exception { | |||
public static void checkResult(final int libnodaveResult) { | |||
if (libnodaveResult != Nodave.RESULT_OK) { | |||
final String msg = Nodave.strerror(libnodaveResult); | |||
// throw new IllegalArgumentException("Result: " + msg); | |||
throw new Exception(msg); | |||
throw new S7CheckResultException("errMsg : "+msg); | |||
} | |||
} | |||
@@ -87,7 +89,7 @@ public abstract class S7BaseConnection implements S7Connector { | |||
/** {@inheritDoc} */ | |||
@Override | |||
public synchronized byte[] read(final DaveArea area, final int areaNumber, final int bytes, final int offset) throws Exception { | |||
public synchronized byte[] read(final DaveArea area, final int areaNumber, final int bytes, final int offset) { | |||
if (bytes > MAX_SIZE) { | |||
final byte[] ret = new byte[bytes]; | |||
//注意这里 嵌套了 递归,让无限递归去解决 MAX_SIZE的问题。 | |||
@@ -108,7 +110,7 @@ public abstract class S7BaseConnection implements S7Connector { | |||
} | |||
} | |||
@Override | |||
public synchronized byte[] read(final DaveArea area, final int areaNumber, final int bytes, final int offset, int bitOffset, TransportSize transportSize) throws Exception { | |||
public synchronized byte[] read(final DaveArea area, final int areaNumber, final int bytes, final int offset, int bitOffset, TransportSize transportSize) { | |||
if(bitOffset==0){ | |||
return read(area,areaNumber,bytes,offset); | |||
} | |||
@@ -128,6 +130,7 @@ public abstract class S7BaseConnection implements S7Connector { | |||
final byte[] buffer = new byte[bytes]; | |||
final int ret = this.dc.readBytes(area, areaNumber, offset,bitOffset, bytes, buffer,transportSize); | |||
//thr resultException | |||
checkResult(ret); | |||
return buffer; | |||
} | |||
@@ -135,7 +138,7 @@ public abstract class S7BaseConnection implements S7Connector { | |||
/** {@inheritDoc} */ | |||
@Override | |||
public synchronized void write(final DaveArea area, final int areaNumber, final int offset, final byte[] buffer) throws Exception { | |||
public synchronized void write(final DaveArea area, final int areaNumber, final int offset, final byte[] buffer) { | |||
if (buffer.length > MAX_SIZE) { | |||
// Split buffer | |||
final byte[] subBuffer = new byte[MAX_SIZE]; | |||
@@ -155,7 +158,7 @@ public abstract class S7BaseConnection implements S7Connector { | |||
} | |||
@Override | |||
public synchronized void write(final DaveArea area, final int areaNumber, final int byteOffset, final int bitOffset, final byte[] buffer, PlcVar var) throws Exception { | |||
public synchronized void write(final DaveArea area, final int areaNumber, final int byteOffset, final int bitOffset, final byte[] buffer, PlcVar var) { | |||
if(bitOffset==0){ | |||
write(area,areaNumber,byteOffset,buffer); | |||
return; | |||
@@ -15,6 +15,7 @@ limitations under the License. | |||
*/ | |||
package com.qgs.dc.s7.my.s7connector.impl.nodave; | |||
import com.qgs.dc.s7.my.s7connector.exception.S7IOException; | |||
import org.slf4j.Logger; | |||
import org.slf4j.LoggerFactory; | |||
@@ -45,7 +46,7 @@ public final class PLCinterface { | |||
this.protocol = protocol; | |||
} | |||
public int read(final byte[] b, int start, int len) { | |||
public int read(final byte[] b, int start, int len){ | |||
int res; | |||
try { | |||
int retry = 0; | |||
@@ -67,18 +68,20 @@ public final class PLCinterface { | |||
} | |||
return res; | |||
} catch (final IOException e) { | |||
logger.info("Socket read字节流失败: "+e); | |||
return 0; | |||
String errMsg = "Socket read字节流失败: "+e.getMessage(); | |||
logger.info(errMsg); | |||
throw new S7IOException(errMsg); | |||
} | |||
} | |||
public int write(final byte[] b, final int start, final int len) { | |||
public int write(final byte[] b, final int start, final int len){ | |||
try { | |||
this.out.write(b, start, len); | |||
return 1; | |||
} catch (final IOException e) { | |||
logger.info("Socket write字节流失败: "+e); | |||
return 0; | |||
String errMsg = "Socket write字节流失败: "+e.getMessage(); | |||
logger.info(errMsg); | |||
throw new S7IOException(errMsg); | |||
} | |||
} | |||
@@ -76,7 +76,7 @@ public abstract class S7Connection { | |||
* it's NULL you can get your data from the resultPointer in daveConnection | |||
* long as you do not send further requests. | |||
*/ | |||
public ResultSet execReadRequest(final PDU p) { | |||
public ResultSet execReadRequest(final PDU p) throws Exception { | |||
PDU p2; | |||
int errorState; | |||
errorState = this.exchange(p); | |||
@@ -269,7 +269,7 @@ public abstract class S7Connection { | |||
* build the PDU for a PDU length negotiation | |||
* 构建第二次握手 | |||
*/ | |||
public int negPDUlengthRequest() { | |||
public int negPDUlengthRequest() throws Exception { | |||
int res; | |||
final PDU p = new PDU(this.msgOut, this.PDUstartOut); | |||
//S7-Param 构造 | |||
@@ -320,7 +320,7 @@ public abstract class S7Connection { | |||
final PDU p1 = new PDU(this.msgOut, this.PDUstartOut); | |||
p1.initReadRequest(); | |||
p1.addVarToReadRequest(area, DBnum, start, len); | |||
//发送read var request | |||
//通过tcp链接 发送read var request,thr S7IOException | |||
res = this.exchange(p1); | |||
if (res != Nodave.RESULT_OK) { | |||
this.semaphore.release(); | |||
@@ -17,6 +17,7 @@ package com.qgs.dc.s7.my.s7connector.impl.nodave; | |||
import com.qgs.dc.s7.my.s7connector.api.utils.ByteUtils; | |||
import com.qgs.dc.s7.my.s7connector.enmuc.S7Client; | |||
import com.qgs.dc.s7.my.s7connector.exception.S7IOException; | |||
import org.slf4j.Logger; | |||
import org.slf4j.LoggerFactory; | |||
@@ -59,7 +60,7 @@ public final class TCPConnection extends S7Connection { | |||
* | |||
* @return the int | |||
*/ | |||
public int connectPLC() { | |||
public int connectPLC() throws Exception { | |||
//COTP包 字节流(是上位机 和plc进行的第一次握手) | |||
final byte[] b4 = { //b4.length = 18 | |||
(byte) 0x11, //(16进制)当前字节以后的字节数 | |||
@@ -113,7 +114,7 @@ public final class TCPConnection extends S7Connection { | |||
* 0 代表成功 | |||
* */ | |||
@Override | |||
public int exchange(final PDU p1) { | |||
public int exchange(final PDU p1){ | |||
this.msgOut[4] = (byte) 0x02; //当前字节以后的字节数(也就是下面两个字节) | |||
this.msgOut[5] = (byte) 0xf0; //PDU Type | |||
this.msgOut[6] = (byte) 0x80; //TPDU number (如果这个字节首位为1 代表就是最后一个传输单元了 如1000 0000 ;; 如果首字母不为1 就代表这个Request是比较长的要分多个unit传输,后7位就代表传输单元序号) | |||
@@ -123,9 +124,9 @@ public final class TCPConnection extends S7Connection { | |||
if(writeRes!=0 && readRes!=0){ | |||
return 0; | |||
}else { | |||
logger.info("exchange 出现了问题:① writeRes:"+(writeRes==0?"writeISOPacket成功":"writeISOPacket失败")+"② readRes:"+(readRes==0?"readISOPacket成功":"readISOPacket失败")); | |||
return 1 ; | |||
String errMsg = "exchange 出现了问题:① writeRes:"+(writeRes==0?"writeISOPacket成功":"writeISOPacket失败")+"② readRes:"+(readRes==0?"readISOPacket成功":"readISOPacket失败"); | |||
logger.info(errMsg); | |||
throw new S7IOException(errMsg); | |||
} | |||
} | |||
@@ -133,15 +134,14 @@ public final class TCPConnection extends S7Connection { | |||
* Read iso packet. | |||
* | |||
* @return the int | |||
* 0 ==> 不成功 | |||
* 0 ==> 不成功(异常) | |||
* -1 ==> 读到一个空的字节流 (这种情况很少) | |||
* 其他 ==> 成功 | |||
*/ | |||
protected int readISOPacket() { | |||
protected int readISOPacket(){ | |||
//read return 为0 就是异常 | |||
int res = this.iface.read(this.msgIn, 0, 4); | |||
if (res == 4) { | |||
// final int len = (0x100 * this.msgIn[2]) + this.msgIn[3]; //读取字节数 这串是bug代码 | |||
final int len = (0x100 * ByteUtils.toUInt(this.msgIn[2])) + ByteUtils.toUInt(this.msgIn[3]); | |||
res += this.iface.read(this.msgIn, 4, len); | |||
} else { | |||
@@ -159,7 +159,7 @@ public final class TCPConnection extends S7Connection { | |||
* 1 ==> 成功 | |||
* 0 ==> 不成功(出现异常了) | |||
*/ | |||
protected int sendISOPacket(int size) { | |||
protected int sendISOPacket(int size){ | |||
size += 4; | |||
//下面包装的 是TPKT 字节包(第一次握手) | |||
this.msgOut[0] = (byte) 0x03; //Version ,默认版本3 | |||
@@ -330,7 +330,7 @@ public enum PlcVar { | |||
* | |||
* | |||
* */ | |||
public Object toObject(byte[] value) throws ParseException, UnsupportedEncodingException { | |||
public Object toObject(byte[] value) { | |||
if(!isArray){ | |||
Object res = null; | |||
switch (dataType) { | |||
@@ -0,0 +1,60 @@ | |||
package com.qgs.dc.s7.retry; | |||
import com.qgs.dc.s7.my.s7connector.exception.S7CheckResultException; | |||
import com.qgs.dc.s7.my.s7connector.exception.S7IOException; | |||
import org.springframework.remoting.RemoteAccessException; | |||
import org.springframework.retry.backoff.FixedBackOffPolicy; | |||
import org.springframework.retry.policy.SimpleRetryPolicy; | |||
import org.springframework.retry.support.RetryTemplate; | |||
import java.util.HashMap; | |||
import java.util.Map; | |||
/** | |||
* @author caixiang | |||
* @description 重试模板和重试策略 | |||
*/ | |||
public class S7RetryTemplate extends RetryTemplate { | |||
private volatile static S7RetryTemplate instance = null; | |||
/** | |||
* 重试间隔时间ms,默认1000ms | |||
* */ | |||
private static long fixedPeriodTime = 500L; | |||
/** | |||
* 最大重试次数,默认为3 | |||
*/ | |||
private static int maxRetryTimes = 3; | |||
/** | |||
* 表示哪些异常需要重试,key表示异常的字节码,value为true表示需要重试 | |||
*/ | |||
private static Map<Class<? extends Throwable>, Boolean> exceptionMap = new HashMap<>(); | |||
private S7RetryTemplate() { | |||
// 代表S7IOException 这个异常是需要重试的(true), 如果你想设置这个异常不去重试,那么可以把它设置为false。 | |||
exceptionMap.put(S7IOException.class,true); | |||
exceptionMap.put(S7CheckResultException.class,true); | |||
} | |||
public static int getMaxRetryTimes() { | |||
return maxRetryTimes; | |||
} | |||
public static S7RetryTemplate getInstance() { | |||
if (instance == null) { | |||
synchronized (S7RetryTemplate.class) { | |||
if (instance == null) { | |||
FixedBackOffPolicy backOffPolicy = new FixedBackOffPolicy(); | |||
// 定义重试间隔-间隔 fixedPeriodTime ms再重试,总共重试3次 | |||
backOffPolicy.setBackOffPeriod(fixedPeriodTime); | |||
instance = new S7RetryTemplate(); | |||
// 定义重试次数- maxRetryTimes | |||
SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy(maxRetryTimes, exceptionMap); | |||
instance.setRetryPolicy(retryPolicy); | |||
instance.setBackOffPolicy(backOffPolicy); | |||
} | |||
} | |||
} | |||
return instance; | |||
} | |||
} |
@@ -0,0 +1,39 @@ | |||
package com.qgs.dc.s7.retrydemo; | |||
import org.apache.commons.lang3.RandomUtils; | |||
import org.springframework.remoting.RemoteAccessException; | |||
/** | |||
* @Author: cx | |||
* @Description: | |||
*/ | |||
public class RetryDemoTask { | |||
/** | |||
* 重试方法 | |||
* @return | |||
*/ | |||
public static boolean retryTask(String param) { | |||
System.out.println("retry-task : 收到请求参数:"+param); | |||
int i = RandomUtils.nextInt(0,11); | |||
System.out.println("retry-task : 随机生成的数:"+i); | |||
if (i == 0) { | |||
System.out.println("retry-task : 为0,抛出参数异常."); | |||
//因为Illeague这个异常我们没有在exceptionMap里面配置,所以 抛出这个异常后 | |||
//spring-retry不会进行重试,而是会直接进入recovery函数 | |||
throw new IllegalArgumentException("retry-task : 参数异常"); | |||
}else if (i == 1){ | |||
System.out.println("retry-task : 为1,返回true."); | |||
return true; | |||
}else if (i == 2){ | |||
System.out.println("retry-task : 为2,返回false."); | |||
return false; | |||
}else{ | |||
//因为RemoteAccessExcep这个异常我们在exceptionMap里面配置了,所以 抛出这个异常后 | |||
//spring-retry会进行重试 | |||
System.out.println("retry-task : 大于2,抛出自定义异常."); | |||
throw new RemoteAccessException("retry-task : 大于2,抛出远程访问异常"); | |||
} | |||
} | |||
} |
@@ -0,0 +1,58 @@ | |||
package com.qgs.dc.s7.retrydemo; | |||
import org.springframework.remoting.RemoteAccessException; | |||
import org.springframework.retry.backoff.FixedBackOffPolicy; | |||
import org.springframework.retry.policy.SimpleRetryPolicy; | |||
import org.springframework.retry.support.RetryTemplate; | |||
import java.util.HashMap; | |||
import java.util.Map; | |||
public class RetryMain { | |||
public static void main(String[] args) { | |||
/** | |||
* 重试间隔时间ms,默认1000ms | |||
* */ | |||
long fixedPeriodTime = 1000L; | |||
/** | |||
* 最大重试次数,默认为3 | |||
*/ | |||
int maxRetryTimes = 3; | |||
/** | |||
* 表示哪些异常需要重试,key表示异常的字节码,value为true表示需要重试 | |||
*/ | |||
Map<Class<? extends Throwable>, Boolean> exceptionMap = new HashMap<>(); | |||
exceptionMap.put(RemoteAccessException.class,true); | |||
// 构建重试模板实例 | |||
RetryTemplate retryTemplate = new RetryTemplate(); | |||
// 设置重试回退操作策略,主要设置重试间隔时间 | |||
FixedBackOffPolicy backOffPolicy = new FixedBackOffPolicy(); | |||
backOffPolicy.setBackOffPeriod(fixedPeriodTime); | |||
// 设置重试策略,主要设置重试次数 | |||
SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy(maxRetryTimes, exceptionMap); | |||
retryTemplate.setRetryPolicy(retryPolicy); | |||
retryTemplate.setBackOffPolicy(backOffPolicy); | |||
Boolean execute = retryTemplate.execute( | |||
//RetryCallback | |||
retryContext -> { | |||
boolean b = RetryDemoTask.retryTask("abc"); | |||
System.err.println("retry-main : 调用的结果:"+b+",times:"+retryContext.getRetryCount()); | |||
return b; | |||
}, | |||
retryContext -> { | |||
//RecoveryCallback | |||
System.err.println("retry-main : 已达到最大重试次数或抛出了不重试的异常~~~"); | |||
return false; | |||
} | |||
); | |||
System.err.println("retry-main : 执行结果:"+execute); | |||
} | |||
} |
@@ -0,0 +1,66 @@ | |||
package com.qgs.dc.s7.retrydemo; | |||
import org.junit.Test; | |||
import org.springframework.remoting.RemoteAccessException; | |||
import org.springframework.retry.backoff.FixedBackOffPolicy; | |||
import org.springframework.retry.policy.SimpleRetryPolicy; | |||
import org.springframework.retry.support.RetryTemplate; | |||
import java.util.HashMap; | |||
import java.util.Map; | |||
/** | |||
* @Author: zgd | |||
* @Description: spring-retry 重试框架 | |||
*/ | |||
public class SpringS7RetryTemplateTest { | |||
/** | |||
* 重试间隔时间ms,默认1000ms | |||
* */ | |||
private long fixedPeriodTime = 1000L; | |||
/** | |||
* 最大重试次数,默认为3 | |||
*/ | |||
private int maxRetryTimes = 3; | |||
/** | |||
* 表示哪些异常需要重试,key表示异常的字节码,value为true表示需要重试 | |||
*/ | |||
private Map<Class<? extends Throwable>, Boolean> exceptionMap = new HashMap<>(); | |||
@Test | |||
public void test() { | |||
//文档:https://blog.csdn.net/minghao0508/article/details/123972703 | |||
exceptionMap.put(RemoteAccessException.class,true); | |||
// 构建重试模板实例 | |||
RetryTemplate retryTemplate = new RetryTemplate(); | |||
// 设置重试回退操作策略,主要设置重试间隔时间 | |||
FixedBackOffPolicy backOffPolicy = new FixedBackOffPolicy(); | |||
backOffPolicy.setBackOffPeriod(fixedPeriodTime); | |||
// 设置重试策略,主要设置重试次数 | |||
SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy(maxRetryTimes, exceptionMap); | |||
retryTemplate.setRetryPolicy(retryPolicy); | |||
retryTemplate.setBackOffPolicy(backOffPolicy); | |||
Boolean execute = retryTemplate.execute( | |||
//RetryCallback | |||
retryContext -> { | |||
boolean b = RetryDemoTask.retryTask("abc"); | |||
System.out.println("调用的结果:"+b); | |||
return b; | |||
}, | |||
retryContext -> { | |||
//RecoveryCallback | |||
System.out.println("已达到最大重试次数或抛出了不重试的异常~~~"); | |||
return false; | |||
} | |||
); | |||
System.out.println("执行结果:"+execute); | |||
} | |||
} |
@@ -0,0 +1,20 @@ | |||
package com.qgs.dc.s7.retrydemo; | |||
import java.util.ArrayList; | |||
import java.util.List; | |||
public class TestMain { | |||
public static void main(String[] args) { | |||
Integer a = 1; | |||
Integer a1 = 2; | |||
Integer a2 = 3; | |||
List<Integer> list = new ArrayList<Integer>(); | |||
list.add(a); | |||
list.add(a1); | |||
list.add(a2); | |||
Integer integer = list.get(0); | |||
list.remove(integer); | |||
System.out.println(); | |||
} | |||
} |