I'm trying to use Gson with an interface:
public interface Photo {
public int getWidth();
}
public class DinosaurPhoto implements Photo {
...
}
public class Wrapper {
private Photo mPhoto; // <- problematic
}
...
Wrapper wrapper = new Wrapper();
wrapper.setPhoto(new DinosaurPhoto());
Gson gson = new Gson();
String raw = gson.toJson(wrapper);
// Throws an error since "Photo" can't be deserialized as expected.
Wrapper deserialized = gson.fromJson(raw, Wrapper.class);
Since the Wrapper class has a member variable that is of type Photo, how do I go about deserializing it using Gson?
Thanks
Custom deserialization is necessary.
Depending on the larger problem to be solved, either a ["type adapter"] 1 or a "type hierarchy adapter" should be used. The type hierarchy adapter "is to cover the case when you want the same representation for all subtypes of a type".
Simply put, you can't do that with GSON.
I was troubled by the same problem when I stumbled upon Jackson.
With it it is very easy:
ObjectMapper mapper = new ObjectMapper();
mapper.enableDefaultTyping();
And then you can go about de/serializing your Java objects and interfaces without having to write additional custom de/serializers, annotaions and really no added code whatsoever.
This was not a part of the question, but may prove useful if you decide to port from Gson to Jackson.
Gson supports private fields by default but for Jackson you have to include this in your code.
mapper.setVisibilityChecker(g.getVisibilityChecker().with(Visibility.ANY));
Sample implementation for your code in main:
ObjectMapper mapper = new ObjectMapper();
mapper.enableDefaultTyping();
mapper.setVisibilityChecker(g.getVisibilityChecker().with(Visibility.ANY));
Wrapper wrapper = new Wrapper();
wrapper.setPhoto(new DinosaurPhoto());
String wrapper_json = mapper.writeValueAsString(wrapper);
Wrapper wrapper_from_json = mapper.readValue(wrapper_json,Wrapper.class);
Gson promised they will work on this problem in future versions, but they haven't solved it so far.
If this is very important for you application I would suggest that you port to Jackson.
I have built a primitive interface shim generator by way of compiling a groovy properties class to interoperate with a GWT Autobeans model. this is a really rough method to sidestep the ASM/cglib learning curve for now. background on this: with Autobeans, you may only use interfaces, and the sun.* proxies are incapable of gson interop for all the access attempts I have experimented with. BUT, when groovy classloader is local to GsonBuilder, things get a tiny bit easier. note, this fails unless the gsonBuilder registration is actually called from within the groovy itself.
to access the shim factory create one as a singleton names JSON_SHIM and call
JSON_SHIM.getShim("{}",MyInterface.class)
to register if needed and create a [blank] instance. if you have interfaces in your interfaces, you must pre-register those too ahead of use. this is just enough magic to use flat Autobeans with gson, not a whole framework.
there is no groovy code in this generator, so someone with javassist-foo can repeat the experiment.
import com.google.gson.GsonBuilder;
import com.google.gson.InstanceCreator;
import com.google.gson.internal.bind.ReflectiveTypeAdapterFactory;
import groovy.lang.GroovyClassLoader;
import org.apache.commons.beanutils.PropertyUtils;
import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.LinkedHashMap;
import java.util.Map;
public class GroovyGsonShimFactory {
private Map<Class, Method> shimMethods = new LinkedHashMap<>();
private void generateGroovyProxy(Class ifaceClass) {
String shimClassName = ifaceClass.getSimpleName() + "$Proxy";
String ifaceClassCanonicalName = ifaceClass.getCanonicalName();
String s = "import com.google.gson.*;\n" +
"import org.apache.commons.beanutils.BeanUtils;\n" +
"import java.lang.reflect.*;\n" +
"import java.util.*;\n\n" +
"public class "+shimClassName+" implements "+ifaceClassCanonicalName+" {\n" ;
{
PropertyDescriptor[] propertyDescriptors = PropertyUtils.getPropertyDescriptors(ifaceClass);
for (PropertyDescriptor p : propertyDescriptors) {
String name = p.getName();
String tname = p.getPropertyType().getCanonicalName();
s += "public " + tname + " " + name + ";\n";
s += " " + p.getReadMethod().toGenericString().replace("abstract", "").replace(ifaceClassCanonicalName + ".", "") + "{return " + name + ";};\n";
Method writeMethod = p.getWriteMethod();
if (writeMethod != null)
s += " " + writeMethod.toGenericString().replace("abstract", "").replace(ifaceClassCanonicalName + ".", "").replace(")", " v){" + name + "=v;};") + "\n\n";
}
}
s+= " public static "+ifaceClassCanonicalName+" fromJson(String s) {\n" +
" return (" +ifaceClassCanonicalName+
")cydesign.strombolian.server.ddl.DefaultDriver.gson().fromJson(s, "+shimClassName+".class);\n" +
" }\n" +
" static public interface foo extends InstanceCreator<"+ifaceClassCanonicalName+">, JsonSerializer<"+ifaceClassCanonicalName+">, JsonDeserializer<"+ifaceClassCanonicalName+"> {}\n" +
" static {\n" +
" cydesign.strombolian.server.ddl.DefaultDriver.builder().registerTypeAdapter("+ifaceClassCanonicalName+".class, new foo() {\n" +
" public "+ifaceClassCanonicalName+" deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {\n" +
" return context.deserialize(json, "+shimClassName+".class);\n" +
" }\n" +
"\n" +
" public "+ifaceClassCanonicalName+" createInstance(java.lang.reflect.Type type) {\n" +
" try {\n" +
" return new "+shimClassName+"();\n" +
" } catch (Exception e) {\n" +
" e.printStackTrace(); \n" +
" }\n" +
" return null;\n" +
" }\n" +
"\n" +
" #Override\n" +
" public JsonElement serialize("+ifaceClassCanonicalName+" src, Type typeOfSrc, JsonSerializationContext context) {\n" +
" LinkedHashMap linkedHashMap = new LinkedHashMap();\n" +
" try {\n" +
" BeanUtils.populate(src, linkedHashMap);\n" +
" return context.serialize(linkedHashMap);\n" +
" } catch (Exception e) {\n" +
" e.printStackTrace(); \n" +
" }\n" +
"\n" +
" return null;\n" +
" }\n" +
" });\n" +
" }\n\n" +
"};";
System.err.println("" + s);
ClassLoader parent = DefaultDriver.class.getClassLoader();
GroovyClassLoader loader = new GroovyClassLoader(parent);
final Class gClass = loader.parseClass(s);
try {
Method shimMethod = gClass.getMethod("fromJson", String.class);
shimMethods.put(ifaceClass, shimMethod);
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
public <T> T getShim(String json, Class<T> ifaceClass) {
if (!shimMethods.containsKey(ifaceClass))
generateGroovyProxy(ifaceClass);
T shim = null;//= gson().shimMethods(json, CowSchema.class);
try {
shim = (T) shimMethods.get(ifaceClass).invoke(null, json);
} catch (IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
return shim;
}
}
Related
Is there some effective and accurate way to track the size of a particular session in a servlet based application?
Java doesn't have a sizeof() method like C (see this post for more information) so you generally can't get the size of anything in Java. However, you can track what goes into and is removed from the session with a HttpSessionAttributeListener (link is JavaEE 8 and below). This will give you some visibility into the number of attributes and, to an extent, the amount of memory being used. Something like:
import javax.servlet.annotation.WebListener;
import javax.servlet.http.HttpSessionAttributeListener;
import javax.servlet.http.HttpSessionBindingEvent;
#WebListener
public class MySessionAttributeListener implements HttpSessionAttributeListener {
#Override
public void attributeAdded(HttpSessionBindingEvent event) {
System.out.println( "the attribute \"" + event.getName() + "\" with the value \"" + event.getValue() + "\" has been added" );
}
#Override
public void attributeRemoved(HttpSessionBindingEvent event) {
System.our.println( "the attribute \"" + event.getName() + "\" with the value \"" + event.getValue() + "\" has been removed" );
}
#Override
public void attributeReplaced(HttpSessionBindingEvent event) {
System.out.println( "the attribute \"" + event.getName() + "\" with the value \"" + event.getValue() + "\" has been replaced" );
}
}
This question already has an answer here:
Exception handling and after advice
(1 answer)
Closed 1 year ago.
I added aspect like below in my spring-boot REST API to log calls to all methods in package "com.leanring.sprint" like so:
#Aspect
#Component
public class LogAdvice {
Logger logger = LoggerFactory.getLogger(LogAdvice.class);
#Pointcut(value = "execution(* com.learning.spring.*.*.*(..))")
public void pointCut() {
}
#Around("pointCut()")
public Object appLogger(ProceedingJoinPoint jp) throws Throwable {
ObjectMapper mapper = new ObjectMapper();
String methodName = jp.getSignature().getName();
String className = jp.getTarget().getClass().toString();
Object[] args = jp.getArgs();
logger.info("Start call: " + className + ":" + methodName + "()" + " with arguments: " + mapper.writeValueAsString(args));
Object object = jp.proceed();
logger.info("End call: " + className + ":" + methodName + "()" + " returned: " + mapper.writeValueAsString(object));
return object;
}
}
This is working fine, but I would also like it to be able to log any exceptions that could occur when a method is called.
How do I do that?
I suppose you could add another #AfterThrowing advice using the same pointcut or wrap jp.proceed(); inside a try-catch block.
Consider that I have parsed a JSON String to a Map<String, Object>, which is arbitrarily nested
My JSON looks like:
{
"root": [
{"k":"v1"},
{"k":"v2"}
]
}
I tried the expression root.?[k == 'v1'], however received the following error:
EL1008E: Property or field 'k' cannot be found on object of type 'java.util.LinkedHashMap' - maybe not public?
The evaluation context needs a MapAccessor:
public static void main(String[] args) throws Exception {
String json = "{\n" +
" \"root\": [\n" +
" {\"k\":\"v1\"},\n" +
" {\"k\":\"v2\"}\n" +
" ]\n" +
"}";
ObjectMapper mapper = new ObjectMapper();
Object root = mapper.readValue(json, Object.class);
Expression expression = new SpelExpressionParser().parseExpression("root.?[k == 'v1']");
StandardEvaluationContext ctx = new StandardEvaluationContext();
ctx.addPropertyAccessor(new MapAccessor());
System.out.println(expression.getValue(ctx, root));
}
result:
[{k=v1}]
Without a MapAccessor, you need
"['root'].?[['k'] == 'v1']"
A MapAccessor will only work with map keys that don't contain periods.
I am use spring boot v1.4.1.RELEASE + mybatis-spring-boot-starter v1.1.1 + h2database v1.4.192 + flywaydb v4.0.3 to build a spring boot web application.
here is my yml config part file or datasource config:
datasource:
km:
driverClassName: org.h2.Driver
url: jdbc:h2:mem:km;MODE=MySQL;
And, when I am use mysql to run my unit test, every unit test was passed. But, when I switch to h2database, the same test was not passed.
The ut code is below:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringBootTest(classes = App.class)
#ActiveProfiles("unit-test")
#Transactional
#Rollback
public class FollowServiceTest {
private int HOST_ID = 1;
private int UID = 100;
#Autowired
private FollowService followService;
#Autowired
private UserFollowDAO followDAO;
#Test
public void testFans() throws Exception {
UserFollow userFollow = new UserFollow();
userFollow.setHostId(HOST_ID);
userFollow.setFollowerId(UID);
followDAO.insert(userFollow);
List<UserFollow> fans = followDAO.findByIndexHostId(HOST_ID, 0, 10);
assertThat("fans is empty", fans, not(empty()));
}
}
UserFollowDAO:
public interface UserFollowDAO {
public static final String COL_ALL = " id, host_id, follower_id, create_time, last_update_time ";
public static final String TABLE = " user_follow ";
#Select(" select " +
COL_ALL +
" from " +
TABLE +
" where " +
"`host_id` = #{hostId} " +
" and id > #{lastId} " +
" limit #{count} "
)
public List<UserFollow> findByIndexHostId(
#Param("hostId") int hostId,
#Param("lastId") int lastId,
#Param("count") int count
);
#Insert(" insert into " + TABLE + " set "
+ " id = #{id}, "
+ " host_id = #{hostId}, "
+ " follower_id = #{followerId}, "
+ " create_time = now(), "
+ " last_update_time = now()")
public int insert(UserFollow bean);
}
Error info:
java.lang.AssertionError: fans is empty
Expected: not an empty collection
but: was <[]>
at org.hamcrest.MatcherAssert.assertThat(MatcherAssert.java:20)
at org.junit.Assert.assertThat(Assert.java:956)
at com.hi.hk.api.service.FollowServiceTest.testFans(FollowServiceTest.java:52)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
Here is my solutions that I have try:
Change datasource config in yml file from url: jdbc:h2:mem:km;MODE=MySQL to url: jdbc:h2:mem:km;MODE=MySQL;LOCK_MODE=1. Not work.
Delete #Transactional and #Rollback annotation from ut code. Not work.
What can I do next to solve this problem?
#pau:
Here is part of my application-unit-test.yml config file:
datasource:
ut: true
km:
driverClassName: org.h2.Driver
url: jdbc:h2:mem:km;MODE=MySQL
And, I have change datasource config as you said:
#Value("${datasource.ut}")
private boolean isUt;
public static final String SQL_SESSION_FACTORY_NAME = "sessionFactoryKm";
public static final String TX_MANAGER = "txManagerKm";
private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
#Bean(name = "datasourceKm")
#Primary
#ConfigurationProperties(prefix = "datasource.km")
public DataSource dataSourceKm() {
if (isUt){
EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder();
return builder.setType(EmbeddedDatabaseType.H2).build();
// Because I am use migration db to execute init sql script, so I haven't set script.
}
return DataSourceBuilder.create().build();
}
Here is flyway bean:
#Bean(name = "KmMigration", initMethod = "migrate")
#Primary
public Flyway flyway() throws SQLException {
logger.info(("================= start km db migration ================="));
Flyway flyway = new Flyway();
flyway.setDataSource(dataSourceKm());
flyway.setBaselineOnMigrate(true);
if (flyway.getDataSource().getConnection().getMetaData().getDatabaseProductName().toString().contains("H2")) {
logger.info(("================= using h2 database to start this app ================="));
flyway.setLocations("/db/migration/ut/km");
} else {
flyway.setLocations("/db/migration/km");
}
// different developer environment. might get different checksum,
// add the statement to skip the checksum error
// TODO: Maintain checksum across all developers environment.
flyway.setValidateOnMigrate(false);
MigrationVersion baselineVersion = flyway.getBaselineVersion();
logger.info(baselineVersion.getVersion());
return flyway;
}
There is some bug in mycode:
#Insert(" insert into " + TABLE + " set "
+ " id = #{id}, "// this line
+ " host_id = #{hostId}, "
+ " follower_id = #{followerId}, "
+ " create_time = now(), "
+ " last_update_time = now()")
public int insert(UserFollow bean);
This insert method will use value of id in bean, that is 0. So, I can't get right value. when I delete this line, this unit test is passed.
I am using spring aop to do logging for my application :
I have before after and afterthrowing advice configured but the line numbers that I see is not of the target class but that of the class used for logging
How can I solve this
Below is my configuration
Spring xml :
<aop:aspectj-autoproxy proxy-target-class="false" />
Class used for logging :
package com.digilegal.services.ahc.logging;
import java.lang.reflect.Modifier;
import org.apache.log4j.Logger;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
#Aspect
public class AHCLogging {
#Before("execution(* com.digilegal.services..*.*(..))")
public void logBefore(JoinPoint joinPoint) {
Logger log = Logger.getLogger(joinPoint.getTarget().getClass());
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
if (!Modifier.isPrivate(signature.getModifiers())
&& !signature.getName().startsWith("get")
&& !signature.getName().startsWith("set")
&& !signature.getName().startsWith("is")) {
log.trace("ENTER METHOD ::"
+ signature.getReturnType().getSimpleName() + " "
+ signature.getName() + "("
+ paramterType(signature.getParameterTypes()) + ")");
}
}
#After("execution(* com.digilegal.services..*.*(..))")
public void logAfter(JoinPoint joinPoint) {
Logger log = Logger.getLogger(joinPoint.getTarget().getClass());
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
if (!Modifier.isPrivate(signature.getModifiers())
&& !signature.getName().startsWith("get")
&& !signature.getName().startsWith("set")
&& !signature.getName().startsWith("is")) {
log.trace("EXIT METHOD ::"
+ signature.getReturnType().getSimpleName() + " "
+ signature.getName() + "("
+ paramterType(signature.getParameterTypes()) + ")");
}
}
#AfterThrowing(pointcut = "execution(* com.digilegal.services..*.* (..))",throwing= "error")
public void logAfterThrowing(JoinPoint joinPoint, Throwable error) {
Logger log = Logger.getLogger(joinPoint.getTarget().getClass());
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
if (!Modifier.isPrivate(signature.getModifiers())
&& !signature.getName().startsWith("get")
&& !signature.getName().startsWith("set")
&& !signature.getName().startsWith("is")) {
log.error("EXCEPTION IN METHOD ::"
+ signature.getReturnType().getSimpleName() + " "
+ signature.getName() + "("
+ paramterType(signature.getParameterTypes()) + ")");
log.error("Exception",error);
}
}
private String paramterType(Class<?>[] classes) {
StringBuffer buffer = new StringBuffer();
String returnValue = "";
for (Class<?> string : classes) {
buffer.append(Modifier.toString(string.getModifiers()));
buffer.append(" ");
buffer.append(string.getSimpleName());
buffer.append(",");
}
returnValue = buffer.toString();
if (returnValue.trim().length() > 0) {
returnValue = returnValue.substring(0, returnValue.length() - 1);
}
return returnValue;
}
}
Am I missing something or is it suppose to be like this
Thanks
Nirav
I think this is not specifically a Spring AOP problem but just the way Log4j works, see Javadoc for PatternLayout:
L
Used to output the line number from where the logging request was issued.
WARNING Generating caller location information is extremely slow and should be avoided unless execution speed is not an issue.
So my recommendation is to use a pattern layout without line number and use Spring AOP's capability of determining line numbers, roughly like this:
joinPoint.getSourceLocation().getLine()