0

This won't work (aspect weaving) - SlugGenerator.slugger remains null. Please can someone have a look a this and give me a helping hand?

  1. My idea is to weave spring's service into the entity's value generator. I'm aware I can use entity's load event to inject that dependency.
  2. I have double checked whether Slugify component is instantiated properly by Spring (I can successfully inject it into controller).

Category.java (entity - domain object):

@Entity(name = "Category")
@Table(name = "category")
@Data
public class Category
{
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @GeneratorType(type = SlugGenerator.class, when = GenerationTime.ALWAYS)
    private String slug;

    // ...
}

SlugGenerator.java (value generator - instantiated by Hibernate):

@NoArgsConstructor
@Configurable(autowire = Autowire.BY_TYPE)
public class SlugGenerator implements ValueGenerator<String>
{
    @Autowired
    private Slugify slugger;

    @Override
    public String generateValue(Session session, Object owner) {
        Category category = (Category) owner;

        return slugger.slugify(category.getTitle());
    }
}

Slugify.java (Spring-managed service):

@Component
public class SlugifyImpl implements Slugify {
    @Override
    public String slugify(String input)
    {
        // ...
    }
}

DispatcherConf.java (Spring conf.):

public class DispatcherConf extends AbstractAnnotationConfigDispatcherServletInitializer {
    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class[] {ApplicationConf.class};
    }

    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class[] {WebConf.class};
    }

    @Override
    protected String[] getServletMappings() {
        return new String[] {"/*"};
    }
}

ApplicationConf.java (Spring conf.):

@Configuration
@EnableSpringConfigured
@Import({DataConf.class})
@ComponentScan(basePackages = {
        "pl.xxx.api.utils",
})
public class ApplicationConf
{
    @Bean
    public RepositoryRestConfigurer repositoryRestConfigurer() {
        return new RepositoryRestConfigurerAdapter() {
            @Override
            public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
                config.setBasePath("/api");
            }
        };
    }
}

WebConf.java (Spring conf.):

@Import(RepositoryRestMvcConfiguration.class)
public class WebConf {
}

build.gradle:

// ...
compile "org.springframework:spring-aspects:$springAspects"
// ...

jvm classpath (among others):

aspectjrt-1.8.13.jar

IntelliJ:

Build,Execution,Deployment > Compiler > Annotation Processors > Enable Annotation Processing set to TRUE
Błażej Kocik
  • 1,689
  • 1
  • 17
  • 20
  • What errors are you running into? – Pankaj Gadge Dec 12 '17 at 00:08
  • What is not visible from your question is whether you're using compile-time weaving or load-time weaving. Without either of those, it won't work. – Nándor Előd Fekete Dec 12 '17 at 05:50
  • @NándorElődFekete, how do I do it? Spring's @EnableLoadTimeWeaving? – Błażej Kocik Dec 12 '17 at 08:12
  • @PankajGadge, please re-read my post :) – Błażej Kocik Dec 12 '17 at 08:13
  • 1
    @BłażejKocik for load-time weaving, see my [answer](https://stackoverflow.com/a/41384525/2699901) to a similar question. In short, for load-time weaving it's best to use the aspectj weaver java agent. Optionally, specify weaver options `-showWeaveInfo -verbose` in `aop.xml` as detailed [here](https://stackoverflow.com/a/35224749/2699901) for debugging purposes. I'm not familiar with gradle so I cannot really help you with setting up compile time weaving in gradle. In maven, there's a plugin called [aspectj-maven-plugin](http://www.mojohaus.org/aspectj-maven-plugin/) – Nándor Előd Fekete Dec 12 '17 at 17:57
  • 1
    @NándorElődFekete, thank you for your time and good answer! :) – Błażej Kocik Dec 12 '17 at 22:45
  • @BłażejKocik glad to help. Did it solve your problem? – Nándor Előd Fekete Dec 12 '17 at 23:02
  • sure, see my comment below. Thanks! btw I didn't manage to inject it into ValueGenerator... (I wonder why...) – Błażej Kocik Dec 12 '17 at 23:03
  • @NándorElődFekete I can't really get CTW working with \@Configurable (have already removed lombok from the project). I have configured intellij for compiling with ajc,too. Included aspectjrt and aspectjtools in gradle. But to no avails – Błażej Kocik Dec 14 '17 at 12:18
  • I'm not familiar with gradle, but is there anything similar to aspectj-maven-plugin's [`aspectLibraries` configuration option](http://www.mojohaus.org/aspectj-maven-plugin/examples/libraryJars.html)? Maybe you need to configure that including `spring-aspects`. – Nándor Előd Fekete Dec 14 '17 at 14:57

2 Answers2

0

Thanks to the comment of @NándorElődFekete I understood how to achieve my goal - injecting a Spring-managed bean into a by-new-operator-created instance.

It is how I've made it:

Please consider these notes:

  • It was not possible to inject the bean into the ValueGenerator implementation (SlugGenerator.java ) - why I don't know. It's an open question :)

  • I have successfully injected the Slugify bean into the entity (Category.java) though.

In Spring's configuration I only used @EnableSpringConfigured.

I have chosen to use load-time wevaing (LTW) and consequently I added to my Glassfish domain's config file the following line:

<jvm-options>-javaagent:/usr/local/lib/java/aspectjweaver-1.8.13.jar</jvm-options>

Category.java:

@Entity(name = "Category")
@Table(name = "category")
@Data
@Configurable(autowire = Autowire.BY_TYPE)
public class Category
{
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @GeneratorType(type = SlugGenerator.class, when = GenerationTime.ALWAYS)
    private String slug;

    @Autowired
    @Transient
    private Slugify slugGenerator;
    // ...
}

class SlugGenerator implements ValueGenerator<String>
{
    @Override
    public String generateValue(Session session, Object owner) {
        Category category = (Category) owner;
        Slugify slugGenerator = category.getSlugGenerator();

        return slugGenerator.slugify(category.getName());
    }
}

Voila, private attribute slugGenerator is populated with the bean of Slugify interface :)

Błażej Kocik
  • 1,689
  • 1
  • 17
  • 20
  • Regarding injecting into `SlugGenerator`: I see you're using Lombok as well. I'm not sure how well it works with AspectJ as Lombok itself is pretty hackish in achieving it's goals, so it might be worth a try to define the class without using Lombok and see whether it works. I've used spring autoconfiguration in non spring managed beans by using `@Configurable` many times and I never really had issues with it. – Nándor Előd Fekete Dec 12 '17 at 23:05
  • Will try this tomorrow. Thanks for this hint – Błażej Kocik Dec 12 '17 at 23:07
  • Lombok and aspectj don't like to play in the same team. Though there IS a way to reconcile them. – Błażej Kocik Jan 20 '18 at 10:51
-1

Maybe this approach will be helpful:

@Component
public final class MyBeanUtils {

    @Autowired private ApplicationContext ctx;

    private static MyBeanUtils instance;

    @PostConstruct
    private void registerInstance() {
        instance = this;
    }

    public static <T> T getBean(Class<T> clazz) {
        return instance.ctx.getBean(clazz);
    }

    public static ApplicationContext appCtx() {
        return instance.ctx;
    }
}

Then use Spring beans in unmanaged classes, for example:

public class SlugGenerator implements ValueGenerator<String> {

    private Slugify slugger;

    public SlugGenerator() {
        slugger = MyBeansUtils.getBean(SlugifyImpl.class);
    }

    //...
}
Cepr0
  • 28,144
  • 8
  • 75
  • 101