Add documentation about plugin builders compared to factories LOG4J2-1411.
Project: http://git-wip-us.apache.org/repos/asf/logging-log4j2/repo Commit: http://git-wip-us.apache.org/repos/asf/logging-log4j2/commit/912e3a8d Tree: http://git-wip-us.apache.org/repos/asf/logging-log4j2/tree/912e3a8d Diff: http://git-wip-us.apache.org/repos/asf/logging-log4j2/diff/912e3a8d Branch: refs/heads/LOG4J2-1395 Commit: 912e3a8d0ef4bf6db2802ac77bb43ab79c43f07b Parents: 00b7de4 Author: Matt Sicker <[email protected]> Authored: Sat Jun 4 19:18:52 2016 -0500 Committer: Matt Sicker <[email protected]> Committed: Sat Jun 4 19:18:52 2016 -0500 ---------------------------------------------------------------------- .../log4j/test/appender/ListAppender.java | 68 +++++++++-- src/changes/changes.xml | 3 + src/site/site.xml | 1 + src/site/xdoc/manual/extending.xml | 112 +++++++++++++++++++ 4 files changed, 174 insertions(+), 10 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/912e3a8d/log4j-core/src/test/java/org/apache/logging/log4j/test/appender/ListAppender.java ---------------------------------------------------------------------- diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/test/appender/ListAppender.java b/log4j-core/src/test/java/org/apache/logging/log4j/test/appender/ListAppender.java index cc897ea..30aa41b 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/test/appender/ListAppender.java +++ b/log4j-core/src/test/java/org/apache/logging/log4j/test/appender/ListAppender.java @@ -28,11 +28,10 @@ import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.LoggerContext; import org.apache.logging.log4j.core.appender.AbstractAppender; import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.config.plugins.PluginAttribute; +import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute; +import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory; import org.apache.logging.log4j.core.config.plugins.PluginElement; -import org.apache.logging.log4j.core.config.plugins.PluginFactory; import org.apache.logging.log4j.core.config.plugins.validation.constraints.Required; -import org.apache.logging.log4j.core.impl.Log4jLogEvent; import org.apache.logging.log4j.core.impl.MutableLogEvent; import org.apache.logging.log4j.core.layout.SerializedLayout; @@ -175,16 +174,65 @@ public class ListAppender extends AbstractAppender { return Collections.unmodifiableList(data); } - @PluginFactory - public static ListAppender createAppender( - @PluginAttribute("name") @Required(message = "No name provided for ListAppender") final String name, - @PluginAttribute("entryPerNewLine") final boolean newLine, - @PluginAttribute("raw") final boolean raw, - @PluginElement("Layout") final Layout<? extends Serializable> layout, - @PluginElement("Filter") final Filter filter) { + public static ListAppender createAppender(final String name, final boolean newLine, final boolean raw, + final Layout<? extends Serializable> layout, final Filter filter) { return new ListAppender(name, filter, layout, newLine, raw); } + @PluginBuilderFactory + public static Builder newBuilder() { + return new Builder(); + } + + public static class Builder implements org.apache.logging.log4j.core.util.Builder<ListAppender> { + + @PluginBuilderAttribute + @Required + private String name; + + @PluginBuilderAttribute + private boolean entryPerNewLine; + + @PluginBuilderAttribute + private boolean raw; + + @PluginElement("Layout") + private Layout<? extends Serializable> layout; + + @PluginElement("Filter") + private Filter filter; + + public Builder setName(final String name) { + this.name = name; + return this; + } + + public Builder setEntryPerNewLine(final boolean entryPerNewLine) { + this.entryPerNewLine = entryPerNewLine; + return this; + } + + public Builder setRaw(final boolean raw) { + this.raw = raw; + return this; + } + + public Builder setLayout(final Layout<? extends Serializable> layout) { + this.layout = layout; + return this; + } + + public Builder setFilter(final Filter filter) { + this.filter = filter; + return this; + } + + @Override + public ListAppender build() { + return new ListAppender(name, filter, layout, entryPerNewLine, raw); + } + } + /** * Gets the named ListAppender if it has been registered. * http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/912e3a8d/src/changes/changes.xml ---------------------------------------------------------------------- diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 76dd0e1..8fe4113 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -58,6 +58,9 @@ <action issue="LOG4J2-1399" dev="ggregory" type="update"> Update Apache Commons CSV from 1.3 to 1.4. </action> + <action issue="LOG4J2-1411" dev="mattsicker" type="add"> + Add documentation about plugin builders compared to factories. + </action> </release> <release version="2.6" date="2016-05-25" description="GA Release 2.6"> <action issue="LOG4J2-1270" dev="rpopma" type="add"> http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/912e3a8d/src/site/site.xml ---------------------------------------------------------------------- diff --git a/src/site/site.xml b/src/site/site.xml index 85fc190..2aa49a0 100644 --- a/src/site/site.xml +++ b/src/site/site.xml @@ -201,6 +201,7 @@ <item name="Appenders" href="/manual/extending.html#Appenders"/> <item name="Layouts" href="/manual/extending.html#Layouts"/> <item name="PatternConverters" href="/manual/extending.html#PatternConverters"/> + <item name="Plugin Builders" href="/manual/extending.html#Plugin_Builders"/> <item name="Custom Plugins" href="/manual/extending.html#Custom_Plugins"/> </item> http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/912e3a8d/src/site/xdoc/manual/extending.xml ---------------------------------------------------------------------- diff --git a/src/site/xdoc/manual/extending.xml b/src/site/xdoc/manual/extending.xml index 3088d82..5b3348a 100644 --- a/src/site/xdoc/manual/extending.xml +++ b/src/site/xdoc/manual/extending.xml @@ -392,6 +392,118 @@ public final class QueryConverter extends LogEventPatternConverter { } }</pre> </subsection> + <subsection name="Plugin Builders"> + <p> + Some plugins take a lot of optional configuration options. When a plugin takes many options, it is more + maintainable to use a builder class rather than a factory method (see <i>Item 2: Consider a builder when + faced with many constructor parameters</i> in <i>Effective Java</i> by Joshua Bloch). There are some other + advantages to using an annotated builder class over an annotated factory method: + </p> + <ul> + <li>Attribute names don't need to be specified if they match the field name.</li> + <li>Default values can be specified in code rather than through an annotation (also allowing a + runtime-calculated default value which isn't allowed in annotations).</li> + <li>Adding new optional parameters doesn't require existing programmatic configuration to be refactored.</li> + <li>Easier to write unit tests using builders rather than factory methods with optional parameters.</li> + <li>Default values are specified via code rather than relying on reflection and injection, so they work + programmatically as well as in a configuration file.</li> + </ul> + <p> + Here is an example of a plugin factory from <code>ListAppender</code>: + </p> + <pre class="prettyprint linenums"><![CDATA[ +@PluginFactory +public static ListAppender createAppender( + @PluginAttribute("name") @Required(message = "No name provided for ListAppender") final String name, + @PluginAttribute("entryPerNewLine") final boolean newLine, + @PluginAttribute("raw") final boolean raw, + @PluginElement("Layout") final Layout<? extends Serializable> layout, + @PluginElement("Filter") final Filter filter) { + return new ListAppender(name, filter, layout, newLine, raw); +} + ]]></pre> + <p> + Here is that same factory using a builder pattern instead: + </p> + <pre class="prettyprint linenums"><![CDATA[ +@PluginBuilderFactory +public static Builder newBuilder() { + return new Builder(); +} + +public static class Builder implements org.apache.logging.log4j.core.util.Builder<ListAppender> { + + @PluginBuilderAttribute + @Required(message = "No name provided for ListAppender") + private String name; + + @PluginBuilderAttribute + private boolean entryPerNewLine; + + @PluginBuilderAttribute + private boolean raw; + + @PluginElement("Layout") + private Layout<? extends Serializable> layout; + + @PluginElement("Filter") + private Filter filter; + + public Builder setName(final String name) { + this.name = name; + return this; + } + + public Builder setEntryPerNewLine(final boolean entryPerNewLine) { + this.entryPerNewLine = entryPerNewLine; + return this; + } + + public Builder setRaw(final boolean raw) { + this.raw = raw; + return this; + } + + public Builder setLayout(final Layout<? extends Serializable> layout) { + this.layout = layout; + return this; + } + + public Builder setFilter(final Filter filter) { + this.filter = filter; + return this; + } + + @Override + public ListAppender build() { + return new ListAppender(name, filter, layout, entryPerNewLine, raw); + } +} + ]]></pre> + <p> + The only difference in annotations is using <code>@PluginBuilderAttribute</code> instead of + <code>@PluginAttribute</code> so that default values and reflection can be used instead of specifying + them in the annotation. Either annotation can be used in a builder, but the former is better suited + for field injection while the latter is better suited for parameter injection. Otherwise, the same + annotations (<code>@PluginConfiguration</code>, <code>@PluginElement</code>, <code>@PluginNode</code>, + and <code>@PluginValue</code>) are all supported on fields. Note that a factory method is still required + to supply a builder, and this factory method should be annotated with <code>@PluginBuilderFactory</code>. + <!-- TODO: this will change with LOG4J2-1188 --> + </p> + <p> + When plugins are being constructed after a configuration has been parsed, a plugin builder will be used + if available, otherwise a plugin factory method will be used as a fallback. If a plugin contains neither + factory, then it cannot be used from a configuration file (it can still be used programmatically of + course). + </p> + <p> + Here is an example of using a plugin factory versus a plugin builder programmatically: + </p> + <pre class="prettyprint linenums"><![CDATA[ +ListAppender list1 = ListAppender.createAppender("List1", true, false, null, null); +ListAppender list2 = ListAppender.newBuilder().setName("List1").setEntryPerNewLine(true).build(); + ]]></pre> + </subsection> <subsection name="Custom Plugins"> <p>See the <a href="plugins.html">Plugins</a> section of the manual.</p> <!-- TODO: some documentation here! -->
