oblac / jodd

Jodd! Lightweight. Java. Zero dependencies. Use what you like.

Home Page:https://jodd.org

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

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);
    }
}

image

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.

Here is what happens to me:
image

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!