注解是一种元数据, 注解方式减少了配置文件内容,更加便于管理,有利于提高开发效率
一、使注解生效
方式-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 的
作用域
- 设置 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
- 是否作用于本地方法
创建并使用自定义注解
前提:
创建
MyFilterAnnotation
和MyLogAnnotation
两种注解( 这里展示了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 { }
创建
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("开始学习......"); } }
操作步骤
- 自定义
MyBeanPostProcessor
实现BeanPostProcessor
接口, 定义一个后置处理器(在对象初始化后 包含代理逻辑)- 列出该bean拥有的注解, 校验让标注了
MyFilterAnnotation
注解的 Bean 对象通过代理对象的方式增强- 列出所有声明的方法, 判断是否有方法标注了
MyLogAnnotation
注解- 如果该 Bean 的方法上存在
MyLogAnnotation
注解,
在调用时能够在该方法前后记录日志, 此处通过 JDK动态代理/cglib 动态代理实现- 使用 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