@data注解不生效_@Enable* 注解,功能组件之王!!!!了解@Enable* 原理,打造自己的功能组建!

在Spring 和 SpringBoot 中有很多这样的注解,例如常见的:@EnableAsync@EnableCaching@EnableConfigurationProperties

每引用一个starer 几乎都有一个 @Enable*相关的注解。

这一注解的作用:就是用来启用某一个功能的配置。启用某一功能,仅需要加上一个注解即可生效,可以使组建之间的相互依赖降低了耦合性。

例如:@EnableAsync 注解启用异步功能,在SpringBoot中如果没有启用这个注解,直接在使用 @Async 是没法起到异步执行的作用的。所有使用这个功能,就先启用 @EnableAsync,不然他的相关配置不生效,也就是配置了@Async 注解,也没有人来管它。

一个简单的注解,能带动一个某一个功能模块甚至带动一整个框架的,比如:@SpringBootApplication。来分析一下这个 @Enable*注解的原理。

其实 @Enable注解很简单,随便找一个注解,点进去一看就能恍然大悟,它的所有核心 都在 *@Import 注解当中。 所有真正核心的 是 @Import**注解,由它去加载它自己对应的配置类,然后启动他的功能。

所以 @Enbale 注解就是没有啥用的,就是营造一种 *高大上的、又神奇装牛X的 感觉。所以它有啥用?其实它没有用。你看既然 使用 @Import注解就可以了,还要它干嘛,我直接使用 @Import注解去加载就好了**,这不是多此一举吗?

真的没有用吗? 有用的!! 存在即合理

举个例子:@EnableAutoConfiguration

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

    Class<?>[] exclude() default {};

    String[] excludeName() default {};
}

1、除了桥梁的作用,它还可以携带上一些参数:在解析处理这个 注解的时候,可以从这里拿到一些 自定义配置的参数,去做相关的操作。

2、除了 @Import 注解还有其它的注解 @AutoConfigurationPackage ,它可以组合多个注解放到一起。

3、高大上又神秘高级,又方面你开发者去使用。假如:启用这个功能可能需要更多的 类加载,还有要其它注解去配和,如果不将其包装到 @Enable*中,那对开发者来说,配置起来又相对麻烦了许多,将其包装到一起,只需要记住使用这一功能记住这个注解即可。极大的方面!!。

4、将功能做组建抽离开来,降低耦合性。

所以此重点就不再是 @Enable* 注解了,而是 @Import注解了,挂羊头卖狗肉!

1、@Import 注解的作用

我们看一下这个注解的源码解释

/** @author Chris Beams
  * @author Juergen Hoeller
  * @since 3.0
  * @see Configuration
  * @see ImportSelector
  * @see ImportResource
  */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {

    /**
     * {@link Configuration}, {@link ImportSelector}, {@link ImportBeanDefinitionRegistrar}
     * or regular component classes to import.
     *
     *  这个value值 有三种类型:
     *      -   其它的常规类型,就相当于Configuration配置累解析
     *      -   ImportSelector
     *      -   ImportBeanDefinitionRegistrar
     *
     *
     */
    Class<?>[] value();

}

@Import 注解导入的类 有三种,分别是:ImportSelector、 ImportBeanDefinitionRegistrar、一种是普通各类,会当作为配置类去处理。

@Import 注解的作用可以用来将 JavaBean 注入到IOC容器中。

1、比如:

@Import({UserService.class})

注入一个JavaBean到 IOC容器中。

2、又比如:

@Import({XXConfig.class})

导入一个配置类,然后继续扩展解析这个配置类。

3、又比如: @Import({XXImportSelector.class})

/**
 * <br>
 *
 * @author 永健
 * @since 2020-09-27 13:50
 */
public class MyImportSelector implements ImportSelector
{

    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata)
    {
        Class<?> zClass = null;
        Set<String> annotationTypes = importingClassMetadata.getAnnotationTypes();
        Iterator<String> zClasses = annotationTypes.iterator();
        while (zClasses.hasNext())
        {
            String type = zClasses.next();
            if (type.equals(EnableConfig.class.getName()))
            {
                Map<String, Object> annotationAttributes = importingClassMetadata.getAnnotationAttributes(EnableConfig.class.getName());
                zClass = (Class<?>) annotationAttributes.get("zClass");
            }
        }
        return new String[]{zClass.getName()};
    }
}

导入实现ImportSelector的类,这个实现可以拿到许多相关注解,然后可以取对应注解上的值,进行你所想要的操作。

4、还比如:

@Import({XXBeanDefinitionRegistry.class})

导入 BeanDefinitionRegistry 的实现类。该接口是Spring中定义的bean的注册类。所以的Bean注册都会走这个类。所以导入一个BeanDefinitionRegistry的实现类,可以自定义实现动态注册 bean的操作。

2、源码了解

如下是处理 @Import注解的的方法

// 处理 @Import 注解,加载某个类,将其假如IOC容器中 擦数: 当前的配置类,当前的源码类,导入的类
    private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
            Collection<SourceClass> importCandidates, boolean checkForCircularImports) {

        if (importCandidates.isEmpty()) {
            return;
        }

        if (checkForCircularImports && isChainedImportOnStack(configClass)) {
            this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
        }
        else {
            this.importStack.push(configClass);
            try {
                // @Import 注解 可以配置多个类没循环遍历
                // 该注解上面说了 有三种类型,遍历做分支处理
                for (SourceClass candidate : importCandidates) {

          // 三种类行,做分支判断
                    if (candidate.isAssignable(ImportSelector.class)) {
                        // Candidate class is an ImportSelector -> delegate to it to determine imports
                        Class<?> candidateClass = candidate.loadClass();
                        ImportSelector selector = BeanUtils.instantiateClass(candidateClass, ImportSelector.class);

                        ParserStrategyUtils.invokeAwareMethods(
                                selector, this.environment, this.resourceLoader, this.registry);

                        //延迟导入处理
                        if (this.deferredImportSelectors != null && selector instanceof DeferredImportSelector) {

                            this.deferredImportSelectors.add(
                                    new DeferredImportSelectorHolder(configClass, (DeferredImportSelector) selector));
                        }else {
                            // 执行 ImportSelector 接口的方法,实现了该ImportSelector的方法,拿到要注入的类全名
                            String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());

              // 返回来的转为一个配置类去加载
                            Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);

              // 继续递归检查
                            processImports(configClass, currentSourceClass, importSourceClasses, false);
                        }
                    }
                    else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
                      // 处理 ImportBeanDefinitionRegistrar 的实现类
            // Candidate class is an ImportBeanDefinitionRegistrar ->
                        // delegate to it to register additional bean definitions
                        Class<?> candidateClass = candidate.loadClass();

                        ImportBeanDefinitionRegistrar registrar =
                                BeanUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class);

                        ParserStrategyUtils.invokeAwareMethods(
                                registrar, this.environment, this.resourceLoader, this.registry);
                        configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());

                    }
                    else {
                        // Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar ->
                        // process it as an @Configuration class
                        // 当作配置类解析
                        this.importStack.registerImport(
                                currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
                        processConfigurationClass(candidate.asConfigClass(configClass));
                    }
                }
            }
            catch (BeanDefinitionStoreException ex) {
                throw ex;
            }
            catch (Throwable ex) {
                throw new BeanDefinitionStoreException(
                        "Failed to process import candidates for configuration class [" +
                        configClass.getMetadata().getClassName() + "]", ex);
            }
            finally {
                this.importStack.pop();
            }
        }
}

这个源码了解就到这里。

3、利用 @Enable* 自己也装一次高大上。

我们来写一个启用切面打印日志功能。

我们新建一个项目,作为一个starter , 一个jar包。 然后其他项目引入这个 jar启用日志打印的操作。

项目如下:

v2-a228903e207405c3b19863254a231f42_b.jpg

1、EnablePrintRequestLog.java

开启日切面功能的注解。

/**
 * <p>
 *
 * </p>
 *
 * @author 永健
 * @since 2020-11-10 19:15
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({LogPrintImportSelector.class, MyImportBeanDefinitionRegistrar.class, TestBean.class})
public @interface EnablePrintRequestLog
{
    /**
     * 拦截哪个包
     *
     * @return
     */
    String targetPackage() default "";
}

2、SysAspect.java

切面类, 此处只拦截 自定义的包下的加了 @RestController 和 @Controller 注解的类。

/**
 * <p>
 *
 * </p>
 *
 * @author 永健
 * @since 2020-11-10 10:15
 */
@Aspect
@Component
public class SysAspect extends Rules
{
    @Autowired
    List<Interceptor> interceptor;

    /**
     * 方法规则拦截 
     * 此处只拦截 加了 @RestController 和 @Controller 注解的类
     *
     */
        @Pointcut("@within(org.springframework.web.bind.annotation.RestController)||@within(org.springframework.stereotype.Controller)")
    public void controllerAspect()
    {
    }

    @Before("controllerAspect()")
    public void before(JoinPoint joinPoint)
    {
        interceptor.forEach(j -> {
            if (getPackages().equals(getTargetClassPackage(joinPoint)))
            {
                try
                {
                    j.before(joinPoint);
                } catch (Exception e)
                {
                    e.printStackTrace();
                }
            }
        });
    }


    private String getTargetClassPackage(JoinPoint point)
    {
        //拦截的实体类
        Object target = point.getTarget();

        //拦截的方法名称
        String methodName = point.getSignature().getName();

        String simpleName = target.getClass().getSimpleName();
        return simpleName.substring(simpleName.lastIndexOf(".") - 1);
    }

    @After(value = "controllerAspect()")
    public void after(JoinPoint joinPoint)
    {
        interceptor.forEach(j -> {
            if (getPackages().equals(getTargetClassPackage(joinPoint)))
            {
                try
                {
                    j.after(joinPoint);
                } catch (Exception e)
                {
                    e.printStackTrace();
                }
            }
        });
    }

    @AfterReturning(value = "controllerAspect()")
    public void afterReturning(JoinPoint joinPoint)
    {
        interceptor.forEach(j -> {
            if (getPackages().equals(getTargetClassPackage(joinPoint)))
            {
                try
                {
                    j.afterReturning(joinPoint);
                } catch (Exception e)
                {
                    e.printStackTrace();
                }
            }
        });
    }

    /**
     * 异常通知
     */
    @AfterThrowing(pointcut = "controllerAspect()", throwing = "e")
    public void doAfterThrowing(JoinPoint joinPoint, Throwable e)
    {
        interceptor.forEach(j -> {
            if (getPackages().equals(getTargetClassPackage(joinPoint)))
            {
                try
                {
                    j.doAfterThrowing(joinPoint, e);
                } catch (Exception ex)
                {
                    ex.printStackTrace();
                }
            }
        });
    }

    @Around(value = "controllerAspect()")
    public void around(JoinPoint joinPoint)
    {
        interceptor.forEach(j -> {
            if (getPackages().equals(getTargetClassPackage(joinPoint)))
            {
                try
                {
                    j.around(joinPoint);
                } catch (Exception e)
                {
                    e.printStackTrace();
                }
            }
        });
    }

}

3、Interceptor.java

为了扩展性,我留了切面的 5种方法的接口:Interceptor.jarva

/**
 * <br>
 *
 * @author 永健
 * @since 2020-11-10 19:18
 */
public interface Interceptor
{
    void before(JoinPoint joinPoint);

    void after(JoinPoint joinPoint);

    void doAfterThrowing(JoinPoint joinPoint,Throwable e);

    void afterReturning(JoinPoint joinPoint);

    void around(ProceedingJoinPoint joinPoint);
}

默认一个实现打印日志:需要扩展继续实现该接口,比如我需要存储记录日志的信息。请求的时候会打印如下信息。

/**
 * <br>
 *
 * @author 永健
 * @since 2020-11-10 19:20
 */
public class DefaultInterceptor implements Interceptor{
    private static final Logger LOGGER = LoggerFactory.getLogger(SysAspect.class);
    @Override
    public void before(JoinPoint joinPoint)    {
        LOGGER.info("########  {}  请求开始 " + LocalDateTime.now() + "   ###############", Thread.currentThread().getName());

        String className = joinPoint.getTarget().getClass().getName();
        String methodName = joinPoint.getSignature().getName();

        LOGGER.info("########  {}  请求的方法--> " + className + "/" + methodName + "()" + "/", Thread.currentThread().getName());
    }

    @Override
    public void after(JoinPoint joinPoint)    {
        LOGGER.info("########  结束了");
    }

    @Override
    public void doAfterThrowing(JoinPoint joinPoint, Throwable e)    {
        LOGGER.error("异常了");
    }

    @Override
    public void afterReturning(JoinPoint joinPoint)    {
        LOGGER.info("######## afterReturning");
    }

    @Override
    public void around(ProceedingJoinPoint joinPoint){
        System.out.println();
        LOGGER.info("######## around");
    }
}

4、LogPrintImportSelector.java

实现 ImportSelector,主要用来拿到注解 @EnablePrintRequestLog 的参数 tagetPackage 的参数。并且加载其全局配置类:GlobConfig.java

5、GlobConfig.java

该类的可以做所有的配置,将所需要的Bean 放到这里注入到IOC容器中,此处为了 测试 @Import注解的使用,i u 没有放在这里。

/**
 * <br>
 *
 * @author 永健
 * @since 2020-11-10 18:58
 */
@Configuration
// 扫描包,让其自动注入 好了
@ComponentScan("com.enable.demo")
public class GlobConfig
{
}

6、MyImportBeanDefinitionRegistrar.java

测试动态注册Bean,注册提供的默认切面打印日志类的实现类。

/**
 * <br>
 *
 * @author 永健
 * @since 2020-11-10 19:43
 */
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar
{
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry)
    {
        BeanDefinition beanDefinition = new GenericBeanDefinition();
        beanDefinition.setBeanClassName(DefaultInterceptor.class.getName());
        registry.registerBeanDefinition("defaultInterceptor", beanDefinition);
    }
}

6、Rules.java

主要用来存放 切面的拦截包路径。

/**
 * <br>
 *
 * @author 永健
 * @since 2020-11-10 19:29
 */
public abstract class Rules
{
    private static String packages;
    protected String getPackages(){
        return packages;
    }
    protected Rules setPackages(String packages){
        this.packages = packages;
        return this;
    }
}

7、TestBean.java

一个测试 @Import 导入普通Bean的类

/**
 * <br>
 *
 * @author 永健
 * @since 2020-11-10 19:49
 */
public class TestBean{
    public void test(){
        System.out.println("I'm test Bean");
    }
}

8、TestController.java

测试对比 该类在 com.enable.demo 的包下。

/**
 * <br>
 *
 * @author 永健
 * @since 2020-11-10 20:22
 */
@RestController
public class TesController{
    @GetMapping("/t")
    public String tes()    {
        return "ok";
    }
}

3、使用

将上面的项目打成一个 jar包,然后在新的项目中引入:如下:

v2-db2ef067df0466ed0498aa837544827c_b.jpg

在新项目中的启动类上加上:该注解:@EnablePrintRequestLog(targetPackage = "generator.test.demo") 并且指定拦截的包名称。

v2-ffa6c661d5043b78dfceacdfcdb7bb34_b.jpg

我们启动测试。

如上:我们定义了两个接口:

1、http://127.0.0.1:9999/test. 该接口在包引用者的包下:generator.test.demo

2、http://127.0.0.1:9999/t 该接口在本身的 jar 包内 :com.enable.demo

所以这时候应该只会有一个请求打印日志。

项目启动完毕。

v2-ece99befd5fd0a02d8dbe789b12a855e_b.jpg

1、请求:http://127.0.0.1:9999/test接口,应该打印请求日志:结果如下:

v2-70f2ce3fcd66616c489c6c63e25755b8_b.jpg

请求结果正确。

2、请求另外一个接口 http://127.0.0.1:9999/t :该接口不应该打印请求日志。

结果:正确

v2-8c48ef685d46536bab6841f748de02c0_b.jpg

3、重写预留的切面增强的方法

/**
 * <br>
 *
 * @author 永健
 * @since 2020-11-10 22:21
 */
@Component
public class YY extends DefaultInterceptor{
    @Override
    public void before(JoinPoint joinPoint){
        System.out.println("重写切面前置增强方法");
    }
}

看打印结果:

v2-1f9156f9586c4bd6caecdc82af769024_b.jpg

3、去除 @EnablePrintRequestLog注解测试。

此时没有打印日志:正确

v2-9a4d47fbf943f70f8e608c2fa66bb8e0_b.jpg

总而言之,言而总之。利用@Enbale***的原理,你也可以很轻松的打造一个低耦合的组件功能,给别人很简单明了的使用,即使你的功能错中复杂,但是对于使用者来说,越少配置越好,拿来即用。

比如:你可以包装一个 权限登陆组件。以后哪个项目中使用到了,注解引入 jar 包 @Enable注解打开就可以使用,多么简单容易,不用重复去写代码.......

你学会了吗??????


https://zhuanlan.zhihu.com/p/156099918​zhuanlan.zhihu.com
https://zhuanlan.zhihu.com/p/143793753​zhuanlan.zhihu.com
https://zhuanlan.zhihu.com/p/264938586​zhuanlan.zhihu.com

v2-654eb72da6e950dad8adf3b9403d0a0f_b.jpg
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值