Skip to content

Commit

Permalink
基于 Byte Buddy 实现 Spring WebMVC Controller 拦截日志
Browse files Browse the repository at this point in the history
  • Loading branch information
Kurok1 committed Dec 4, 2022
1 parent c8d815e commit 6ab2a88
Show file tree
Hide file tree
Showing 7 changed files with 176 additions and 4 deletions.
6 changes: 6 additions & 0 deletions stage-1/src/biz-project/biz-web/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,12 @@
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy</artifactId>
</dependency>

</dependencies>

<build>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package com.acme.biz.web.configuration;

import net.bytebuddy.dynamic.DynamicType;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;

/**
* 基于{@link net.bytebuddy.ByteBuddy}实现的注解拦截
*
* @author <a href="mailto:maimengzzz@gmail.com">韩超</a>
* @since 1.0.0
*/
public abstract class AbstractAnnotationByteBuddyBeanPostProcessor<A extends Annotation> implements InstantiationAwareBeanPostProcessor,
BeanClassLoaderAware, BeanFactoryAware {

private ClassLoader classLoader;
private AutowireCapableBeanFactory autowireCapableBeanFactory;
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader;
}

@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.autowireCapableBeanFactory = (AutowireCapableBeanFactory) beanFactory;
}

@Override
public Object postProcessBeforeInstantiation(Class<?> beanType, String beanName) throws BeansException {
if (hasLogAnnotatedMethod(beanType, getAnnotationClass())) {
Class<?> dynamicType = doIntercept(beanType).load(this.classLoader)
.getLoaded();
try {
return this.autowireCapableBeanFactory.createBean(dynamicType);
} catch (Exception e) {
throw new BeanCreationException(e.getMessage());
}
}

return null;
}

protected abstract DynamicType.Unloaded<?> doIntercept(Class<?> beanType);

protected abstract Class<A> getAnnotationClass();

protected ClassLoader getClassLoader() {
return classLoader;
}

protected AutowireCapableBeanFactory getAutowireCapableBeanFactory() {
return autowireCapableBeanFactory;
}

/**
* 是否存在被指定注解标记的方法
* @param beanClass 被代理的bean class
* @param annotationClass 注解
* @return 存在与否
*/
public boolean hasLogAnnotatedMethod(Class<?> beanClass, Class<A> annotationClass) {
Method[] methods = beanClass.getDeclaredMethods();
for (Method method : methods) {
if (method.isAnnotationPresent(annotationClass))
return true;
}
return false;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.acme.biz.web.configuration;

import com.acme.biz.web.interceptor.LoggingInterceptor;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.implementation.MethodDelegation;
import net.bytebuddy.matcher.ElementMatchers;
import org.springframework.context.annotation.Configuration;

/**
* TODO
*
* @author <a href="mailto:maimengzzz@gmail.com">韩超</a>
* @since 1.0.0
*/
@Configuration
public class LoggingBeanPostProcessor extends AbstractAnnotationByteBuddyBeanPostProcessor<LoggingInterceptor.Log> {


private final LoggingInterceptor loggingInterceptor = new LoggingInterceptor();

@Override
protected DynamicType.Unloaded<?> doIntercept(Class<?> beanType) {
return new ByteBuddy()
.subclass(beanType)
.method(ElementMatchers.isAnnotatedWith(LoggingInterceptor.Log.class))
.intercept(MethodDelegation.to(this.loggingInterceptor))
.make();
}

@Override
protected Class<LoggingInterceptor.Log> getAnnotationClass() {
return LoggingInterceptor.Log.class;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package com.acme.biz.web.interceptor;

import net.bytebuddy.implementation.bind.annotation.Origin;
import net.bytebuddy.implementation.bind.annotation.RuntimeType;
import net.bytebuddy.implementation.bind.annotation.SuperCall;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Method;
import java.util.concurrent.Callable;

/**
* TODO
*
* @author <a href="mailto:maimengzzz@gmail.com">韩超</a>
* @since 1.0.0
*/
public class LoggingInterceptor {

private static final Logger logger = LoggerFactory.getLogger(LoggingInterceptor.class);

@RuntimeType
public Object doLog(@Origin Method method, @SuperCall Callable<?> callable) {
// intercept any method of any signature
logger.info("before invoke ...");
long start = System.currentTimeMillis();
try {
return callable.call();
} catch (Exception e) {
logger.error("error occupied", e);
throw new RuntimeException(e);
} finally {
System.out.println(method + "invoke finished, it took " + (System.currentTimeMillis() - start));
}
}

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Log {

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import com.acme.biz.api.interfaces.UserRegistrationService;
import com.acme.biz.api.model.User;
import com.acme.biz.web.interceptor.LoggingInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
Expand All @@ -36,6 +37,7 @@ public class UserRegistrationController implements UserRegistrationService {

@Override
@ResponseBody
@LoggingInterceptor.Log
public Boolean registerUser(User user) {
return userRegistrationService.registerUser(user);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import com.acme.biz.api.exception.UserException;
import com.acme.biz.api.interfaces.UserRegistrationService;
import com.acme.biz.api.model.User;
import com.acme.biz.web.interceptor.LoggingInterceptor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
Expand Down Expand Up @@ -47,12 +48,16 @@ public class InMemoryUserRegistrationService implements UserRegistrationService

private Map<Long, User> usersCache = new ConcurrentHashMap<>();

@Autowired
private Tracer tracer;
private final Tracer tracer;

public InMemoryUserRegistrationService(Tracer tracer) {
this.tracer = tracer;
}

@Autowired
private CurrentTraceContext currentTraceContext;

@LoggingInterceptor.Log
@Override
public Boolean registerUser(User user) throws UserException {
Long id = user.getId();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ management.endpoint.env.post.enabled = true
management.endpoints.web.basePath = /management

# Metrics Prometheus Pushgateway \u5BFC\u51FA\u6FC0\u6D3B
management.metrics.export.prometheus.pushgateway.enabled = true
management.metrics.export.prometheus.pushgateway.enabled = false
management.metrics.export.prometheus.pushgateway.baseUrl = http://127.0.0.1:9091
management.metrics.export.prometheus.pushgateway.pushRate = 10s
management.metrics.export.prometheus.pushgateway.job = ${spring.application.name}-metrics-push-job
Expand All @@ -20,7 +20,7 @@ eureka.client.serviceUrl.defaultZone = http://127.0.0.1:12345/eureka
eureka.client.instanceInfoReplicationIntervalSeconds = 10

# Eureka Instance \u914D\u7F6E
eureka.instance.metadataMap.prometheus.scrape = true
eureka.instance.metadataMap.prometheus.scrape = false
eureka.instance.metadataMap.prometheus.path = ${management.endpoints.web.basePath:/actuator}/prometheus
eureka.instance.metadataMap.prometheus.port = ${management.server.port:${server.port}}

Expand Down

0 comments on commit 6ab2a88

Please sign in to comment.