この記事は、オブジェクトの管理方法をストーリーテリング形式で解説したもので、一定の価値がありますので、興味のある方はぜひご覧ください。授業への理解がさらに深まると幸いです。
ある夜、「コード内のオブジェクトを管理する方法」という質問が突然頭に浮かびました。
Xiaoyi は私が最初に仕事を始めたときの私で、彼はこう言いました: new を使用してオブジェクトを作成し、それを直接使用するだけです。
public class HelloWorld { public void hello() { System.out.println("hello world!"); } } HelloWorld helloWorld = new HelloWorld(); helloWorld.hello();
ほら、HelloWorld クラスがあります。new を使用してオブジェクトを直接作成でき、その後、このオブジェクト内のすべてのメソッドを使用できます。とてもシンプルです。
Er Yi は私で 2 年間働いています。彼は軽蔑の表情で Xiao Yi に言いました、「一日中 Hello World を言わないでくださいね? それに、あなたはそれ以外何も知りません」新しい、少し追求してもらえますか?
Xiaoyi は Eryi に「新しいこと以外に何ができるの?」と言いました。
Eryi さんは、クラスの newInstance またはコンストラクターの newInstance を通じてオブジェクト インスタンスを作成できると述べました。
ただし、Class の newInstance は、引数なしの可視 (Accessible) コンストラクターを持つクラスのオブジェクトのみをインスタンス化でき、Constructor にはこれらの制限がないことを覚えておく必要があります。
勤続 3 年の Da Yi さんは、あなたの方法はオブジェクトの作成に使用できますが、すべて手動で作成されるため、原始的すぎて生産性が低すぎると言いました。
従業員が自分の仕事をうまくやりたいと思うなら、まず自分のツールを磨く必要があり、また効率的な生産性ツールを見つける必要もあります。 IOCコンテナについてご存知ですか?
以前は、オブジェクト内で別のオブジェクトのメソッドを呼び出したい場合は、新規作成やリフレクションによって手動でオブジェクトを作成していましたが、毎回それを行うのは大変で、クラス間の結合も面倒でした。すごく高い。
IOC コンテナを介して、管理のためにすべてのオブジェクトをコンテナに渡すことができます。使用する前にオブジェクトを定義するだけで済みます。その後、オブジェクトが使用されるときに、IOC コンテナがコンテナの初期化を支援します。オブジェクト、これの方が便利ではないでしょうか?
Da Yi 氏は話し終えた後、例を挙げました:
@Bean public class RegisterService { public void register() { // do register } } @Bean public class LoginService { public void login() { // do login } } @Bean public class HelloWorld { @Autowired private RegisterService registerService; @Autowired private LoginService loginService; public void hello() { // 注册 registerService.register(); // ... // 登录 loginService.login(); } }
IOC コンテナは、Bean と呼ばれるアノテーションを使用して、システムの起動時に Bean によってアノテーションが付けられたすべてのクラスをスキャンし、これらのクラスをインスタンス化します。をクリックし、コンテナ内のすべてのオブジェクトを保存します。次に、Autowired でマークされたすべてのプロパティまたはメソッドをスキャンし、コンテナーから一致するオブジェクト (名前またはタイプなど) を見つけて、特定のオブジェクトをこれらのプロパティに割り当てます。このように、これらのオブジェクトを直接使用することができます。
Lao Yi は、5 年間働いている私です。Da Yi の言葉を聞いた後、彼は質問を提起しました。この種の IOC コンテナは新しいプロジェクトに使用できますが、レガシーな古いプロジェクトには使用できません。変換に IOC を使用するのは現実的ではありません。
例を示します。古いレガシー プロジェクトには、コア インターフェイス Handler:
public interface Handler<REQ, RES> { RES handle(REQ request); }
があります。Handler インターフェイスには多くの実装クラスがあります。リクエストごとに異なる実装クラスを呼び出す必要があります。処理には Handler 実装クラスを使用しますが、これらの実装クラスを IOC コンテナで管理する場合、処理前にどの Handler 実装クラスを使用すればよいか分からないため、明らかに適していません。
Da Yi は、Handler インターフェースに固定実装クラスがいくつかしかなく、処理に使用されるクラスが 1 つだけであれば、起動前の構成によって決定できると考えました。 @Conditional を使用すると、特定の条件に従ってロードする特定のオブジェクトを決定できますが、使用時に Handler オブジェクトの型を決定するのは非常に困難です。
皆が沈黙しているのを見て、ラオ・イーは続けた。
メソッド呼び出し時に異なるハンドラーを使用して異なるリクエストを処理するには、リクエスト クラスと処理クラスの 2 つのクラスを決定する必要があります。リクエスト クラスと処理クラスは次のとおりです。一つ一つお作り致します。
リクエスト クラスが Packet クラスであり、それぞれの特定のリクエスト クラスがこの基本クラスを継承していると仮定します。
したがって、特定の各パケットのタイプを確認したい場合は、さまざまな方法があります。各パケットに一意の名前を付けることができます。たとえば:
public abstract class Packet { public abstract String name(); }
各パケットにパケットはフラグを指定します (例:
public abstract class Packet { public abstract int symbol(); }
)。ただし、いずれの方法であっても、各パケット実装クラスは、パケットの種類を「マーク」するためのメソッドを抽象クラスに実装する必要があります。
2 つの特定のパケットがあると仮定して、例として 2 番目の方法を取り上げます。
public class RegisterPacket extends Packet { // 注册所需要的其他参数 int symbol() { return 1; } } public class LoginPacket extends Packet { // 登录所需要的其他参数 int symbol() { return 2; } }
この方法では、リクエスト オブジェクトを受信したときに、request.symbol を呼び出すことでリクエストを知ることができます。 () どのタイプのパケットですか? 現時点では、それを処理する特定のハンドラー実装クラスを見つけるだけで済みます。
リクエストのクラスは決定できますが、Handlerの処理クラスを決定するにはどうすればよいですか?次のように、Handler インターフェースでシンボル メソッドを定義することもできますか:
public interface Handler<REQ, RES> { int symbol(); RES handle(REQ request); }
この場合、シンボル メソッドがすべての実装クラスに実装されていて、Handler がどのような種類のリクエストを処理するかをマークする必要があります。 、つまりキャンです。
public RegisterHandler implements Handler<RegisterPacket, RES> { int symbol(){ return 1; } RES handle(RegisterPacket request){ // 具体的处理方法 } } public LoginHandler implements Handler<LoginPacket, RES> { int symbol(){ return 2; } RES handle(LoginPacket request){ // 具体的处理方法 } }
最後に、すべての Handler 実装クラスをインスタンス化し、HandlerProvider に保存します。これらを使用したい場合は、HandlerProvider から取得できます:
public interface HandlerProvider { Handler getHandler(int symbol); }
すべての Handler を取得する方法実装クラスには 2 つのメソッドがあります。
一种是通过 ServiceLoader.load(Handler.class) 的方式来获取,不过这种通过 spi 的方式需要在项目的 resources/META-INF/services/ 目录下创建一个 xxx.Handler 的文件,并在文件中将所有 Handler 的实现类的完全类限定符列出来。
另一种比较简单的方式是通过扫描的方式,获取到所有 Handler 的实现类。
到现在为止,我们的实现还算可以,但是有一个问题,那就是在 Handler 接口中我们增加了一个方法,这样做就对原来的代码进行了侵入。
为了让原来的代码保持不变,我们可以定义一个注解来标注在所有的 Handler 实现类上,比如这样:
@Symbol(1) public RegisterHandler implements Handler<RegisterPacket, RES> { RES handle(RegisterPacket request){ // 具体的处理方法 } } @Symbol(2) public LoginHandler implements Handler<LoginPacket, RES> { RES handle(LoginPacket request){ // 具体的处理方法 } }
这样就将 Handler 的实现和标注进行了解耦了,也可以通过扫描 @Symbol 注解来获取到所有的 Handler 实现类,不过这样做的缺点就是假如我忘记对某个 Handler 实现类添加 @Symbol 注解,到时候就获取不到该 Handler 了。
大家听完老弈的话之后,都陷入了沉思,我靠,还可以这么玩,真有趣。
这时候现在的我,也就是逅弈,说了一句,如果我有一个接口,他只有几个固定的实现类,我不想搞那一套那么重的实现方式,但是我也需要动态的获取实现类来对请求进行处理,那我该怎么办呢?
比如我有一个序列化的接口,如下所示:
public interface Serializer { byte[] serialize(Packet packet); }
然后只有五种具体的序列化的实现类,如下所示:
public class JdkSerializer implements Serializer { @Override public byte[] serialize(Packet packet) { // 具体的序列化操作 } } public class FastJsonSerializer implements Serializer { @Override public byte[] serialize(Packet packet) { // 具体的序列化操作 } } public class HessianSerializer implements Serializer { @Override public byte[] serialize(Packet packet) { // 具体的序列化操作 } } public class KryoSerializer implements Serializer { @Override public byte[] serialize(Packet packet) { // 具体的序列化操作 } } public class ProtoStuffSerializer implements Serializer { @Override public byte[] serialize(Packet packet) { // 具体的序列化操作 } }
那么我们该怎么确定使用哪种序列化方式对参数 packet 进行序列化呢?
使用老弈刚刚说的那一套也确实能够实现,不过太麻烦了,又得对 Packet 定义 symbol,又得对 Hander 实现类进行标注,还得扫描所有的实现类。
我只有五个实现类,不需要搞那么麻烦的。
其实很简单,只需要定义一个枚举类,表示序列化的算法,然后对 Packet 增加一个 algorithm 方法用来表示,使用何种序列化算法,如下所示:
public enum SerializeAlgorithm { JDK((byte) 1), FAST_JSON((byte) 2), HESSIAN((byte) 3), KRYO((byte) 4), PROTO_STUFF((byte) 5); private byte type; SerializeAlgorithm(byte type) { this.type = type; } } public abstract class Packet implements Serializable { public abstract byte algorithm(); }
然后定义一个 SerializerChooser 根据不同的算法选择不同的 Serializer 实现类即可:
public interface SerializerChooser { Serializer choose(byte algorithm); }
因为根据算法是可以知道对应的序列化接口的,所以就没有必要去扫描了,直接把几种序列化的实现类枚举出来即可,对象的实例可以使用单例模式,如下所示:
public class DefaultSerializerChooser implements SerializerChooser { private DefaultSerializerChooser() { } public static SerializerChooser getInstance() { return Singleton.get(DefaultSerializerChooser.class); } @Override public Serializer choose(byte algorithm) { SerializeAlgorithm serializeAlgorithm = SerializeAlgorithm.getEnum(algorithm); switch (serializeAlgorithm) { case JDK: { return Singleton.get(JdkSerializer.class); } case FAST_JSON: { return Singleton.get(FastJsonSerializer.class); } case HESSIAN: { return Singleton.get(HessianSerializer.class); } case KRYO: { return Singleton.get(KryoSerializer.class); } case PROTO_STUFF: { return Singleton.get(ProtoStuffSerializer.class); } default: { return null; } } } }
我说完后,大家又一次陷入了沉思,我知道大家都在思考,他们会在每一次思考中获得进步和成长,正如我在思考后得到成长一样。
小鸟总有一天会成长为老鸟,我还走在成长的路上。
以上がJava でオブジェクトをより適切に管理する方法の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。