Reputation: 13853
I have the following interface:
public interface Mapper {
public SomeType methodOne(HashMap<String, Object>);
public OtherType methodTwo(HashMap<String, Object>);
...
}
Then is it possible to call either methodOne
or methodTwo
as parameters within another class?
public class Other {
public void doSomething(HashMap<String, Object> params, ????) {
Mapper mapper = new ConcreteMapper();
mapper.methodOne(params);
}
}
I tried java reflection but I could not get the type of HashMap<String, Object>
at compiled time. I wonder is there a way to work around this problem?
The motivation comes from a real problem that I encountered at my work place. For example, the only thing different for each REST
method call is getAddressPhone
and AddressPhoneType
. Those noisy steps like open SQL session, instantiate Mapper
are essentially the same.
public Response getAddressPhone(@PathParam("acc_nbr") String accNbr, @HeaderParam("AuthToken") String authToken) {
SqlSession session = ConnectionFactory.getSqlSessionFactory().openSession();
Logger log = LoggerFactory.getLoggerFactory();
AddressPhoneType addressPhone = null;
try {
MyAccountMapper mapper = session.getMapper(MyAccountMapper.class);
HashMap<String, Object> map = new HashMap<String, Object>();
map.put("acc_nbr", accNbr);
addressPhone = mapper.getAddressPhone(map);
if (addressPhone == null) {
return Response.ok(null).status(Status.NOT_FOUND).build();
}
} catch (Exception e) {
log.debug("getAddressPhone(map):" + e.getMessage());
return Response.ok().status(Status.BAD_REQUEST).build();
} finally {
session.close();
}
return Response.ok(gson.toJson(addressPhone)).header("AuthToken", authToken).status(Status.OK).build();
}
EDIT
I'm using MyBatis mapper to perform MySql queries, so I have the following interface called MyAccountMapper
. Since each of these method is mapping with an id in SQL query, there is no definition for them:
public interface MyAccountMapper {
AddressPhoneType getAddressPhone(HashMap<String, Object> map);
AdminUserInfoType getAdminUserInfo(HashMap<String, Object> map);
DueDateInfoType getDueDateInfo(HashMap<String, Object> map);
....
}
Now in all my web service call (I'm using JAX-RS), I need to call these methods to do something with my database and as you can see from the above example, most of the stuffs like setting up mapper, create logger are the same. The only different is the method call and the return type.
@GET
@Path("/admin/user/info/{acc_nbr}")
@Produces("application/json")
public Response getAdminUserInfo(@PathParam("acc_nbr") String accNbr) {
SqlSession session = ConnectionFactory.getSqlSessionFactory().openSession();
AdminUserInfoType adminUserInfoType = null;
Logger log = LoggerFactory.getLoggerFactory();
try {
MyAccountMapper mapper = session.getMapper(MyAccountMapper.class);
HashMap<String, Object> map = new HashMap<String, Object>();
map.put("acc_nbr", accNbr);
adminUserInfoType = mapper.getAdminUserInfo(map);
if (adminUserInfoType == null) {
return Response.ok(gson.toJson(null)).status(Status.NOT_FOUND).build();
}
} catch (Exception e) {
log.debug("getAdminUserInfo(map):" + e.getMessage());
return Response.ok("getAdminUserInfo(map):"+ MyBatisErrorMessage.SELECT_ERROR).status(Status.BAD_REQUEST).build();
} finally {
session.close();
}
return Response.ok(gson.toJson(adminUserInfoType)).status(Status.OK).build();
}
@GET
@Path(value = "/address/phone/{acc_nbr}")
@Produces(MediaType.APPLICATION_JSON)
public Response getAddressPhone(@PathParam("acc_nbr") String accNbr,
@HeaderParam("AuthToken") String authToken) {
SqlSession session = ConnectionFactory.getSqlSessionFactory().openSession();
Logger log = LoggerFactory.getLoggerFactory();
AddressPhoneType addressPhone = null;
try {
MyAccountMapper mapper = session.getMapper(MyAccountMapper.class);
HashMap<String, Object> map = new HashMap<String, Object>();
map.put("acc_nbr", accNbr);
addressPhone = mapper.getAddressPhone(map);
if (addressPhone == null) {
return Response.ok(null).status(Status.NOT_FOUND).build();
}
} catch (Exception e) {
log.debug("getAddressPhone(map):" + e.getMessage());
return Response.ok("getAddressPhone(map):"+ MyBatisErrorMessage.SELECT_ERROR).status(Status.BAD_REQUEST).build();
} finally {
session.close();
}
return Response.ok(gson.toJson(addressPhone)).header("AuthToken", authToken).status(Status.OK).build();
}
Upvotes: 0
Views: 761
Reputation: 4184
Well, this is how one could invoke either method, using reflection, given an concrete instance:
Mapper concreteMapper = new ConcreteMapper();
Method method1 = concreteMapper.getClass().getMethod("methodOne", HashMap.class);
Method method2 = concreteMapper.getClass().getMethod("methodTwo", HashMap.class);
method1.invoke(concreteMapper, new HashMap<String, Object>());
method2.invoke(concreteMapper, new HashMap<String, Object>());
But if you are just trying to avoid the boiler plate code consider using something like the command pattern. Or, if you using spring, this would be a good use case for its AOP.
Upvotes: 1
Reputation: 176
I think that a mapper factory is needed and the mapper interface also needs to be modifed.
public interface Mapper {
public SomeType method(HashMap<String, Object> map);
}
public class MapperFactory {
public static Mapper createMapper(some parameters here) {
// create a mapper according to the parameters.
}
}
// usage within another class
public class Other {
public void doSomething(HashMap<String, Object> params, ????) {
Mapper mapper = new MapperFactory.createMapper(......);
mapper.method(params);
}
}
Upvotes: 1
Reputation: 53694
public interface Method {
public Object invoke(Map<> params);
}
public void invokeTheMethod(Map<> params, Method method) {
// ... do common work here ...
Object result = method.invoke(params);
// ... handle result here ...
}
// run common invoker with Mapper.methodOne
invokeTheMethod(params, new Method() {
public Object invoke(Map<> params) {
return mapper.methodOne(params);
});
Upvotes: 2
Reputation: 234795
Because functions aren't objects in Java (at least not yet), you can't do what you want in a simple way.
If you don't mind throwing away the return values (like you are doing with Other.doSomething
), then an interface and a couple of implementations can do the job. The implementations can be singletons.
interface MethodCaller {
void callMethod(HashMap<String, Object> args, Mapper mapper);
MethodCaller methodOneCaller = new MethodCaller() {
@Override
public void callMethod(HashMap<String, Object> args, Mapper mapper) {
mapper.methodOne(args);
}
}
MethodCaller methodTwoCaller = new MethodCaller() {
@Override
public void callMethod(HashMap<String, Object> args, Mapper mapper) {
mapper.methodTwo(args);
}
}
}
Then you can pass one of the callers to whomever wants to use them:
public class Other {
public void doSomething(HashMap<String, Object> params, MethodCaller caller) {
Mapper mapper = new ConcreteMapper();
caller.callMethod(params, mapper);
}
}
Other other = . . .;
HashMap<String, Object> stuff = . . .;
other.doSomething(stuff, MethodCaller.methodOneCaller);
other.doSomething(stuff, MethodCaller.methodTwoCaller);
Upvotes: 1