interceptor inject petitebean bug!
yujinping opened this issue · comments
jodd 5.0.12
Current behavior
when using interceptor as PetiteBean and the interceptor has some field should be inject!
NullException happend during jodd start process.
some code like this:
Inteceptor:
@PetiteBean
public class ApiInterceptor implements ActionInterceptor {
private static final Logger logger = LoggerFactory.getLogger(ApiInterceptor.class);
@PetiteInject
LoginService loginService;
@Override
public Object intercept(ActionRequest actionRequest) throws Exception {
return actionRequest.invoke();
}
}
LoginService code like this:
@PetiteBean
public class LoginService {
private static final Logger logger = LoggerFactory.getLogger(LoginService.class);
@PetiteInject
AdminDao adminDao;
@PetiteInject
TokenDao tokenDao;
public LoginService(){}
@Transaction
public void checkValidateLoginToken(String token) {
// do something here!
}
}
When require inject loginService ,code
@PetiteInject
LoginService loginService;
throw NullException,when remove @PetiteBean
of fieldloginService
,the exception gone,but this is not expect!
Exception stack is:
09:51:24.605 [main] INFO jodd.madvoc.AutomagicMadvocConfigurator - Scanning is complete.
09:51:24.620 [main] DEBUG jodd.proxetta.ProxettaFactory - processing: io/github/joy/hotel/action/api/hotels/UpdateHotelAction
09:51:24.621 [main] DEBUG jodd.proxetta.ProxettaFactory - Proxy not applied: io.github.joy.hotel.action.api.hotels.UpdateHotelAction
09:51:24.631 [main] ERROR jodd.joy.JoddJoy - java.lang.NullPointerException
java.lang.NullPointerException: null
at java.lang.reflect.Array.newArray(Native Method) ~[?:1.8.0_202]
at java.lang.reflect.Array.newInstance(Array.java:75) ~[?:1.8.0_202]
at jodd.util.ClassUtil.getRawType(ClassUtil.java:966) ~[jodd-all-5.0.12.jar:?]
at jodd.introspector.MethodDescriptor.<init>(MethodDescriptor.java:95) ~[jodd-all-5.0.12.jar:?]
at jodd.introspector.Methods.createMethodDescriptor(Methods.java:94) ~[jodd-all-5.0.12.jar:?]
at jodd.introspector.Methods.inspectMethods(Methods.java:84) ~[jodd-all-5.0.12.jar:?]
at jodd.introspector.Methods.<init>(Methods.java:52) ~[jodd-all-5.0.12.jar:?]
at jodd.introspector.ClassDescriptor.getMethods(ClassDescriptor.java:232) ~[jodd-all-5.0.12.jar:?]
at jodd.introspector.ClassDescriptor.getAllMethodDescriptors(ClassDescriptor.java:275) ~[jodd-all-5.0.12.jar:?]
at jodd.petite.resolver.InitMethodResolver.resolve(InitMethodResolver.java:48) ~[jodd-all-5.0.12.jar:?]
at jodd.petite.PetiteResolvers.resolveInitMethodPoint(PetiteResolvers.java:103) ~[jodd-all-5.0.12.jar:?]
at jodd.petite.PetiteContainer.initBeanDefinition(PetiteContainer.java:175) ~[jodd-all-5.0.12.jar:?]
at jodd.petite.PetiteContainer.getBean(PetiteContainer.java:160) ~[jodd-all-5.0.12.jar:?]
at jodd.petite.PetiteContainer.getBean(PetiteContainer.java:122) ~[jodd-all-5.0.12.jar:?]
at jodd.petite.PetiteContainer.lookupMixingScopedBean(PetiteContainer.java:94) ~[jodd-all-5.0.12.jar:?]
at jodd.petite.BeanData.wireProperties(BeanData.java:219) ~[jodd-all-5.0.12.jar:?]
at jodd.petite.BeanData.wireBean(BeanData.java:210) ~[jodd-all-5.0.12.jar:?]
at jodd.petite.PetiteContainer.registerBeanAndWireAndInjectParamsAndInvokeInitMethods(PetiteContainer.java:216) ~[jodd-all-5.0.12.jar:?]
at jodd.petite.PetiteContainer.getBean(PetiteContainer.java:162) ~[jodd-all-5.0.12.jar:?]
at jodd.petite.PetiteContainer.getBean(PetiteContainer.java:122) ~[jodd-all-5.0.12.jar:?]
at jodd.petite.PetiteContainer.lookupMixingScopedBean(PetiteContainer.java:94) ~[jodd-all-5.0.12.jar:?]
at jodd.petite.BeanData.wireProperties(BeanData.java:219) ~[jodd-all-5.0.12.jar:?]
at jodd.petite.BeanData.wireBean(BeanData.java:210) ~[jodd-all-5.0.12.jar:?]
at jodd.petite.PetiteContainer.registerBeanAndWireAndInjectParamsAndInvokeInitMethods(PetiteContainer.java:216) ~[jodd-all-5.0.12.jar:?]
at jodd.petite.PetiteContainer.createBean(PetiteContainer.java:304) ~[jodd-all-5.0.12.jar:?]
at jodd.petite.PetiteContainer.createBean(PetiteContainer.java:285) ~[jodd-all-5.0.12.jar:?]
at jodd.madvoc.petite.PetiteInterceptorManager.createWrapper(PetiteInterceptorManager.java:46) ~[jodd-all-5.0.12.jar:?]
at jodd.madvoc.petite.PetiteInterceptorManager.createWrapper(PetiteInterceptorManager.java:36) ~[jodd-all-5.0.12.jar:?]
at jodd.madvoc.component.WrapperManager.resolve(WrapperManager.java:85) ~[jodd-all-5.0.12.jar:?]
at jodd.madvoc.component.WrapperManager.resolveAll(WrapperManager.java:106) ~[jodd-all-5.0.12.jar:?]
at jodd.madvoc.component.ActionMethodParser.parseActionInterceptors(ActionMethodParser.java:246) ~[jodd-all-5.0.12.jar:?]
at jodd.madvoc.component.ActionMethodParser.parse(ActionMethodParser.java:161) ~[jodd-all-5.0.12.jar:?]
at jodd.madvoc.component.ActionsManager.registerAction(ActionsManager.java:133) ~[jodd-all-5.0.12.jar:?]
at jodd.madvoc.proxetta.ProxettaAwareActionsManager.registerAction(ProxettaAwareActionsManager.java:80) ~[jodd-all-5.0.12.jar:?]
at jodd.madvoc.AutomagicMadvocConfigurator.lambda$acceptActionClass$1(AutomagicMadvocConfigurator.java:228) ~[jodd-all-5.0.12.jar:?]
at java.util.ArrayList.forEach(ArrayList.java:1257) ~[?:1.8.0_202]
at jodd.madvoc.AutomagicMadvocConfigurator.start(AutomagicMadvocConfigurator.java:118) ~[jodd-all-5.0.12.jar:?]
at jodd.madvoc.component.MadvocComponentLifecycle.invoke(MadvocComponentLifecycle.java:84) ~[jodd-all-5.0.12.jar:?]
at jodd.madvoc.component.MadvocContainer.lambda$fireEvent$3(MadvocContainer.java:146) ~[jodd-all-5.0.12.jar:?]
at jodd.petite.PetiteBeans.lambda$forEachBeanType$0(PetiteBeans.java:795) ~[jodd-all-5.0.12.jar:?]
at jodd.petite.PetiteBeans.forEachBean(PetiteBeans.java:784) ~[jodd-all-5.0.12.jar:?]
at jodd.petite.PetiteBeans.forEachBeanType(PetiteBeans.java:793) ~[jodd-all-5.0.12.jar:?]
at jodd.madvoc.component.MadvocContainer.fireEvent(MadvocContainer.java:139) ~[jodd-all-5.0.12.jar:?]
at jodd.madvoc.WebApp.start(WebApp.java:255) ~[jodd-all-5.0.12.jar:?]
at io.github.joy.JoyWeb.start(JoyWeb.java:26) ~[joy-app-core.jar:?]
at jodd.joy.JoyMadvoc.start(JoyMadvoc.java:149) ~[jodd-joy-5.0.12.jar:5.0.12]
at jodd.joy.JoddJoy.start(JoddJoy.java:258) [jodd-joy-5.0.12.jar:5.0.12]
at jodd.joy.JoyContextListener.contextInitialized(JoyContextListener.java:82) [jodd-joy-5.0.12.jar:5.0.12]
at org.eclipse.jetty.server.handler.ContextHandler.callContextInitialized(ContextHandler.java:957) [jetty-runner-9.4.15.v20190215.jar:9.4.15.v20190215]
at org.eclipse.jetty.servlet.ServletContextHandler.callContextInitialized(ServletContextHandler.java:553) [jetty-runner-9.4.15.v20190215.jar:9.4.15.v20190215]
at org.eclipse.jetty.server.handler.ContextHandler.startContext(ContextHandler.java:922) [jetty-runner-9.4.15.v20190215.jar:9.4.15.v20190215]
at org.eclipse.jetty.servlet.ServletContextHandler.startContext(ServletContextHandler.java:365) [jetty-runner-9.4.15.v20190215.jar:9.4.15.v20190215]
at org.eclipse.jetty.webapp.WebAppContext.startWebapp(WebAppContext.java:1497) [jetty-runner-9.4.15.v20190215.jar:9.4.15.v20190215]
at org.eclipse.jetty.webapp.WebAppContext.startContext(WebAppContext.java:1459) [jetty-runner-9.4.15.v20190215.jar:9.4.15.v20190215]
at org.eclipse.jetty.server.handler.ContextHandler.doStart(ContextHandler.java:852) [jetty-runner-9.4.15.v20190215.jar:9.4.15.v20190215]
at org.eclipse.jetty.servlet.ServletContextHandler.doStart(ServletContextHandler.java:278) [jetty-runner-9.4.15.v20190215.jar:9.4.15.v20190215]
at org.eclipse.jetty.webapp.WebAppContext.doStart(WebAppContext.java:545) [jetty-runner-9.4.15.v20190215.jar:9.4.15.v20190215]
at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:68) [jetty-runner-9.4.15.v20190215.jar:9.4.15.v20190215]
at org.eclipse.jetty.util.component.ContainerLifeCycle.start(ContainerLifeCycle.java:138) [jetty-runner-9.4.15.v20190215.jar:9.4.15.v20190215]
at org.eclipse.jetty.util.component.ContainerLifeCycle.doStart(ContainerLifeCycle.java:117) [jetty-runner-9.4.15.v20190215.jar:9.4.15.v20190215]
at org.eclipse.jetty.server.handler.AbstractHandler.doStart(AbstractHandler.java:113) [jetty-runner-9.4.15.v20190215.jar:9.4.15.v20190215]
at org.eclipse.jetty.server.handler.ContextHandlerCollection.doStart(ContextHandlerCollection.java:168) [jetty-runner-9.4.15.v20190215.jar:9.4.15.v20190215]
at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:68) [jetty-runner-9.4.15.v20190215.jar:9.4.15.v20190215]
at org.eclipse.jetty.util.component.ContainerLifeCycle.start(ContainerLifeCycle.java:138) [jetty-runner-9.4.15.v20190215.jar:9.4.15.v20190215]
at org.eclipse.jetty.util.component.ContainerLifeCycle.doStart(ContainerLifeCycle.java:117) [jetty-runner-9.4.15.v20190215.jar:9.4.15.v20190215]
at org.eclipse.jetty.server.handler.AbstractHandler.doStart(AbstractHandler.java:113) [jetty-runner-9.4.15.v20190215.jar:9.4.15.v20190215]
at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:68) [jetty-runner-9.4.15.v20190215.jar:9.4.15.v20190215]
at org.eclipse.jetty.util.component.ContainerLifeCycle.start(ContainerLifeCycle.java:138) [jetty-runner-9.4.15.v20190215.jar:9.4.15.v20190215]
at org.eclipse.jetty.server.Server.start(Server.java:415) [jetty-runner-9.4.15.v20190215.jar:9.4.15.v20190215]
at org.eclipse.jetty.util.component.ContainerLifeCycle.doStart(ContainerLifeCycle.java:108) [jetty-runner-9.4.15.v20190215.jar:9.4.15.v20190215]
at org.eclipse.jetty.server.handler.AbstractHandler.doStart(AbstractHandler.java:113) [jetty-runner-9.4.15.v20190215.jar:9.4.15.v20190215]
at org.eclipse.jetty.server.Server.doStart(Server.java:382) [jetty-runner-9.4.15.v20190215.jar:9.4.15.v20190215]
at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:68) [jetty-runner-9.4.15.v20190215.jar:9.4.15.v20190215]
at org.eclipse.jetty.runner.Runner.run(Runner.java:522) [jetty-runner-9.4.15.v20190215.jar:9.4.15.v20190215]
at org.eclipse.jetty.runner.Runner.main(Runner.java:567) [jetty-runner-9.4.15.v20190215.jar:9.4.15.v20190215]
09:51:24.636 [main] INFO jodd.joy.JoyDb - DB stop
09:51:24.637 [main] INFO jodd.joy.JoyPetite - PETITE stop
09:51:24.638 [main] INFO jodd.joy.JoddJoy - Joy is down. Bye, bye!
Expected behavior
Jodd && Jodd joy should correct inject any petitebean in the interceptor or interceptor stack!
Steps to Reproduce the Problem
copy the demo code and use it in a subclass of RestActionConfig
to effective the interceptor!
LIKE:
public class RestConfig extends RestActionConfig {
@SuppressWarnings(value = {"unchecked"})
public RestConfig() {
super();
// setInterceptors(
// CORSInterceptor.class,
// ApiInterceptor.class,
// ServletConfigInterceptor.class
// );
setInterceptors(AppInterceptorStack.class);
}
}
Do you have any generics in the declarations of any of these classes on some method parameter? From the exception, it looks so? Maybe in LoginService
?
Would you be so kind to put the breakpoint in jodd.util.ClassUtil.getRawType:966
and let me know the value of genericComponentType
?
LoginService has to field:AdminDao and TokenDao
delare like this;
@PetiteBean
public class AdminDao extends BaseDao<Admin> {
private static final Logger logger = LoggerFactory.getLogger(AdminDao.class);
public AdminDao() {
super(Admin.class);
}
@Transaction
public Admin findUserByUsernameAndPassword(String username, String password) {
if (logger.isDebugEnabled()) {
}
final String q = "SELECT $C{a.*} FROM $T{Admin a} WHERE $a.username=:username AND $a.password=:password";
Map<String, Object> params = new HashMap<>();
params.put("username", username);
params.put("password", password);
Admin admin = one(q, params);
return admin;
}
}
and BaseDao like this:
public class BaseDao<T> extends JoyDao<T, Long> {
private static final Logger logger = LoggerFactory.getLogger(BaseDao.class);
public BaseDao(Class<T> klass) {
super(klass);
}
}
then have an common JoyDao like this:
public class JoyDao<T, Id> implements Dao<T, Id> {
private static final Logger logger = LoggerFactory.getLogger(JoyDao.class);
protected Integer DEFAULT_PAGE_SIZE = 20;
//default SqlCreator MySQL
private Class<T> entityClass;
private GenericDao dao;
public JoyDao(Class<T> klass) {
this.entityClass = klass;
initialize();
}
private GenericDao createGenericDao() {
if (dao == null) {
dao = new GenericDao(createDbOom());
}
return dao;
}
@Override
public DbOom createDbOom() {
oom = DbOom.get();
oom.queryConfig().setForcePreparedStatement(true);
oom.queryConfig().setDebug(showDebugSql);
return oom;
}
@Override
@ReadWriteTransaction
public T selectForUpdate(Id id) {
StringBuffer tql = new StringBuffer();
tql.append(" SELECT $C{c.*} FROM $T{" + entityClass.getSimpleName() + " c} WHERE $c.id = :id FOR UPDATE ");
Map<String, Object> params = new HashMap<>();
params.put("id", id);
DbOomQuery query = createQuery(tql.toString(), params);
List<T> list = query.list(entityClass);
if (list == null || list.size() <= 0) return null;
return list.get(0);
}
@Override
@ReadWriteTransaction
public void insert(T o) {
createGenericDao().save(o);
}
@Override
@ReadWriteTransaction
public void update(T o) {
createGenericDao().update(o);
}
@Override
public T one(String sql, Map<String, Object> params, Class<?>... classes) {
List<T> list = paginate(sql, params, 0, 1, classes);
list = createListWrapper(list);
if (list.size() > 0)
return list.get(0);
else
return null;
}
}
breakpoint :966,
class io.github.joy.hotel.dao.RoomDao$$JoddProxy
cause I visit rooms action ,so the action and dao is 👍
@MadvocAction
public class RoomsAction extends BaseAction {
private static final Logger logger = LoggerFactory.getLogger(RoomsAction.class);
@PetiteInject
RoomDao roomDao;
@RestAction
@GET
public Listing<Room> get(@In String keyword, @In Integer page, @In Integer limit) {
logger.info("roomDao={}",roomDao);
// Long hotelId = LogonHolder.get().getHotelId();
// Integer size = NumberUtil.getInteger(limit, 20);
// Integer start = (NumberUtil.getInteger(page, 1) - 1) * size;
// return roomDao.queryRoomPage(keyword, hotelId, start, size);
return new Listing<>();
}
}
althouh,Did not use any method of RoomDao ,but the null exception has happend
@PetiteBean
public class RoomDao extends BaseDao<Room> {
public RoomDao() {
super(Room.class);
}
}
Ok, this is something I can use! The issue here is with the proxy and reflections; you found one pesky bug :)
Thank you, I will now look whats going on.
@yujinping still trying to reproduce. can you do please one more thing for me?
I assume this method gives proxies a hard time:
public T one(String sql, Map<String, Object> params, Class<?>... classes)
Can you just remove it temporarily - or remove the last argument that is an array. According to exception, the array argument generics makes a problem, although I can not reproduce.
Of course, please don't do that if it is too much work. I am trying to reproduce, but so far no luck. The fix will be easy, I assume, just to find the place where to apply it :)
If you want and have time we can do remote debugging via Skype/Viber/Slack/Gitter/Hangouts/Zoom :) Just ping me.
Let me explain a bit, the issue is that genericComponentType
is null
, which is very weird, as it should not be null
; as GenericArrayType.getGenericComponentType()
should return a value.
Which version of Java are you using?
Still not being able to reproduce :(
The quick, untested patch would be null checking in jodd.util.ClassUtil.getRawType:966
, and returning Object[].class
if genericComponentType
is null
; but again, I would like to real see whats going on.
btw, what is that error for type
on your screenshot? It says sun.refl....
? Which type is that? That is also not something that I would expect!?
To simplify the problem, I removed most of the code, leaving only action & dao for testing:
RoomDao:
@PetiteBean
public class RoomDao extends BaseDao<Room> {
public RoomDao() {
super(Room.class);
}
}
BaseDao:
public class BaseDao<T> extends JoyDao<T, Long> {
private static final Logger logger = LoggerFactory.getLogger(BaseDao.class);
public BaseDao(Class<T> klass) {
super(klass);
}
}
JoyDao:
public class JoyDao<T, Id> implements Dao<T, Id> {
private static final Logger logger = LoggerFactory.getLogger(JoyDao.class);
private Class<T> entityClass;
private GenericDao dao;
public JoyDao(Class<T> klass) {
this.entityClass = klass;
}
private GenericDao createGenericDao() {
if (dao == null) {
dao = new GenericDao(createDbOom());
}
return dao;
}
@Override
public DbOom createDbOom() {
oom = DbOom.get();
return oom;
}
@ReadWriteTransaction
public void deleteById(Id... id) {
if (id == null)
return;
for (Id entityId : id) {
createGenericDao().deleteById(entityClass, entityId);
}
}
}
change the parameters of deleteById
,
When public void deleteById(Id... id)
,exception happened;When public void deleteById(Id id)
,so the problem is java problem ,cannot create generic typed array!
keypoint is change 'Id... id' to 'Id id',
So.maybe my code should replace the array with a List.
or in ClassUtil 966 ,when the type is null && is array ,using a hack method to create generic typed array!
java version: java8
Actually, my code did not call any method of the RoomDao!If 'Id... id',throw Null Exception runtime,change to 'Id id',then the jodd work fine!
My guess is right!
When I change my code public void deleteById(Id... id)
to public void deleteById(List<Id> id)
,jodd work fine!
Yes, the issue is with the arrays and generics. You don't have to call the class, because Jodd is scanning and analyzing the classes before they are actually used, to check if there are more injection points and so on.
Jodd should work with generic arrays - have tests for that, but for some reason, in your scenario, it does not work. Thanx for the details, I will try to reproduce!!!
Replicated! :) Working on it :)
Need more time, sorry... its about proxy creation and bytecode generation.
don't worry!thanks for your hard work!keep on !
Thank you for trusting me... I fixed it, now I need to add more tests, just to check if everything is fine.
Done, will release on Sunday :)
I'm waiting for the new release! Is there any problem?
Ah, no problem at all, sorry @yujinping. Will release it today!