注解是一种元数据, 注解方式减少了配置文件内容,更加便于管理,有利于提高开发效率

一、使注解生效

方式-1:在XML配置文件中使注解生效

  • 扫描某个包下的注解

    <context:component-scan base-package="com.dpl.annotation"/>

  • 扫描多个包下的注解

    <context:component-scan base-package="com.dpl.annotation.service, com.dpl.dao" />

  • 扫描包下的注解(带过滤效果)

    <context:component-scan base-package="com.dpl" resource-pattern="bean/*.class" />

方式-2:直接使用注解

配置注解 与 扫描注解, 直接写在配置类上

  • @ComponentScan
  • @Configuration

测试:

二、常用注解

1. 组件相关注解

  • @Component ( 普通Bean 使用 @Component)

    • @Controller
    • @Service
    • @Repository

2. @Bean 相关注解

  • @Bean ( 声明所修饰的方法的返回值成为一个Bean, 可以用于导入第三方包 )

    @Bean注解本身不直接提供这些功能,需要和这些注解组合使用

    • @Scope

      • 设置 Bean 的作用域
    • @Lazy

      • 延迟加载,表示这个 Bean 在使用的时候,才会去创建
      • @Lazy的使用位置

        • 修饰类
        • 修饰@Bean注解的方法
        • 修饰@Autowired注解或@Resource注解标注的属性
    • @DependsOn

      • 当前 Bean 依赖于 xxx 对象, 使标注@DependsOn的优先完成初始化
    • @Primary

      • 当注入多个相同类型的bean时,优先注入修饰有@Primary的
    • @Profile

      • 选择性的包含某些 Bean
      • 在项目开发和运行过程中,会有多种环境,不同的环境下有不同的实现方式,

        用“@Profile注解+资源配置文件”的方式去适配不同的配置

    • @Order

      • 设置 Bean 加载的优先级, 值越低优先级越高

@Component 和 @Bean的区别

  • 修饰的位置不同
  • 处理方式不同
  • @Bean 更灵活性、自定义性更强

    @Bean 可以和@Conditional、@ConditionalOnBean注解搭配使用

3. @Import 注解

  • @Import只能用在类上 ,@Import 通过快速导入的方式把实例加入Spring的IoC容器中
  • Bean注入IoC容器的方式有很多种,@Import注解就是其中一种,可以导入第三方包
  • 有三种用法

    • 填写多个class(数组方式)

      // 伪代码
      @Import({类名1.class, 类名2.class, ...})
      public class ImportDemo {
      }
    • ImportSelector方式

      // 1、创建Myclass类并实现ImportSelector接口
      public class MyClass implements ImportSelector {
          @Override
          public String[] selectImports(AnnotationMetadata annotationMetadata) {
              return new String[]{"com.xx.Test.TestDemo3"};
          }
      }
      // 2、使用@Import 传入Myclass类
      @Import({MyClass.class})
      public class Myclass{ }
    • mportBeanDefinitionRegistrar方式

      // 1. 自定义类实现 `ImportBeanDefinitionRegistrar` 重写注册方法
      public class ImportBeanDefinitionRegistrarTest implements ImportBeanDefinitionRegistrar{
          @Override
          public void registerBeanDefinitions(AnnotationMetadata amd, BeanDefinitionRegistry registry) {
              BeanDefinition beanDefinition = new GenericBeanDefinition();
              beanDefinition.setBeanClassName(TestBean.class.getName());
              MutablePropertyValues values = beanDefinition.getPropertyValues();
              values.addPropertyValue("id", 1);
              values.addPropertyValue("name", "ZhangSan");
              //这里注册自定义的 Bean
              registry.registerBeanDefinition("testBean", beanDefinition );
      
          }
      }
      
      // 2. 导入
      @Configuration
      @Import(ImportBeanDefinitionRegistrarTest.class) 
      public class ConfigurationTest {
      
      }
    • FactoryBean 方式导入

      FactoryBean 允许用户通过编码方式自定义对象,并注册到容器中.

      获取FactoryBean,其实是调用其getObject()方法返回的对象

      // 1. 自定义类实现 `FactoryBean` 接口并重写方法
      public class StudentFactoryBean implements FactoryBean {
          public Student getObject() throws Exception {
              return new Student("S1001","张三丰",20);
          }
          public Class<?> getObjectType() {
              return Student.class;
          }
      }
      
      // 2. @Import 导入组件
      @Configuration
      @Import({Student.class}) 
      public class FactoryBeanConfig {
      
          // 将自定义的工厂bean注入到容器中
          @Bean("studentFactoryBean")
          public StudentFactoryBean getStudentFactoryBean(){
              System.out.println("StudentFactoryBean被注入容器...");
              return new StudentFactoryBean();
          }
      }
      
      // 3. 测试
      public class FactoryBeanTest { 
          @Test
          public void test(){
              AnnotationConfigApplicationContext context = 
              new AnnotationConfigApplicationContext(FactoryBeanConfig.class);
              Object obj = context.getBean("studentFactoryBean");
              System.out.println(obj);
          }
      }

4. Bean 操作的相关注解

  • @Autowired 注解

    • 自动注入,将其标注在引用类型的属性字段上,会将其注入到IoC容器
    • 自动装配Bean的方式:byType、byName、constructor
    • @Autowired注解实现自动注入的过程

      • 1、IoC容器启动,自动装载 AutowiredAnnotationBeanPostProcessor(后置处理器)
      • 2、后置处理器装载后,容器扫描到属性上标注有 @Autowied、@Resource注解
      • 3、到IoC容器中自动查找到需要的 Bean,装配给对象的属性
  • @Qualifier 注解

    • @Qualifier + @Autowired,实现按指定名称注入对象的效果
  • @Resource 注解

    • 用来注入依赖对象, 根据名称注入
    • Spring 容器会遍历 Bean 中所有字段、方法,标注有 @Resource 注解的,都注入到 IoC
    • 不是 Spring 注解,由JDK提供的、在 javax 中定义的、遵循JSR-250规范的 Java注解
  • @Inject 注解

    • 由 JSR 330 提供,是 Java EE 的依赖注入方式
    • 作用和 @Autoware 注解一样,都是根据类型对其进行自动装配
    • 需要引入 javax.inject.jar 包(麻烦、较为少用)

@Resource 与 @Autowired 的区别

5. 其他注解

Spring AOP 注解

  • Spring 支持 AspectJ 的注解式切面编程,这个里面涉及到一些注解

    • @Aspect(用于声明一个切面)
    • @After(后置通知,修饰在方法上,表示在方法执行之后执行)
    • @Before(前置通知,修饰在方法上,表示在方法执行之前执行)
    • @Around(环绕通知,修饰在方法上,表示在方法执行之前和之后执行)
    • @PointCut(声明一个切点)
  • 使用 AOP 注解,需要在 Java 配置类中使用 @EnableAspectJAutoProxy注解

    开启 Spring 对 AspectJ 代理的支持

Spring MVC 注解

  • @EnableWebMvc(在配置类中开启Web MVC的配置支持)
  • @Controller(@Component的子注解,用于描述控制层组件)
  • @RequestMapping(用于映射Web请求,包括访问路径和参数)
  • @ResponseBody(支持将返回值放到response中)
  • @RequestBody(允许request的参数在request体中)
  • @PathVariable(用于接收路径参数)
  • @RestController(组合注解,相当于@Controller + @ResponseBody)

Spring 事务注解

使用 @Transactional 注解,可以开启声明式事务

Spring Enable 开启支持类型注解

  • @EnableAspectAutoProxy(开启对AspectJ自动代理的支持)
  • @EnableAsync(开启异步方法的支持)
  • @EnableScheduling(开启计划任务的支持)
  • @EnableConfigurationProperties(开启对此注解配置Bean的支持)
  • EnableJpaRepositories(开启对SpringData JPA Repository的支持)
  • @EnableTransactionManagement(开启注解式事务的支持)
  • @EnableCaching(开启注解式的缓存支持)
  • ......

三、自定义注解

自定义注解之元注解

  • @Target

    • 描述该注解作用在什么地方 ( 类上、方法上、接口上、等等... )
    • 具体值参见: ElementType
  • @Retention

    • 描述该注解在什么时候使用 ( 编译时、运行时、等等... )
    • 具体值参见: RetentionPolicy
  • @Documented

    • 标注该注解作用于 Javadoc
  • @Repeatable

    • 标注该注解可重复使用
  • @Inherited

    • 被 @Inherited 注解修饰的注解,如果作用于某个类上,其子类是可以继承的该注解的
  • @Native

    • 是否作用于本地方法

创建并使用自定义注解

前提:

  1. 创建 MyFilterAnnotationMyLogAnnotation 两种注解

    ( 这里展示了ElementType.TYPE 和 ElementType.METHOD 两种方式, 你也可以只用写一个注解及其校验逻辑 )

    @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    @Component
    // 该注解用于过滤出当前类的bean是否需要记录日志 (第一个分支条件)
    public @interface MyFilterAnnotation {
    
        boolean value() default true;
    
    }
    @Target({ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    // 该注解用于标记哪些方法需要记录日志, 作用于方法
    public @interface MyLogAnnotation {
    
    
    }
  2. 创建 BaseStudent 接口 和 其实现类 Student

    @MyFilterAnnotation
    public interface BaseStudent {
    
        @MyLogAnnotation
        void study();
    
    }
    @Data
    @MyFilterAnnotation
    public class Student implements BaseStudent{
    
        private Long id;
    
        private String name;
    
        private String sex;
    
        private Integer age;
    
        @MyLogAnnotation
        public void study() {
            System.out.println("开始学习......");
        }
    
    }

操作步骤

  1. 自定义 MyBeanPostProcessor 实现BeanPostProcessor接口, 定义一个后置处理器(在对象初始化后 包含代理逻辑)
  2. 列出该bean拥有的注解, 校验让标注了 MyFilterAnnotation 注解的 Bean 对象通过代理对象的方式增强
  3. 列出所有声明的方法, 判断是否有方法标注了 MyLogAnnotation 注解
  4. 如果该 Bean 的方法上存在 MyLogAnnotation 注解,
    在调用时能够在该方法前后记录日志, 此处通过 JDK动态代理/cglib 动态代理实现
  5. 使用 JDK动态代理/cglib 动态代理实现 的两种方法

完整代码如下:

package com.dpl.spring.annotation.custom;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

import lombok.SneakyThrows;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import org.springframework.stereotype.Component;

/**
 * <p>
 *
 * @author dpl
 * @since 2023/12/8 16:28
 */
@Component
public class MyBeanPostProcessor implements BeanPostProcessor {

    @SneakyThrows
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        Object instance = bean;
        // 自定义需求
        // 1. 列出该bean拥有的注解, 校验让标注了 `MyFilterAnnotation` 注解的 Bean 对象通过代理对象的方式增强
            if (bean.getClass().isAnnotationPresent(MyFilterAnnotation.class)) {
                // 2. 列出所有声明的方法, 判断是否有方法标注了 `MyLogAnnotation` 注解
                Method[] declaredMethods = bean.getClass().getDeclaredMethods();
                for (Method method : declaredMethods) {
                    if (method.isAnnotationPresent(MyLogAnnotation.class)) {
                        // 3. 如果该 Bean 的方法上存在 `MyLogAnnotation` 注解,
                        // 在调用时能够在该方法前后记录日志, 此处通过 JDK动态代理/cglib 动态代理实现

                        // Cglib 实现
                        instance = this.getInstanceByCglib(bean);

                        // Jdk 动态代理实现
                        //instance = this.getInstanceByJdk(bean);

                        break;
                    }
                }
            }
        return instance;
    }


    /**
     * 使用 Cglib方式创建代理对象
     * @param obj
     * @return
     */
    private Object getInstanceByCglib(Object obj) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(obj.getClass());
        enhancer.setCallback(new MethodInterceptor() {
            @Override
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy)
                    throws Throwable {

                // 调用方法前的逻辑
                System.out.println("开始记录日志.... Cglib");

                Object o1 = methodProxy.invokeSuper(o, objects);

                // 调用方法后的逻辑
                System.out.println("方法执行完成.... Cglib");

                return o1;
            }
        });
        return enhancer.create();
    }

    /**
     * 使用 Jdk方式创建代理对象
     * @param obj
     * @return
     */
    private Object getInstanceByJdk(Object obj) {

        InvocationHandler handler = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Object result = null;
                // 在方法执行前添加自定义逻辑
                System.out.println("开始记录日志...... Jdk");

                result = method.invoke(obj, args);

                // 在方法执行后添加自定义逻辑
                System.out.println("方法执行完成.... Jdk");
                return result;
            }
        };
        // 返回的对象id还是那个id,但是对象变了。
        // 通过动态代理获得代理对象
        return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(),handler);
    }
}

测试代码

/**
 * <p>
 *
 * @author dpl
 * @since 2023/12/8 11:13
 */
public class CustomTest {

    @Test
    public void test1() {
        ApplicationContext context = new AnnotationConfigApplicationContext(CustomConfiguration.class);

        for (String name : context.getBeanDefinitionNames()) {
            System.out.println(name);
        }

        BaseStudent bean = context.getBean(BaseStudent.class);
        bean.study();
    }
}

以下为测试结果:

org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
customConfiguration
myBeanPostProcessor
student
开始记录日志.... Cglib
开始学习......
方法执行完成.... Cglib

Process finished with exit code 0
最后修改:2023 年 12 月 13 日
如果觉得我的文章对你有用,请点个赞吧~