现在基于spring开发都是使用注解方式,很少在去基于xml配置文件方式。到了springboot这种现象就更加突出了。估计对于大多数人来说(当然也包括我),有两个难点:
1)注解难点是注解太多了,根本记不住。这个问题其实很简单,那就是写博客,记笔记,用的查一下。
2)注解很多时候不如xml方式直观,比如说:spring事务的管理是基于AOP方式实现的,然而通过一个注解@Transactional就能实现,我们却不知道它的底层实现。这个问题并没有什么好的方式,只有查相关资料或者阅读源码了。
一、注解分类
注解有这么几类:用于创建对象,用于注入属性的,用于配置的文件的,用于业务的比如事务,还有一些开关类注解。下面就从这几点罗列一下
二、创建bean的注解
注解名称 | 属性 | 备注 |
@Component | value - 指定bean id,如果没有指定value,则bean id默认值为类名字,且首字母是小写 | 1)只能用于类上 |
@Controller | 同上 | 1)用于类上,代表表现层 2)@Component的派生注解 |
@Service | 同上 | 1)用于类上,代表业务层 2)@Component的派生注解 |
@Repository | 同上 | 1)用于类上,代表持久层 2)@Component的派生注解 |
@Configuration | value - 同@Component proxyBeanMethods - 默认是true,代表是否生成代理对象,用于代理@Bean标注的对象 | 1)只能用于类上,也生成bean对象注入到spring容器中 2)@Component的派生注解 |
@Bean | name:代表bean id,默认为当前方法名字initMethod:定义bean的初始化方法 destroyMethod:定义bean的销毁方法 autowireCandidate:默认值是true,表示当前bean可用于属性注入 | 1)用于方法上,将返回值作为bean对象存入spring ioc容器 |
@Import | value - 指定Class名 | 导入一个类,创建该bean对象注入到容器中 需要配合@Configuration @Component一起使用 |
@ImporResource | value - 指定配置文件名称 | 导入一个spring的xml配置文件, |
三、属性注入
上面是用于创建bean对象注解,接下来介绍属性注入(bean注入),属性分为基本类型,复杂类型(map,list,set)和自定义类类型。
注解名称 | 属性 | 备注 |
@Value | value - 表达式 支持SpEL表达是 @Value("${xxxx}") @Value("#{xxxx}") | 1)可注入,基本数据类型,数组,String类型,Date,map,list,set 2)通常需要配置文件properties文件,并且与@PropertySource一起使用 |
@PropertySource | value - 配置文件路径 | 1)加载配置文件,供@Value使用,例如: @PropertySource("classpath:jdbcConfig.properties") |
@Autowired | required - 默认值是true,如果找不到符合条件的bean则抛出异常 | 1)通过类型注入,可以用于构造函数,方法,类成员,方法参数 2)自动注册时,通过类型判断:
注意:spring容器底层采用map保存bean对象。 |
@Qualifier | Value 用于指定bean id | 1)通过名称进行注入 2)可用于类成员,方法,方法参数和类上 3)在修饰类成员变量时不能单独使用,必须与Autowired配合使用。 4)修饰方法形参的时候可单独使用 通过value指定bean id来解决Autowired场景下匹配出多个bean的问题 |
@Resource | Name用于指定bean id | 1)先按照名称查找,如果没有找到则按照类型查找 2)这个注解是javax提供的,并非spring原生注解 |
对于@Value注解,这里有一篇专门介绍:《Spring常用注解-@Value @ConfigurationProperties》
四、业务相关-AOP相关注解
AOP有三元组:切面、切点、通知,所以需要有对应的注解,具体如下:
4.1、切面类
说明 | 备注 | |
@EnableAspectJAutoProxy | 表示启用AOP功能 | 这个注解是必须的 |
@Aspect | 修饰一个类,表示当前类是一个切面类,实际是代理类对象 | 1)用于类上 2)需要导入aspectjweaver包 |
4.2、切点
属性 | 备注 | |
@Pointcut | execution,切入点表达式,对待增强方法的描述 | 举例说明: @Pointcut("execution(* com.example.service.AccountService.saveMoney(..))") public void myPointCut(){ } |
4.3、通知
通知有多种,各个通知的执行时机,可参考下面的例子
说明 | 备注 | |
@Before | 前置通知 | 这些通知,需要一个切入点来支持有两种方式 1)直接定义execution表达式 例如: @Before("execution(* com.example.service.AccountService.saveMoney(..))" ) 2)引用@Pointcut @Before("myPointCut()") |
@AfterReturning | 后置通知 | |
@AfterThrowing | 异常通知 | |
@After | 最终通知 | |
@Around | 环绕通知 |
4.4、AOP完全注解例子
1)创建被代理类和代理类
package com.example.service;
import org.springframework.stereotype.Component;
/**
* 通过@Componet注入一个bean
*
* 被代理类
*
*/
@Component
public class AccountService {
public int saveMoney(int money) {
System.out.println(" >>>> save money:"+money);
return money;
}
}
package com.example.proxy;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
/**
* 通过@Component注入到容器
* 通过@Aspect声明是一个切面,实际为代理类
*/
@Component
@Aspect
public class AccountServiceProxy {
//前置通知
@Before("execution(* com.example.service.AccountService.saveMoney(..))")
public void beforeMethod() {
System.out.println("2. advice before");
}
//后置通知
@AfterReturning("execution(* com.example.service.AccountService.saveMoney(..))")
public void afterReturning() {
System.out.println("3. advice afterReturning");
}
//最终通知
@After("execution(* com.example.service.AccountService.saveMoney(..))")
public void afterMethod() {
System.out.println("4. advice after");
}
//环绕通知 这个地方前后加了打印
@Around("execution(* com.example.service.AccountService.saveMoney(..))")
public Object aroundMethod(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("1. around advice before");
Object o = proceedingJoinPoint.proceed();
System.out.println("5. around advice after");
return o;
}
//异常通知
@AfterThrowing("execution(* com.example.service.AccountService.saveMoney(..))")
public void afterThrowing() {
System.out.println("3. advice afterThrowing...");
}
}
2)创建Main类并且启动用代理
/**
* 需要设置包扫描
* 开启Aspecj动态代理
*/
@ComponentScan("com.example")
@EnableAspectJAutoProxy
public class AopApp {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(AopApp.class);
AccountService accountService = context.getBean("accountService", AccountService.class);
accountService.saveMoney(10);
}
}
输出结果(不同spring版本可能不一样,下面输出结果版本:5.3.12):
# 没有异常情况下:环绕通知前,前置通知,后置通知,最终通知,环绕通知后
1. around advice before
2. advice before
>>>> save money:10
3. advice afterReturning
4. advice after
5. around advice after
有异常的情况下,修改方法以及输出:
@Component
public class AccountService {
public int saveMoney(int money) {
System.out.println(" >>>> save money:"+money);
int i = money / 0; //制造异常
return money;
}
}
# 有异常场景下,执行顺序:环绕通知前 前置通知 异常通知 最终通知
1. around advice before
2. advice before
>>>> save money:10
3. advice afterThrowing...
4. advice after
Exception in thread "main" java.lang.ArithmeticException: / by zero
通过测试结果可知:
1)环绕通知是最先执行的,接着是前置通知
2)无论是否有异常,最终通知一定会执行
3)至于后置通知和异常通知需要看具体情况
五、其他注解
属性 | 说明 | |
@ComponentScan @ComponentScans | 用于替换xml文件中context:component-scan标签 | |
@Import | 导入某个类,作为配置类使用 // Test 类 这里不需要任何注解(@Component、@Service)这些都不需要
当AppConfig类被处理的时候, 顺便就把Test类给注册成bean了。 | |
@ContextConfiguration | locations:指定xml文件的位置,加上classpath关键字表示类路径下 classes:指定注解类所在位置 | 用于单元测试,spring和juint整合 |
@Order | value代表执行顺序值,数字越小优先级越高,默认是最小优先级 | 用于指定bean执行顺序 比如:两个代理类,A和B,代理同一个类C,那么就可以用该注解指定A、B的顺序 |
@Scope | 属性value,用于指定bean的作用范围。具体取值为:singletion、prototype | 用于写到类上面,多例模式下,对象回收靠jvm垃圾回收期 |
@PreDestroy | 用于指定销毁方法,底层是通过BeanPostProcessors实现 具体是InitDestroyAnnotationBeanPostProcessor的before方法 | |
@PostConstruct | 用于指定初始化方法,底层是通过BeanPostProcessors实现 具体是InitDestroyAnnotationBeanPostProcessor的before方法 | |
@Primary | 标识主bean | 1)当一个类型在spring容器中存在多个bean对象,例如 @Bean public User user1() { return new User()} @Bean public User user2() { return new User()} 2)@Autowired进行注入,例如: @Autowired private User myUser; spring运行就会报错,可以通过@Primary解决 解决方式: @Bean @Primary public User user1() { return new User()} 当有冲突的时候用主Bean进行注入 |
六、举例说明
6.1、@Import
@Import 可以和接口ImportSelector或者ImportBeanDefinitionRegistrar接口一起作用,做一些定制化操作,这里import就是一个桥梁。
public class Teacher implements ImportBeanDefinitionRegistrar {
private String name = "teacher is english";
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) {
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
beanDefinition.setBeanClass(Student.class);
registry.registerBeanDefinition("student123", beanDefinition);
}
@Override
public String toString() {
return "Teacher{" +
"name='" + name + '\'' +
'}';
}
}
@Component
@Import(Teacher.class)
public class User {
}
注意通过Import注入了student,但是Teacher不会注入。 如果Teacher没有实现任何接口,就会注入Teacher
七、总结
Spring内容太多了,需要一点点积累,后续还要继续更新。