diff --git a/txlcn-tc/src/main/java/com/codingapi/txlcn/tc/annotation/TableId.java b/txlcn-tc/src/main/java/com/codingapi/txlcn/tc/annotation/TableId.java new file mode 100644 index 000000000..c4c999c1d --- /dev/null +++ b/txlcn-tc/src/main/java/com/codingapi/txlcn/tc/annotation/TableId.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2011-2019, hubin (jobob@qq.com). + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.codingapi.txlcn.tc.annotation; + +import java.lang.annotation.*; + +/** + * @author dinghuang123@gmail.com + * @since 2019/7/29 + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface TableId { + + /** + * 字段值(驼峰命名方式,该值可无) + */ + String value() default ""; + +} diff --git a/txlcn-tc/src/main/java/com/codingapi/txlcn/tc/annotation/TableName.java b/txlcn-tc/src/main/java/com/codingapi/txlcn/tc/annotation/TableName.java new file mode 100644 index 000000000..f11909f29 --- /dev/null +++ b/txlcn-tc/src/main/java/com/codingapi/txlcn/tc/annotation/TableName.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2011-2019, hubin (jobob@qq.com). + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.codingapi.txlcn.tc.annotation; + +import java.lang.annotation.*; + +/** + * @author dinghuang123@gmail.com + * @since 2019/7/29 + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface TableName { + + /** + * 实体对应的表名 + */ + String value() default ""; +} diff --git a/txlcn-tc/src/main/java/com/codingapi/txlcn/tc/core/transaction/txc/analy/PrimaryKeyListVisitor.java b/txlcn-tc/src/main/java/com/codingapi/txlcn/tc/core/transaction/txc/analy/PrimaryKeyListVisitor.java index 620ec5f22..6821c1814 100644 --- a/txlcn-tc/src/main/java/com/codingapi/txlcn/tc/core/transaction/txc/analy/PrimaryKeyListVisitor.java +++ b/txlcn-tc/src/main/java/com/codingapi/txlcn/tc/core/transaction/txc/analy/PrimaryKeyListVisitor.java @@ -76,8 +76,14 @@ private Map newKeyValues(List expressions) { Map keyValues = new HashMap<>(); for (int i = 0; i < columns.size(); i++) { columns.get(i).setTable(table); + //解决Long类型字段作为主键空指针异常 if (primaryKeys.contains(columns.get(i).getFullyQualifiedName())) { - Object expression = expressions.get(i).getASTNode().jjtGetValue(); + Object expression = null; + if (expressions.get(i).getASTNode() != null) { + expressions.get(i).getASTNode().jjtGetValue(); + } else { + expression = expressions.get(i); + } keyValues.put(columns.get(i).getFullyQualifiedName(), Reflection.invokeN(expression.getClass(), "getValue", expression, new Object[0])); } diff --git a/txlcn-tc/src/main/java/com/codingapi/txlcn/tc/core/transaction/txc/analy/PrimaryKeyListVisitorHandler.java b/txlcn-tc/src/main/java/com/codingapi/txlcn/tc/core/transaction/txc/analy/PrimaryKeyListVisitorHandler.java new file mode 100644 index 000000000..95a192b7f --- /dev/null +++ b/txlcn-tc/src/main/java/com/codingapi/txlcn/tc/core/transaction/txc/analy/PrimaryKeyListVisitorHandler.java @@ -0,0 +1,35 @@ +package com.codingapi.txlcn.tc.core.transaction.txc.analy; + +import com.codingapi.txlcn.tc.core.transaction.txc.analy.def.PrimaryKeysProvider; +import com.codingapi.txlcn.tc.core.transaction.txc.analy.util.AnnotationUtils; +import com.codingapi.txlcn.tc.core.transaction.txc.analy.util.ClassUtils; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import java.util.List; +import java.util.Map; + +/** + * @author dinghuang123@gmail.com + * @since 2019/7/28 + */ +@Component +public class PrimaryKeyListVisitorHandler implements PrimaryKeysProvider { + + @Value("${tx-lcn.primary-key-package}") + private String primaryKeyPackage; + + @Override + public Map> provide() { + if (primaryKeyPackage != null) { + //扫描所有注解,把主键拿进来 + // 获取特定包下所有的类(包括接口和类) + List> clsList = ClassUtils.getClasses(primaryKeyPackage); + //输出所有使用了特定注解的类的注解值 + return AnnotationUtils.getPrimaryKeyList(clsList); + } else { + return null; + } + } + +} diff --git a/txlcn-tc/src/main/java/com/codingapi/txlcn/tc/core/transaction/txc/analy/util/AnnotationUtils.java b/txlcn-tc/src/main/java/com/codingapi/txlcn/tc/core/transaction/txc/analy/util/AnnotationUtils.java new file mode 100644 index 000000000..4b7828d43 --- /dev/null +++ b/txlcn-tc/src/main/java/com/codingapi/txlcn/tc/core/transaction/txc/analy/util/AnnotationUtils.java @@ -0,0 +1,66 @@ +package com.codingapi.txlcn.tc.core.transaction.txc.analy.util; + +import com.codingapi.txlcn.tc.annotation.TableId; +import com.codingapi.txlcn.tc.annotation.TableName; + +import java.lang.reflect.Field; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * @author dinghuang123@gmail.com + * @since 2019/7/29 + */ +public class AnnotationUtils { + + private static Pattern humpPattern = Pattern.compile("[A-Z]"); + + public static Map> getPrimaryKeyList(List> clsList) { + if (clsList != null && clsList.size() > 0) { + Map> map = new HashMap<>(clsList.size()); + for (Class cls : clsList) { + //获取类中的所有的方法 + boolean classHasAnnotation = cls.isAnnotationPresent(TableName.class); + if (classHasAnnotation) { + TableName tableName = cls.getAnnotation(TableName.class); + if (tableName.value().length() != 0) { + Field[] fields = cls.getDeclaredFields(); + String tableIdVale = null; + for (Field field : fields) { + boolean fieldHasAnnotation = field.isAnnotationPresent(TableId.class); + if (fieldHasAnnotation) { + TableId tableId = field.getAnnotation(TableId.class); + //输出注解属性 + if (tableId.value().length() != 0) { + tableIdVale = tableId.value(); + } else { + //转驼峰 + tableIdVale = humpToLine(field.getName()); + } + break; + } + } + map.put(tableName.value(), Collections.singletonList(tableIdVale)); + } + } + } + return map; + } else { + return null; + } + } + + public static String humpToLine(String str) { + Matcher matcher = humpPattern.matcher(str); + StringBuffer sb = new StringBuffer(); + while (matcher.find()) { + matcher.appendReplacement(sb, "_" + matcher.group(0).toLowerCase()); + } + matcher.appendTail(sb); + return sb.toString(); + } +} diff --git a/txlcn-tc/src/main/java/com/codingapi/txlcn/tc/core/transaction/txc/analy/util/ClassUtils.java b/txlcn-tc/src/main/java/com/codingapi/txlcn/tc/core/transaction/txc/analy/util/ClassUtils.java new file mode 100644 index 000000000..f50e7cb4c --- /dev/null +++ b/txlcn-tc/src/main/java/com/codingapi/txlcn/tc/core/transaction/txc/analy/util/ClassUtils.java @@ -0,0 +1,116 @@ +package com.codingapi.txlcn.tc.core.transaction.txc.analy.util; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.IOException; +import java.net.JarURLConnection; +import java.net.URL; +import java.net.URLDecoder; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.List; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; + +/** + * @author dinghuang123@gmail.com + * @since 2019/7/29 + */ +public class ClassUtils { + + private static final Logger LOGGER = LoggerFactory.getLogger(ClassUtils.class); + + private static final String FILE = "file"; + private static final String JAR = "jar"; + private static final String CLASS_SIGN = ".class"; + + /** + * 从包package中获取所有的Class + * + * @param packageName packageName + * @return List + */ + public static List> getClasses(String packageName) { + List> classes = new ArrayList<>(); + boolean recursive = true; + String packageDirName = packageName.replace('.', '/'); + Enumeration dirs; + try { + dirs = Thread.currentThread().getContextClassLoader().getResources(packageDirName); + while (dirs.hasMoreElements()) { + URL url = dirs.nextElement(); + String protocol = url.getProtocol(); + if (FILE.equals(protocol)) { + String filePath = URLDecoder.decode(url.getFile(), "UTF-8"); + findAndAddClassesInPackageByFile(packageName, filePath, recursive, classes); + } else if (JAR.equals(protocol)) { + JarFile jar; + try { + jar = ((JarURLConnection) url.openConnection()).getJarFile(); + Enumeration entries = jar.entries(); + while (entries.hasMoreElements()) { + JarEntry entry = entries.nextElement(); + String name = entry.getName(); + if (name.charAt(0) == '/') { + name = name.substring(1); + } + if (name.startsWith(packageDirName)) { + int idx = name.lastIndexOf('/'); + if (idx != -1) { + packageName = name.substring(0, idx).replace('/', '.'); + } + if (idx != -1) { + if (name.endsWith(CLASS_SIGN) && !entry.isDirectory()) { + // 去掉后面的".class" 获取真正的类名 + String className = name.substring(packageName.length() + 1, name.length() - 6); + try { + classes.add(Class.forName(packageName + '.' + className)); + } catch (ClassNotFoundException e) { + LOGGER.error(e.getMessage()); + } + } + } + } + } + } catch (IOException e) { + LOGGER.error(e.getMessage()); + } + } + } + } catch (IOException e) { + LOGGER.error(e.getMessage()); + } + return classes; + } + + /** + * 以文件的形式来获取包下的所有Class + * + * @param packageName packageName + * @param packagePath packagePath + * @param recursive recursive + * @param classes classes + */ + private static void findAndAddClassesInPackageByFile(String packageName, String packagePath, final boolean recursive, List> classes) { + File dir = new File(packagePath); + if (!dir.exists() || !dir.isDirectory()) { + return; + } + File[] dirFiles = dir.listFiles(file -> (recursive && file.isDirectory()) || (file.getName().endsWith(CLASS_SIGN))); + assert dirFiles != null; + for (File file : dirFiles) { + if (file.isDirectory()) { + findAndAddClassesInPackageByFile(packageName + "." + file.getName(), file.getAbsolutePath(), recursive, classes); + } else { + String className = file.getName().substring(0, file.getName().length() - 6); + try { + classes.add(Class.forName(packageName + '.' + className)); + } catch (ClassNotFoundException e) { + LOGGER.error(e.getMessage()); + } + } + } + } +} diff --git a/txlcn-tracing/src/main/java/com/codingapi/txlcn/tracing/TracingAutoConfiguration.java b/txlcn-tracing/src/main/java/com/codingapi/txlcn/tracing/TracingAutoConfiguration.java index 7719c5ddc..bc2212d18 100644 --- a/txlcn-tracing/src/main/java/com/codingapi/txlcn/tracing/TracingAutoConfiguration.java +++ b/txlcn-tracing/src/main/java/com/codingapi/txlcn/tracing/TracingAutoConfiguration.java @@ -15,6 +15,8 @@ */ package com.codingapi.txlcn.tracing; +import com.codingapi.txlcn.tracing.spring.TracingSpringContextUtils; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; @@ -28,4 +30,8 @@ @ComponentScan public class TracingAutoConfiguration { + @Bean + public TracingSpringContextUtils tracingSpringContextUtils() { + return new TracingSpringContextUtils(); + } } diff --git a/txlcn-tracing/src/main/java/com/codingapi/txlcn/tracing/http/ribbon/TxlcnZoneAvoidanceRule.java b/txlcn-tracing/src/main/java/com/codingapi/txlcn/tracing/http/ribbon/TxlcnZoneAvoidanceRule.java index 744e232d2..9a1329f53 100644 --- a/txlcn-tracing/src/main/java/com/codingapi/txlcn/tracing/http/ribbon/TxlcnZoneAvoidanceRule.java +++ b/txlcn-tracing/src/main/java/com/codingapi/txlcn/tracing/http/ribbon/TxlcnZoneAvoidanceRule.java @@ -17,6 +17,8 @@ import com.alibaba.fastjson.JSONObject; import com.codingapi.txlcn.tracing.TracingContext; +import com.codingapi.txlcn.tracing.spring.SpringConfig; +import com.codingapi.txlcn.tracing.spring.TracingSpringContextUtils; import com.netflix.loadbalancer.Server; import com.netflix.loadbalancer.ZoneAvoidanceRule; import lombok.extern.slf4j.Slf4j; @@ -57,7 +59,19 @@ public Server choose(Object key) { // 1. 自己加入此事务组调用链 assert Objects.nonNull(registration); - TracingContext.tracing().addApp(registration.getServiceId(), registration.getHost() + ":" + registration.getPort()); + //兼容springBoot 2.0以下版本 + Boolean exitHost; + try { + exitHost = registration.getHost() != null; + } catch (NoSuchMethodError noSuchMethodError) { + exitHost = false; + } + if (!exitHost) { + SpringConfig springConfig = (SpringConfig) TracingSpringContextUtils.getContext().getBean("springConfig"); + TracingContext.tracing().addApp(registration.getServiceId(), springConfig.getHost() + ":" + springConfig.getPort()); + } else { + TracingContext.tracing().addApp(registration.getServiceId(), registration.getHost() + ":" + registration.getPort()); + } // 2. 获取所有要访问服务的实例 List servers = getLoadBalancer().getAllServers(); diff --git a/txlcn-tracing/src/main/java/com/codingapi/txlcn/tracing/spring/SpringConfig.java b/txlcn-tracing/src/main/java/com/codingapi/txlcn/tracing/spring/SpringConfig.java new file mode 100644 index 000000000..28a639135 --- /dev/null +++ b/txlcn-tracing/src/main/java/com/codingapi/txlcn/tracing/spring/SpringConfig.java @@ -0,0 +1,49 @@ +package com.codingapi.txlcn.tracing.spring; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import java.net.InetAddress; +import java.net.UnknownHostException; + +/** + * @author dinghuang123@gmail.com + * @since 2019/7/29 + */ +@Component +public class SpringConfig { + + private static final Logger LOGGER = LoggerFactory.getLogger(SpringConfig.class); + + @Value("${server.port}") + public Integer port; + + @Value("${spring.application.name}") + public String serviceName; + + public String host; + + public Integer getPort() { + return this.port; + } + + public String getServiceName() { + return this.serviceName; + } + + public String getHost() { + if (this.host == null) { + InetAddress address = null; + try { + address = InetAddress.getLocalHost(); + } catch (UnknownHostException e) { + LOGGER.info(e.getMessage()); + } + this.host = address.getHostAddress(); + } + return this.host; + } + +} diff --git a/txlcn-tracing/src/main/java/com/codingapi/txlcn/tracing/spring/TracingSpringContextUtils.java b/txlcn-tracing/src/main/java/com/codingapi/txlcn/tracing/spring/TracingSpringContextUtils.java new file mode 100644 index 000000000..6e4f8d784 --- /dev/null +++ b/txlcn-tracing/src/main/java/com/codingapi/txlcn/tracing/spring/TracingSpringContextUtils.java @@ -0,0 +1,45 @@ +package com.codingapi.txlcn.tracing.spring; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.support.DefaultListableBeanFactory; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.support.AbstractRefreshableApplicationContext; +import org.springframework.context.support.GenericApplicationContext; + +/** + * spring工厂调用辅助类 + * + * @author dinghuang123@gmail.com + * @since 2019/2/26 + */ +public class TracingSpringContextUtils implements ApplicationContextAware { + private static final Logger LOGGER = LoggerFactory.getLogger(TracingSpringContextUtils.class); + + private static DefaultListableBeanFactory springFactory; + + private static ApplicationContext context; + + @Override + public void setApplicationContext(ApplicationContext applicationContext) { + LOGGER.info("设置Spring上下文"); + context = applicationContext; + if (applicationContext instanceof AbstractRefreshableApplicationContext) { + AbstractRefreshableApplicationContext springContext = + (AbstractRefreshableApplicationContext) applicationContext; + springFactory = (DefaultListableBeanFactory) springContext.getBeanFactory(); + } else if (applicationContext instanceof GenericApplicationContext) { + GenericApplicationContext springContext = (GenericApplicationContext) applicationContext; + springFactory = springContext.getDefaultListableBeanFactory(); + } + } + + public static DefaultListableBeanFactory getSpringFactory() { + return springFactory; + } + + public static ApplicationContext getContext() { + return context; + } +} diff --git a/txlcn-tracing/src/main/resources/META-INF/spring.factories b/txlcn-tracing/src/main/resources/META-INF/spring.factories new file mode 100644 index 000000000..74f9765f1 --- /dev/null +++ b/txlcn-tracing/src/main/resources/META-INF/spring.factories @@ -0,0 +1,2 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ +com.codingapi.txlcn.tracing.TracingAutoConfiguration \ No newline at end of file