Assuming there is a simple class Student
#Data #NoArgsConstructor #AllArgsConstructor
public class Student {
private Integer age;
private String name;
}
Add a logging aspect With Spring AOP in aop.xml
<aop:config>
<aop:aspect id="log" ref="logging">
<aop:pointcut id="selectAll" expression="execution(* com.tutorial.Student.getName(..))"/>
<aop:before pointcut-ref="selectAll" method="beforeAdvice"/>
<aop:after pointcut-ref="selectAll" method="afterAdvice"/>
</aop:aspect>
</aop:config>
<bean id="student" class="com.tutorial.Student">
<property name="name" value="Zara" />
<property name="age" value="11"/>
</bean>
excluding aspects fields
public class ExcludeAspects implements ExclusionStrategy {
#Override
public boolean shouldSkipField(FieldAttributes f) {
if(f.getName().startsWith("CGLIB$"))
return true;
return false;
}
#Override
public boolean shouldSkipClass(Class<?> clazz) {
return false;
}
}
main,note the output of the first bean is empty ("{}"):
public static void main(String[] args) {
Gson gson = new GsonBuilder().setPrettyPrinting().addSerializationExclusionStrategy(new ExcludeAspects()).create();
ApplicationContext context = new ClassPathXmlApplicationContext("aop.xml");
//return "{}"
Student student = (Student) context.getBean("student");
gson.toJson(student);
//works fine
Student student2 = new Student(11,"Zara");
gson.toJson(student2);
}
Update According to the accepted answer, unProxy works for me.
Your code seems to imply that your aspects are working, i.e. before/after advices from your configuration get executed. If they don't, you have problems in other places. I am further assuming that
your aspects work as designed and you have checked that,
you are using Spring AOP, not AspectJ with load-time weaving,
somehow GSON is seeing CGLIB proxies, not the original objects underneath.
Then the problem could be that GSON - I have zero experience with it, never used it before - uses reflection in order to search for fields in the proxy class. But it will not find any as the proxy only overrides methods, but does not have fields because the latter are in the original class (parent to the proxy). If this is true, you need to configure GSON to search in the original class, not in the proxy class. Then you also would not have to exclude anything.
Update:
My educated guess above was correct.
Just because I was curious about how to get the original object from a CGLIB proxy, I looked at it in a debugger. It seems like every proxy has a public final method getTargetSource which you can invoke via reflection:
package com.tutorial;
import org.springframework.aop.TargetSource;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
public class Application {
public static void main(String[] args) throws Exception {
Gson gson = new GsonBuilder().setPrettyPrinting().create();
ApplicationContext context = new ClassPathXmlApplicationContext("aop.xml");
Student student = (Student) context.getBean("student");
TargetSource targetSource = (TargetSource)
student
.getClass()
.getMethod("getTargetSource", null)
.invoke(student, null);
System.out.println(gson.toJson(targetSource.getTarget()));
}
}
This works for me with your code, but I did not use Lombok (which you did not mention at all, I just found out when trying to compile your code!) in the mix but manually created constructors, getters and setters just to get up and running.
Besides, you do not need the ExclusionStrategy anymore.
Console log:
{
"age": 11,
"name": "Zara"
}
BTW, Lombok is known to cause trouble in connection with AspectJ because of class naming conflicts, see my answer here. This might also affect Spring AOP.
I think that you use an unhealthy (because incompatible) mix of technologies here, if you find a solution and do not want to end up writing custom type adapters for each bean class it will be quite hacky. If you remove Lombok, at least you can switch from Spring AOP to AspectJ with LTW in order to get rid of the proxy problem. AspectJ does not use proxies, so GSON might work better with it.
Update 2:
My first update was just a quick hack during a tea break. Not being a Spring user, I had too look up the API doc first in order to find interface Advised. It contains method getTargetSource(), i.e.:
We can cast our Spring bean (AOP proxy) to Advised and thus avoid ugly reflection.
Going one step further, we can dynamically determine if a given object is an (advised) proxy or not, i.e. if you change or deactivate your aspect, the same code will still work.
package com.tutorial;
import org.springframework.aop.framework.Advised;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
public class Application {
public static void main(String[] args) throws Exception {
try (ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("aop.xml")) {
Gson gson = new GsonBuilder().setPrettyPrinting().create();
Student student = (Student) context.getBean("student");
System.out.println(gson.toJson(unProxy(student)));
}
}
public static Object unProxy(Object object) throws Exception {
return object instanceof Advised
? ((Advised) object).getTargetSource().getTarget()
: object;
}
}
Update 3: I was curious and also installed Lombok for my IDE. Actually the sample from above does work in connection with Gson and my little unProxy(Object) method. So you are good to go. :-)
you can use #Expose annotation to ignore aop fields.
eg:
Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create();
String json = gson.toJson(new Book());
public class book {
#Expose
public String name;
#Expose
public int some;
...
}
Implement ExclusionStrategy like:
#RequiredArgsConstructor
public class ExcludeListedClasses implements ExclusionStrategy {
#NonNull
private Set<Class<?>> classesToExclude;
#Override
public boolean shouldSkipField(FieldAttributes f) {
return false;
}
#Override
public boolean shouldSkipClass(Class<?> clazz) {
return classesToExclude.contains(clazz);
}
}
Use like:
ExclusionStrategy es = new ExcludeListedClasses( new HashSet<Class<?>>() {{
add(Logging.class);
}} );
Gson gson = new GsonBuilder().setPrettyPrinting()
.addSerializationExclusionStrategy(es).create();
There might appear other unserializable classes also, added by aspects or so. Just add those also to the set provided when constructing ExcludeListedClasses.
The difference to Arun's answer is that this way you do not need to add #Expose annotation to each class's each field where would possible be unserializable fields.
You can also use method shouldSkipField(..) in a similar way if you want to skip serialization by field name, for example.
Related
I have been refactoring a huge method in the project I work and came up with this idea to create a validation service like this -
public class TrickyValidation {
String validationVariable1;
String validationVariable2;
String validationVariable3;
HashMap<String, Object> itemsMap;
Object dependentObject;
#Autowired
SpringService service;
public static boolean doTrickyValidation(HashMap<String, Object> itemsMap, Object dependentObject) {
return new TrickyValidation(itemsMap, dependentObject).validate();
}
private TrickyValidation(Object itemsMap, Object dependentObject) {
this.itemsMap = itemsMap;
this.someDependentObject = dependentObject;
init();
}
private boolean validate() {
// loads of logic for validation by using validationVaribales
return true;
}
private void init() {
// Some methods to extract thease variables from itemsMap, dependentObject etc..
this.validationVariable1 = service.get(dependentObject);
this.validationVariable1 = ...;
this.validationVariable1 = ...;
}
}
My goal what I want to do here is to Encapsulate everything as much as possible and use clean code principles.
I feel a bit here like fighting spring framework because I don't want
that "TrickyValidation" class would be #Servcie and belong to spring container. Will Autowired even work here?
Is it a good design? Most likely I will use this validation in a loop. I like this solution because when I have to validate things I just simply call one and only public static method of this class TrickyValidation.doTrickyValidation(map, obj)
Any suggestions are welcome on how to improve this, or why it's a bad idea.
This code probably won't work because in the init method of the object you're trying to access service which is not autowired into this instance. In general the autowiring works only for objects managed (created by) Spring.
In this case you create "manually" the object of class TrickyValidation...
IMO the better design is to split the "Validator" object that can be Spring managed and the Validation itself that is not spring based.
#Component
public class Validator {
#Autowired
private Service service;
public boolean doTrickyValidation(HashMap<String, Object> itemsMap, Object dependentObject) {
// resolve the validation strategy from the items passed to this method.
TrickyValidation validation = resolveTrickyValidation(itemsPam, dependentObject);
return validation.validate();
}
private TrickyValidation resolveTrickyValidation(...) {
// construct the proper validation strategy
// access service if you want
}
}
With the following Java code:
public class Bean{
private String value;
public Bean(#NonNull String value) {
//Usually fail-fast validation can be added here if it is needed
this.value = value;
}
public String getValue() {return this.value;}
}
Is it possible to check the constructor argument value by means of the annotation, #NonNull at run time other than compile time? Personally I still did not find any checker-framework, which can do validation checking at run time. However, is it possible to implement an Annotation processor to do run time checking?
You should take a look at #NotNull from javax.validation.constraints.
I use it in my models and it throw a Constraint exception when I try to save a model with a null #NotNull value.
The import is import javax.validation.constraints.NotNull;
If you are using Spring and mongodb, you'll have to configure it so it works, I have found a piece of code somewhere on the Internet (can't remember where), you may use it:
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.mongodb.core.mapping.event.ValidatingMongoEventListener;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
#Configuration
public class CustomRepositoryRestConfigurerAdapter {
#Bean
public LocalValidatorFactoryBean localValidatorFactoryBean() {
return new LocalValidatorFactoryBean();
}
#Bean
public ValidatingMongoEventListener validatingMongoEventListener(
#Qualifier("localValidatorFactoryBean") LocalValidatorFactoryBean lfb
) {
return new ValidatingMongoEventListener(lfb);
}
}
Yes. Lombok's #NonNull is a runtime check which just inserts an if-statement with a throw:
With Lombok
import lombok.NonNull;
public class NonNullExample extends Something {
private String name;
public NonNullExample(#NonNull Person person) {
super("Hello");
this.name = person.getName();
}
}
Vanilla Java
import lombok.NonNull;
public class NonNullExample extends Something {
private String name;
public NonNullExample(#NonNull Person person) {
super("Hello");
if (person == null) {
throw new NullPointerException("person is marked #NonNull but is null");
}
this.name = person.getName();
}
}
Misconception at your end: there is no single answer to your question.
Some annotations, when used on source code like this are mainly targeting compile time. Like some static analysis tool that analyses the data flow to tell you that you are violating "annotated" contracts here or there.
But some annotations are also "meant" to be used at runtime, for example to be used with "beans". Such objects might come in as parameter of a HTTP request, and then you have some framework checking if the content received as JSON for example is actually valid, according to the rules specified via annotations. See this tutorial for some examples.
My current situation:
I want to inject the following class into my application:
public interface IConfigAccessor<T extends IConfig> {
...
}
ConfigAccessors are a proxy-objects, created dynamically at runtime. The creation of these object works as follows:
public class ConfigFactory implements IConfigFactory {
private final IConfigUpdater updater;
#Inject
public ConfigFactory(IConfigUpdater updater) {
this.updater = updater;
}
#Override
public <T extends IConfig> IConfigAccessor<T> register(final String configKey, final Class<T> configClass) {
ConfigCache<T> configCache = new ConfigCache<>(new SomeOtherThings(), configKey, configClass);
updater.register(configCache);
return new ConfigAccessor<>(configCache, configKey, configClass);
}
}
As you can see, to create these objects, I need to inject the ConfigUpdater and other depdencies. This means, that guice needs to be fully configured already.
To get the instance out of Guice, I use the following code:
IConfigFactory configClient = injector.getInstance(IConfigFactory.class);
IConfigAccessor<ConcreteConfig> accessor = configClient.register("key", ConcreteConfig.class)
How I want to inject them via Guice:
Currently, I can get the requried objects, but I have to manually pass them around in my application.
Instead, what I want to have is the following:
public class SomeClass {
#Inject
public SomeClass(#Config(configKey="key") IConfigAccessor<ConcreteConfig> accessor) {
// hurray!
}
}
What's the correct approach/technology to get this working?
After a lot of research, I'm feeling a bit lost on how to approach this topic. There are a lot of different things Guice offers, including simple Providers, custom Listeners which scan classes and identify custom annotations, FactoryModuleBuilders and more.
My problem is quite specific, and I'm not sure which of these things to use and how to get it working. I'm not even sure if this is even possible with Guice?
Edit: What I have so far
I have the following annotation which I want to use inside constructor paramters:
#Target({ ElementType.FIELD, ElementType.PARAMETER })
#Retention(RetentionPolicy.RUNTIME)
public #interface InjectConfig {
String configKey();
}
Inside the module, I can bind a provider to IConfigAccessor (with the above annotation) as such:
bind(IConfigAccessor.class).annotatedWith(InjectConfig.class)
.toProvider(new ConfigProvider<>());
However, there are two problems whith this:
The provider cannot provide IConfigAccessor. To create such an instance, the provider would need an IConfigUpdater, but since I use 'new' for the provider, I can't inject it.
Inside the provider, there is no way to find out about the configKey used in the Annotation.
Second approach:
Let's assume that I already know all configurations and configKeys I want to inject during startup. In this case, I could loop over all possible configKeys and have the following binding:
String configKey = "some key";
final Class<? extends IConfig> configClass =...;
bind(IConfigAccessor.class).annotatedWith(Names.named(configKey))
.toProvider(new ConfigProvider<>(configKey, configClass));
However, problem (1) still resides: The provider cannot get an IConfigUpdater instance.
The main problem here is that you cannot use the value of the annotation in the injection. There is another question which covers this part:
Guice inject based on annotation value
Instead of binding a provider instance, you should bind the provider class, and get the class by injecting a typeliteral.
That way, your config factory can look like that:
public class ConfigFactory<T extends IConfig> implements IConfigFactory {
#Inject private final IConfigUpdater updater;
#Inject private TypeLiteral<T> type;
#Override
public IConfigAccessor<T> register(final String configKey) {
Class<T> configClass = (Class<T>)type.getRawType();
ConfigCache<T> configCache = new ConfigCache<>(new SomeOtherThings(), configKey, configClass);
updater.register(configCache);
return new ConfigAccessor<>(configCache, configKey, configClass);
}
}
And then SomeClass:
public class SomeClass {
#Inject
public SomeClass(ConfigFactory<ConcreteConfig> accessor) {
ConcreteConfig config = accessor.register("key");
}
}
Since SomeClass needs to know "key" anyway, this is not too much a change information-wise. The downside is that the SomeClass API now gets a factory instead of the concrete config.
[EDIT]
And here is someone who actually did inject annotated values using custom injection.
My domain Objects are enhanced using lombok, which generates the java.beans #ConstructorProperties annotation for the constructors of immutable objects.
Now in my frontend artifact, I'd like to serialize these objects to JSON using Jackson 2.
For Jackson 1, this could be done using Jackson Extensions. Is there such a solution for Jackson 2 as well or do I have to write it myself?
My main problem is that I want to keep my domain Objects frontend agnostic, so I wouldn't like to pollute them with Jackson annotations.
And no: Java 8 parameter names is not an option, as I am stuck with Java 7 for the time being.
Sean Patrick Floyd has already written a solution, but I am posting my solution because his is proprietary. This is a Jackson module that uses an AnnotationIntrospector to make a constructor annotated with #ConstructorProperties a jackson #JsonCreator.
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.json.PackageVersion;
import com.fasterxml.jackson.databind.Module;
import com.fasterxml.jackson.databind.introspect.Annotated;
import com.fasterxml.jackson.databind.introspect.AnnotatedConstructor;
import com.fasterxml.jackson.databind.introspect.NopAnnotationIntrospector;
import com.fasterxml.jackson.databind.module.SimpleModule;
import java.beans.ConstructorProperties;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
public class ConstructorPropertiesModule extends SimpleModule {
public ConstructorPropertiesModule() {
super(PackageVersion.VERSION);
}
#Override
public void setupModule(Module.SetupContext context) {
super.setupModule(context);
context.insertAnnotationIntrospector(new ConstructorPropertiesAnnotationIntrospector());
}
public static class ConstructorPropertiesAnnotationIntrospector extends NopAnnotationIntrospector {
#Override
public boolean hasCreatorAnnotation(Annotated a) {
if (!(a instanceof AnnotatedConstructor)) {
return false;
}
AnnotatedConstructor ac = (AnnotatedConstructor) a;
Constructor<?> c = ac.getAnnotated();
ConstructorProperties properties = c.getAnnotation(ConstructorProperties.class);
if (properties == null) {
return false;
}
for (int i = 0; i < ac.getParameterCount(); i++) {
final String name = properties.value()[i];
final int index = i;
JsonProperty jsonProperty = new JsonProperty() {
#Override
public String value() {
return name;
}
#Override
public boolean required() {
return false;
}
#Override
public Class<? extends Annotation> annotationType() {
return JsonProperty.class;
}
#Override
public int index() {
return index;
}
};
ac.getParameter(i).addOrOverride(jsonProperty);
}
return true;
}
}
}
The module can then be registered to an object mapper to deserialize JSON using the #ConstructorProperties annotation:
ObjectMapper m = new ObjectMapper();
m.registerModules(new ConstructorPropertiesModule());
As others stated Jackson now supports #ConstructorProperties - unfortunatelly. Because it messed up things.
The logic Jackson applies is quite unfortunate. If multiple #ConstructorProperties annotated constructor are present it will create the object via the one with most parameters. Ops. This is problem especially with Lombok which annotates all constructors with #ConstructorProperties. But anyway, this annotation is not there solely for Jackson. It makes sense to annotate every single constructor for any code inspection tool which may use this information. Lombok is right here.
Imagine following object:
#Data
#Builder
#NoArgsConstructor // for Jackson
#AllArgsConstructor // for builder
public class MyDto {
private Type1 value1 = Type1.NONE;
private Type2 value2;
}
Here Jackson will always use the all-args constructor because it is annotated with #ConstructorProperties and has most parameters.
This also means that if you set only value2 in your JSON object the value1 becomes null. Not what you would expect.
Conclusion: the current behaviour (when used with Lombok or annotate more than one constructor) doesn't allow for the easy class-level default values.
Workaround: #AllArgsConstructor(suppressConstructorProperties=true) - but this is claimed to be deprecated soon as it's present just for java 1.5 compatibility purposes.
This issue has finally been resolved in Jackson 2.7 and #ConstructorProperties are now supported out-of-the-box.
See https://github.com/FasterXML/jackson-databind/issues/905
I'm afraid you will have to write a similar wrapper for Jackson2 yourself.
I am using the following bean definition to make my spring app talking in JSON
<bean id="jacksonMessageConverter" class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter" />
Is it possible with this message converter bean to use the #JsonView annotation?
#JsonView is already supported in the Jackson JSON Processor from v1.4 onwards.
New Edit: Updated for Jackson 1.9.12
According to the v1.8.4 documentation the function I was using writeValueUsingView is now Deprecated Use ObjectMapper.viewWriter(java.lang.Class) instead… however that has also been Deprecated Since 1.9, use writerWithView(Class) instead! (see v1.9.9 documentation)
So here is an updated example, tested with Spring 3.2.0 and Jackson 1.9.12 which simply returns {id: 1} and not the extended {name: "name"} since it is using the .writerWithView(Views.Public.class). Switching to Views.ExtendPublic.class will result in {"id":1,"name":"name"}
package com.demo.app;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.codehaus.jackson.map.annotate.JsonView;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.ObjectWriter;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
#Controller
public class DemoController {
private final ObjectMapper objectMapper = new ObjectMapper();
#RequestMapping(value="/jsonOutput")
#ResponseBody
public String myObject(HttpServletResponse response) throws IOException {
ObjectWriter objectWriter = objectMapper.writerWithView(Views.Public.class);
return objectWriter.writeValueAsString(new MyObject());
}
public static class Views {
static class Public {}
static class ExtendPublic extends Public {}
}
public class MyObject {
#JsonView(Views.Public.class) Integer id = 1;
#JsonView(Views.ExtendPublic.class) String name = "name";
}
}
Previous Edit: You need to instantiate the ObjectMapper and write out the object using a custom view as shown here, or in this example:
Define views:
class Views {
static class Public {}
static class ExtendedPublic extends PublicView {}
...
}
public class Thing {
#JsonView(Views.Public.class) Integer id;
#JsonView(Views.ExtendPublic.class) String name;
}
Use views:
private final ObjectMapper objectMapper = new ObjectMapper();
#RequestMapping(value = "/thing/{id}")
public void getThing(#PathVariable final String id, HttpServletResponse response) {
Thing thing = new Thing();
objectMapper.writeValueUsingView(response.getWriter(), thing, Views.ExtendPublic.class);
}
If you are using Jackson >= 1.7 you might find that the #JSONFilter better suits your needs.
#JsonView annotation was not supported on Spring but this issue is solved!
Follow this
Add support for Jackson serialization views
Spring MVC now supports Jackon's serialization views for rendering
different subsets of the same POJO from different controller
methods (e.g. detailed page vs summary view).
Issue: SPR-7156
This is the SPR-7156.
Status: Resolved
Description
Jackson's JSONView annotation allows the developer to control which aspects of a method are serialiazed. With the current implementation, the Jackson view writer must be used but then the content type is not available. It would be better if as part of the RequestBody annotation, a JSONView could be specified.
Available on Spring ver >= 4.1
Thank you Spring!