Java管理对象方法总结
有一天晚上我脑海中突然冒出来一个问题:“怎样管理我们代码中的对象”。
小弈是刚工作时的我,他说:通过new来创建一个对象然后直接使用就好了啊。
publicclassHelloWorld{ publicvoidhello(){ System.out.println("helloworld!"); } } HelloWorldhelloWorld=newHelloWorld(); helloWorld.hello();
你们看,我有一个HelloWorld类,我用new就能直接创建一个对象,然后就能使用这个对象中所有的方法了,多简单啊。
二弈是工作两年的我,他一脸鄙视的对小弈说,你别整天HelloWorld好不好,还有啊,除了new你就不会其他的了,能不能有点追求啊?
小弈对二弈说那你说除了new还有什么办法啊?
二弈说可以通过Class的newInstance或者Constructor的newInstance来创建对象实例啊。
不过你得记住,Class的newInstance只能对那些拥有可见的(Accessible)无参构造函数的类,才能进行对象的实例化,而Constructor就没有这些限制。
大弈是工作三年的我,他说,虽然你们的方法都可以用来创建对象,但都还是手动创建的,太原始了,生产力太低。
工欲善其事,必先利其器,我们也得找个高效的生产力工具。IOC容器你们了解吧?
以前我们在一个对象中如果要调用另外一个对象的方法时,都是通过new或者反射来手动创建该对象,但是每次都这样做太累了,并且类之间的耦合也很高。
通过IOC容器,我们可以把所有的对象交给容器来管理,在使用之前只需要定义一下对象,然后再使用到该对象时,IOC容器就会帮我们把该对象初始化好,这样是不是更方便呢?
大弈说完,举了一个例子:
@Bean publicclassRegisterService{ publicvoidregister(){ //doregister } } @Bean publicclassLoginService{ publicvoidlogin(){ //dologin } } @Bean publicclassHelloWorld{ @Autowired privateRegisterServiceregisterService; @Autowired privateLoginServiceloginService; publicvoidhello(){ //注册 registerService.register(); //... //登录 loginService.login(); } }
IOC容器通过一种叫Bean的注解,在系统启动时扫描所有通过Bean标注的类,对这些类进行实例化,然后将所有的对象都保存在容器中。再扫描所有通过Autowired标注的属性或者方法,从容器中找到与之匹配(通过名称或者类型等)的对象将具体的对象赋值给这些属性。这样我们就可以直接将这些对象拿来使用了,作为一个伸手党是不是很幸福啊。
老弈是工作五年的我,他听了大弈的话后,提出了一个问题,对于新的项目可以使用这种IOC的容器,可是对于那些遗留的老项目来说,要使用IOC来改造是不太符合实情的。
我举个例子,在一个遗留的老项目中,有一个核心的接口Handler:
publicinterfaceHandler{ REShandle(REQrequest); }
Handler接口有很多的实现类,我们需要对不同的请求来调用不同的Handler实现类进行处理,如果用IOC容器来管理这些实现类,显然不太合适,因为我们处理之前是不知道该用哪个Handler实现类的。
大弈想了想,如果Handler接口只有几个固定的实现类,并且在使用时只会使用一个来进行处理,那么倒是可以在启动前通过配置的方式来确定具体使用哪种Handler,比如可以通过@Conditional根据某些条件来确定加载具体的对象,但是这种要在使用时才能确定Handler对象的类型确实比较棘手。
老弈看大家都不说话了,就继续说了下去。
为了要在调用方法时使用不同的Handler来处理不同的而请求,需要确定两种类,一种是请求类,一种是处理类,并且要让请求类和处理类一一对应起来。
假设我们的请求类是一个Packet类,每一个具体的请求类都继承自这个基类。
那么想要确定每一个具体的Packet是什么类型的,可以有很多种方法,可以为每个Packet取一个唯一的名字,例如:
publicabstractclassPacket{ publicabstractStringname(); }
也可以为每一个Packet指定一个标志,例如:
publicabstractclassPacket{ publicabstractintsymbol(); }
但是不管哪种方式,每一个Packet的实现类都需要实现抽象类中的方法,来“标志”自己是哪种Packet。
我们以第二种方式举例,假设我们有两个具体的Packet:
publicclassRegisterPacketextendsPacket{ //注册所需要的其他参数 intsymbol(){ return1; } } publicclassLoginPacketextendsPacket{ //登录所需要的其他参数 intsymbol(){ return2; } }
这样当我们接收到request对象时,通过调用request.symbol()就知道这个request是哪种类型的Packet了,这时只要找到具体的Handler实现类来处理就可以了。
那请求类已经可以确定了,怎样确定Handler处理类呢?我们是否也可以在Handler接口中定义一个symbol方法呢,像这样:
publicinterfaceHandler{ intsymbol(); REShandle(REQrequest); }
这样的话,只要在所有的实现类中实现symbol方法来标注该Handler是用来处理何种request的即可。
publicRegisterHandlerimplementsHandler{ intsymbol(){ return1; } REShandle(RegisterPacketrequest){ //具体的处理方法 } } publicLoginHandlerimplementsHandler { intsymbol(){ return2; } REShandle(LoginPacketrequest){ //具体的处理方法 } }
最后把所有的Handler实现类都实例化后保存在一个HandlerProvider中,要使用时再到HandlerProvider中来获取即可:
publicinterfaceHandlerProvider{ HandlergetHandler(intsymbol); }
那怎样获取到所有的Handler的实现类呢,有两种方法。
一种是通过ServiceLoader.load(Handler.class)的方式来获取,不过这种通过spi的方式需要在项目的resources/META-INF/services/目录下创建一个xxx.Handler的文件,并在文件中将所有Handler的实现类的完全类限定符列出来。
另一种比较简单的方式是通过扫描的方式,获取到所有Handler的实现类。
到现在为止,我们的实现还算可以,但是有一个问题,那就是在Handler接口中我们增加了一个方法,这样做就对原来的代码进行了侵入。
为了让原来的代码保持不变,我们可以定义一个注解来标注在所有的Handler实现类上,比如这样:
@Symbol(1) publicRegisterHandlerimplementsHandler{ REShandle(RegisterPacketrequest){ //具体的处理方法 } } @Symbol(2) publicLoginHandlerimplementsHandler { REShandle(LoginPacketrequest){ //具体的处理方法 } }
这样就将Handler的实现和标注进行了解耦了,也可以通过扫描@Symbol注解来获取到所有的Handler实现类,不过这样做的缺点就是假如我忘记对某个Handler实现类添加@Symbol注解,到时候就获取不到该Handler了。
大家听完老弈的话之后,都陷入了沉思,我靠,还可以这么玩,真有趣。
这时候现在的我,也就是逅弈,说了一句,如果我有一个接口,他只有几个固定的实现类,我不想搞那一套那么重的实现方式,但是我也需要动态的获取实现类来对请求进行处理,那我该怎么办呢?
比如我有一个序列化的接口,如下所示:
publicinterfaceSerializer{ byte[]serialize(Packetpacket); }
然后只有五种具体的序列化的实现类,如下所示:
publicclassJdkSerializerimplementsSerializer{ @Override publicbyte[]serialize(Packetpacket){ //具体的序列化操作 } } publicclassFastJsonSerializerimplementsSerializer{ @Override publicbyte[]serialize(Packetpacket){ //具体的序列化操作 } } publicclassHessianSerializerimplementsSerializer{ @Override publicbyte[]serialize(Packetpacket){ //具体的序列化操作 } } publicclassKryoSerializerimplementsSerializer{ @Override publicbyte[]serialize(Packetpacket){ //具体的序列化操作 } } publicclassProtoStuffSerializerimplementsSerializer{ @Override publicbyte[]serialize(Packetpacket){ //具体的序列化操作 } }
那么我们该怎么确定使用哪种序列化方式对参数packet进行序列化呢?
使用老弈刚刚说的那一套也确实能够实现,不过太麻烦了,又得对Packet定义symbol,又得对Hander实现类进行标注,还得扫描所有的实现类。
我只有五个实现类,不需要搞那么麻烦的。
其实很简单,只需要定义一个枚举类,表示序列化的算法,然后对Packet增加一个algorithm方法用来表示,使用何种序列化算法,如下所示:
publicenumSerializeAlgorithm{ JDK((byte)1), FAST_JSON((byte)2), HESSIAN((byte)3), KRYO((byte)4), PROTO_STUFF((byte)5); privatebytetype; SerializeAlgorithm(bytetype){ this.type=type; } } publicabstractclassPacketimplementsSerializable{ publicabstractbytealgorithm(); }
然后定义一个SerializerChooser根据不同的算法选择不同的Serializer实现类即可:
publicinterfaceSerializerChooser{ Serializerchoose(bytealgorithm); }
因为根据算法是可以知道对应的序列化接口的,所以就没有必要去扫描了,直接把几种序列化的实现类枚举出来即可,对象的实例可以使用单例模式,如下所示:
publicclassDefaultSerializerChooserimplementsSerializerChooser{ privateDefaultSerializerChooser(){ } publicstaticSerializerChoosergetInstance(){ returnSingleton.get(DefaultSerializerChooser.class); } @Override publicSerializerchoose(bytealgorithm){ SerializeAlgorithmserializeAlgorithm=SerializeAlgorithm.getEnum(algorithm); switch(serializeAlgorithm){ caseJDK:{ returnSingleton.get(JdkSerializer.class); } caseFAST_JSON:{ returnSingleton.get(FastJsonSerializer.class); } caseHESSIAN:{ returnSingleton.get(HessianSerializer.class); } caseKRYO:{ returnSingleton.get(KryoSerializer.class); } casePROTO_STUFF:{ returnSingleton.get(ProtoStuffSerializer.class); } default:{ returnnull; } } } }
我说完后,大家又一次陷入了沉思,我知道大家都在思考,他们会在每一次思考中获得进步和成长,正如我在思考后得到成长一样。