Skip to content

Want to support dynamic Class injection base #33206

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Air-Cooled opened this issue Jul 12, 2024 · 6 comments
Closed

Want to support dynamic Class injection base #33206

Air-Cooled opened this issue Jul 12, 2024 · 6 comments
Labels
status: invalid An issue that we don't feel is valid

Comments

@Air-Cooled
Copy link

Air-Cooled commented Jul 12, 2024

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.

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged or decided on label Jul 12, 2024
@snicoll
Copy link
Member

snicoll commented Jul 12, 2024

Rather than asking for a code change, can you please first share the context? What are you trying to do?

@snicoll snicoll added the status: waiting-for-feedback We need additional information before we can continue label Jul 12, 2024
@Air-Cooled
Copy link
Author

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

}

@spring-projects-issues spring-projects-issues added status: feedback-provided Feedback has been provided and removed status: waiting-for-feedback We need additional information before we can continue labels Jul 13, 2024
@snicoll
Copy link
Member

snicoll commented Jul 17, 2024

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.

@snicoll snicoll closed this as not planned Won't fix, can't repro, duplicate, stale Jul 17, 2024
@snicoll snicoll added status: invalid An issue that we don't feel is valid and removed status: waiting-for-triage An issue we've not yet triaged or decided on status: feedback-provided Feedback has been provided labels Jul 17, 2024
@Air-Cooled
Copy link
Author

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!

@snicoll
Copy link
Member

snicoll commented Jul 17, 2024

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.

@Air-Cooled
Copy link
Author

😳I'm sorry, is my use of error; Thank you for your patient guidance.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
status: invalid An issue that we don't feel is valid
Projects
None yet
Development

No branches or pull requests

3 participants