2016-01-15 1 views
4

Мне нужно добавить подпись с использованием сертификата X509 в запросе SOAP в Apache JMeter. У меня уже есть .p12 со мной. Пожалуйста, помогите, как я могу достичь этого в Apache JMeter. Я знаю, как это сделать в SOAPUI, но не найти никакого способа в JMeter.Добавить подпись в запросе SOAP в Apache JMeter

ответ

2

Для шифрования сообщения необходимо выполнить некоторые скрипты, используя JSR223 PreProcessor. Идея состоит в том, чтобы получить текущий корпус пробоотборника, зашифровать его и заменить на лету.

  1. Добавить JSR223 препроцессор как ребенок запроса
  2. Используйте следующий код в качестве ссылки:

    import com.sun.org.apache.xml.internal.security.Init; 
    import com.sun.org.apache.xml.internal.security.c14n.Canonicalizer; 
    import com.sun.org.apache.xml.internal.security.signature.XMLSignature; 
    import com.sun.org.apache.xml.internal.security.transforms.Transforms; 
    import com.sun.org.apache.xml.internal.security.utils.Constants; 
    import com.sun.org.apache.xml.internal.security.utils.XMLUtils; 
    import org.apache.jmeter.protocol.http.sampler.SoapSampler; 
    import org.apache.commons.io.FileUtils; 
    import org.w3c.dom.Document; 
    import org.w3c.dom.Element; 
    
    import javax.xml.parsers.DocumentBuilder; 
    import javax.xml.parsers.DocumentBuilderFactory; 
    import java.io.ByteArrayInputStream; 
    import java.io.File; 
    import java.io.FileInputStream; 
    import java.io.FileOutputStream; 
    import java.security.KeyStore; 
    import java.security.PrivateKey; 
    import java.security.cert.X509Certificate; 
    import java.text.SimpleDateFormat; 
    import java.util.Date;   
    
    //write sampler body into "signature.xml" file 
    
    String body = sampler.getXmlData(); 
    FileUtils.writeStringToFile(new File("signature.xml"),body); 
    
    //X509 properties 
    
    String keystoreType = "JKS"; 
    String keystoreFile = "wso2carbon.jks"; 
    String keystorePass = "wso2carbon"; 
    String privateKeyAlias = "wso2carbon"; 
    String privateKeyPass = "wso2carbon"; 
    String certificateAlias = "wso2carbon"; 
    
    
    Element element = null; 
    String BaseURI = signatureFile.toURI().toURL().toString(); 
    //SOAP envelope to be signed 
    
    
    //get the private key used to sign, from the keystore 
    KeyStore ks = KeyStore.getInstance(keystoreType); 
    FileInputStream fis = new FileInputStream(keystoreFile); 
    ks.load(fis, keystorePass.toCharArray()); 
    PrivateKey privateKey = 
    
    (PrivateKey) ks.getKey(privateKeyAlias, privateKeyPass.toCharArray()); 
    //create basic structure of signature 
    javax.xml.parsers.DocumentBuilderFactory dbf = 
        javax.xml.parsers.DocumentBuilderFactory.newInstance(); 
    dbf.setNamespaceAware(true); 
    DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance(); 
    DocumentBuilder dBuilder = dbFactory.newDocumentBuilder(); 
    String request = sampler.getXmlData(); 
    ByteArrayInputStream in = new ByteArrayInputStream(request.getBytes()); 
    Document doc = dBuilder.parse(in); 
    in.close(); 
    Init.init(); 
    XMLSignature sig = 
        new XMLSignature(doc, BaseURI, XMLSignature.ALGO_ID_SIGNATURE_RSA); 
    
    element = doc.getDocumentElement(); 
    element.normalize(); 
    element.getElementsByTagName("soapenv:Header").item(0).appendChild(sig.getElement()); 
    
    { 
    Transforms transforms = new Transforms(doc); 
    transforms.addTransform(Canonicalizer.ALGO_ID_C14N_EXCL_OMIT_COMMENTS); 
    //Sign the content of SOAP Envelope 
    sig.addDocument("", transforms, Constants.ALGO_ID_DIGEST_SHA1); 
    } 
    
    //Signing procedure 
    { 
    X509Certificate cert = 
         (X509Certificate) ks.getCertificate(certificateAlias); 
    sig.addKeyInfo(cert); 
    sig.addKeyInfo(cert.getPublicKey()); 
    sig.sign(privateKey); 
    } 
    
    //write signature to file 
    FileOutputStream f = new FileOutputStream(signatureFile); 
    XMLUtils.outputDOMc14nWithComments(doc, f); 
    f.close(); 
    
    
    //set sampler's XML data from file 
    String request = FileUtils.readFileToString(signatureFile); 
    sampler.setXmlData(request); 
    

    Вам нужно будет заменить хранилище ключей шифрования и связанные биты в соответствии с конфигурацией и обслуживание определение

Для получения более подробной информации см. Take the Pain out of Load Testing Secure Web Services

+0

Привет, Дмитрий, спасибо за ваш ответ. Я пытался это сделать, но он не работает. В этом скрипте я изменил параметры в свойствах X509 (keystoreFile, keystorePass, privateKeyAlias, privateKeyPass, certificateAlias). Язык сценария, который я выбрал в JSR223 PreProcessor, представляет собой Java. Не могли бы вы подробнее рассказать о других изменениях, которые мне нужно выполнить, чтобы правильно подписать мой запрос? Благодарю. –

+0

Возможно, вы должны использовать [groovy] (https://blazemeter.com/blog/beanshell-vs-jsr223-vs-java-jmeter-scripting-its-performance), как он упоминается в указанной статье. Поддержка Java-движка реализована через Beanshell, производительность которой не очень хороша, и она не соответствует 100% -ной совместимости с Java. –

+0

Я пробовал использовать groovy, но результат все тот же. Как было предложено в статье, которую вы упомянули, я использовал файл .groovy, который был создан для написания кода в пробоотборнике. Не могли бы вы точно указать, какие изменения мне нужно делать внутри скрипта или какой-либо дополнительный шаг, который мне нужно сделать? –

2

не знает Wheter вам нужен этот ответ еще, но я столкнулся с той же проблемой, и получил установленный через пару дней работы, и я полагаю, другие могут найти это полезно, а также ...

I основал мой ответ на ответ Дмитрия Т; the blog post on blazemeter, this bugreport + некоторые изготовленные под заказ взломали.

Давайте предположим, что вы должны подписать этот запрос:

<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:ser="http://custom.namespace.com/service-v1.0-rc2"> 
    <soap:Header> 
     <wsa:MessageID xmlns:wsa="http://www.w3.org/2004/12/addressing">a2749a0f-555-9135-367ed901d244</wsa:MessageID> 
    </soap:Header> 
    <soap:Body> 
     <ser:request> 
     <ser:person> 
      <ser:id>11552</ser:id> 
      <ser:number>81067776992</ser:number> 
     </ser:person> 
     </ser:request> 
    </soap:Body> 
</soap:Envelope> 

Вы следовали руководство по blazemeter до части с кодом пользовательского Java, чтобы подписать запрос.

Вместо использования этого кода необходимо выполнить следующие действия:

Скопируйте и вставьте следующий код в JMeter (3.0):

import com.example.wss.SOAPSecurity; 
import org.apache.jmeter.services.FileServer; 

// get SOAP message from parent sampler body 
String soapData = sampler.getArguments().getArgument(0).getValue(); 

String baseDir = FileServer.getFileServer().getBaseDir(); 

String pathToKeystore = baseDir + File.separator + "keystore_files" + File.separator + "your.jks"; 
String keystorePassword = "yourPassword"; 
int timeToLive = 5000; 
String signingAlias = "yourAlias"; 
String encryptAlias = "yourEncryptingAlias"; 
String secureSoap = ""; 

try { 
    secureSoap = SOAPSecurity.secureSoapMessageFromString(soapData, pathToKeystore, keystorePassword, null, null, timeToLive, signingAlias, yourEncryptingAlias); 
} 
catch (Exception ex){ 
    log.warn("Error in script", ex); 
    throw ex; 
} 

// replace parent sampler body with secured SOAP message 
sampler.getArguments().getArgument(0).setValue(secureSoap); 
vars.put("SoapDataRaw", secureSoap); 

Использование заводной в качестве переводчика.

Это файл Java класс приспособлен для моего случая использования:

package com.example.wss; 

import org.apache.ws.security.WSConstants; 
import org.apache.ws.security.WSEncryptionPart; 
import org.apache.ws.security.WSSecurityException; 
import org.apache.ws.security.components.crypto.Crypto; 
import org.apache.ws.security.components.crypto.CryptoFactory; 
import org.apache.ws.security.components.crypto.Merlin; 
import org.apache.ws.security.message.WSSecEncrypt; 
import org.apache.ws.security.message.WSSecHeader; 
import org.apache.ws.security.message.WSSecSignature; 
import org.apache.ws.security.message.WSSecTimestamp; 

import javax.xml.parsers.DocumentBuilder; 
import javax.xml.parsers.DocumentBuilderFactory; 
import javax.xml.parsers.ParserConfigurationException; 
import javax.xml.soap.*; 
import javax.xml.transform.Source; 
import javax.xml.transform.Transformer; 
import javax.xml.transform.TransformerConfigurationException; 
import javax.xml.transform.TransformerException; 
import javax.xml.transform.TransformerFactory; 
import javax.xml.transform.dom.DOMResult; 

import javax.net.ssl.*; 
import javax.xml.transform.dom.DOMSource; 
import java.io.*; 
import java.security.*; 
import java.security.cert.CertificateException; 
import java.util.ArrayList; 
import java.util.List; 
import java.util.Properties; 
import org.w3c.dom.Document; 
import org.w3c.dom.Element; 
import org.xml.sax.InputSource; 
import org.xml.sax.SAXException; 

public class SOAPSecurity { 

    private KeyStore keystore; 
    private KeyManagerFactory keyManagerFactory; 
    private String keystorePassword; 

    private TrustManagerFactory trustManagerFactory; 
    private KeyStore truststore; 
    private String truststorePassword; 

    private Crypto crypto; 

    public SOAPSecurity(String pathToKeystore, String keystorePassword, String pathToTruststore, String truststorePassword) throws IOException, NoSuchAlgorithmException, KeyStoreException, CertificateException, UnrecoverableKeyException, KeyManagementException, WSSecurityException { 

     keystore = KeyStore.getInstance("JKS"); 
     InputStream fileReader = new FileInputStream(new File(pathToKeystore)); 
     keystore.load(fileReader, keystorePassword.toCharArray()); 
     this.keystorePassword = keystorePassword; 

     keyManagerFactory = KeyManagerFactory.getInstance("SunX509"); 
     keyManagerFactory.init(keystore, keystorePassword.toCharArray()); 

     Properties properties = new Properties(); 
     properties.setProperty("org.apache.ws.security.crypto.provider", "org.apache.ws.security.components.crypto.Merlin"); 
     crypto = CryptoFactory.getInstance(properties); 
     ((Merlin) crypto).setKeyStore(keystore); 

     truststore = KeyStore.getInstance("JKS"); 
     fileReader = new FileInputStream(new File(pathToTruststore)); 
     truststore.load(fileReader, truststorePassword.toCharArray()); 
     this.truststorePassword = truststorePassword; 

     trustManagerFactory = TrustManagerFactory.getInstance("PKIX"); 
     trustManagerFactory.init(truststore); 
    } 

    //EDITOR: added constructor without truststore 
    public SOAPSecurity(String pathToKeystore, String keystorePassword) throws IOException, NoSuchAlgorithmException, KeyStoreException, CertificateException, UnrecoverableKeyException, KeyManagementException, WSSecurityException { 

     keystore = KeyStore.getInstance("JKS"); 
     InputStream fileReader = new FileInputStream(new File(pathToKeystore)); 
     keystore.load(fileReader, keystorePassword.toCharArray()); 
     this.keystorePassword = keystorePassword; 

     keyManagerFactory = KeyManagerFactory.getInstance("SunX509"); 
     keyManagerFactory.init(keystore, keystorePassword.toCharArray()); 

     Properties properties = new Properties(); 
     properties.setProperty("org.apache.ws.security.crypto.provider", "org.apache.ws.security.components.crypto.Merlin"); 
     crypto = CryptoFactory.getInstance(properties); 
     ((Merlin) crypto).setKeyStore(keystore); 
    } 

    public static String secureSoapMessageFromFile(String messagePath, String pathToKeystore, String keystorePassword, 
            String pathToTruststore, String trustStorePassword, int timeToLive, 
            String signingAlias, String encryptAlias) throws 
      SAXException, ParserConfigurationException, SOAPException, IOException, WSSecurityException, 
      TransformerException, UnrecoverableKeyException, CertificateException, NoSuchAlgorithmException, 
      KeyStoreException, KeyManagementException { 

     SOAPSecurity soapSecurity = new SOAPSecurity(pathToKeystore, keystorePassword, pathToTruststore, trustStorePassword); 
     SOAPMessage soapMessage = SOAPSecurity.createSOAPRequestFromFile(messagePath); 
     return soapSecurity.applyWSSecurity(soapMessage, timeToLive, signingAlias, encryptAlias); 
    } 

    public static String secureSoapMessageFromString(String messageString, String pathToKeystore, String keystorePassword, 
              String pathToTruststore, String trustStorePassword, int timeToLive, 
              String signingAlias, String encryptAlias) throws 
      SAXException, ParserConfigurationException, SOAPException, IOException, WSSecurityException, 
      TransformerException, UnrecoverableKeyException, CertificateException, NoSuchAlgorithmException, 
      KeyStoreException, KeyManagementException { 

     SOAPSecurity soapSecurity = new SOAPSecurity(pathToKeystore, keystorePassword); 
     SOAPMessage soapMessage = SOAPSecurity.createSOAPRequestFromString(messageString); 
     return soapSecurity.applyWSSecurity(soapMessage, timeToLive, signingAlias, encryptAlias); 
    } 

    //EDITOR: ...and static signing methods as well 
    public static String secureSoapMessageFromString(String messageString, String pathToKeystore, String keystorePassword, 
              int timeToLive, 
              String signingAlias, String encryptAlias) throws 
      SAXException, ParserConfigurationException, SOAPException, IOException, WSSecurityException, 
      TransformerException, UnrecoverableKeyException, CertificateException, NoSuchAlgorithmException, 
      KeyStoreException, KeyManagementException { 

     SOAPSecurity soapSecurity = new SOAPSecurity(pathToKeystore, keystorePassword); 
     SOAPMessage soapMessage = SOAPSecurity.createSOAPRequestFromString(messageString); 
     return soapSecurity.applyWSSecurity(soapMessage, timeToLive, signingAlias, encryptAlias); 
    } 

    public static String secureSoapMessageFromFile(String messagePath, String pathToKeystore, String keystorePassword, 
            int timeToLive, 
            String signingAlias, String encryptAlias) throws 
      SAXException, ParserConfigurationException, SOAPException, IOException, WSSecurityException, 
      TransformerException, UnrecoverableKeyException, CertificateException, NoSuchAlgorithmException, 
      KeyStoreException, KeyManagementException { 

     SOAPSecurity soapSecurity = new SOAPSecurity(pathToKeystore, keystorePassword); 
     SOAPMessage soapMessage = SOAPSecurity.createSOAPRequestFromFile(messagePath); 
     return soapSecurity.applyWSSecurity(soapMessage, timeToLive, signingAlias, encryptAlias); 
    } 

    public interface SOAPDocWriter { 
     Document writeDocument(String s, DocumentBuilder documentBuilder) throws IOException, SAXException; 
    } 

    public static SOAPMessage createSOAPRequestFromFile(String messagePath) throws SOAPException, IOException, ParserConfigurationException, SAXException { 

     SOAPDocWriter pathWriter = (s, d) -> { 
      File messageFile = new File(s); 
      return d.parse(new InputSource(new FileInputStream(messageFile))); 
     }; 

     return createSOAPRequestLambda(messagePath, pathWriter); 
    } 

    public static SOAPMessage createSOAPRequestFromString(String messageString) throws SOAPException, IOException, ParserConfigurationException, SAXException { 

     SOAPDocWriter stringWriter = (s, d) -> d.parse(new InputSource(new StringReader(s))); 
     return createSOAPRequestLambda(messageString, stringWriter); 
    } 

    private static SOAPMessage createSOAPRequestLambda(String s, SOAPDocWriter w) throws SOAPException, IOException, ParserConfigurationException, SAXException 
    { 
     //MessageFactory messageFactory = MessageFactory.newInstance(); 
     //we use the SOAP 1.2 specification 
     MessageFactory messageFactory = MessageFactory.newInstance(SOAPConstants.SOAP_1_2_PROTOCOL); 
     SOAPMessage soapMessage = messageFactory.createMessage(); 
     SOAPPart soapPart = soapMessage.getSOAPPart(); 

     SOAPEnvelope soapEnvelope = soapPart.getEnvelope(); 

     SOAPBody soapBody = soapEnvelope.getBody(); 

     DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); 
     documentBuilderFactory.setNamespaceAware(true); 
     DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); 
     Document document = w.writeDocument(s, documentBuilder); 

     soapBody.addDocument(document); 

     soapMessage.saveChanges(); 

     return soapMessage; 
    } 

    public static Document toDocument(SOAPMessage soapMsg) throws TransformerConfigurationException, TransformerException, SOAPException, IOException { 
     Source src = soapMsg.getSOAPPart().getContent(); 
     TransformerFactory tf = TransformerFactory.newInstance(); 
     Transformer transformer = tf.newTransformer(); 
     DOMResult result = new DOMResult(); 
     transformer.transform(src, result); 
     return (Document) result.getNode(); 
    } 

    /** 
    * Secures a soap message according to the given security actions 
    * 
    * @param soapMessage the soap message to be secured 
    * @param timestampTimeToLive optional: the time to live for the timestamp 
    * @param signatureKeyAlias optional: the alias for the signature key in the keystore 
    * @param encryptionKeyAlias optional: the alias for the encryption key in the keystore 
    * @throws WSSecurityException 
    * @throws IOException 
    * @throws SOAPException 
    * @throws TransformerException 
    */ 
    public String applyWSSecurity(SOAPMessage soapMessage, 
            int timestampTimeToLive, 
            String signatureKeyAlias, 
            String encryptionKeyAlias) throws WSSecurityException, IOException, SOAPException, TransformerException { 

     Document soapMessageDocument = toDocument(soapMessage); 

     // add security header 
     WSSecHeader securityHeader = new WSSecHeader(); 
     securityHeader.setMustUnderstand(false); 

     //we keep the security header element because we need it afterwards 

     Element secHead = securityHeader.insertSecurityHeader(soapMessageDocument); 

     /* 
     for a reason not yet clear to me this method of signing creates a soap request inside a soap request 
     I don't know why but I know how to work around it: 
     */ 

     //append the security header to the soap:Header parent 
     soapMessageDocument.getElementsByTagName("soap:Header").item(0).appendChild(secHead); 

     //move the soap:Envelope inner soap message to the root of the document and omit the env:Envelope tree 
     soapMessageDocument.replaceChild(soapMessageDocument.getElementsByTagName("soap:Envelope").item(0), 
       soapMessageDocument.getElementsByTagName("env:Envelope").item(0)); 

     //for debugging purposes -> this output shows up in the console output of JMeter.bat 
     //System.out.println("insert:"+soapMessageDocument.getFirstChild().getNodeName()+",soap:"+soapMessageDocument.getElementsByTagName("soap:Envelope").item(0).getNodeName()); 

     WSSecTimestamp timestamp = null; 

     // timestamp document 
     timestamp = new WSSecTimestamp(); 
     timestamp.setTimeToLive(timestampTimeToLive); 
     timestamp.build(soapMessageDocument, securityHeader); 

     // sign document 

     /* 
     EDITOR: originals are commented out and replaced by own values 
     values should be adapted from SOAPUI and searched for at: 
     https://ws.apache.org/wss4j/apidocs/org/apache/wss4j/dom/WSConstants.html 
     */ 

     WSSecSignature signatureBuilder = new WSSecSignature(); 
     signatureBuilder.setUserInfo(signatureKeyAlias, keystorePassword); 
     signatureBuilder.setKeyIdentifierType(WSConstants.BST_DIRECT_REFERENCE); 
     //signatureBuilder.setSignatureAlgorithm("http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"); 
     signatureBuilder.setSignatureAlgorithm("http://www.w3.org/2000/09/xmldsig#rsa-sha1"); 
     signatureBuilder.setSigCanonicalization(WSConstants.C14N_EXCL_OMIT_COMMENTS); 
     /* 
     also setDigestAlgo can be set 
     https://ws.apache.org/wss4j/apidocs/org/apache/wss4j/dom/message/WSSecSignature.html#setDigestAlgo-java.lang.String- 
     but I used the default so I didn't bother  
     */ 

     //also custom 
     signatureBuilder.setUseSingleCertificate(true); 

     List<WSEncryptionPart> signatureParts = new ArrayList<WSEncryptionPart>(); 

     //WSEncryptionPart timestampPart = new WSEncryptionPart(timestamp.getId()); 
     WSEncryptionPart timestampPart = new WSEncryptionPart("Timestamp","http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd","Content"); 
     signatureParts.add(timestampPart); 

     //WSEncryptionPart bodyPart = new WSEncryptionPart(WSConstants.ELEM_BODY, WSConstants.URI_SOAP11_ENV, "Element"); 
     WSEncryptionPart bodyPart = new WSEncryptionPart("Body","http://www.w3.org/2003/05/soap-envelope", "Content"); 
     signatureParts.add(bodyPart); 
     signatureBuilder.setParts(signatureParts); 

     signatureBuilder.build(soapMessageDocument, crypto, securityHeader); 

     // encrypt document 
/* 
      we didn't encrypt so no need for this code 

      (encryption code untested (by me)) 

     WSSecEncrypt encrypt = new WSSecEncrypt(); 
     encrypt.setKeyIdentifierType(WSConstants.BST_DIRECT_REFERENCE); 
     encrypt.setSymmetricEncAlgorithm(WSConstants.AES_128_GCM); 
     encrypt.setKeyEncAlgo(WSConstants.KEYTRANSPORT_RSAOEP); 
     encrypt.setUserInfo(encryptionKeyAlias, keystorePassword); 

     List<WSEncryptionPart> encryptionParts = new ArrayList<WSEncryptionPart>(); 
     WSEncryptionPart encryptionSignaturePart = new WSEncryptionPart("Signature", WSConstants.SIG_NS, "Element"); 
     WSEncryptionPart encryptionBodyPart = new WSEncryptionPart("Body", WSConstants.URI_SOAP11_ENV, "Content"); 
     encryptionParts.add(encryptionBodyPart); 
     encryptionParts.add(encryptionSignaturePart); 
     encrypt.setParts(encryptionParts); 
     encrypt.build(soapMessageDocument, crypto, securityHeader); 
*/ 

     DOMSource domSource = new DOMSource(soapMessageDocument); 
     soapMessage.getSOAPPart().setContent(domSource); 

     soapMessage.saveChanges(); 
     ByteArrayOutputStream out = new ByteArrayOutputStream(); 
     soapMessage.writeTo(out); 
     String strMsg = new String(out.toByteArray()); 
     return strMsg; 
    } 
} 

Поместите код в ком \ например \ WSS \ SOAPSecurity.java файл

Поместить WSS4J-1.6.18.jar в том же каталоге. http://archive.apache.org/dist/ws/wss4j/1.6.18/

Я скомпилировал/сборки/развернуть его с помощью этого .sh сценария (вы можете использовать Cygwin для этого):

/cygdrive/c/Program\ Files\ \(x86\)/Java/jdk1.8.0_73/bin/javac.exe -cp wss4j-1.6.18.jar com/example/wss/SOAPSecurity.java 
cp SOAPSecurity.jar SOAPSecurity.jar.bak$1 
zip -r test.zip com/ 
mv test.zip SOAPSecurity.jar 
cp SOAPSecurity.jar /cygdrive/c/Program\ Files\ \(x86\)/apache-jmeter-3.0/lib/ 

Run JMeter 3.0 с .bat файла

-> SOAPSecurity .jar должен находиться в папке apache-jmeter-3.0/lib/

-> У меня также есть эта конфигурация в системе JMeter.Свойства файла:

-Djavax.net.ssl.keyStore=C:/path/to/client.jks 
-Djavax.net.ssl.keyStorePassword=verySecret 

-> и добавил элемент конфигурации хранилища ключей для запроса мыла в JMeter

Итак, это о нем, не стесняйтесь, чтобы дать ему спину и дайте мне знать, работает ли он для вы, если бы я не предоставил некоторую помощь, если вы хотите отлаживать себя, отличный способ сделать это, адаптируя/размещая System.out.println в пользовательском классе, он дает очень полезную информацию о том, что происходит, и может быть полной спасательной жизнью,

Продолжайте веселиться !!!

S.

Смежные вопросы