JavaRush /Java Blog /Random-KO /Java의 외부화 가능 인터페이스

Java의 외부화 가능 인터페이스

Random-KO 그룹에 게시되었습니다
안녕하세요! 오늘은 Java 객체 직렬화 및 역직렬화에 대한 소개를 계속하겠습니다. 지난 강의에서 우리는 직렬화 가능 마커 인터페이스 에 대해 소개하고, 그 사용 예를 살펴봤으며, 임시 키워드 를 사용하여 직렬화 프로세스를 제어하는 ​​방법도 배웠습니다 . 물론 "프로세스를 관리한다"는 말은 강한 단어입니다. 하나의 키워드, 하나의 버전 ID가 있으며 기본적으로 그게 전부입니다. 프로세스의 나머지 부분은 Java 내부에 "하드와이어"되어 있으며 이에 대한 액세스가 없습니다. 편의성 측면에서는 물론 좋습니다. 하지만 프로그래머는 작업에서 자신의 편안함에만 집중해서는 안 됩니다. 그렇죠? :) 고려해야 할 다른 요소가 있습니다. 따라서 직렬화 가능은 Java의 직렬화-역직렬화를 위한 유일한 도구는 아닙니다. 오늘 우리는 외부화 가능 인터페이스 에 대해 알아 보겠습니다 . 하지만 연구를 진행하기 전에도 다음과 같은 합리적인 질문이 있을 수 있습니다. 왜 다른 도구가 필요한가요? Serializable나는 내 일에 대처했고 전체 프로세스의 자동 구현은 기뻐할 수밖에 없습니다. 우리가 살펴본 예도 복잡하지 않았습니다. 그래서 거래는 무엇입니까? 본질적으로 동일한 작업에 다른 인터페이스를 사용하는 이유는 무엇입니까? 사실은 Serializable여러 가지 단점이 있다는 것입니다. 그 중 일부를 나열해 보겠습니다.
  1. 성능. 인터페이스에는 Serializable많은 장점이 있지만 고성능은 분명히 그중 하나가 아닙니다.

외부화 가능 인터페이스 소개 - 2

첫째 , 내부 메커니즘은 Serializable운영 중에 대량의 서비스 정보와 다양한 유형의 임시 데이터를 생성합니다.
두 번째로 (관심이 있다면 지금 이 내용을 자세히 살펴보고 여유 시간에 읽을 필요는 없습니다.) 작업은 SerializableReflection API 사용을 기반으로 합니다. 이 장치를 사용하면 Java에서는 불가능해 보이는 작업을 수행할 수 있습니다. 예를 들어 비공개 필드의 값을 변경합니다. JavaRush에는 Reflection API에 대한 훌륭한 기사가 있습니다 . 여기에서 이에 대해 읽을 수 있습니다.

  1. 유연성. .NET Framework를 사용할 때 직렬화-역직렬화 프로세스를 전혀 제어하지 않습니다 Serializable.

    한편으로 이는 매우 편리합니다. 성능에 크게 신경 쓰지 않으면 코드를 작성하지 않아도 되는 기능이 편리해 보이기 때문입니다. 하지만 직렬화 논리에 자체 기능 중 일부(그 중 하나의 예는 아래에 있음)를 정말로 추가해야 하는 경우에는 어떻게 해야 할까요?

    본질적으로 우리가 프로세스를 제어해야 하는 것은 transient일부 데이터를 제외하는 키워드뿐이고 그게 전부입니다. 일종의 "툴킷"과 같습니다. //

  2. 안전. 이 점은 이전 점에서 부분적으로 이어집니다.

    이전에는 이에 대해 많이 생각해 본 적이 없었지만, 수업 중 일부 정보가 "다른 사람의 귀"(더 정확하게는 눈)를 위한 것이 아니라면 어떻게 될까요? 간단한 예는 현대 사회에서 여러 법률에 의해 규제되는 비밀번호 또는 기타 개인 사용자 데이터입니다.

    를 사용하면 Serializable실제로는 아무것도 할 수 없습니다. 우리는 모든 것을 있는 그대로 직렬화합니다.

    그러나 좋은 방법으로는 이러한 종류의 데이터를 파일에 쓰거나 네트워크를 통해 전송하기 전에 암호화해야 합니다. 하지만 Serializable이런 기회는 주지 않습니다.

외부화 가능 인터페이스 소개 - 3자, 마지막으로 Externalizable.
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;

public class UserInfo implements Externalizable {

   private String firstName;
   private String lastName;
   private String superSecretInformation;

private static final long SERIAL_VERSION_UID = 1L;

   //...конструктор, геттеры, сеттеры, toString()...

   @Override
   public void writeExternal(ObjectOutput out) throws IOException {

   }

   @Override
   public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {

   }
}
보시다시피, 우리는 상당한 변화를 겪었습니다! 주요한 점은 분명합니다. 인터페이스를 구현할 때 및 Externalizable의 두 가지 필수 메서드를 구현해야 합니다 . 앞서 말했듯이 직렬화 및 역직렬화에 대한 모든 책임은 프로그래머에게 있습니다. 그러나 이제 이 프로세스에 대한 통제력 부족 문제를 해결할 수 있습니다! 전체 프로세스는 사용자가 직접 프로그래밍하므로 훨씬 더 유연한 메커니즘이 생성됩니다. 게다가 보안 문제도 해결됐다. 보시다시피, 우리 클래스에는 암호화되지 않은 상태로 저장할 수 없는 개인 데이터 필드가 있습니다. 이제 우리는 이 제약 조건을 충족하는 코드를 쉽게 작성할 수 있습니다. 예를 들어, 비밀 데이터를 암호화하고 해독하기 위해 클래스에 두 개의 간단한 비공개 메서드를 추가합니다. 이를 파일에 쓰고 암호화된 형식으로 파일에서 읽습니다. 나머지 데이터는 있는 그대로 쓰고 읽습니다. 결과적으로 우리 클래스는 다음과 같습니다. writeExternal()readExternal()
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.Base64;

public class UserInfo implements Externalizable {

   private String firstName;
   private String lastName;
   private String superSecretInformation;

   private static final long serialVersionUID = 1L;

   public UserInfo() {
   }

   public UserInfo(String firstName, String lastName, String superSecretInformation) {
       this.firstName = firstName;
       this.lastName = lastName;
       this.superSecretInformation = superSecretInformation;
   }

   @Override
   public void writeExternal(ObjectOutput out) throws IOException {
       out.writeObject(this.getFirstName());
       out.writeObject(this.getLastName());
       out.writeObject(this.encryptString(this.getSuperSecretInformation()));
   }

   @Override
   public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
       firstName = (String) in.readObject();
       lastName = (String) in.readObject();
       superSecretInformation = this.decryptString((String) in.readObject());
   }

   private String encryptString(String data) {
       String encryptedData = Base64.getEncoder().encodeToString(data.getBytes());
       System.out.println(encryptedData);
       return encryptedData;
   }

   private String decryptString(String data) {
       String decrypted = new String(Base64.getDecoder().decode(data));
       System.out.println(decrypted);
       return decrypted;
   }

   public String getFirstName() {
       return firstName;
   }

   public String getLastName() {
       return lastName;
   }

   public String getSuperSecretInformation() {
       return superSecretInformation;
   }
}
우리는 강의에서 이미 접한 것과 동일한 ObjectOutput out및 매개변수를 사용하는 두 가지 방법을 구현했습니다 . 적시에 필요한 데이터를 암호화하거나 해독하며, 이 형식으로 이를 사용하여 객체를 직렬화합니다. 실제로 어떻게 보이는지 살펴보겠습니다. ObjectInputSerializable
import java.io.*;

public class Main {

   public static void main(String[] args) throws IOException, ClassNotFoundException {

       FileOutputStream fileOutputStream = new FileOutputStream("C:\\Users\\Username\\Desktop\\save.ser");
       ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);

       UserInfo userInfo = new UserInfo("Ivan", "Ivanov", "Ivan Ivanov's passport data");

       objectOutputStream.writeObject(userInfo);

       objectOutputStream.close();

   }
}
encryptString()및 메소드 에서는 decryptString()비밀 데이터가 어떤 형식으로 작성되고 읽혀지는지 확인하기 위해 콘솔에 출력을 구체적으로 추가했습니다. 위의 코드는 다음 줄을 콘솔에 출력합니다. SXZhbiBJdmFub3YncyBwYXNzcG9ydCBkYXRh 암호화가 성공했습니다! 파일의 전체 내용은 다음과 같습니다. ¬н sr UserInfoГ!}ҐџC'ћ xpt Ivant Ivanovt $SXZhbiBJdmFub3YncyBwYXNzcG9ydCBkYXRhx 이제 우리가 작성한 역직렬화 논리를 사용해 보겠습니다.
public class Main {

   public static void main(String[] args) throws IOException, ClassNotFoundException {

       FileInputStream fileInputStream = new FileInputStream("C:\\Users\\Username\\Desktop\\save.ser");
       ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);


       UserInfo userInfo = (UserInfo) objectInputStream.readObject();
       System.out.println(userInfo);

       objectInputStream.close();

   }
}
글쎄, 여기에는 복잡한 것이 하나도 없는 것 같습니다. 작동할 것입니다! 실행해 보겠습니다... 스레드 "main"에서 예외가 발생했습니다. java.io.InvalidClassException: UserInfo; 유효한 생성자가 없습니다. 외부화 가능 인터페이스 소개 - 4 죄송합니다 :( 그렇게 간단하지 않은 것으로 밝혀졌습니다! 역직렬화 메커니즘에서 예외가 발생하여 기본 생성자를 생성해야 했습니다. 이유가 무엇인지 궁금합니다. Serializable생성자 없이 관리했습니다... :/ 여기서 또 다른 중요한 뉘앙스가 발생합니다. Serializable과 의 차이점은 Externalizable프로그래머를 위한 "확장된" 액세스와 프로세스를 보다 유연하게 관리할 수 있는 능력뿐만 아니라 프로세스 자체에도 있습니다. 우선 역 직렬화 메커니즘에서 ... 사용되면 Serializable메모리는 단순히 객체에 할당된 후 스트림에서 값을 읽어 해당 필드를 모두 채웁니다. 을 사용하면 Serializable객체 생성자가 호출되지 않습니다! 모든 작업은 리플렉션을 통해 수행됩니다(지난번에 간략하게 언급한 Reflection API). 강의). 의 경우 역 Externalizable직렬화 메커니즘이 달라집니다. 처음에는 기본 생성자가 호출됩니다. 그런 다음 UserInfo생성된 객체 메서드에서 readExternal()객체의 필드를 채우는 역할을 합니다. 즉, 인터페이스를 구현하는 모든 클래스에 Externalizable기본 생성자가 있어야 하는 이유는 무엇입니까 ? 이를 클래스에 추가 UserInfo하고 코드를 다시 실행해 보겠습니다.
import java.io.*;

public class Main {

   public static void main(String[] args) throws IOException, ClassNotFoundException {

       FileInputStream fileInputStream = new FileInputStream("C:\\Users\\Username\\Desktop\\save.ser");
       ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);


       UserInfo userInfo = (UserInfo) objectInputStream.readObject();
       System.out.println(userInfo);

       objectInputStream.close();
   }
}
콘솔 출력: Ivan Ivanov의 여권 데이터 UserInfo{firstName='Ivan', lastName='Ivanov', superSecretInformation='Ivan Ivanov의 여권 데이터'} 완전히 다른 문제입니다! 먼저, 비밀 데이터가 포함된 해독된 문자열이 콘솔에 출력된 후 파일에서 문자열 형식으로 개체가 복구되었습니다! 이것이 우리가 모든 문제를 성공적으로 해결한 방법입니다 :) 직렬화와 역직렬화라는 주제는 간단해 보이지만 보시다시피 강의가 너무 길어졌습니다. 그리고 그게 전부가 아닙니다! 이러한 각 인터페이스를 사용할 때 더 많은 미묘한 부분이 있지만 이제 새로운 정보의 양으로 인해 두뇌가 폭발하지 않도록 몇 가지 중요한 사항을 간략하게 나열하고 추가 독서에 대한 링크를 제공하겠습니다. 그렇다면 또 무엇을 알아야 합니까? 첫째Serializable , 직렬화할 때( 또는 를 사용하든 상관 없음 Externalizable) 변수에 주의하세요 static. 사용 시 Serializable이러한 필드는 전혀 직렬화되지 않습니다(따라서 static필드가 객체가 아닌 클래스에 속하므로 해당 값이 변경되지 않습니다). 하지만 이를 사용하면 Externalizable프로세스를 직접 제어할 수 있으므로 기술적으로는 이것이 가능합니다. 하지만 이는 미묘한 오류가 많기 때문에 권장되지 않습니다. 둘째 , 수식어를 사용하는 변수에도 주의를 기울여야 합니다 final. 사용하면 Serializable평소와 같이 직렬화 및 역직렬화되지만, 사용하면 변수를 Externalizable역직렬화final 할 수 없습니다 ! 이유는 간단합니다. 모든 final-필드는 기본 생성자가 호출될 때 초기화되고 그 후에는 해당 값을 변경할 수 없습니다. 따라서 final-field를 포함하는 객체를 직렬화하려면 Serializable. 셋째 , 상속을 사용할 때 일부 클래스의 자손인 모든 상속 클래스에는 Externalizable기본 생성자가 있어야 합니다. 다음은 직렬화 메커니즘에 대한 좋은 기사에 대한 링크입니다: 또 봐요! :)
코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION