In org.springframework.beans.propertyeditors.ClassEditor
, the Class
is obtained by ClassUtils.resolveClassName
as a string, but when the Class
is dynamically generated by javax.tools.JavaCompiler
, the Class
cannot be obtained by ClassUtils.resolveClassName
. Expect to be passed from a primitive type that can be used directly instead of a string conversion, thanks.
Comment From: snicoll
Rather than asking for a code change, can you please first share the context? What are you trying to do?
Comment From: Air-Cooled
Create a SpringBoot project and add the following additional dependencies and startup classes to test; overwrite org.springframework.beans.propertyeditors.ClassEditor
when errors are reported, as shown at the end.
<dependency>
<groupId>com.itranswarp</groupId>
<artifactId>compiler</artifactId>
<version>1.0</version>
</dependency>
import com.itranswarp.compiler.JavaStringCompiler;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import java.util.Map;
@SpringBootApplication
public class DynamicCompilationApplication implements CommandLineRunner {
public static void main(String[] args) throws Exception {
// Initializes the dynamic build class example
buildClassesDynamically();
SpringApplication.run(DynamicCompilationApplication.class, args);
}
/**
* {@link org.springframework.beans.propertyeditors.ClassEditor#setAsText} gets the value here to avoid error
*/
public static Class<?> appClass;
public static Object app;
private static void buildClassesDynamically() throws Exception {
String javaName = "DynamicApplication";
String javaPath = DynamicCompilationApplication.class.getPackage().getName();
JavaStringCompiler javaStringCompiler = new JavaStringCompiler();
Map<String, byte[]> classBytes = javaStringCompiler.compile(javaName + ".java",
"package " + javaPath + ";\n" +
"import org.springframework.boot.CommandLineRunner;" +
"public class " + javaName + " implements CommandLineRunner {" +
"public void run(String... args) { System.err.println(\"Holle Word!\"); }" +
"}"
);
appClass = javaStringCompiler.loadClass(javaPath + "." + javaName, classBytes);
app = appClass.getDeclaredConstructor().newInstance();
}
@Autowired
public ConfigurableApplicationContext context;
@Override
public void run(String... args) throws Exception {
BeanDefinitionRegistry beanFactory = (BeanDefinitionRegistry) context.getBeanFactory();
// case 1: Direct register
String beanName = "dynamic";
// Dynamic type register
beanFactory.registerBeanDefinition(beanName, BeanDefinitionBuilder.genericBeanDefinition(appClass).getRawBeanDefinition());
// Console output:Holle Word!
context.getBean(beanName, CommandLineRunner.class).run(args);
// case 2: Use FactoryBean injection, this will cause an exception
String beanName2 = "factoryDynamic";
BeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition(CustomFactoryBean.class).getRawBeanDefinition();
beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(appClass.getName());
beanDefinition.getPropertyValues().add("type", appClass);
beanFactory.registerBeanDefinition(beanName2, beanDefinition);
// TODO : An error will be reported here, and you need to modify the org.springframework.beans.propertyeditors.ClassEditor, as shown below
context.getBean(beanName2, CommandLineRunner.class).run(args);
}
// Like MapperFactoryBean
public static class CustomFactoryBean<T> implements FactoryBean<T> {
private Class<T> type;
public CustomFactoryBean(Class<T> type) {
// TODO :There is also a question, since the parameter construct is called, why is the Setter method called separately again?
this.type = type;
}
public void setType(Class<T> type) {
this.type = type;
}
@Override
public T getObject() throws Exception {
return (T) app;
}
@Override
public Class<?> getObjectType() {
return type;
}
}
}
/*
* Copyright 2002-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.beans.propertyeditors;
import java.beans.PropertyEditorSupport;
import com.test.dynamic.DynamicCompilationApplication;
import org.springframework.lang.Nullable;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
/**
* Property editor for {@link Class java.lang.Class}, to enable the direct
* population of a {@code Class} property without recourse to having to use a
* String class name property as bridge.
*
* <p>Also supports "java.lang.String[]"-style array class names, in contrast to the
* standard {@link Class#forName(String)} method.
*
* @author Juergen Hoeller
* @author Rick Evans
* @see Class#forName
* @see ClassUtils#forName(String, ClassLoader)
* @since 13.05.2003
*/
public class ClassEditor extends PropertyEditorSupport {
@Nullable
private final ClassLoader classLoader;
/**
* Create a default ClassEditor, using the thread context ClassLoader.
*/
public ClassEditor() {
this(null);
}
/**
* Create a default ClassEditor, using the given ClassLoader.
*
* @param classLoader the ClassLoader to use
* (or {@code null} for the thread context ClassLoader)
*/
public ClassEditor(@Nullable ClassLoader classLoader) {
this.classLoader = (classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader());
}
@Override
public void setAsText(String text) throws IllegalArgumentException {
if (StringUtils.hasText(text)) {
// TODO Changes
try {
setValue(ClassUtils.resolveClassName(text.trim(), this.classLoader));
} catch (Exception e) {
if (text.equals(DynamicCompilationApplication.appClass.getName())){
setValue(DynamicCompilationApplication.appClass);
} else throw e;
}
} else {
setValue(null);
}
}
@Override
public String getAsText() {
Class<?> clazz = (Class<?>) getValue();
if (clazz != null) {
return ClassUtils.getQualifiedName(clazz);
} else {
return "";
}
}
}
Comment From: snicoll
I don't really understand what you're trying to do. In the second example, you're passing the name of the class but that class doesn't exist. You are the one forcing the editor to kick in an translate the String
into a Class
.
If you're looking for support on using Spring, please ask your question on StackOverflow.
Comment From: Air-Cooled
What's wrong with dynamically compiling a Class? The point is why would FactoryBean inputs be converted to String and then back again, which, performance aside, doesn't seem elegant. Perhaps to be compatible with xml injection Baen, what can be done to bypass this cumbersome transformation?
What I need is dynamically compiled classes that can also be registered through FactoryBean, the purpose of which is to simplify repetitive code in a project without breaking its structure (MVC). Cost reduction and efficiency, dynamic and flexible, the future can be expected!
Comment From: snicoll
What's wrong with dynamically compiling a Class?
Nothing, I guess?
The point is why would FactoryBean inputs be converted to String and then back again
You are doing that, not us.
beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(appClass.getName());
If you have more questions, as I've asked already, please follow up on StackOverflow.
Comment From: Air-Cooled
😳I'm sorry, is my use of error; Thank you for your patient guidance.