diff --git a/src/main/java/com/profiler/interceptor/InterceptorRegistry.java b/src/main/java/com/profiler/interceptor/InterceptorRegistry.java index d724e694e..8f945e84a 100644 --- a/src/main/java/com/profiler/interceptor/InterceptorRegistry.java +++ b/src/main/java/com/profiler/interceptor/InterceptorRegistry.java @@ -1,5 +1,7 @@ package com.profiler.interceptor; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicInteger; public class InterceptorRegistry { @@ -7,50 +9,61 @@ public class InterceptorRegistry { private static final Interceptor DUMMY = new LoggingInterceptor("com.profiler.interceptor.DUMMY"); public static final InterceptorRegistry REGISTRY = new InterceptorRegistry(); - private final static int DEFAULT_MAX = 1024; - private final int max; + private final static int DEFAULT_MAX = 1024; + private final int max; - private final AtomicInteger id = new AtomicInteger(0); - private final Interceptor[] index; + private final AtomicInteger id = new AtomicInteger(0); + private final Interceptor[] index; - InterceptorRegistry() { - this(DEFAULT_MAX); - } +// private final ConcurrentMap nameIndex = new ConcurrentHashMap(); - InterceptorRegistry(int max) { - this.max = max; - this.index = new Interceptor[max]; - } + InterceptorRegistry() { + this(DEFAULT_MAX); + } + + InterceptorRegistry(int max) { + this.max = max; + this.index = new Interceptor[max]; + } - int addInterceptor0(Interceptor interceptor) { - if (interceptor == null) { - return -1; - } - int newId = id.getAndIncrement(); - if (newId > max) { - throw new IllegalArgumentException("id" + id); - } + int addInterceptor0(Interceptor interceptor) { + if (interceptor == null) { + return -1; + } + int newId = id.getAndIncrement(); + if (newId > max) { + throw new IllegalArgumentException("id" + id); + } - this.index[newId] = interceptor; - return newId; - } + this.index[newId] = interceptor; +// this.nameIndex.put(interceptor.getClass().getName(), newId); + return newId; + } - Interceptor getInterceptor0(int key) { + Interceptor getInterceptor0(int key) { Interceptor interceptor = index[key]; if (interceptor == null) { // 로직이 잘못되었을 경우 에러가 발생하지 않도록 더미를 리턴. return DUMMY; } return interceptor; - } + } - public static int addInterceptor(Interceptor interceptor) { - return REGISTRY.addInterceptor0(interceptor); - } +// public Interceptor findInterceptor0(String interceptorName) { +// Integer indexNumber = this.nameIndex.get(interceptorName); +// if (indexNumber != null) { +// return index[indexNumber]; +// } +// return null; +// } - public static Interceptor getInterceptor(int key) { - return REGISTRY.getInterceptor0(key); - } + public static int addInterceptor(Interceptor interceptor) { + return REGISTRY.addInterceptor0(interceptor); + } + + public static Interceptor getInterceptor(int key) { + return REGISTRY.getInterceptor0(key); + } } diff --git a/src/main/java/com/profiler/interceptor/bci/ClassLoadChecker.java b/src/main/java/com/profiler/interceptor/bci/ClassLoadChecker.java new file mode 100644 index 000000000..4552ed894 --- /dev/null +++ b/src/main/java/com/profiler/interceptor/bci/ClassLoadChecker.java @@ -0,0 +1,50 @@ +package com.profiler.interceptor.bci; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +public class ClassLoadChecker { + + private static final Object EXIST = new Object(); + + private ConcurrentMap load = new ConcurrentHashMap(); + + public boolean exist(ClassLoader cl, String className) { + LoadClass key = new LoadClass(cl, className); + Object old = load.putIfAbsent(key, EXIST); + if (old == null) { + return false; + } + return true; + } + + class LoadClass { + private ClassLoader classLoader; + private String className; + + LoadClass(ClassLoader classLoader, String className) { + this.classLoader = classLoader; + this.className = className; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + LoadClass loadClass = (LoadClass) o; + + if (classLoader != null ? !classLoader.equals(loadClass.classLoader) : loadClass.classLoader != null) return false; + if (className != null ? !className.equals(loadClass.className) : loadClass.className != null) return false; + + return true; + } + + @Override + public int hashCode() { + int result = classLoader != null ? classLoader.hashCode() : 0; + result = 31 * result + (className != null ? className.hashCode() : 0); + return result; + } + } +} diff --git a/src/main/java/com/profiler/interceptor/bci/JavaAssistByteCodeInstrumentor.java b/src/main/java/com/profiler/interceptor/bci/JavaAssistByteCodeInstrumentor.java index 85e49d859..580d26075 100644 --- a/src/main/java/com/profiler/interceptor/bci/JavaAssistByteCodeInstrumentor.java +++ b/src/main/java/com/profiler/interceptor/bci/JavaAssistByteCodeInstrumentor.java @@ -3,6 +3,7 @@ package com.profiler.interceptor.bci; import java.net.URL; import java.net.URLClassLoader; import java.security.ProtectionDomain; +import java.util.concurrent.ConcurrentMap; import java.util.logging.Level; import java.util.logging.Logger; @@ -17,6 +18,8 @@ public class JavaAssistByteCodeInstrumentor implements ByteCodeInstrumentor { private ClassPool classPool; + private ClassLoadChecker classLoadChecker = new ClassLoadChecker(); + public JavaAssistByteCodeInstrumentor() { this.classPool = createClassPool(null); } @@ -75,17 +78,24 @@ public class JavaAssistByteCodeInstrumentor implements ByteCodeInstrumentor { if (logger.isLoggable(Level.INFO)) { logger.info("defineClass class:" + defineClass + " cl:" + classLoader); } - try { - CtClass clazz = classPool.get(defineClass); - defineNestedClass(clazz, classLoader, protectedDomain); - return clazz.toClass(classLoader, protectedDomain); +// 아래 classLoaderChecker가 생겼으니 classLoader 를 같이 락으로 잡아야 되지 않는가? +// synchronized (classLoader) + if (this.classLoadChecker.exist(classLoader, defineClass)) { + return classLoader.loadClass(defineClass); + } else { + CtClass clazz = classPool.get(defineClass); + defineNestedClass(clazz, classLoader, protectedDomain); + return clazz.toClass(classLoader, protectedDomain); + } } catch (NotFoundException e) { throw new InstrumentException(defineClass + " class not fund. Cause:" + e.getMessage(), e); } catch (CannotCompileException e) { throw new InstrumentException(defineClass + " class define fail. cl:" + classLoader + " Cause:" + e.getMessage(), e); - } - } + } catch (ClassNotFoundException e) { + throw new InstrumentException(defineClass + " class not fund. Cause:" + e.getMessage(), e); + } + } private void defineNestedClass(CtClass clazz, ClassLoader classLoader, ProtectionDomain protectedDomain) throws NotFoundException, CannotCompileException { CtClass[] nestedClasses = clazz.getNestedClasses(); diff --git a/src/main/java/com/profiler/modifier/DefaultModifierRegistry.java b/src/main/java/com/profiler/modifier/DefaultModifierRegistry.java index ba91af0a3..c3ea8f798 100644 --- a/src/main/java/com/profiler/modifier/DefaultModifierRegistry.java +++ b/src/main/java/com/profiler/modifier/DefaultModifierRegistry.java @@ -16,10 +16,7 @@ import com.profiler.modifier.db.mssql.MSSQLConnectionModifier; import com.profiler.modifier.db.mssql.MSSQLPreparedStatementModifier; import com.profiler.modifier.db.mssql.MSSQLResultSetModifier; import com.profiler.modifier.db.mssql.MSSQLStatementModifier; -import com.profiler.modifier.db.mysql.MySQLConnectionImplModifier; -import com.profiler.modifier.db.mysql.MySQLPreparedStatementModifier; -import com.profiler.modifier.db.mysql.MySQLResultSetModifier; -import com.profiler.modifier.db.mysql.MySQLStatementModifier; +import com.profiler.modifier.db.mysql.*; import com.profiler.modifier.db.oracle.OraclePreparedStatementModifier; import com.profiler.modifier.db.oracle.OracleResultSetModifier; import com.profiler.modifier.db.oracle.OracleStatementModifier; @@ -98,6 +95,9 @@ public class DefaultModifierRegistry implements ModifierRegistry { Modifier mysqlPreparedStatementModifier = new MySQLPreparedStatementModifier(byteCodeInstrumentor); addModifier(mysqlPreparedStatementModifier); + MySQLPreparedStatementJDBC4Modifier myqlPreparedStatementJDBC4Modifier = new MySQLPreparedStatementJDBC4Modifier(byteCodeInstrumentor); + addModifier(myqlPreparedStatementJDBC4Modifier); + Modifier mysqlResultSetModifier = new MySQLResultSetModifier(byteCodeInstrumentor); addModifier(mysqlResultSetModifier); } diff --git a/src/main/java/com/profiler/modifier/db/mysql/MySQLConnectionImplModifier.java b/src/main/java/com/profiler/modifier/db/mysql/MySQLConnectionImplModifier.java index e5c2f73eb..cd4fa826a 100644 --- a/src/main/java/com/profiler/modifier/db/mysql/MySQLConnectionImplModifier.java +++ b/src/main/java/com/profiler/modifier/db/mysql/MySQLConnectionImplModifier.java @@ -37,6 +37,8 @@ public class MySQLConnectionImplModifier extends AbstractModifier { mysqlConnection.addTraceVariable("__url", "__setUrl", "__getUrl", "java.lang.String"); + + // 해당 Interceptor를 공통클래스 만들경우 system에 로드해야 된다. Interceptor createConnection = newInterceptor(classLoader, protectedDomain, "com.profiler.modifier.db.mysql.interceptors.CreateConnectionInterceptor"); String[] params = new String[] { "java.lang.String", "int", "java.util.Properties", "java.lang.String", "java.lang.String" diff --git a/src/main/java/com/profiler/modifier/db/mysql/MySQLPreparedStatementJDBC4Modifier.java b/src/main/java/com/profiler/modifier/db/mysql/MySQLPreparedStatementJDBC4Modifier.java new file mode 100644 index 000000000..f350f7c91 --- /dev/null +++ b/src/main/java/com/profiler/modifier/db/mysql/MySQLPreparedStatementJDBC4Modifier.java @@ -0,0 +1,67 @@ +package com.profiler.modifier.db.mysql; + +import com.profiler.interceptor.Interceptor; +import com.profiler.interceptor.bci.ByteCodeInstrumentor; +import com.profiler.interceptor.bci.InstrumentClass; +import com.profiler.interceptor.bci.InstrumentException; +import com.profiler.interceptor.bci.NotFoundInstrumentException; +import com.profiler.modifier.AbstractModifier; +import com.profiler.util.*; + +import java.lang.reflect.Method; +import java.security.ProtectionDomain; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class MySQLPreparedStatementJDBC4Modifier extends AbstractModifier { + + private final Logger logger = Logger.getLogger(MySQLPreparedStatementJDBC4Modifier.class.getName()); + private final String[] includes = new String[] { "setRowId", "setNClob", "setSQLXML" }; + + public MySQLPreparedStatementJDBC4Modifier(ByteCodeInstrumentor byteCodeInstrumentor) { + super(byteCodeInstrumentor); + } + + public String getTargetClass() { + return "com/mysql/jdbc/JDBC4PreparedStatement"; + } + + @Override + public byte[] modify(ClassLoader classLoader, String className, ProtectionDomain protectedDomain, byte[] classFileBuffer) { + if (logger.isLoggable(Level.INFO)) { + logger.info("Modifing. " + className); + } + checkLibrary(classLoader, className); + try { + InstrumentClass preparedStatement = byteCodeInstrumentor.getClass(className); + + bindVariableIntercept(preparedStatement, classLoader, protectedDomain); + + return preparedStatement.toBytecode(); + } catch (InstrumentException e) { + if (logger.isLoggable(Level.WARNING)) { + logger.log(Level.WARNING, this.getClass().getSimpleName() + " modify fail. Cause:" + e.getMessage(), e); + } + return null; + } + } + + private void bindVariableIntercept(InstrumentClass preparedStatement, ClassLoader classLoader, ProtectionDomain protectedDomain) throws InstrumentException { + BindVariableFilter exclude = new IncludeBindVariableFilter(includes); + List bindMethod = PreparedStatementUtils.findBindVariableSetMethod(exclude); + Interceptor interceptor = newInterceptor(classLoader, protectedDomain, "com.profiler.modifier.db.mysql.interceptors.PreparedStatementBindVariableInterceptor"); + for (Method method : bindMethod) { + String methodName = method.getName(); + String[] parameterType = JavaAssistUtils.getParameterType(method.getParameterTypes()); + try { + preparedStatement.addInterceptor(methodName, parameterType, interceptor); + } catch (NotFoundInstrumentException e) { + // bind variable setter메소드를 못찾을 경우는 그냥 경고만 표시, 에러 아님. + if (logger.isLoggable(Level.FINE)) { + logger.log(Level.FINE, "bindVariable api not found. Cause:" + e.getMessage(), e); + } + } + } + } +} diff --git a/src/main/java/com/profiler/modifier/db/mysql/MySQLPreparedStatementModifier.java b/src/main/java/com/profiler/modifier/db/mysql/MySQLPreparedStatementModifier.java index 76065b0c2..ab3221bd2 100644 --- a/src/main/java/com/profiler/modifier/db/mysql/MySQLPreparedStatementModifier.java +++ b/src/main/java/com/profiler/modifier/db/mysql/MySQLPreparedStatementModifier.java @@ -90,8 +90,8 @@ public class MySQLPreparedStatementModifier extends AbstractModifier { preparedStatement.addInterceptor(methodName, parameterType, interceptor); } catch (NotFoundInstrumentException e) { // bind variable setter메소드를 못찾을 경우는 그냥 경고만 표시, 에러 아님. - if (logger.isLoggable(Level.INFO)) { - logger.log(Level.INFO, "bindVariable api not found. Cause:" + e.getMessage(), e); + if (logger.isLoggable(Level.FINE)) { + logger.log(Level.FINE, "bindVariable api not found. Cause:" + e.getMessage(), e); } } } diff --git a/src/test/java/com/profiler/modifier/db/mysql/MySQLConnectionImplModifierTest.java b/src/test/java/com/profiler/modifier/db/mysql/MySQLConnectionImplModifierTest.java index c2ebf56fc..47204f8bc 100644 --- a/src/test/java/com/profiler/modifier/db/mysql/MySQLConnectionImplModifierTest.java +++ b/src/test/java/com/profiler/modifier/db/mysql/MySQLConnectionImplModifierTest.java @@ -31,6 +31,8 @@ public class MySQLConnectionImplModifierTest { MySQLPreparedStatementModifier preparedStatementModifier = new MySQLPreparedStatementModifier(loader.getInstrumentor()); loader.addModifier(preparedStatementModifier); + MySQLPreparedStatementJDBC4Modifier preparedStatementJDBC4Modifier = new MySQLPreparedStatementJDBC4Modifier(loader.getInstrumentor()); + loader.addModifier(preparedStatementJDBC4Modifier); // loader.delegateLoadingOf(ConnectionTrace.class.getName()); loader.initialize(); @@ -80,6 +82,7 @@ public class MySQLConnectionImplModifierTest { private void preparedStatement(Connection connection) throws SQLException { PreparedStatement preparedStatement = connection.prepareStatement("select 1"); + logger.info("PreparedStatement className:" + preparedStatement.getClass().getName()); ResultSet resultSet = preparedStatement.executeQuery(); resultSet.close(); preparedStatement.close();