The need to monitor the operation time of the database in the application and to automatically pass link tracking information in the underlying framework is often encountered, especially when building the infrastructure.
There is only one core goal, and that is to encapsulate it at the bottom level without the care of the upper users. Today, I’ll talk to you about how to deal with the common underlying extension burying point.
The framework comes with extension points
This is handy if the framework you’re using is designed with extension points in mind. For example, Mybatis interceptor, we can monitor Sql in the interceptor, rewrite.
For example, Alibaba’s Sentinel framework allows SPI to expand slots, adjust the scheduling order, and add custom slots to realize traffic limiting alarms.
The quality of open source frameworks varies widely, with some being better designed in the early days and leaving plenty of extension points for users. Others are not so comprehensive that you need to extend when you use them and find that extension points are not available. For scenarios where the framework itself does not provide extension points, look below.
Modify the source code
If the framework does not have extension points, the most straightforward way is to modify the source code of the open source framework to extend its desired functionality. This is usually done by cloning the source code into a private repository, modifying it, testing it, and repackaging it for use.
For example, we used XXL-job to do task scheduling before, and also modified some codes to expand the configuration information of monitoring notification on the interface. By default, only mailbox is supported, and mobile phones and nails can be extended.
The downside of modifying source code is that it needs to be aligned with the original version of the framework. Misalignment means that some bugs have been fixed and some features have been added, and they are no longer usable. To align, you need to constantly merge new versions of code into your own branches.
There are also many companies, based on the open source version, build their own version of the company, and then directly follow the internal use requirements to expand and maintain, will not be synchronized with the community version, if you have a dedicated team to do this, it is also a way.
File overwrite with the same name
Changing the source code requires constant synchronization of the new version of the code, and sometimes only a single class can be modified, such as monitoring the underlying operations, if the framework does not provide extension points.
Another trick is to create a class in the project that is exactly the same as the one you want to modify, with the same package name + class name and method name. You can change the implementation of the method. In this way, you can overwrite the classes in the JAR. It depends on the order in which the classes are loaded.
The advantage of this approach is that you don’t have to synchronize new versions of the code as often. If your framework version is updated, as long as the package name and class name remain the same, you will only be covering that class, and the new features and bug fixes won’t matter.
Plane to intercept
Sections are very useful for a lot of unified processing, but also for scenarios where you have a buried spot.
For example, we need to monitor all the operations of Mongodb in the project. We can modify the driver source code of Mongodb, and we can create a file of the same name to cover it. There are many ways to find a suitable one and the most important one to achieve the requirements.
Mongodb to Spring operation to illustrate, in Spring Data Mongodb will MongoTemplate to operate Mongodb. The simplest way to do this is to bury the MongoTemplate class directly so that all operations can be monitored.
Go directly to all of MongoTemplate’s methods with a section and then bury the point. It’s easy.
@Aspect public class MongoTemplateAspect { @Pointcut("execution(* org.springframework.data.mongodb.core.MongoTemplate.*(..) )") public void pointcut() {} @Around("pointcut()") public Object around(final ProceedingJoinPoint pjp) throws Throwable { String callMethod = pjp.getSignature().getDeclaringType().getSimpleName() + "." + pjp.getSignature().getName(); Map<String, Object> data = new HashMap<>(); data.put("params", JsonUtils.toJson(pjp.getArgs())); return CatTransactionManager.newTransaction(() -> { try { return pjp.proceed(); } catch (Throwable throwable) { throw new RuntimeException(throwable); } }, "Mongo", callMethod, data); }}Copy the code
For example, if you want to monitor Redis, which uses the same spring-integrated framework, there is a class called RedisTemplate, which can also be used with facets.
If you want to monitor the Connection layer, all operations in Template are implemented based on Connection.
We can also replace connection-related implementations with sections, for example, by cutting to the method that gets Connection, and then replacing the Connection object with an object that has buried monitoring.
@Aspect public class RedisAspect { @Pointcut("target(org.springframework.data.redis.connection.RedisConnectionFactory)") public void connectionFactory() {} @Pointcut("execution(org.springframework.data.redis.connection.RedisConnection *.getConnection(..) )") public void getConnection() {} @Pointcut("execution(org.springframework.data.redis.connection.RedisClusterConnection *.getClusterConnection(..) )") public void getClusterConnection() {} @Around("getConnection() && connectionFactory()") public Object aroundGetConnection(final ProceedingJoinPoint pjp) throws Throwable { RedisConnection connection = (RedisConnection) pjp.proceed(); return new CatMonitorRedisConnection(connection); } @Around("getClusterConnection() && connectionFactory()") public Object aroundGetClusterConnection(final ProceedingJoinPoint pjp) throws Throwable { RedisClusterConnection clusterConnection = (RedisClusterConnection) pjp.proceed(); return new CatMonitorRedisClusterConnection(clusterConnection); }}Copy the code
On native CatMonitorRedisConnection RedisConnection did increase, also won’t affect the function of the original RedisConnection.
public class CatMonitorRedisConnection implements RedisConnection { private final RedisConnection connection; private CatMonitorHelper catMonitorHelper; public CatMonitorRedisConnection(RedisConnection connection) { this.connection = connection; this.catMonitorHelper = new CatMonitorHelper(); }
@Override public byte[] get(byte[] key) { return catMonitorHelper.execute(RedisCommand.GET, key, () -> connection.get(key)); } Copy the code
Copy the code
}
Java Agent
The Java Agent can make changes to the bytecode of loaded classes at run time, adding code logic that we need to monitor. No need to modify the original code, zero invasion.
I have seen the application of Java Agent in many excellent open source frameworks, such as APM framework SkyWalking, asynchronous transmission context transmittable-thread-local, etc.
Java Agent has a certain threshold compared to other methods, after all, it is not a common technical point used in daily development. If you want to understand this extension, you can look at the source code of some of the open source frameworks already in use to get a general idea of how to use it. If there is any thread pool that has any issues with transmittable-thread-local, javassist will be used to manipulate the bytecode. If there is any thread pool that has any issues with Transmittable-thread-local, javassist will be used to manipulate the bytecode.
try { final CtMethod afterExecute = clazz.getDeclaredMethod("afterExecute", new CtClass[]{runnableClass, throwableClass}); // unwrap runnable if IsAutoWrapper String code = "$1 = com.alibaba.ttl.threadpool.agent.internal.transformlet.impl.Utils.unwrapIfIsAutoWrapper($1);" ; logger.info("insert code before method " + signatureOfMethod(afterExecute) + " of class " + afterExecute.getDeclaringClass().getName() + ": " + code); afterExecute.insertBefore(code); modified = true; } catch (NotFoundException e) { // clazz does not override afterExecute method, do nothing. }Copy the code
About the author: Yin Jihuan, a simple technology lover, the author of Spring Cloud Micro-service – Full stack Technology and Case Analysis, Spring Cloud Micro-service Introduction combat and Progress, the founder of the public account Monkey World.
I have organized a very complete learning material. If you are interested, you can search “Monkey World” on wechat and reply to the keyword “learning material” to obtain the Spring Cloud, Spring Cloud Alibaba, Sharing-JDBC sub-library sub-table and task scheduling framework XXL-job that I have organized. MongoDB, crawler and other related information.