How to load beans using custom classloader in spring boot
i need to encrypted the spring boot project.
at first i try using proguard, but fail because spring use lots of Annotations and it has DI .
then i try using custom classloader. i want to encrypt my class files first, and then use my custom classloader to load the encryped class file, then decrypted it。
here it is my demo: https://github.com/CaiBaoHong/boot-by-custom-loader
when it is starting up it fail:
ConfigServletWebServerApplicationContext :
Exception encountered during context initialization - cancelling refresh attempt:
org.springframework.beans.factory.UnsatisfiedDependencyException:
Error creating bean with name 'methodValidationPostProcessor' defined in class path resource
[org/springframework/boot/autoconfigure/validation/ValidationAutoConfiguration.class]:
Unsatisfied dependency expressed through method 'methodValidationPostProcessor' parameter 0;
nested exception is
org.springframework.beans.factory.CannotLoadBeanClassException:
Error loading class [com.abc.bootbycustomloader.controller.UserController]
for bean with name 'userController' defined in file
[D:\code\boot-by-custom-loader\out\production\classes\com\abc\bootbycustomloader\controller\UserController.class]: problem with class file or dependent class;
nested exception is java.lang.LinkageError: loader (instance of com/abc/bootbycustomloader/loader/MyClassLoader):
attempted duplicate class definition for name:
"com/abc/bootbycustomloader/controller/UserController"
#SpringBootApplication
public class ServerApplication {
public static void main(String[] args) {
MyResourceLoader rLoader = new MyResourceLoader();
SpringApplication app = new SpringApplicationBuilder().build();
app.addPrimarySources(Arrays.asList(ServerApplication.class));
app.setResourceLoader(rLoader);
app.run(args);
}
}
#RestController
#RequestMapping("/user")
public class UserController {
#RequestMapping("/hello")
public String hello(){
System.out.println("hello hello hello...");
return "hello";
}
}
public class MyResourceLoader extends DefaultResourceLoader {
private ClassLoader cl = new MyClassLoader();
#Override
public Resource getResource(String location) {
System.out.println("getResource: "+location);
return super.getResource(location);
}
#Override
public ClassLoader getClassLoader() {
return cl;
}
}
public class MyClassLoader extends ClassLoader {
#Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
if (name.equals("com.abc.bootbycustomloader.controller.UserController")) {
// assump that UserController is the encrypted class
// i need to load this encrypted class, and decrypted it!
System.out.println("!!!!!encrypted!!!!! : " + name);
// load the class from a special place, mock the decrypted processing
String path = "D:\\_clz\\UserController.class";
byte[] data = new byte[0];
try {
data = Files.readAllBytes(Paths.get(path));
} catch (IOException e) {
e.printStackTrace();
}
// mock decrypted processing success, return the decrypted class
Class<?> clz = defineClass(name, data, 0, data.length);
return clz;
} else {
// assump that other class is not encrypted class
// just load it as usual
return super.loadClass(name);
}
}
}
Related
I'm making a library to be used with Spring Boot. This lib define some annotations that can be used in methods.
How can I find (at runtime) the package of the application where the library is being used?
I need this in order to scan for the annotated methods.
You can implement BeanFactoryPostProcessor:
1. Using ConfigurableListableBeanFactory you can iterate over BeanDefinition
2. Determine if bean's class has your annotation
3. Get the package from the bean's class name
Example:
#Component
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
#Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
Set<String> packages = findAnnotationUsagePackages(MyAnnotation.class, beanFactory);
...
}
private Set<String> findAnnotationUsagePackages(Class<? extends Annotation> annotationClass,
ConfigurableListableBeanFactory beanFactory) {
Set<String> annotationUsagePackages = new HashSet<>();
for (String beanDefinitionName : beanFactory.getBeanDefinitionNames()) {
BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanDefinitionName);
if (beanDefinition instanceof ScannedGenericBeanDefinition) {
ScannedGenericBeanDefinition genericBeanDefinition = (ScannedGenericBeanDefinition) beanDefinition;
if (AnnotationUtils.isCandidateClass(genericBeanDefinition.getBeanClass(), annotationClass)) {
String beanClassName = genericBeanDefinition.getBeanClassName();
if (beanClassName != null) {
annotationUsagePackages.add(ClassUtils.getPackageName(beanClassName));
}
}
}
}
return annotationUsagePackages;
}
}
About the AnnotationUtils.isCandidateClass():
Determine whether the given class is a candidate for carrying the specified annotation (at type, method or field level)
Also pay attention to the AbstractBeanDefinition.getBeanClass():
Throws: IllegalStateException - if the bean definition does not define a bean class, or a specified bean class name has not been resolved into an actual Class yet
P.S. also you can collect classes or meta-information inside AnnotationUtils.isCandidateClass condition block
I'm running a spring boot project with a large number of test and I want to use extent report. I have created a TestExecutionListener and I use the setting in one test.
I don't want to copy the same annotation in all test.
Where do I need to set the TestExecutionListener? and how?
Is it possible to set up in the application.properties?
The spring-test module declares all default TestExecutionListeners in its META-INF/spring.factories properties file.
Creating a simple Spring application:
MyBean:
#Component
public class MyBean {
public void doSomething() {
System.out.println("-- in MyBean.doSomething() method --");
}
}
AppConfig:
#Configuration
#ComponentScan
public class AppConfig {
}
Writing a TestExecutionListener:
public class MyListener implements TestExecutionListener {
#Override
public void beforeTestClass(TestContext testContext) throws Exception {
System.out.println("MyListener.beforeTestClass()");
}
#Override
public void prepareTestInstance(TestContext testContext) throws Exception {
System.out.println("MyListener.prepareTestInstance()");
}
#Override
public void beforeTestMethod(TestContext testContext) throws Exception {
System.out.println("MyListener.beforeTestMethod()");
}
#Override
public void afterTestMethod(TestContext testContext) throws Exception {
System.out.println("MyListener.afterTestMethod()");
}
#Override
public void afterTestClass(TestContext testContext) throws Exception {
System.out.println("MyListener.afterTestClass");
}
}
Writing JUnit test:
A TextExecutionListener can be registered via #TestExecutionListeners annotation:
#RunWith(SpringRunner.class)
#ContextConfiguration(classes = AppConfig.class)
#TestExecutionListeners(value = {MyListener.class,
DependencyInjectionTestExecutionListener.class})
#TestPropertySource("/test.properties")
public class MyTests {
#Autowired
private MyBean myBean;
#Test
public void testDoSomething() {
myBean.doSomething();
}
}
Detailed infomation: https://www.logicbig.com/tutorials/spring-framework/spring-core/test-execution-listener.html
EDIT:
Declaring test property sources
Test properties files can be configured via the locations or value attribute of #TestPropertySource as shown in the following example.
Both traditional and XML-based properties file formats are supported — for example, "classpath:/com/example/test.properties" or "file:///path/to/file.xml".
Each path will be interpreted as a Spring Resource. A plain path — for example, "test.properties" — will be treated as a classpath resource that is relative to the package in which the test class is defined. A path starting with a slash will be treated as an absolute classpath resource, for example: "/org/example/test.xml". A path which references a URL (e.g., a path prefixed with classpath:, file:, http:, etc.) will be loaded using the specified resource protocol. Resource location wildcards (e.g. */.properties) are not permitted: each location must evaluate to exactly one .properties or .xml resource.
#ContextConfiguration
#TestPropertySource("/test.properties")
public class MyTests {
// class body...
}
More info: https://docs.spring.io/autorepo/docs/spring-framework/4.2.0.RC2/spring-framework-reference/html/integration-testing.html
https://www.baeldung.com/spring-test-property-source
I have the following class:
public class ServiceFactory {
private ServiceFactory() {
}
public static <T extends XXXX> T loadService(Class<T> klass) {
ApplicationContext applicationContext = ApplicationContextProvider.getApplicationContext();
return applicationContext.getBean(klass);
}
}
It loads beans at runtime (I have a specific reason to do it like this).
I need to check if the bean is annotated with #Scope(BeanDefinition.SCOPE_PROTOTYPE) or just enforce it to be a prototype.
How would I do this?
First you need to find a bean name for your class. Then you may look for BeanDefinition using that name and get scope.
public <T> String findScope(ConfigurableApplicationContext applicationContext, Class<T> type) {
String[] names = applicationContext.getBeanFactory().getBeanNamesForType(type);
if(names.length != 1){
throw new IllegalArgumentException("Could not find bean of type" + type.getCanonicalName());
}
return applicationContext.getBeanFactory().getBeanDefinition(names[0]).getScope();
}
This is driving me nuts. I have the following files, it is a very simple setup.
public class MainApp {
public static void main(String[] args) {
//read the spring config java class
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext("Config.class");
//System.out.println("Bean names: " + Arrays.toString(context.getBeanNamesForType(AccountDAO.class)));
//get the bean from spring container
AccountDAO accountDAO = context.getBean("accountDAO", AccountDAO.class);
//call the business method
accountDAO.addAccount();
//close the spring context
context.close();
}
}
Config.java:
#Configuration
#ComponentScan("com.aop")
#EnableAspectJAutoProxy
public class Config {
}
LoggingAspectDemo.java:
#Aspect
#Component
public class LoggingAspectDemo {
//this is where we add all our related advices for the logging
//let's start with an #Before advice
#Before("execution(public void addAccount())")
public void beforeAddAccountAdvice() {
System.out.println("\n=======>>>> Executing #Before advice on method addAccount() <<<<========");
}
}
AccountDAO.java
#Component
public class AccountDAO {
public void addAccount() {
System.out.println(getClass() + ": Doing my Db work: Adding an account");
}
}
Everytime I run the MainApp.java, I get:
Exception in thread "main" org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'accountDAO' available
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeanDefinition(DefaultListableBeanFactory.java:687)
at org.springframework.beans.factory.support.AbstractBeanFactory.getMergedLocalBeanDefinition(AbstractBeanFactory.java:1207)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:284)
All the files are under "com.aop" package so #ComponentScan should be scanning all the components. It looks simple enough but I can't get my hands around the problem, can anyone help me where I am going wrong?
You're invoking the constructor of AnnotationConfigApplicationContext with "Config.class" as String argument, but this constructor is actually for invoking with base packages i.e. the argument must be a package name.
Since you want to use it with the Configuration class, use the constructor which accepts Class instance instead i.e.
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
Currently I'm facing a strange error using Spring AOP. My simple goal is to resgister the following class as an aspect:
#Aspect
public class AopProxyInitializer {
#Pointcut("execution(public * *(..))")
public void publicMethodPointcut() {
}
#Around("publicMethodPointcut()")
public Object showInstrumentationOutput(ProceedingJoinPoint joinPoint) {
try {
return joinPoint.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return null;
}
}
Doing so via XML works fine:
<aop:aspectj-autoproxy expose-proxy="true"/>
<bean class="com.big.instrumentation.spring.aspect.AopProxyInitializer"/>
But trying to reach the same result using this Java configuration (together with my other beans) fails:
#Configuration
#EnableAspectJAutoProxy
public class SpringInstrumentationConfig {
#Bean
public SpringContextProvider provider() {
return new SpringContextProvider();
}
#Bean
public SpringAdvisedBeanService beanService
(SpringContextProvider provider) {
return new SpringAdvisedBeanService(provider);
}
#Bean
public AopProxyInitializer aopProxyInitializer()
{
return new AopProxyInitializer();
}
}
The outcome is the following exception:
org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'aopProxyInitializer': Requested bean is currently in creation: Is there an unresolvable circular reference?
Do you have any idea why this is the case? Thanks in advance!
Problem: #Pointcut("execution(public * *(..))") includes the SpringInstrumentationConfig class which causes the exception. You can either add && !target(path.to.SpringInstrumentationConfig) to the publicMethodPointcut or move the declaration af aspects from configuration class to the context.
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(AopProxyInitializer.class);