I have a simple JFrame as the main window of my Java desktop application and I would like to configure it as a Spring bean. I would like to set properties, inject dependencies and launch it. Here's my frame class:
public class MainFrame extends JFrame {
public MainFrame() {
setTitle("Static Title");
setVisible(true);
}
}
My Spring application context:
<bean class="com.example.MainFrame">
<property name="title" value="Injected Title" />
</bean>
Then I fire it all up...
public static void main(String ... args) {
new ClassPathXmlApplicationContext("applicationContext.xml");
}
...which is followed by this java.beans.IntrospectionException:
type mismatch between indexed and non-indexed methods: location
The frame is actually displayed but there's that exception and the title remains "Static Title". So I have a few questions...
I've seen this being done by IBM in a 2005 tutorial but with Spring 1.2, and I don't even know what JRE. So how do I approach this exception? Is it possible to configure a JFrame as a Spring bean or do I need to proxy it or something?
I'm also wary of not launching the application from the event dispatching thread. So if there's a cleaner way of doing this I'd like to know about it. I can easily dispatch everything except that I don't know how to dispatch the construction itself.
Finally feel free to criticise the overall concept. I haven't come across many examples of Spring managed Swing applications. I'm using Spring-3.1 with Java-1.6.
Thanks.
I'm having the same problem, and it seems that's actually a bug on Spring:
https://jira.springsource.org/browse/SPR-8491
I think I'll wrap a FactoryBean around the panel and see if it works. I'll edit this post later, in case it works (or not)
-- edit --
Okay, instantiating it through a FactoryBean does get around the problem. The declaration becomes a little awkward, but that will have to do, at least until the aforementioned bug is fixed.
package com.ats.jnfe.swing;
import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.text.MessageFormat;
import java.util.HashMap;
import java.util.Map;
import org.springframework.beans.BeanInstantiationException;
import org.springframework.beans.factory.BeanInitializationException;
import org.springframework.beans.factory.FactoryBean;
/**
* Contorna, em caráter temporário, o bug apontado em:
* https://jira.springsource.org/browse/SPR-8491
* Quando o erro acima for resolvido, esta classe estará obsoleta.
*
* #author HaroldoOliveira
*/
public class SwingFactoryBean<T> implements FactoryBean<T> {
private Class<T> beanClass;
private Map<String, Object> injection;
private String initMethod;
private Map<String, PropertyDescriptor> properties;
private BeanInfo beanInfo;
private Method initMethodRef;
public T getObject() throws Exception {
T t = this.getBeanClass().newInstance();
if (this.getInjection() != null) {
for (Map.Entry<String, Object> en : this.getInjection().entrySet()) {
try {
this.properties.get(en.getKey()).getWriteMethod()
.invoke(t, en.getValue());
} catch (Exception e) {
throw new BeanInitializationException(MessageFormat.format(
"Error initializing property {0} of class {1}",
en.getKey(), this.getBeanClass().getName()), e);
}
}
}
if (this.initMethodRef != null) {
this.initMethodRef.invoke(t);
}
return t;
}
public Class<?> getObjectType() {
return this.getBeanClass();
}
public boolean isSingleton() {
return false;
}
public void initialize() {
try {
this.beanInfo = Introspector.getBeanInfo(this.getBeanClass());
this.properties = new HashMap<String, PropertyDescriptor>();
PropertyDescriptor[] descriptors = this.beanInfo.getPropertyDescriptors();
for (PropertyDescriptor pd : descriptors) {
this.properties.put(pd.getName(), pd);
}
if (this.getInitMethod() != null) {
this.initMethodRef = this.getBeanClass()
.getMethod(this.getInitMethod());
}
} catch (Exception e) {
throw new BeanInitializationException(
"Error initializing SwingFactoryBean: " + e.getMessage(), e);
}
}
public Class<T> getBeanClass() {
if (this.beanClass == null) {
throw new BeanInitializationException("Class not informed.");
}
return this.beanClass;
}
public void setBeanClass(Class<T> beanClass) {
this.beanClass = beanClass;
}
public Map<String, Object> getInjection() {
return injection;
}
public void setInjection(Map<String, Object> injection) {
this.injection = injection;
}
public String getInitMethod() {
return initMethod;
}
public void setInitMethod(String initMethod) {
this.initMethod = initMethod;
}
}
Usage example:
<bean id="certificadoNFeConfiguracaoDialog" class="com.ats.jnfe.swing.SwingFactoryBean" init-method="initialize" scope="prototype" lazy-init="true">
<property name="beanClass" value="com.ats.ecf.view.swing.util.dialog.OKCancelDialog" />
<property name="initMethod" value="inicializa" />
<property name="injection">
<map>
<entry key="selector">
<bean class="com.ats.jnfe.swing.SwingFactoryBean" init-method="initialize">
<property name="beanClass" value="com.ats.jnfe.swing.CertificadoNFeConfigPanel" />
<property name="initMethod" value="inicializa" />
<property name="injection">
<map>
<entry key="fachada" value-ref="certificadoNFeConfiguracaoFacade" />
</map>
</property>
</bean>
</entry>
</map>
</property>
</bean>
Runs with Spring 3.0.7.RELEASE, 3.1.4.RELEASE und 3.2.3.RELEASE.
It seems it has been a bug as mentioned in another answer.
My thought would be to keep Swing out of Spring. Past anything trivial, wiring up a GUI using something else is going to be too tedious. Instead, I would change what you are doing and just use main() to create the Spring content and then create your GUI.
If all you are doing in Spring would be creating the MainFrame and starting it, maybe the easiest thing is to create a FactoryBean that creates the frame. The factory could also call setVisible() via a SwingUtilities.invokeLater() call.
Confirmed runs with Spring 4.0.2.RELEASE too.
Related
I have the following code snippet that I was trying to fix:
In a spring context file, there is a bean configuration, something like this:
<bean id="myBean" >
<property name="interface">
<value>com.company.data.DataClass</value>
</property>
</bean>
With a corresponding setter as follows:
public void setInterface(Class<?>[] interfaces)
{
this.worker.setInterfaces(interfaces);
}
This works when the class exists.
But in certain environments the class may not exist and then an error is thrown.
Instead we'd like to handle the error when the Class isn't available.
I tried to fix the setter code as follows, but now it fails now when the Class actually does exist:
java.lang.ClassCastException: java.lang.String cannot be cast to [Ljava.lang.Class
public void setInterface(Object interfaceTest)
{
try
{
Class<?>[] interfaces = (Class<?>[])interfaceTest);
this.worker.setInterfaces(interfaces);
}
catch(Exception ex)
{
this.notValidInterface = true;
}
}
I'm not sure why the handling here is different.
Use String to define the value and Class.forName() to attempt to load the class checking if it exists.
<bean id="myBean">
<property name="interface">
<value>com.company.data.DataClass</value>
</property>
</bean>
public void setInterface(String[] interfaces) {
List<Class<?>> cls = new ArrayList<>();
for (String i : interfaces) {
try {
cls.add(Class.forName(i));
} catch (ClassNotFoundException ex) {
// handle the error
}
}
this.worker.setInterfaces(cls.toArray());
}
I have the following bean:
package com.test;
#Component
public class Sample{
String modified = null;
#Value("${url}")
private String url;
public Sample(){
System.out.println(url );
if(baseUrl.equals(""){
throw new RuntimeException("missing");
}
else{
modified = "test"+url;
}
}
}
I have added:
<context:annotation-config />
<context:property-placeholder location="classpath:test.properties"/> & <context:component-scan base-package="com.test"/>
and trying to access above "modified" field as below
<bean id="url" class="java.lang.String">
<constructor-arg value="#{sample.modified}" />
</bean>
in my application context. But I keep getting the following error:
Field or property 'sample' cannot be found on object of type 'org.springframework.beans.factory.config.BeanExpressionContext'
Not sure why i get this error?
When Spring creates the object it uses the default constructor. It can't set the property until after it constructs it. Instead of what you have, try this to see if the value is being set.
#PostConstruct
public void init(){
System.out.println(url );
if(baseUrl.equals(""){
throw new RuntimeException("missing");
}
}
JustinKSU's answer is right. You have another option: inject value via constructor using #Autowired:
#Component
public class Sample {
#Autowired
public Sample(#Value("${url}") String url) {
System.out.println(url);
if(url.equals("") {
throw new RuntimeException("missing");
}
}
}
I am wondering how to implement batch operations with my insert statements using MyBatis 3 & Spring 3?
For example, here is what is currently being done:
spring.xml:
<bean id="jndiTemplateDatasource" class="org.springframework.jndi.JndiTemplate">
<property name="environment">
<props>
<prop key="java.naming.factory.initial">${context.factory}</prop>
</props>
</property>
</bean>
<bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiTemplate" ref="jndiTemplateDatasource"/>
<property name="jndiName" value="${connectionpool.jndi}"/>
</bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<tx:annotation-driven transaction-manager="transactionManager"/>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="configLocation" value="classpath:mybatis-config.xml"/>
</bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.test" />
</bean>
MyService.xml:
<insert id="insertMyRecord" parameterType="com.test.MyRecord" >
insert into ... // code removed
</insert>
MyService.java:
public interface MyService {
public void insertMyRecord (MyRecord);
}
MyController.java:
#Controller
public class MyController {
#Autowired
private MyService myService;
#Transactional
#RequestMapping( .... )
public void bulkUpload (#RequestBody List<MyRecord> myRecords) {
for (MyRecord record : myRecords) {
myService.insertMyRecord(record);
}
}
}
Disclaimer: That is just pseudo code for demonstration purposes
So what can I do to turn that into a batch process?
Ideally I want to be able to do it with least "intrusion" into code, i.e. use annotations more preferred, but if not possible what is the next best thing?
Also, this needs to be configured just for this one service, not for everything in the project.
The accepted answer above doesn't actually get you batch mode for MyBatis. You need to choose the proper Executor via ExecutorType.BATCH. That is either passed as a parameter to SqlSession.openSession in standard MyBatis API or, if using MyBatis-Spring, as an option to the SqlSessionTemplate. That is done via:
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg index="0" ref="sqlSessionFactory" />
<constructor-arg index="1" value="BATCH" />
</bean>
There is nothing else that needs to be done.
This is running and tested example ...
Update multiple rows using batch (ibatis + java )
In this ex. I am updating attending count from table with respective to partyid.
public static int updateBatch(List<MyModel> attendingUsrList) {
SqlSession session = ConnectionBuilderAction.getSqlSession();
PartyDao partyDao = session.getMapper(PartyDao.class);
try {
if (attendingUsrList.size() > 0) {
partyDao.updateAttendingCountForParties(attendingUsrList);
}
session.commit();
} catch (Throwable t) {
session.rollback();
logger.error("Exception occurred during updateBatch : ", t);
throw new PersistenceException(t);
} finally {
session.close();
}
}
Model class where variable is defined :
public class MyModel {
private long attending_count;
private String eid;
public String getEid() {
return eid;
}
public void setEid(String eid) {
this.eid = eid;
}
public long getAttending_count() {
return attending_count;
}
public void setAttending_count(long attending_count) {
this.attending_count = attending_count;
}
}
party.xml code
Actual query where batch execute
<foreach collection="attendingUsrList" item="model" separator=";">
UPDATE parties SET attending_user_count = #{model.attending_count}
WHERE fb_party_id = #{model.eid}
</foreach>
Interface code here
public interface PartyDao {
int updateAttendingCountForParties (#Param("attendingUsrList") List<FBEventModel>attendingUsrList);
}
Here is my batch session code
public static synchronized SqlSession getSqlBatchSession() {
ConnectionBuilderAction connection = new ConnectionBuilderAction();
sf = connection.getConnection();
SqlSession session = sf.openSession(ExecutorType.BATCH);
return session;
}
SqlSession session = ConnectionBuilderAction.getSqlSession();
I'm not sure I understand the question fully correct but I will try to give you my thoughts.
For making the single service I would recommend to generify the service interface:
public void bulkUpload (#RequestBody List<T> myRecords)
Then you can check the type of the object and call the propper mapper repository.
Then you can generify it more by creating a common interface:
public interface Creator<T> {
void create(T object);
}
and extend it by your mapper interface:
public interface MyService extends Creator<MyRecord>{}
Now the most complicated step: you get the object of a particular type, see what exact mapper implements the Creator interface for this class (using java reflection API) and invoke the particular method.
Now I give you the code I use in one of my projects:
package com.mydomain.repository;
//imports ...
import org.reflections.Reflections;
#Repository(value = "dao")
public class MyBatisDao {
private static final Reflections REFLECTIONS = new Reflections("com.mydomain");
#Autowired
public SqlSessionManager sqlSessionManager;
public void create(Object o) {
Creator creator = getSpecialMapper(Creator.class, o);
creator.create(o);
}
// other CRUD methods
#SuppressWarnings("unchecked")
private <T> T getSpecialMapper(Class<T> specialClass, Object parameterObject) {
Class parameterClass = parameterObject.getClass();
Class<T> mapperClass = getSubInterfaceParametrizedWith(specialClass, parameterClass);
return sqlSessionManager.getMapper(mapperClass);
}
private static <T, P> Class<? extends T> getSubInterfaceParametrizedWith(Class<T> superInterface, Class<P> parameterType) {
Set<Class<? extends T>> subInterfaces = REFLECTIONS.getSubTypesOf(superInterface);
for (Class<? extends T> subInterface: subInterfaces) {
for (Type genericInterface : subInterface.getGenericInterfaces()) {
if (!(genericInterface instanceof ParameterizedType)) continue;
ParameterizedType parameterizedType = (ParameterizedType) genericInterface;
Type rawType = parameterizedType.getRawType();
if (rawType instanceof Class<?> && ((Class<?>) rawType).isAssignableFrom(superInterface)) {
for (Type type: parameterizedType.getActualTypeArguments()) {
if (type instanceof Class<?> && ((Class<?>) type).isAssignableFrom(parameterType)) {
return subInterface;
}
}
}
}
}
throw new IllegalStateException(String.format("No extension of %s found for parametrized type %s ", superInterface, parameterType));
}
}
Warning! This approach can have bad performance impact so use it in non-performance-critical actions
If you want bulk insert I would recommend to use mybatis foreach for bulk insert as described here.
If you think you don't want to write sql for every type of objects you better use Hibernate or any other advanced ORM. MyBatis is just an SQL mapping interface.
For example, I have a class
public class Car{
private Motor motor;
public void setMotor(Motor motor){
this.motor = motor;
}
}
My bean looks like
<bean id="car" class="Car">
<property name="motor" ref="${motorProvider.getAvailableMotor()}"/>
</bean>
This method: motorProvider.getAvailableMotor() returns a bean name(string), of which motor I should use.
But there can be a situation when such bean(with such name) is not created. How can I check it?
There are several patterns how to do this. Here is one I often use:
public class Car{
private Motor motor;
#Autowired
private ApplicationContext applicationContext;
#PostConstruct
public void init() {
try {
motor = applicationContext.getBean( Motor.class );
} catch( NoSuchBeanDefinitionException e ) {
motor = new DefaultMotor();
}
}
}
Note you could also call applicationContext.containsBeanDefinition(name) but that would search your context twice (once in containsBeanDefinition() and then second time when you call getBean()) so catching the exception is usually faster.
Since we catch a specific exception that says "bean doesn't exist", using if/else has almost no advantage anymore.
SPeL; something like:
<property name="motor" value="#(if(${motorProvider} != null) ${motorProvider.getAvailableMotor()})"/>
I think it was discussed also here: Spring - set a property only if the value is not null . As they said before for more information see: http://static.springsource.org/spring/docs/3.0.5.RELEASE/reference/expressions.html
Given a set of classes wired together by spring. There are several classes that are used with different configuration in multiple instances in the environment. They have different beanid of course.
The problems:
When they make log entries, we dont know exactly which bean made the log, since the log4j displays the classname only
I know that I could use logger instantiated by spring InitializationBean+BeanNameAware interface methods, but I do not want to do it, since I do not want to implement them in all classes
The solution could be:
Having some effect on bean factory to store the id of the beans in a map with the bean reference (key is the ref, name is the value)
Creating an aspect to be applied on every method, that would set an "BeanName" MDC entry in Log4j before the call, and would restore it to the previous value after the call. Meanwhile the previous beannames could be stored in a threadlocal in a stack.
The questions:
How can I change/configure the bean factory to do this trick for me? Is there any customization point I could use to this aim?
How can I avoid memory leaks in the map in the beanid registry? Maybe the registry is not needed at all, if somehow spring can look up the id for a reference.
Do you have any better idea, that would not result in changing ten thousand classes?
Thanks in advance.
UPDATE:
- Does anyone have solution for the prototype beans?
I have managed to hack something together based on this Spring AOP Example.
I am not yet up to speed with Spring 3 so I have implemented this using Spring 2.5 - I dare say there are more elegant ways of achieving what you want. I have implemented this using System.out's for simplicity but these could easily be converted to log4j calls.
Initially I create a map between the Spring's bean names and the string representation of the object (InitBean). This map is used inside the MethodInterceptor - I did try making the MethodInterceptor an InitializingBean but the MethodInterceptor stopped working for some reason.
Performing an equals between the bean passed in via the MethodInterceptor and the other beans in the application context did not work. e.g. by using something like "ctx.getBeansOfType(GoBean.class)" inside the MethodInterceptor. I presume this is because the object passed in via the MethodInvocation was a GoBean whereas objects obtained from the application context at this point are proxied (e.g. something like example.GoBean$$EnhancerByCGLIB$$bd27d40e).
This is why I had to resort to a comparison of object string representations (which is not ideal). Also I specifically do not want to activate the MethodInterceptor logic when calling the "toString" method on an object (as since I'm using toString elsewhere leads to infinite loops and StackOverflow).
I hope this is useful,
applicationContext.xml
<beans>
<bean name="initBean" class="example.InitBean"/>
<bean name="methodLoggingInterceptor" class="example.MethodLoggingInterceptor">
<property name="initBean" ref="initBean"/>
</bean>
<bean name="proxyCreator" class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<property name="beanNames">
<list>
<value>go*</value>
</list>
</property>
<property name="interceptorNames">
<list>
<value>methodLoggingInterceptor</value>
</list>
</property>
</bean>
<bean name="goBean1" class="example.GoBean" />
<bean name="goBean2" class="example.GoBean" />
<bean name="goBean3" class="example.GoBean" />
</beans>
GoBean.java
public class GoBean {
public void execute(){
System.out.println(new Date());
}
}
SimpleTestClass.java
public static void main( String[] args ){
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
ArrayList<GoBean> goBeans = new ArrayList<GoBean>();
goBeans.add((GoBean) ctx.getBean("goBean1"));
goBeans.add((GoBean) ctx.getBean("goBean2"));
goBeans.add((GoBean) ctx.getBean("goBean3"));
for(GoBean g: goBeans){
g.execute();
}
}
InitBean.java
public class InitBean implements ApplicationContextAware, InitializingBean {
private ApplicationContext ctx;
private Map<String, String> beanMap = new HashMap<String,String>();
public void setApplicationContext(ApplicationContext ac) throws BeansException {
ctx = ac;
}
public void afterPropertiesSet() throws Exception {
for(String beanName: ctx.getBeanNamesForType(GoBean.class)){
beanMap.put(ctx.getBean(beanName).toString(), beanName);
}
}
public Map<String,String> getBeanMap(){
return beanMap;
}
}
MethodLoggingInterceptor.java
public class MethodLoggingInterceptor implements MethodInterceptor{
private InitBean initBean;
public Object invoke(MethodInvocation method) throws Throwable {
if (!"toString".equals(method.getMethod().getName())) {
StringBuilder sb = new StringBuilder();
Object obj = method.getThis();
if (obj instanceof GoBean) {
Map<String,String> beanMap = initBean.getBeanMap();
String objToString = obj.toString();
if (beanMap.containsKey(objToString)) {
System.out.println(beanMap.get(objToString));
sb.append("bean: ");
sb.append(beanMap.get(objToString));
sb.append(" : ");
}
}
sb.append(method.getMethod().getDeclaringClass());
sb.append('.');
sb.append(method.getMethod().getName());
System.out.println(sb.toString() + " starts");
Object result = method.proceed();
System.out.println(sb.toString() + " finished");
return result;
} else {
return method.proceed();
}
}
public void setInitBean(InitBean ib) {
this.initBean = ib;
}
}