public class JavaClassLoader extends URLClassLoader implements ApplicationContextAware { ..... protected final Map<String, TimestampClass> compiled = new ConcurrentHashMap<>(); protected final ConcurrentHashMap<String, Lock> locks = new ConcurrentHashMap<>(); protected final ProxyClassLoader proxyClassLoader; protected final SourceProvider sourceProvider; protected XmlWebApplicationContext applicationContext; private static volatile boolean refreshing = false; ..... @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = (XmlWebApplicationContext) applicationContext; this.applicationContext.setClassLoader(this); } public Class loadClass(final String fullClassName, boolean resolve) throws ClassNotFoundException { String containerClassName = StringUtils.substringBefore(fullClassName, "$"); try { lock(containerClassName); Class clazz; if (!sourceProvider.getSourceFile(containerClassName).exists()) { clazz = super.loadClass(fullClassName, resolve); return clazz; } CompilationScope compilationScope = new CompilationScope(this, containerClassName); if (!compilationScope.compilationNeeded()) { return getTimestampClass(fullClassName).clazz; } String src; try { src = sourceProvider.getSourceString(containerClassName); } catch (IOException e) { throw new ClassNotFoundException("Could not load java sources for class " + containerClassName); } try { log.debug("Compiling " + containerClassName); final DiagnosticCollector<JavaFileObject> errs = new DiagnosticCollector<>(); SourcesAndDependencies sourcesAndDependencies = new SourcesAndDependencies(rootDir, this); sourcesAndDependencies.putSource(containerClassName, src); sourcesAndDependencies.collectDependencies(containerClassName); Map<String, CharSequence> sourcesForCompilation = sourcesAndDependencies.collectSourcesForCompilation(containerClassName); @SuppressWarnings("unchecked") Map<String, Class> compiledClasses = createCompiler().compile(sourcesForCompilation, errs); Map<String, TimestampClass> compiledTimestampClasses = wrapCompiledClasses(compiledClasses); compiled.putAll(compiledTimestampClasses); linkDependencies(compiledTimestampClasses, sourcesAndDependencies.dependencies); clazz = compiledClasses.get(fullClassName); updateSpringContext(); return clazz; } catch (Exception e) { proxyClassLoader.restoreRemoved(); throw new RuntimeException(e); } finally { proxyClassLoader.cleanupRemoved(); } } finally { unlock(containerClassName); } } private void updateSpringContext() { if (!refreshing) { refreshing = true; applicationContext.refresh(); refreshing = false; } } ..... /** * Add dependencies for each class and ALSO add each class to dependent for each dependency */ private void linkDependencies(Map<String, TimestampClass> compiledTimestampClasses, Multimap<String, String> dependecies) { for (Map.Entry<String, TimestampClass> entry : compiledTimestampClasses.entrySet()) { String className = entry.getKey(); TimestampClass timestampClass = entry.getValue(); Collection<String> dependencyClasses = dependecies.get(className); timestampClass.dependencies.addAll(dependencyClasses); for (String dependencyClassName : timestampClass.dependencies) { TimestampClass dependencyClass = compiled.get(dependencyClassName); if (dependencyClass != null) { dependencyClass.dependent.add(className); } } } } ..... }
class SourcesAndDependencies { private static final String IMPORT_PATTERN = "import (.+?);"; private static final String IMPORT_STATIC_PATTERN = "import static (.+)\\..+?;"; public static final String WHOLE_PACKAGE_PLACEHOLDER = ".*"; final Map<String, CharSequence> sources = new HashMap<>(); final Multimap<String, String> dependencies = HashMultimap.create(); private final SourceProvider sourceProvider; private final JavaClassLoader javaClassLoader; SourcesAndDependencies(String rootDir, JavaClassLoader javaClassLoader) { this.sourceProvider = new SourceProvider(rootDir); this.javaClassLoader = javaClassLoader; } public void putSource(String name, CharSequence sourceCode) { sources.put(name, sourceCode); } /** * Recursively collects all dependencies for class using imports * * @throws java.io.IOException */ public void collectDependencies(String className) throws IOException { CharSequence src = sources.get(className); List<String> importedClassesNames = getDynamicallyLoadedImports(src); String currentPackageName = className.substring(0, className.lastIndexOf('.')); importedClassesNames.addAll(sourceProvider.getAllClassesFromPackage(currentPackageName));//all src from current package for (String importedClassName : importedClassesNames) { if (!sources.containsKey(importedClassName)) { addSource(importedClassName); addDependency(className, importedClassName); collectDependencies(importedClassName); } else { addDependency(className, importedClassName); } } } /** * Decides what to compile using CompilationScope (hierarchical search) * Find all classes dependent from those we are going to compile and add them to compilation as well */ public Map<String, CharSequence> collectSourcesForCompilation(String rootClassName) throws ClassNotFoundException, IOException { Map<String, CharSequence> dependentSources = new HashMap<>(); collectDependent(rootClassName, dependentSources); for (String dependencyClassName : sources.keySet()) { CompilationScope dependencyCompilationScope = new CompilationScope(javaClassLoader, dependencyClassName); if (dependencyCompilationScope.compilationNeeded()) { collectDependent(dependencyClassName, dependentSources); } } sources.putAll(dependentSources); return sources; } /** * Find all dependent classes (hierarchical search) */ private void collectDependent(String dependencyClassName, Map<String, CharSequence> dependentSources) throws IOException { TimestampClass removedClass = javaClassLoader.proxyClassLoader.removeFromCache(dependencyClassName); if (removedClass != null) { for (String dependentName : removedClass.dependent) { dependentSources.put(dependentName, sourceProvider.getSourceString(dependentName)); addDependency(dependentName, dependencyClassName); collectDependent(dependentName, dependentSources); } } } private void addDependency(String dependent, String dependency) { if (!dependent.equals(dependency)) { dependencies.put(dependent, dependency); } } private void addSource(String importedClassName) throws IOException { sources.put(importedClassName, sourceProvider.getSourceString(importedClassName)); } private List<String> unwrapImportValue(String importValue) { if (importValue.endsWith(WHOLE_PACKAGE_PLACEHOLDER)) { String packageName = importValue.replace(WHOLE_PACKAGE_PLACEHOLDER, ""); if (sourceProvider.directoryExistsInFileSystem(packageName)) { return sourceProvider.getAllClassesFromPackage(packageName); } } else if (sourceProvider.sourceExistsInFileSystem(importValue)) { return Collections.singletonList(importValue); } return Collections.emptyList(); } private List<String> getDynamicallyLoadedImports(CharSequence src) { List<String> importedClassNames = new ArrayList<>(); List<String> importValues = getMatchedStrings(src, IMPORT_PATTERN, 1); for (String importValue : importValues) { importedClassNames.addAll(unwrapImportValue(importValue)); } importValues = getMatchedStrings(src, IMPORT_STATIC_PATTERN, 1); for (String importValue : importValues) { importedClassNames.addAll(unwrapImportValue(importValue)); } return importedClassNames; } private List<String> getMatchedStrings(CharSequence source, String pattern, int groupNumber) { ArrayList<String> result = new ArrayList<>(); Pattern importPattern = Pattern.compile(pattern, Pattern.CASE_INSENSITIVE); Matcher matcher = importPattern.matcher(source); while (matcher.find()) { result.add(matcher.group(groupNumber)); } return result; } }
public class CharSequenceCompiler<T> { ..... // The compiler instance that this facade uses. private final JavaCompiler compiler; public CharSequenceCompiler(ProxyClassLoader loader, Iterable<String> options) { compiler = ToolProvider.getSystemJavaCompiler(); if (compiler == null) { throw new IllegalStateException("Cannot find the system Java compiler. " + "Check that your class path includes tools.jar"); } ..... } ..... public synchronized Map<String, Class<T>> compile( final Map<String, CharSequence> classes, final DiagnosticCollector<JavaFileObject> diagnosticsList) throws CharSequenceCompilerException { List<JavaFileObject> sources = new ArrayList<JavaFileObject>(); for (Map.Entry<String, CharSequence> entry : classes.entrySet()) { String qualifiedClassName = entry.getKey(); CharSequence javaSource = entry.getValue(); if (javaSource != null) { final int dotPos = qualifiedClassName.lastIndexOf('.'); final String className = dotPos == -1 ? qualifiedClassName : qualifiedClassName.substring(dotPos + 1); final String packageName = dotPos == -1 ? "" : qualifiedClassName .substring(0, dotPos); final JavaFileObjectImpl source = new JavaFileObjectImpl(className, javaSource); sources.add(source); // Store the source file in the FileManager via package/class // name. // For source files, we add a .java extension javaFileManager.putFileForInput(StandardLocation.SOURCE_PATH, packageName, className + JAVA_EXTENSION, source); } } // Get a CompliationTask from the compiler and compile the sources final JavaCompiler.CompilationTask task = compiler.getTask(null, javaFileManager, diagnostics, options, null, sources); final Boolean result = task.call(); if (result == null || !result) { StringBuilder cause = new StringBuilder("\n"); for (Diagnostic d : diagnostics.getDiagnostics()) { cause.append(d).append(" "); } throw new CharSequenceCompilerException("Compilation failed. Causes: " + cause, classes .keySet(), diagnostics); } try { // For each class name in the input map, get its compiled // class and put it in the output map Map<String, Class<T>> compiled = new HashMap<String, Class<T>>(); for (String qualifiedClassName : classLoader.classNames()) { final Class<T> newClass = loadClass(qualifiedClassName); compiled.put(qualifiedClassName, newClass); } return compiled; } catch (ClassNotFoundException e) { throw new CharSequenceCompilerException(classes.keySet(), e, diagnostics); } catch (IllegalArgumentException e) { throw new CharSequenceCompilerException(classes.keySet(), e, diagnostics); } catch (SecurityException e) { throw new CharSequenceCompilerException(classes.keySet(), e, diagnostics); } } ...... }
@Component("someBean") public class SomeBeanImpl implements SomeBean { @Override public String get() { return "reloaded";// not reloaded } }
@Controller("welcomeController") public class WelcomeController { @Autowired protected SomeBean someBean; @RequestMapping(value = "/hello", method = RequestMethod.GET) public ModelAndView welcome() { ModelAndView model = new ModelAndView(); model.setViewName("index"); model.addObject("version", someBean.get() + " a bit more");// a bit more return model; } }
private void updateSpringContext(Collection<Class> classes) { if (beanFactory != null) { for (Class clazz : classes) { Service serviceAnnotation = (Service) clazz.getAnnotation(Service.class); ManagedBean managedBeanAnnotation = (ManagedBean) clazz.getAnnotation(ManagedBean.class); Component componentAnnotation = (Component) clazz.getAnnotation(Component.class); Controller controllerAnnotation = (Controller) clazz.getAnnotation(Controller.class); String beanName = null; if (serviceAnnotation != null) { beanName = serviceAnnotation.value(); } else if (managedBeanAnnotation != null) { beanName = managedBeanAnnotation.value(); } else if (componentAnnotation != null) { beanName = componentAnnotation.value(); } else if (controllerAnnotation != null) { beanName = controllerAnnotation.value(); } if (StringUtils.isNotBlank(beanName)) { GenericBeanDefinition beanDefinition = new GenericBeanDefinition(); beanDefinition.setBeanClass(clazz); beanFactory.registerBeanDefinition(beanName, beanDefinition); } } } }
public class CubaDefaultListableBeanFactory extends DefaultListableBeanFactory { ..... /** * Reset all bean definition caches for the given bean, * including the caches of beans that depends on it. * * @param beanName the name of the bean to reset */ protected void resetBeanDefinition(String beanName) { String[] dependentBeans = getDependentBeans(beanName); super.resetBeanDefinition(beanName); if (dependentBeans != null) { for (String dependentBean : dependentBeans) { resetBeanDefinition(dependentBean); registerDependentBean(beanName, dependentBean); } } } }
Source: https://habr.com/ru/post/248981/
All Articles