CodeGym /Java Blog /무작위의 /Java의 외부화 가능 인터페이스
John Squirrels
레벨 41
San Francisco

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

무작위의 그룹에 게시되었습니다
안녕! 오늘 우리는 계속해서 Java 개체의 직렬화 및 역직렬화에 대해 알아볼 것입니다 . 지난 강의에서는 Serializable 마커 인터페이스에 대해 알아보고 사용 예를 검토했으며 일시적인 키워드를 사용하여 직렬화 프로세스를 제어하는 ​​방법도 배웠습니다. 글쎄, 우리가 '프로세스를 제어한다'고 말하는 것은 과장일 수 있습니다. 하나의 키워드, 하나의 버전 식별자가 있고 그게 전부입니다. 나머지 프로세스는 Java 내부에 숨겨져 있으며 액세스할 수 없습니다. 물론 편의성 측면에서 이것은 좋습니다. 하지만 프로그래머는 자신의 편안함에만 이끌려서는 안 됩니다. :) 고려해야 할 다른 요소가 있습니다. 그렇기 때문에 직렬화 가능Java에서 직렬화-역직렬화를 위한 유일한 메커니즘은 아닙니다. 오늘 우리는 Externalizable 인터페이스 에 대해 알게 될 것입니다 . 그러나 연구를 시작하기 전에 합리적인 질문이 생길 수 있습니다. 왜 다른 메커니즘이 필요한가요? Serializable작업을 수행했으며 전체 프로세스의 자동 구현에 대해 좋아하지 않는 것은 무엇입니까? 그리고 우리가 살펴본 예도 복잡하지 않았습니다. 그래서 문제가 무엇입니까? 기본적으로 동일한 작업에 다른 인터페이스가 필요한 이유는 무엇입니까? 몇 가지 단점이 있는 것이 사실입니다 Serializable. 우리는 그들 중 일부를 나열합니다:
  1. 성능. 인터페이스 Serializable에는 많은 장점이 있지만 고성능은 분명히 장점 중 하나가 아닙니다.

    Externalizable 인터페이스 소개 - 2

    첫째, Serializable 의 내부 구현은 많은 양의 서비스 정보와 각종 임시 데이터를 생성합니다.

    둘째, Serializable Reflection API에 의존합니다. 이를 통해 Java에서 불가능해 보이는 작업을 수행할 수 있습니다. 예를 들어 개인 필드의 값을 변경합니다. CodeGym에는 Reflection API에 대한 훌륭한 기사가 있습니다 . 거기에서 그것에 대해 읽을 수 있습니다.

  2. 유연성. 인터페이스 를 사용할 때 직렬화-역직렬화 프로세스를 제어하지 않습니다 Serializable.

    한편으로는 매우 편리합니다. 성능에 특별히 관심이 없다면 코드를 작성할 필요가 없기 때문입니다. 그러나 직렬화 논리에 자체 기능(아래에서 예를 제공)을 추가해야 하는 경우에는 어떻게 해야 합니까?

    기본적으로 우리가 프로세스를 제어해야 하는 것은 transient일부 데이터를 제외하는 키워드뿐입니다. 그게 다야. 이것이 우리의 전체 도구 상자입니다.

  3. 보안. 이 항목은 부분적으로 이전 항목에서 파생됩니다.

    우리는 이전에 이것에 대해 생각하는 데 많은 시간을 할애하지 않았지만, 수업의 일부 정보가 다른 사람들의 눈과 귀를 위한 것이 아니라면 어떻게 될까요? 간단한 예로 암호나 기타 개인 사용자 데이터가 있으며, 오늘날에는 여러 법률이 적용됩니다.

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

    그러나 올바른 방법으로 수행한다면 이러한 종류의 데이터를 파일에 쓰거나 네트워크를 통해 보내기 전에 암호화해야 합니다. 그러나 Serializable이것을 가능하게 하지 않습니다.

Externalizable 인터페이스 소개 - 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;

   // ...constructor, getters, setters, 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사용 하는 두 가지 방법을 구현했습니다 . 적시에 필요한 데이터를 암호화하거나 해독하고 암호화된 데이터를 사용하여 개체를 직렬화합니다. 이것이 실제로 어떻게 보이는지 봅시다: 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("Paul", "Piper", "Paul Piper'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; 유효한 생성자가 없습니다 Externalizable 인터페이스 소개 - 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();
   }
}
콘솔 출력: Paul Piper의 여권 데이터 UserInfo \ firstName = 'Paul', lastName = 'Piper', superSecretInformation = 'Paul Piper의 여권 데이터' } 이제 완전히 다릅니다! 먼저 비밀 정보가 포함된 복호화된 문자열이 콘솔에 표시되었습니다. 그런 다음 파일에서 복구한 개체가 문자열로 표시되었습니다! 그래서 우리는 모든 문제를 성공적으로 해결했습니다 :) 직렬화 및 역직렬화 주제는 간단해 보이지만 보시다시피 교훈이 길었습니다. 그리고 우리가 다루지 않은 훨씬 더 많은 것들이 있습니다! 이러한 각 인터페이스를 사용할 때 여전히 많은 미묘함이 관련되어 있습니다. 그러나 과도한 새 정보로 인해 머리가 폭발하는 것을 방지하기 위해 몇 가지 더 중요한 사항을 간략하게 나열하고 추가 읽을거리에 대한 링크를 제공합니다. 그렇다면 또 무엇을 알아야 할까요? 첫째Serializable , 직렬화 중에( 또는 를 사용하는지 여부에 관계없이 Externalizable) 변수에 주의하십시오 static. 를 사용할 때 Serializable이러한 필드는 전혀 직렬화되지 않습니다(따라서 static필드가 개체가 아닌 클래스에 속하기 때문에 해당 값이 변경되지 않음). 그러나 당신이 사용할 때Externalizable, 프로세스를 직접 제어하므로 기술적으로 직렬화할 수 있습니다. 하지만 그렇게 하면 미묘한 버그가 많이 생길 수 있으므로 권장하지 않습니다. 둘째 , 수식어가 있는 변수에도 주의를 기울여야 합니다 final. 를 사용하면 Serializable평소와 같이 직렬화 및 역직렬화되지만 를 사용하면 변수를 Externalizable역직렬화하는 것이 불가능합니다final ! 이유는 간단합니다. final기본 생성자가 호출되면 모든 필드가 초기화되고 그 후에는 해당 값을 변경할 수 없습니다. 따라서 final필드가 있는 객체를 직렬화하려면 에서 제공하는 표준 직렬화를 사용하십시오 Serializable. 셋째 , 상속을 사용하면 일부를 상속하는 모든 자손 클래스Externalizable클래스에는 기본 생성자도 있어야 합니다. 다음은 직렬화 메커니즘에 대한 좋은 기사 링크입니다. 다음 시간까지! :)
코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION