{"id":21230092,"url":"https://github.com/fgforrest/proxycian","last_synced_at":"2025-07-10T16:32:43.836Z","repository":{"id":39356904,"uuid":"470884472","full_name":"FgForrest/Proxycian","owner":"FgForrest","description":"Small Java library for generating dynamic proxies on top of ByteBuddy or Javassist. You can generate data transfer objects, rich traits or even whole implicit DAO implimentations dynamically at runtime easily. This library solves the complex stuff so you can focus on application logic. Serializability, cloning are already solved by us. We also aim for transparent and easily debuggable proxies, because as we know proxies is usually part of \"magic\" for the team. Hence the name of this library - Proxycian as a magician for the proxies ;)","archived":false,"fork":false,"pushed_at":"2024-02-05T13:40:54.000Z","size":237,"stargazers_count":6,"open_issues_count":0,"forks_count":4,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-07-06T11:17:57.949Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/FgForrest.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null}},"created_at":"2022-03-17T07:08:33.000Z","updated_at":"2024-11-20T09:40:25.000Z","dependencies_parsed_at":"2024-02-05T09:45:17.830Z","dependency_job_id":null,"html_url":"https://github.com/FgForrest/Proxycian","commit_stats":null,"previous_names":[],"tags_count":28,"template":false,"template_full_name":null,"purl":"pkg:github/FgForrest/Proxycian","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/FgForrest%2FProxycian","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/FgForrest%2FProxycian/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/FgForrest%2FProxycian/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/FgForrest%2FProxycian/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/FgForrest","download_url":"https://codeload.github.com/FgForrest/Proxycian/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/FgForrest%2FProxycian/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":264608136,"owners_count":23636685,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-20T23:35:16.508Z","updated_at":"2025-07-10T16:32:43.404Z","avatar_url":"https://github.com/FgForrest.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Proxycian\n\nA small Java library for generating dynamic proxies on top of [ByteBuddy](https://github.com/raphw/byte-buddy)\nor [Javassist](https://github.com/jboss-javassist/javassist). You can generate data transfer objects, rich traits or\neven whole implicit DAO implementations dynamically at runtime easily. This library solves the complex stuff, so you can\nfocus on application logic. Serializability and cloning are already solved by us. We also aim for transparent and easily\ndebuggable proxies, because as we know proxies is usually part of \"magic\" for the team. \nHence, the name of this library - Proxycian as a magician for the proxies ;)\n\n## History\n\nDevelopers in [FG Forrest](https://www.fg.cz) use dynamic proxies successfully for over 10 years. Our initial \nimplementation was based on the [Spring framework](https://spring.io/) abstractions, but we quickly realized that\ntheir implementation is overly complex and prone to subtle errors leading to memory leaks as well as being poorly \nobservable / debuggable. Our second implementation took advantage of [JBoss Javassist](https://www.javassist.org/)\nbut it still kept many unnecessary abstractions and principles we learned from the Spring implementation.\n\nThis implementation is our third take on proxies that is the leanest and the most opinionated one so far. It was designed\nfrom scratch and based on current and actively developed libraries and with the emphasis on:\n\n- simplicity\n- clear and transparent classes / method implementation caching\n- debuggability - you just want your debugger to step in the dynamic implementation without much fuss around\n- transparency - you can easily find out why the library chose the implementation it chose\n\nLet us know if we achieved our goals or not. Opinions and feedback is welcome.\n\n## What we do solve with Proxycian\n\nLet's see a few practical examples on what you can do with Proxycian:\n\n### 1. stateful and dynamic traits\n\nJava doesn't have multiple inheritance, but you can imitate it to certain level with default methods on interfaces,\nbut there will be always limitations. Classes might implement multiple interfaces, but these interfaces can't have fields\nand keep data in them. Also, you cannot decide which traits your class will have in runtime.\n\nProxycian allows us to create self-sustainable traits keeping both logic and data that don't require any orchestration\nor cooperation with the main class which they are attached to. Also, we can easily create specialized class with set of traits\nselected in runtime, usually on some text-based configuration or user interaction with the application.\n\n### 2. interception / delegation\n\nWe use Proxycian to wrap external classes - such as Spring [ReloadableResourceBundle](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/support/ReloadableResourceBundleMessageSource.html)\nor [JDBC DataSource](https://docs.oracle.com/javase/7/docs/api/javax/sql/DataSource.html) to intercept calls in the \ndevelopment environment to provide information for the developers - reporting which message codes / messages were\nused while rendering the page, or which (and how many) SQL queries were executed through the DataSource.\n\nThere are many use-cases where the interception might come handy. You can make a class wrapping original and delegate\nmethod calls to it by hand, but it quickly gets incomprehensible and maintaining is costly. AOP with dynamic proxies\noffer a more clever and shorter way to achieve the same.\n\n### 3. mocking contracts\n\nThere are situations when your application works with some interface for which the real implementation is not yet\nknown, but eventually it will be resolved. In our modular system the modules export and require some interfaces. In order\nto fulfill them we needed to configure modules, how they depended on each other. This was the tricky for part of\nour developers and soon there were situations when two modules might have needed each other (circular dependency).\nThis is a sign of poorly architected modules, but life brings situations when a circular dependency might\nresemble the least of all evils.\n\nProxycian allows us to create a dynamic proxy, which implements required interfaces and use it for immediate wiring by \nthe dependency injection mechanism. The module has its requirement fulfilled and can be started. Any call to the proxy\nmethod will end with an exception, but we usually need to call method after entire system starts. As soon the other\nmodule that provides the interface starts, the proxy internal state is filled with a reference to \nthe implementation and delegates each method call to it.\n\n### 4. implicit DAO/Service interface implementation\n\nDo you know [Ruby's Active Record](https://guides.rubyonrails.org/active_record_basics.html) or \n[Spring Data](https://spring.io/projects/spring-data) libraries? You can easily implement your own using Proxycian.\nIt's matter of a few lines of code.\n\n## Prerequisites\n\n- JDK 1.8, 11 or 17 (all are supported with same JARs)\n- Log4J 2 (2.17+)\n- Apache Commons Lang 3 (3.12+)\n- ByteBuddy / Javassist are bundled in our library, there will be no conflict with possible existing libraries on your\n  classpath in different version\n- Configured toolchains:\n```\n\u003c?xml version=\"1.0\" encoding=\"UTF-8\"?\u003e\n\u003ctoolchains\u003e\n      \u003ctoolchain\u003e\n        \u003ctype\u003ejdk\u003c/type\u003e\n        \u003cprovides\u003e\n          \u003cversion\u003e8\u003c/version\u003e\n          \u003cvendor\u003eoracle\u003c/vendor\u003e\n        \u003c/provides\u003e\n        \u003cconfiguration\u003e\n          \u003cjdkHome\u003e/opt/Java/jdk8u262-b10/bin/java\u003c/jdkHome\u003e\n        \u003c/configuration\u003e\n      \u003c/toolchain\u003e\n      \u003ctoolchain\u003e\n        \u003ctype\u003ejdk\u003c/type\u003e\n        \u003cprovides\u003e\n          \u003cversion\u003e11\u003c/version\u003e\n          \u003cvendor\u003eoracle\u003c/vendor\u003e\n        \u003c/provides\u003e\n        \u003cconfiguration\u003e\n          \u003cjdkHome\u003e/opt/Java/jdk-11.0.8/bin/java\u003c/jdkHome\u003e\n        \u003c/configuration\u003e\n      \u003c/toolchain\u003e\n            \u003ctoolchain\u003e\n        \u003ctype\u003ejdk\u003c/type\u003e\n        \u003cprovides\u003e\n          \u003cversion\u003e17\u003c/version\u003e\n          \u003cvendor\u003eoracle\u003c/vendor\u003e\n        \u003c/provides\u003e\n        \u003cconfiguration\u003e\n          \u003cjdkHome\u003e/opt/Java/jdk-17.0.2/bin/java\u003c/jdkHome\u003e\n        \u003c/configuration\u003e\n      \u003c/toolchain\u003e\n\u003c/toolchains\u003e\n```\n\n## How to compile\n\nCurrently, we are supporting default flow with **JDK 1.8** and [Multi-Release JAR](https://openjdk.java.net/jeps/238)\n\nUse standard Maven 3 command to compile for `multi-jar`:\n\n```\nmvn clean install\n```\n\nTo compile for **JDK 1.8, 11 and 17** with specific profile use specific command:\n```\nmvn clean install -Pmulti-jar  \n```\nProfiles for specific JVM versions are created too.\n```\nmvn clean install -Pjava8\nmvn clean install -Pjava11\nmvn clean install -Pjava17\n```\n\n## How to run tests\n\nRun your tests in an IDE or run:\n\n```\nmvn clean verify -Pjava8\nmvn clean verify -Pjava11\nmvn clean verify -Pjava7\n```\n\nRunning tests with specific JVM are possible via commands `test` or `verify`, with standard `JUnit` runner the [Multi-Release JAR](https://openjdk.java.net/jeps/238) is ignored and the default `JDK 1.8` class is executed.\n\nHelp us maintain at least 80% code coverage!\n\n### How to verify authenticity\n\nDownload our [PGP public key](https://keys.openpgp.org/search?q=9D1149B0C74E939DD766C7A93DE3CDCCF660797F) and verify via [PGP verify Maven plugin](https://www.simplify4u.org/pgpverify-maven-plugin/#). See [this article](https://medium.com/netcracker/dependency-verification-checksum-vs-pgp-582e76207019) to find out why.\n\n## How to use\n\n### How to integrate to your application\n\nInclude the Proxycian library in your Maven descriptor (`pom.xml`):\n\n``` xml\n\u003cdependency\u003e   \n    \u003cgroupId\u003eone.edee.oss\u003c/groupId\u003e   \n    \u003cartifactId\u003eproxycian_bytebuddy\u003c/artifactId\u003e   \n    \u003cversion\u003e1.3.0\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\nOr Gradle:\n\n```\ndependencies {\n    compile 'one.edee.oss:proxycian_bytebuddy:1.3.0'\n}\n```\n\nOr use `proxycian_javassist` if you prefer this implementation (it has much smaller memory JAR size). Otherwise,\nByteBuddy is preferred implementation because it's actively maintained and supports the newest JDK version.\n\n### How to generate a dynamic proxy class\n\n***Note:** In this documentation we stick to ByteBuddy implementation in examples, but you can easily translate all of them\nto Javassist implementation by replacing word `ByteBuddy` with `Javassist`. The contracts are identical in Proxycian.*\n\nTo create new a class with the requested contracts, just use:\n\n``` java\nfinal Class\u003c?\u003e theInstance = ByteBuddyProxyGenerator.getProxyClass(\n\tPerson.class,\n\tTrait1.class,\n\tTrait2.class\n);\n```\n\nIf you call it for the first time, a new class extending `Person.class` and implementing `Trait1.class` and `Trait2.class`\nis created for you. If you call it second time, you'll receive the previously created (cached) class with that contract.\nThe cache is kept in the static field of the generator and might be anytime cleared by \ncalling `ByteBuddyProxyGenerator.clearClassCache()`.\n\nIf you want to extend some class, it must be stated as the first class of the proxy contract, but you might also create\nproxies based on a bunch of interfaces and no superclass (then the `java.lang.Object` becomes the superclass of the proxy).\n\nYou can also create proxies based on superclasses without a default constructor (i.e. having only one constructor with one or\nmore arguments). Imagine that the `Person.class` has the only constructor `protected Person(String firstName, String lastName)`:\n\n``` java\nfinal Class\u003c?\u003e theInstance = ByteBuddyProxyGenerator.getProxyClass(\n\tnew Class\u003c?\u003e[]{\n\t\tPerson.class,\n\t\tTrait1.class,\n\t\tTrait2.class\n\t},\n\tnew Class\u003c?\u003e[]{\n\t\tString.class,\n\t\tString.class\n\t}\n);\n```\n\nYou can also specify a classloader that will maintain the created class, but this is usually not necessary. Proxycian\nuses by default the same classloader that loads `ByteBuddyProxyGenerator.class` itself.\n\nBut this is not the way Proxycian was meant to be used - read the next chapter for a general usage scenario.\n\n### How to generate a dynamic proxy instance\n\nCreating classes is not the common way how you'll create proxies. You usually want the instance of the proxy and not \nthe class. You can achieve this in a single call, when both classes and a first instance is created at once:\n\n``` java\nfinal Object theInstance = ByteBuddyProxyGenerator.instantiate(\n\tnew ProxyRecipe(\n\t\tPerson.class,\n\t\tnew Class[] {Trait1.class, Trait2.class}, \n\t\tnew Advice[]{LocalDataStoreAdvice.INSTANCE}\n\t),\n\tnew GenericBucket()\n);\n```\n\nProxycian uses an abstraction of `ProxyRecipe` that is used to wrap the definition of the contract (i.e. what interfaces will\nthe proxy have) as well as the logic that will handle the calls to methods of the contract (interfaces). `Advice` is \nan abstraction for implementation of the logic and call filtering logic - i.e. which method calls will be serviced by this\nparticular Advice.\n\nSecond argument - in our case `GenericBucket` is a single object maintaining the state of the proxy. It's the state\nyou'll provide and control.\n\n***Note:** It's recommended to cache the ProxyRecipe and not create it again and again with each call of `instantiate` \nmethod as you see in the example.*\n\n#### Advice\n\nAdvice is **strictly** stateless and if you create a new Advice, we recommend defining the no instance fields,\ncreating private non-args constructor and providing a single public static field INSTANCE, which provides access to the\nadvice instance (as you see in the example: `LocalDataStoreAdvice.INSTANCE`).\n\nThere are two different kind of Advices:\n\n- regular `Advice`: the advice only defines the filters for methods it intercepts, and the implementation of them;\n  see example advice `one.edee.oss.proxycian.trait.beanMemoryStore.BeanMemoryStoreAdvice`\n- `IntroductionAdvice`: is the same as regular advice but also introduces a new interface (or set of interfaces) to the proxy;\n  this means that it is not necessary to state this interface in the ProxyRecipe explicitly, but it will be automatically\n  added to the proxy contract whenever the IntroductionAdvice is part of the recipe\n\nEach advice may require a state object to implement certain contracts, so that they can keep the necessary state in it (remember, \nAdvices are stateless). If they don't work with the state, they just require generic `Object.class` contract to be fulfilled\nby the state object which matches everything.\n\nThe simple Advice may look like this:\n\n``` java\npublic class ExampleAdvice implements Advice\u003cObject\u003e {\n\tprivate static final long serialVersionUID = 4100044042153442374L;\n\n\t@Override\n\tpublic Class\u003cObject\u003e getRequestedStateContract() {\n\t\treturn Object.class;\n\t}\n\n\t@Override\n\tpublic List\u003cMethodClassification\u003c?, Object\u003e\u003e getMethodClassification() {\n\t\treturn Collections.singletonList(\n\t\t\tnew PredicateMethodClassification\u003c\u003e(\n\t\t\t\t/* description */   \"Hello world method\",\n\t\t\t\t\n\t\t\t\t/* matcher */       (method, proxyState) -\u003e \n\t\t\t\t\t\"helloWorld\".equals(method.getName()) \u0026\u0026 \n\t\t\t\t\t\tString.class.equals(method.getReturnType()) \u0026\u0026 \n\t\t\t\t\t\tmethod.getParameterCount() == 1 \u0026\u0026\n\t\t\t\t\t\tString.class.equals(method.getParameterTypes()[0]),\n\t\t\t\t\n\t\t\t\t/* methodContext */ MethodClassification.noContext(),\n\t\t\t\t\n\t\t\t\t/* invocation */    (proxy, method, args, methodContext, proxyState, invokeSuper) -\u003e \n\t\t\t\t\t\t\"Hello world, \" + args[0]\n\t\t\t)\n\t\t);\n\t}\n\n}\n```\n\nThis advice reacts to a method call with following the signature `String helloWorld(String myName);` and when calling\n`proxy.helloWorld(\"Jan\")` returns `Hello world, Jan` in response. Advice provides a set of so called `MethodClassification`\nthat are used to intercept the proper methods on a proxy interface. There are two types of method classifications:\n\n##### PredicateMethodClassification / TransparentPredicateMethodClassification\n\nThe classification consists of 4 parts:\n\n**1. description** - a simple string description of the classification, it's used only for developer orientation \nin debugging sessions, it has no other real usage in the Proxycian.\n\n**2. matcher** - represents a simple predicate that accepts `java.lang.Method` and a reference to the proxy state object,\nand returns TRUE if this method classifier intercepts this method call.\n\n***Note:** you can use static helper methods in `one.edee.oss.proxycian.util.ReflectionUtils` interface in your predicate.\nFor example if you want to check whether the called method is the same method in particular interface, you can use this\nexpression: `ReflectionUtils.isMethodDeclaredOn(method, LocalDataStore.class, \"getLocalData\", String.class)` where\n`method` is the called method, `LocalDataStore.class` is the checked interface, `\"getLocalData\"` is the name of the\nmethod in the interface and `String.class` is the single method parameter. You can find more handy methods here as well.*\n\n**3. method context** - it represents function that takes `java.lang.Method` and references it to the proxy state object and\nreturns a DTO object that contains extracted information from the method signature that is necessary for the implementation\nlogic. The DTO is created only for the first call and cached so all additional method calls will reuse this method context.\nIt may therefore contain rather complex logic without fear of affecting proxy method call performance.\n\n**4. method implementation** - the last piece of puzzle will provide the final logic for the method. This is the only\npart executed everytime the proxy method is called\n\n***Note:** if you want to invoke the original method (for example you want only to do something before / after the original\nmethod executes), use expression: `return invokeSuper.call()`*\n\nThe method call interception logic is straightforward - when a method on a proxy is called for the first time, we need to \nresolve the proper implementation. The Proxycian will iterate over all advices and within them, over all the method \nclassifiers, the advice provides and selects **the first** method classifier, which predicate returns true. If predicates\nof your advices overlap (the very same method might be intercepted and handled by Advice1 as well as Advice2), the Advice\nwhich is defined first wins. The predicates may overlap even within single advice, so even the order in which you specify\nmethod classifiers is crucial.\n\nThis rule can be changed, however. If you use `Transparent` variants of `MethodClassification` - i.e. those that implement\n`TransparentMethodClassification` interface, the examination continues with additional advices and creates a method invoker\nchain. Transparent advice may in its execution function use `return invokeSuper.call()` expression to delegate call to\nadditionally selected method invocation.\n\nThere are also \"system methods\" that are automatically handled by the Proxycian and these have their own priority. \nThe ordering of the method classifier is as follows:\n\n1. `ProxyStateAccessor#getProxyState()`\n2. all your method classifiers\n3. `Object#hashCode()`\n4. `Object#equals()`\n5. `Object#toString()`\n6. `Object#clone()`\n7. `Object#writeReplace()` - serialization in general\n8. when method is still not classified, the original method will be invoked - if it is \"abstract\", the call will fail\n\nWhen method classifier is selected, a function that creates method context is called and its result is cached into the\n`ByteBuddyProxyGenerator` method cache for the key `one.edee.oss.proxycian.cache.ClassMethodCacheKey`. Finally,\nthe implementation part is executed. Next time the same method is called (maybe on another instance of the same proxy class),\nthe implementation with method context is quickly retrieved from the internal hash map and executed. With each call on the\nproxy instance, you pay the price of a single lookup to the hash map and delegating a call to an associated implementation object.\n\nMethod cache can be reset at any time by calling `ByteBuddyProxyGenerator.clearMethodClassificationCache()`. Your method\nclassification can also add custom data to the method cache key, should it be necessary.\n\n##### DirectMethodClassification / TransparentDirectMethodClassification\n\nThis implementation is similar to `PredicateMethodClassification` in its principle. It just combines the predicate with the \nmethod context creation together. These two aspects of the method classification contract are some time very similar and\nmight be quite expensive. It makes sense to support this approach as well, so that the same logic can be used for matching\nthe method as well as creating method context for it.\n\nThe `PredicateMethodClassification` in our example can be easily translated to `DirectMethodClassification` and vice versa:\n\n``` java\nnew DirectMethodClassification\u003c\u003e(\n\t/* description */   \"Hello world method\",\n\n\t/* matcher */       (method, proxyState) -\u003e {\n\t\tif (\"helloWorld\".equals(method.getName()) \u0026\u0026 String.class.equals(method.getReturnType()) \u0026\u0026\n\t\t\tmethod.getParameterCount() == 1 \u0026\u0026 String.class.equals(method.getParameterTypes()[0])) {\n\t\t\treturn (proxy, theMethod, args, theProxyState, invokeSuper) -\u003e \"Hello world, \" + args[0];\n\t\t} else {\n\t\t\treturn null;\n\t\t}\n\t}\n)\n```\n\nSo if your method signature analysis is complex, and you would need to do the same operations both in predicate and\nthe execution function - use `DirectMethodClassification` otherwise stick to the `PredicateMethodClassification`.\n\n#### State object\n\nState object is the target for all Java base methods, such as `equals`, `hashCode`, `toString` and the serialization and clone \nfacility. We have single state objects on purpose - it's much easier to track, debug and control data this way. The state of the\nobject must fulfill the contract required by all the Advices of the proxy (do not confuse the contract required by the \nadvices with the contract of the proxy itself!). The state object lives with the proxy instance and gets garbage \ncollected with it.\n\nThere is no required interface for the state object if you create NON-serializable instance of the class. However, if\nyou need an instance, that can be serialized using default Java serialization facility, you must implement \n`ProxyStateWithConstructorArgs` interface. The state itself must be Serializable and must allow keeping the original\nconstructor arguments used for instance creation, so that they can be reused in deserialization phase.\n\n### Instantiation callback\n\nThere are certain use-cases when you have to \"prepare\" the instance immediately after creation, even before the method\nclassification logic gets in the way. For such case there is `one.edee.oss.proxycian.OnInstantiationCallback`. With it, you can \nimplement and pass to the instantiation method. In this callback you can freely invoke method of the instance and no\ndynamic logic stated in advices will be executed. Of course, calling abstract methods will trigger an exception.\n\n## Prepared traits ready to use\n\n### BeanMemoryStoreAdvice\n\nThis advice will intercept all method calls that follow [Java Beans](https://en.wikipedia.org/wiki/JavaBeans) contract\nand stores the date into internal `HashMap` in the proxy state. This map can be accessed via. expression:\n`((BeanMemoryStore)((ProxyStateAccessor)instance).getProxyState()).getLocalDataStoreIfPresent()`. In addition to \nstandard Java Beans contracts, there is support for adding and removing 1:N items one by one. See example interface:\n\n```java\npublic interface JavaBeanWithMultipleItems {\n\tList\u003cString\u003e getItems();\n\tvoid setItems(List\u003cString\u003e items);\n\tboolean addItem(String item);\n\tboolean removeItem(String item);\n}\n```\n\nThe behaviour describes following test case:\n\n```java\n@Test\npublic void shouldProxyJavaBeanWithMultipleItems() {\n\tfinal Object theInstance = ByteBuddyProxyGenerator.instantiateSerializable(\n\t\tnew ProxyRecipe(\n\t\t\tnew Class[] {JavaBeanWithMultipleItems.class},\n\t\t\tnew Advice[] {BeanMemoryStoreAdvice.ALL_METHOD_INSTANCE}\n\t\t),\n\t\tnew GenericBucket()\n\t);\n\n\tassertTrue(theInstance instanceof JavaBeanWithMultipleItems);\n\tfinal JavaBeanWithMultipleItems proxy = (JavaBeanWithMultipleItems) theInstance;\n\n\tproxy.addItem(\"A\");\n\tproxy.addItem(\"B\");\n\tproxy.addItem(\"C\");\n\n\tassertArrayEquals(new String[] {\"A\", \"B\", \"C\"}, proxy.getItems().toArray(new String[0]));\n\n\tproxy.removeItem(\"B\");\n\n\tassertArrayEquals(new String[] {\"A\", \"C\"}, proxy.getItems().toArray(new String[0]));\n}\n```\n\n### LocalDataStoreAdvice\n\nThis is your handy advice that allows you to store any data from execution functions, or even default methods of \nyour interfaces. See contract of `LocalDataStore` interface for more information.\n\nIf you add this advice to your dynamic proxy, you can then define multiple other \"traits\" that are merely interfaces\nwith default methods that can take advantage of the internal memory store to become \"stateful\". See following example:\n\n```java\npublic interface ExpensiveComputer extends LocalDataStore {\n\n  default double computePi() {\n    return computeLocalDataIfAbsent(\"cachedPi\", () -\u003e {\n      double pi = 0;\n      for (int i = 1; i \u003c 1_000_000; i++) {\n        pi += Math.pow(-1, i+1) / (2 * i - 1);\n      }\n      return 4 * pi;\n    });\n  }\n\n}\n```\n\nYou can add `ExpensiveComputed` to any dynamic proxy, that have `LocalDataStoreAdvice`, and you will have an object that \nwill return a computed PI to 1 mil. iterations. As you can see the expensive computation will happen only once on that\ninstance of dynamic proxy, because next time you call that method you'll receive memoized value from the first call.\n\n### DelegateCallsAdvice\n\nThis advice lets you delegate calls to all methods of single interface directly to the state object or the object that\nis reachable from the state object. This allows you to compose multiple interface delegations at once. Beware, this\nexample is quite long (to shorten it a little bit we use [Lombok](https://projectlombok.org/) annotations in it):\n\n```java\npublic interface NameInterface {\n\n\tString getFullName();\n\n\tString getFirstName();\n\tString getLastName();\n\n\tvoid setFirstName(String firstName);\n\tvoid setLastName(String lastName);\n\n}\n\n@Data\npublic static class NameImplementation implements Serializable, NameInterface {\n\tprivate static final long serialVersionUID = -3190496823269251991L;\n\tprivate String firstName;\n\tprivate String lastName;\n\n\tpublic String getFullName() {\n\t\treturn firstName + \" \" + lastName;\n\t}\n\n}\n\npublic interface AgeInterface {\n\n\tint getAge();\n\tvoid setAge(int ageInYears);\n\n}\n\n@Data\npublic static class AgeImplementation implements Serializable, AgeInterface {\n\tprivate static final long serialVersionUID = -2520784598151746890L;\n\tprivate int age;\n}\n\npublic interface PersonInterface {\n\n\tString getPersonDescription();\n\n}\n\n@Data\npublic static class CompositionState implements Serializable, PersonInterface {\n\tprivate static final long serialVersionUID = 3427985599976732264L;\n\tprivate final NameImplementation nameHolder = new NameImplementation();\n\tprivate final AgeImplementation ageHolder = new AgeImplementation();\n\n\t@Override\n\tpublic String getPersonDescription() {\n\t\treturn nameHolder.getFullName() + \" of age \" + ageHolder.getAge();\n\t}\n}\n\n  @Test\n  public void ByteBuddyProxyRecipeGenerator_DelegateCallsOnSubProperty() {\n    final Object theInstance = ByteBuddyProxyGenerator.instantiateSerializable(\n            new ProxyRecipe(\n                    DelegateCallsAdvice.getInstance(NameInterface.class, o -\u003e ((CompositionState)o).getNameHolder()),\n                    DelegateCallsAdvice.getInstance(AgeInterface.class, o -\u003e ((CompositionState)o).getAgeHolder()),\n                    DelegateCallsAdvice.getInstance(PersonInterface.class)\n            ),\n            new CompositionState()\n    );\n\n    assertTrue(theInstance instanceof NameInterface);\n    final NameInterface nameProxyContract = (NameInterface) theInstance;\n    nameProxyContract.setFirstName(\"Jan\");\n    nameProxyContract.setLastName(\"Novotný\");\n\n    assertTrue(theInstance instanceof AgeInterface);\n    final AgeInterface ageProxyContract = (AgeInterface) theInstance;\n    ageProxyContract.setAge(43);\n\n    assertTrue(theInstance instanceof PersonInterface);\n    assertEquals(\"Jan Novotný of age 43\", ((PersonInterface)theInstance).getPersonDescription());\n  }\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffgforrest%2Fproxycian","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffgforrest%2Fproxycian","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffgforrest%2Fproxycian/lists"}