这篇文章主要讲解了“java注解怎么定义实现”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“java注解怎么定义实现”吧!
主要是定义统一行为(接口或抽象类),并实现不同策略下的处理逻辑(对应实现类)。客户端使用时自己选择相应的处理类,利用工厂或其他方式。
本文要说的是用注解实现策略模式的方式,以及一些注意点。
话不多说,还是以最常 见的订单处理为例。首先定义这样一个订单实体类:
@Datapublic class Order { /** * 订单来源 */ private String source; /** * 支付方式 */ private String payMethod; /** * 订单编号 */ private String code; /** * 订单金额 */ private BigDecimal amount; // ...其他的一些字段}
假如对于不同来源(pc端、移动端)的订单需要不同的逻辑处理。项目中一般会有OrderService这样一个类,如下,里面有一坨if-else的逻辑,目的是根据订单的来源的做不同的处理。
@Servicepublic class OrderService { public void orderService(Order order) { if(order.getSource().equals("pc")){ // 处理pc端订单的逻辑 }else if(order.getSource().equals("mobile")){ // 处理移动端订单的逻辑 }else { // 其他逻辑 } }}
策略模式就是要干掉上面的一坨if-else,使得代码看起来优雅且高大上。
现在就让我们开始干掉这一坨if-else。先总览下结构:
public interface OrderHandler { void handle(Order order);}
2.定义一个OrderHandlerType注解,来表示某个类是用来处理何种来源的订单。
@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Documented@Servicepublic @interface OrderHandlerType { String source();}
3.接下来就是实现pc端和移动端订单处理各自的handler,并加上我们所定义的OrderHandlerType注解。
@OrderHandlerType(source = "mobile")public class MobileOrderHandler implements OrderHandler { @Override public void handle(Order order) { System.out.println("处理移动端订单"); }}@OrderHandlerType(source = "pc")public class PCOrderHandler implements OrderHandler { @Override public void handle(Order order) { System.out.println("处理PC端订单"); }}
4.以上准备就绪后,就是向spring容器中注入各种订单处理的handler,并在OrderService.orderService方法中,通过策略(订单来源)去决定选择哪一个OrderHandler去处理订单。我们可以这样做:
@Servicepublic class OrderService { private Map<String, OrderHandler> orderHandleMap; @Autowired public void setOrderHandleMap(List<OrderHandler> orderHandlers) { // 注入各种类型的订单处理类 orderHandleMap = orderHandlers.stream().collect( Collectors.toMap(orderHandler -> AnnotationUtils.findAnnotation(orderHandler.getClass(), OrderHandlerType.class).source(), v -> v, (v1, v2) -> v1)); } public void orderService(Order order) { // ...一些前置处理 // 通过订单来源确定对应的handler OrderHandler orderHandler = orderHandleMap.get(order.getSource()); orderHandler.handle(order); // ...一些后置处理 }}
在OrderService中,维护了一个orderHandleMap,它的key为订单来源,value为对应的订单处理器Handler。通过@Autowired去初始化orderHandleMap(这里有一个lambda表达式,仔细看下其实没什么难度的)。这样一来,OrderService.orderService里的一坨if-else不见了,取而代之的仅仅是两行代码。即,先从orderHandleMap中根据订单来源获取对应的OrderHandler,然后执行OrderHandler.handle方法即可。
这种做法的好处是,不论以后业务如何发展致使订单来源种类增加,OrderService的核心逻辑不会改变,我们只需要实现新增来源的OrderHandler即可,且团队中每人开发各自负责的订单来源对应的OrderHandler即可,彼此间互不干扰。
到此,似乎已经讲完了通过注解实现策略模式,干掉if-else的方法,就这样结束了吗?不,真正的重点从现在才开始。
现在回过头看orderHandleMap这个Map,它的key是订单来源,假如,我们想通过订单来源+订单支付方式这两个属性来决定到底使用哪一种OrderHandler怎么办?比如PC端支付宝支付的订单是一种处理逻辑(PCAliPayOrderHandler),PC端微信支付的订单是另外一种处理逻辑(PCWeChatOrderHandler),对应的还有移动端支付宝支付(MobileAliPayOrderHandler)和移动端微信支付(MobileWeChatOrderHandler)。
这时候我们的key应该存什么呢,可能有人会说,我直接存订单来源+订单支付方式组成的字符串不就行了吗?确实可以,但是如果这时业务逻辑又变了(向pm低头),PC端支付宝支付和微信支付是同一种处理逻辑,而移动端支付宝支付和微信支付是不同的处理逻辑,那情况就变成了PCAliPayOrderHandler和PCWeChatOrderHandler这两个类是同一套代码逻辑。我们干掉了if-else,但却造出了两份相同的代码,这是一个作为有强迫症的程序员所不能容忍的。怎么干掉这两个逻辑相同的类呢?
首先,我们可以回顾下,注解它究竟是个什么玩意?不知道大家有没有注意到定义注解的语法,也就是@interface,与定义接口的语法想比,仅仅多了一个@。翻看jdk,可以找到这么一个接口Annotation,如下
/** * The common interface extended by all annotation types. Note that an * interface that manually extends this one does <i>not</i> define * an annotation type. Also note that this interface does not itself * define an annotation type. * * More information about annotation types can be found in section 9.6 of * <cite>The Java™ Language Specification</cite>. * * The {@link java.lang.reflect.AnnotatedElement} interface discusses * compatibility concerns when evolving an annotation type from being * non-repeatable to being repeatable. * * @author Josh Bloch * @since 1.5 */public interface Annotation { // …省略}
开头就表明了,The common interface extended by all annotation types。说的很明白了,其实注解它就是个接口,对,它就是个接口而已,@interface仅仅是个语法糖。那么,注解既然是个接口,就必然会有相应的实现类,那实现类哪里来呢?上述中我们仅仅定义了OrderHandlerType注解,别的什么也没有做。这时候不得不提动态代理了,一定是jdk在背后为我们做了些什么。
为了追踪JVM在运行过程中生成的JDK动态代理类。我们可以设置VM启动参数如下:
-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true
该参数可以保存所生成的JDK动态代理类到本地。额外说一句,若我们想追踪cglib所生成的代理类,即对应的字节码文件,可以设置参数:
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "保存的路径");
添加参数后再次启动项目,可以看到我们项目里多了许多class文件(这里只截取了部分,笔者启动的项目共生成了97个动态代理类。由于我的项目是springboot环境,因此生成了许多如ConditionalOnMissingBean、Configuration、Autowired等注解对应的代理类):
那接下来就是要找到我们所关心的,即jdk为我们自定义的OrderHandlerType注解所生成的代理类。由于jdk生成的动态代理类都会继承Proxy这个类,而java又是单继承的,所以,我们只需要找到实现了OrderHandlerType的类即可。遍历这些类文件,发现$Proxy63
.class实现了我们定义的OrderHandlerType注解:
public final class $Proxy63 extends Proxy implements OrderHandlerType { private static Method m1; private static Method m2; private static Method m4; private static Method m3; private static Method m0; public $Proxy63(InvocationHandler var1) throws { super(var1); } // …省略}
我们知道,jdk动态代理其实现的核心是:
private Map<OrderHandlerType, OrderHandler> orderHandleMap;
如此一来,不管决定订单处理器orderhandler的因素怎么变,我们便可以以不变应万变(这不就是我们所追求的代码高扩展性和灵活性么)。那当我们的map的key变成了OrderHandlerType之后,注入和获取的逻辑就要相应改变,注入的地方很好改变,如下:
public class OrderService { private Map<OrderHandlerType, OrderHandler> orderHandleMap; @Autowired public void setOrderHandleMap(List<OrderHandler> orderHandlers) { // 注入各种类型的订单处理类 orderHandleMap = orderHandlers.stream().collect( Collectors.toMap(orderHandler -> AnnotationUtils.findAnnotation(orderHandler.getClass(), OrderHandlerType.class), v -> v, (v1, v2) -> v1)); } // ...省略}
那获取的逻辑要怎么实现?我们怎么根据order的来源和支付方式去orderHandleMap里获取对应的OrderHandler呢?问题变成了如何关联order的来源和支付方式与OrderHandlerType注解。
还记得刚才所说的注解就是个接口吗,既然是个接口,我们自己实现一个类不就完事了么,这样就把order的来源和支付方式与OrderHandlerType注解关联起来了。说干就干,现在我们有了这么一个类,
public class OrderHandlerTypeImpl implements OrderHandlerType { private String source; private String payMethod; OrderHandlerTypeImpl(String source, String payMethod) { this.source = source; this.payMethod = payMethod; } @Override public String source() { return source; } @Override public String payMethod() { return payMethod; } @Override public Class<? extends Annotation> annotationType() { return OrderHandlerType.class; }}
在获取对应OrderHandler时我们可以这样写,
public void orderService(Order order) { // ...一些前置处理 // 通过订单来源确以及支付方式获取对应的handler OrderHandlerType orderHandlerType = new OrderHandlerTypeImpl(order.getSource(), order.getPayMethod()); OrderHandler orderHandler = orderHandleMap.get(orderHandlerType); orderHandler.handle(order); // ...一些后置处理}
看起来没什么问题了,来运行一下。不对劲啊,空指针,那个异常它来了。
private int hashCodeImpl() { int var1 = 0; Entry var3; for(Iterator var2 = this.memberValues.entrySet().iterator(); var2.hasNext(); var1 += 127 * ((String)var3.getKey()).hashCode() ^ memberValueHashCode(var3.getValue())) { var3 = (Entry)var2.next(); } return var1;}private Boolean equalsImpl(Object var1) { if (var1 == this) { return true; } else if (!this.type.isInstance(var1)) { return false; } else { Method[] var2 = this.getMemberMethods(); int var3 = var2.length; for(int var4 = 0; var4 < var3; ++var4) { Method var5 = var2[var4]; String var6 = var5.getName(); Object var7 = this.memberValues.get(var6); Object var8 = null; AnnotationInvocationHandler var9 = this.asOneOfUs(var1); if (var9 != null) { var8 = var9.memberValues.get(var6); } else { try { var8 = var5.invoke(var1); } catch (InvocationTargetException var11) { return false; } catch (IllegalAccessException var12) { throw new AssertionError(var12); } } if (!memberValueEquals(var7, var8)) { return false; } } return true; }}
具体的逻辑也比较简单,就不分析了。那我们就按照AnnotationInvocationHandler中的实现,在我们的OrderHandlerTypeImpl中按照相同的逻辑重写下这两个方法,如下
public class OrderHandlerTypeImpl implements OrderHandlerType { // …省略 @Override public int hashCode() { int hashCode = 0; hashCode += (127 * "source".hashCode()) ^ source.hashCode(); hashCode += (127 * "payMethod".hashCode()) ^ payMethod.hashCode(); return hashCode; } @Override public boolean equals(Object obj) { if (!(obj instanceof OrderHandlerType)) { return false; } OrderHandlerType other = (OrderHandlerType) obj; return source.equals(other.source()) && payMethod.equals(other.payMethod()); }}
再次运行看看是否达到我们预期,果不其然,这次可以正常获取到了handler,至此,大功告成。
感谢各位的阅读,以上就是“java注解怎么定义实现”的内容了,经过本文的学习后,相信大家对java注解怎么定义实现这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是亿速云,小编将为大家推送更多相关知识点的文章,欢迎关注!
亿速云「云服务器」,即开即用、新一代英特尔至强铂金CPU、三副本存储NVMe SSD云盘,价格低至29元/月。点击查看>>
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。
原文链接:https://my.oschina.net/u/4072299/blog/4416025