如何使用ByteBuddy为Map包装JavaBean添加自定义Get/Set方法
Got it, let's break this down and get your Map-to-JavaBean wrapper working with ByteBuddy. You’ve already got the builder set up to accept a Map via constructor—great start! Now we just need to wire up the getters and setters to delegate directly to that underlying map.
Here's a step-by-step implementation with concrete code examples, including both fixed-property and dynamic-property approaches:
Core Concept
The goal is to create a dynamic class that:
- Stores the input
Mapin a private field - Generates getter methods that pull values from the map using specific keys
- Generates setter methods that push values into the map using those same keys
Full Implementation (Fixed Properties)
Let's say you want to expose keys like username and email as bean properties. Here's how to build the dynamic class:
import net.bytebuddy.ByteBuddy; import net.bytebuddy.dynamic.DynamicType; import net.bytebuddy.implementation.FieldAccessor; import net.bytebuddy.implementation.MethodDelegation; import net.bytebuddy.matcher.ElementMatchers; import java.util.Map; public class MapBeanGenerator { // Generate a bean instance from a backing map public static <T> T createBean(Map<String, Object> backingMap, Class<T> beanInterface) throws Exception { // Start building the dynamic class DynamicType.Builder<?> builder = new ByteBuddy() .subclass(Object.class) .implement(beanInterface) // Optional: Enforce compile-time method contracts // Add private field to hold the backing map .defineField("backingMap", Map.class, net.bytebuddy.description.modifier.Visibility.PRIVATE) // Add constructor that accepts and assigns the map .defineConstructor(net.bytebuddy.description.modifier.Visibility.PUBLIC) .withParameter(Map.class) .intercept( MethodCall.invoke(Object.class.getConstructor()) .andThen(FieldAccessor.ofField("backingMap").setsArgumentAt(0)) ); // Add getter for "username" builder = builder.defineMethod("getUsername", String.class, net.bytebuddy.description.modifier.Visibility.PUBLIC) .intercept(MethodDelegation.to(MapGetters.class).withArgument("username")); // Add setter for "username" builder = builder.defineMethod("setUsername", void.class, net.bytebuddy.description.modifier.Visibility.PUBLIC) .withParameter(String.class) .intercept(MethodDelegation.to(MapSetters.class).withArgument("username")); // Repeat for other properties like "email" builder = builder.defineMethod("getEmail", String.class, net.bytebuddy.description.modifier.Visibility.PUBLIC) .intercept(MethodDelegation.to(MapGetters.class).withArgument("email")); builder = builder.defineMethod("setEmail", void.class, net.bytebuddy.description.modifier.Visibility.PUBLIC) .withParameter(String.class) .intercept(MethodDelegation.to(MapSetters.class).withArgument("email")); // Compile and instantiate the dynamic class Class<?> dynamicClass = builder.make().load(MapBeanGenerator.class.getClassLoader()).getLoaded(); return (T) dynamicClass.getConstructor(Map.class).newInstance(backingMap); } // Interceptor class for getter logic public static class MapGetters { public static Object get(Map backingMap, String key) { return backingMap.get(key); } } // Interceptor class for setter logic public static class MapSetters { public static void set(Map backingMap, String key, Object value) { backingMap.put(key, value); } } // Optional: Interface defining bean methods for type safety public interface UserBean { String getUsername(); void setUsername(String username); String getEmail(); void setEmail(String email); } // Example usage public static void main(String[] args) throws Exception { Map<String, Object> userMap = Map.of("username", "jane_doe", "email", "jane@example.com"); UserBean userBean = createBean(userMap, UserBean.class); System.out.println(userBean.getUsername()); // Output: jane_doe userBean.setEmail("jane.new@example.com"); System.out.println(userMap.get("email")); // Output: jane.new@example.com } }
Dynamic Property Generation (No Hardcoded Keys)
If you want to generate getters/setters for all keys in the input Map automatically, replace the fixed method definitions with a loop over the map's entries:
// Inside createBean(), after setting up the constructor for (Map.Entry<String, Object> entry : backingMap.entrySet()) { String key = entry.getKey(); Class<?> valueType = entry.getValue().getClass(); // Generate getter name (e.g., "getUsername" for key "username") String getterName = "get" + Character.toUpperCase(key.charAt(0)) + key.substring(1); builder = builder.defineMethod(getterName, valueType, net.bytebuddy.description.modifier.Visibility.PUBLIC) .intercept(MethodDelegation.to(MapGetters.class).withArgument(key)); // Generate setter name (e.g., "setUsername" for key "username") String setterName = "set" + Character.toUpperCase(key.charAt(0)) + key.substring(1); builder = builder.defineMethod(setterName, void.class, net.bytebuddy.description.modifier.Visibility.PUBLIC) .withParameter(valueType) .intercept(MethodDelegation.to(MapSetters.class).withArgument(key)); }
Key Details to Note
- Field Access: We use
FieldAccessorto assign the input Map to the dynamic class's privatebackingMapfield. - Method Delegation:
MethodDelegationroutes getter/setter calls to static interceptor methods, which handle the actual Map operations. - Type Safety: Using an interface (like
UserBean) ensures your generated bean adheres to a contract, giving you compile-time checks for method names and types.
内容的提问来源于stack exchange,提问作者Naros

