# Spring注解 **Repository Path**: vshdi/spring_annotations ## Basic Information - **Project Name**: Spring注解 - **Description**: 通过Spring注解,来了解SpringBoot的自动配置原理 - **Primary Language**: Java - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 3 - **Created**: 2020-08-14 - **Last Updated**: 2022-01-18 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README ![](https://fp1.fghrsh.net/2020/08/21/823b4308c56ba7dbe0de5ddb5a325493.png) # 组件注册 [![841b12239be34a006b402f4502c819e8.png](http://fp1.fghrsh.net/2020/08/21/841b12239be34a006b402f4502c819e8.png)](https://img.fghrsh.net/image/3pREc) ## **给容器注册组件的四种方法** `容器会默认调用组件的无参构造器放在一个对象在IoC容器中` 方式一:自己编写的类 加入了@Controller、@Service、@Repository、@Component注解的类 方式二:通过@Bean注解导入第三方类 方式三:通过@Import直接导入第三方类(默认在Ioc容器内的名称就是全类名(包名+类名)) 方式四:使用Spring提供的FactoryBean(工厂Bean) ## @Bean ### 组件的生命周期 构造 => 初始化前 => **初始化** => 初始化后 => **销毁** **初始化:** ​ 对象创建成功,并赋值好,调用初始化方法 **销毁:** ​ Singleton 单实例:容器关闭的时候 ​ Properties 实例模式:容器不会管理这个bean,容器不会调用销毁方法 ### 指定初始化销毁 #### 1、指定初始化(赋值之后) 和 销毁 的回调函数 ​ @Bean(initMethod = "init",destroyMethod = "destory") > 创建实体类的时候自己构建初始化、销毁的方法 > > 然后使用@Bean(initMethod = "init",destroyMethod = "destory")来调用 ```java public class HelloWorld { String hello="Hello demo"; public HelloWorld() { System.out.println("world constructor..."); } //初始化方法 public void init(){ System.out.println("通过@Bean指定init-method进行初始化。。。"); } //销毁方法 public void destory(){ System.out.println("通过@Bean指定destroy-method进行销毁。。。"); } @Override public String toString() { return "HelloWorld{" + "hello='" + hello + '\'' + '}'; } } ``` 调用初始化、销毁方法 ```java @Bean(initMethod = "init",destroyMethod = "destory") public HelloWorld helloWorld(){ return new HelloWorld(); } ``` #### 2、实现接口的方式(InitializingBean, DisposableBean)自定义初始化、销毁逻辑 ```java @Component public class Boy implements InitializingBean, DisposableBean { public Boy() { System.out.println("boy constructor..."); } @Override public void destroy() throws Exception { System.out.println("实现InitializingBean接口进行销毁。。。"); } @Override public void afterPropertiesSet() throws Exception { System.out.println("实现DisposableBean接口进行初始化。。。"); } } ``` `使用的时候,扫描所在包即可` ``` @ComponentScan({"com.spring.lifecycle.config","com.spring.lifecycle.bean"}) ``` #### 3、可以使用JSR250 ​ @PostConstruct,在bean创建成功并且属性复制成功,来执行初始化方法 ​ @PreDestory,在容器销毁bean之前通知我们进行清理工作 ```java @Component public class Girl { public Girl() { System.out.println("girl constructor..."); } @PostConstruct public void init(){ System.out.println("注解@PostConstruct初始化bean"); } @PreDestroy public void destory(){ System.out.println("注解@PreDestroy销毁bean"); } } ``` `使用的时候,扫描组件所在包即可` #### 4、实现接口 BeanPostProcessor ```java @Component public class DemoInit3 implements BeanPostProcessor { public Object postProcessBeforeInitialization(Object o, String s) throws BeansException { System.out.println(o+"BeforeInitialization=>"+s);//打印bean初始化前的全类名和名称 return o; } public Object postProcessAfterInitialization(Object o, String s) throws BeansException { System.out.println(o+"AfterInitialization=>"+s);//打印bean初始化前的全类名和名称 return o; } } ``` `使用的时候,扫描组件所在包即可` **BeanPostProcessor的原理** ### @Scope作用域 **种类** > singleton 单例模式**(默认)** > > prototype 原型模式 > > request 请求域 > > session 会话域 > > 这两个一般不会使用(一般放在请求域中或者session域中) #### singleton|单例模型 `特点`**单例模式组件 在容器创建后的时候直接创建bean加入到ioc容器,** **之后每次调用都使用同一个bean** **单例组件** ```java @Configuration public class SingletonConfig { @Bean public Teacher singleton(){ System.out.println("bean加入了ioc容器"); return new Teacher(); } } ``` #### prototype|原型模型 `特点`:**原型模式下的组件,在IoC容器创建的时候不调用,** **而是在每一次调用时获取bean的时候创建bean加入ioc容器** #### @Lazy|懒加载 **针对单例模式** ```java @Bean @Lazy public Teacher singleton(){ System.out.println("bean加入了ioc容器"); return new Teacher(); } ``` 在容器创建完后不会创建bean, 只是在第一次获取bean的时候创建bean 后面获取的时候不会再创建bean ### @Condition限制组件的创建 **满足指定条件的时候才将某个 bean 加载到应用上下文中** 可以给方法注解,也可以给整个类注解 #### 条件类 ```java public class DemoCondition implements Condition { @Override public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) { //获取ioc容器使用的beanFactory ConfigurableListableBeanFactory beanFactory = conditionContext.getBeanFactory(); //获取类加载器 ClassLoader classLoader = conditionContext.getClassLoader(); //获取当前环境(java运行环境所在的环境) Environment environment = conditionContext.getEnvironment();// //获取定义bean的注册类 BeanDefinitionRegistry registry = conditionContext.getRegistry(); //获取资源加载器 ResourceLoader resourceLoader = conditionContext.getResourceLoader(); String property = environment.getProperty("os.name"); if (property.contains("Windows 10")){ System.out.println("输出信息:==>"+property); return true; } //默认返回false,前面通过获取到的信息进行if条件判断,符合则return true return false; } ``` #### **使用方法** 给想要判断的方法 或者 类加上条件注解@Conditional ```java @Configuration public class Config { // @Scope("prototype") @Conditional({DemoCondition.class}) @Bean("hello") public HelloWorld hello() { System.out.println("bean加入ioc容器"); return new HelloWorld(); } } ``` 符合条件的组件才会创建 ## @ComponentScan包扫描 包扫描:默认装载**@Controller、@Service、@Repository、@Component**注解的配置类 ```java //配置类==配置文件 @Configuration //告诉Spring这是一个配置类 @ComponentScan(value = "com.spring.annotation") public class BeanConfig { @Bean(name = "personOfConfig") //给容器中注册一个Bean类型为返回值的类型,id默认是用方法名作为id public Person person(){ return new Person("lisi",20); } } ``` ### 扫描规则 > 希望排查那些包,包含哪些包 ```java /** * 扫描规则 * @ComponentScan value:指定要扫描的包 * excludeFilters = Filter[],指定扫描的时候按照什么规则排除那些组件 * includeFilters = Filter[],指定扫描的时候按照什么规则包含那些组件 注意:需要禁用默认的过滤规则:useDefaultFilters = false */ @ComponentScan(value = "com.spring.annotation",excludeFilters = { @ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {Controller.class, Service.class}) }) @ComponentScan(value = "com.spring.annotation",includeFilters = { @ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {Controller.class, Service.class}) },useDefaultFilters = false) ``` **其他扫描规则** > FilterType.ANNOTATION 是按照注解的方式 过滤**(默认的方式)** > FilterType.ASPECTJ 按照ASPECTJ 表达式(一般不会使用) > FilterType.REGEX 按照正则表达式 > FilterType.ASSIGNABLE_TYPE 按照指定的类型 > FilterType.CUSTOM 按照自定义规则 ### **命名多个扫描规则** ```java /** * @ComponentScan 因为 @Repeatable 可以命名多个扫描规则 * 也可以使用@ComponentScans命名多个@ComponentScan */ @ComponentScans(value = { @ComponentScan(value = "com.spring.annotation",excludeFilters = { @ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {Controller.class, Service.class}) }), @ComponentScan(value = "com.spring.annotation",includeFilters = { @ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {Service.class}) },useDefaultFilters = false) }) ``` ### **自定义的扫描规则** 如果使用自定义的规则,则需要编写java类并且实现TypeFilter接口的match方法@Filter中的classes指定自定义的实现类 ```java import org.springframework.core.io.Resource; import org.springframework.core.type.AnnotationMetadata; import org.springframework.core.type.ClassMetadata; import org.springframework.core.type.classreading.MetadataReader; import org.springframework.core.type.classreading.MetadataReaderFactory; import org.springframework.core.type.filter.TypeFilter; import java.io.IOException; public class MyFilterType implements TypeFilter { @Override public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException { AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata(); ClassMetadata classMetadata = metadataReader.getClassMetadata(); Resource resource = metadataReader.getResource(); String className = classMetadata.getClassName(); System.out.println("=====>"+className); if (className.contains("Controller")){//扫描包下面的类中 类名包含Controller则匹配成功,同理可以改成其它 return true; } return false; } } ``` 使用 ```java @ComponentScan(value = "bean", includeFilters = { @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {MyTypeFilter.class}) },useDefaultFilters = false) ``` ## @Import导入第三方类 **总结** - 导入生成组件id默认是全类名(com.spring.annotation.bean.Red) - ImportBeanDefinitionRegistrar中导入的组件名可以自定义 ### @Import的三种使用方式 #### **直接@Import** 原先通过@Bean注解导入 现在直接通过@Import导入 ```java @Configuration @Import(Green.class) public class ImportConfig { } ``` **Green.class** ```java public class Green { public Green() { super(); System.out.println("import 绿色成功"); } } ``` #### **实现ImportSelector接口** 场景:一次性导入多个组件 ```java public class DemoImportSelector implements ImportSelector { @Override public String[] selectImports(AnnotationMetadata annotationMetadata) { return new String[]{"com.spring.annotation.bean.Blue","com.spring.annotation.bean.Red"}; } } ``` 使用 ```java @Configuration @Import({Green.class, DemoImportSelector.class}) public class ImportConfig { } ``` #### 实现ImportBeanDefinitionRegistrar接口 场景:添加组件前设置条件 ```java public class DemoImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar { @Override public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) { boolean b1 = beanDefinitionRegistry.containsBeanDefinition("com.spring.annotation.bean.Blue"); boolean b2 = beanDefinitionRegistry.containsBeanDefinition("com.spring.annotation.bean.Red"); boolean b3 = beanDefinitionRegistry.containsBeanDefinition("com.spring.annotation.bean.Green"); if (b1 && b2 && b3){ //组件1和组件2和组件3都存在则添加新组件 RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(Rainbow.class); beanDefinitionRegistry.registerBeanDefinition("nameByYourself",rootBeanDefinition); } } } ``` ## FactoryBean bean工厂 `IoC容器会调用FactoryBean的getObject方法,把泛型的对象返回到容器中` **注意:** 1. 通过getBean("demoFactoryBean")方法获取的实际类型不是工厂类,而是传入的泛型 2. 要获取工厂bean本身需要加"&"。例如getBean("&demoFactoryBean") 1)实现FactoryBean接口 ```java // 注意:传入泛型为Bean返回的类型 public class DemoFactoryBean implements FactoryBean { //返回一个Color对象,这个对象会添加到IOC容器中 @Override public Color getObject() throws Exception { return new Color(); } // @Override public Class getObjectType() { return Color.class; } //判断是否是单实例? //true这个bean是单例的,在IOC容器中保存一份 //false 多实例的,每次获取都会创建一个新的bean @Override public boolean isSingleton() { return true;//单实例 } } ``` 2)通过@Bean注解 加入到spring的ioc容器中 ```java @Configuration public class BeanFactoryConfig { @Bean public DemoFactoryBean demoFactoryBean(){ return new DemoFactoryBean(); } } ``` ![](https://fp1.fghrsh.net/2020/08/21/749dc519d8dcdfcda06dcbbebc2b9146.png) # 组件赋值 ## 加载外部配置文件、赋值 ### @Value - 支持直接赋值 - 支持spel表达式 - 支持从配置文件读取值 ### @PropertySource 加载外部配置文件 **配置文件 **appllication.properties ```properties boy.home=boy-home ``` **实体类** Boy.class ```java @Component public class Boy implements InitializingBean, DisposableBean { @Value("张三")//直接赋值 private String name; @Value("#{20-2}")//spel表达式 private Integer age; @Value("${boy.home}")//从配置文件读取值 private String home; public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public String getHome() { return home; } public void setHome(String home) { this.home = home; } public Boy(String name, Integer age, String home) { this.name = name; this.age = age; this.home = home; } @Override public String toString() { return "Boy{" + "name='" + name + '\'' + ", age=" + age + ", home='" + home + '\'' + '}'; } public Boy() { System.out.println("boy constructor..."); } @Override public void destroy() throws Exception { System.out.println("实现InitializingBean接口进行销毁。。。"); } @Override public void afterPropertiesSet() throws Exception { System.out.println("实现DisposableBean接口进行初始化。。。"); } } ``` ## @Profile|环境切换 用于切换配置环境 可以标注在方法上(bean上),也可以标注在配置类上 ### 方法(bean)上 **参数代表的环境的 \*id\*, 当参数为 \*default\* 时,默认此配置生效** #### 配置类 ```java @Configuration @PropertySource("classpath:/db.properties") public class MainConfigOfProfile implements EmbeddedValueResolverAware { @Value("${db.user}") private String user; private String driverClass; /** * 测试环境 */ @Profile("test") @Bean("testDataSource") public DataSource dataSourceTest(@Value("${db.password}") String pwd) throws Exception { ComboPooledDataSource dataSource = new ComboPooledDataSource(); dataSource.setUser(user); dataSource.setPassword(pwd); dataSource.setJdbcUrl("jdbc:mysql://locaohost:3306/test"); dataSource.setDriverClass(driverClass); return dataSource; } /** * 开发环境 */ @Profile("dev") @Bean("devDataSource") public DataSource dataSourceDev(@Value("${db.password}") String pwd) throws Exception { ComboPooledDataSource dataSource = new ComboPooledDataSource(); dataSource.setUser(user); dataSource.setPassword(pwd); dataSource.setJdbcUrl("jdbc:mysql://locahost:3306/dev"); dataSource.setDriverClass(driverClass); return dataSource; } /** * 生产环境 */ @Profile("pro") @Bean("proDataSource") public DataSource dataSourcePro(@Value("${db.password}") String pwd) throws Exception { ComboPooledDataSource dataSource = new ComboPooledDataSource(); dataSource.setUser(user); dataSource.setPassword(pwd); dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/pro"); dataSource.setDriverClass(driverClass); return dataSource; } @Override public void setEmbeddedValueResolver(StringValueResolver resolver) { driverClass = resolver.resolveStringValue("${db.driver}"); } } ``` #### 测试 命令行参数 ```JAVA -Dspring.profiles.active=test ``` 测试类 ```java package com.lymboy.spring.annotation; public class IOCTest_Profile { private AnnotationConfigApplicationContext applicationContext; @Test public void test00() { applicationContext = new AnnotationConfigApplicationContext(MainConfigOfProfile.class); printNames(); applicationContext.close(); } public void printNames() { String[] names = applicationContext.getBeanNamesForType(DataSource.class); for (String name : names) { System.out.println(name); } } } ``` 代码测试 ```java @Test public void test01() { applicationContext = new AnnotationConfigApplicationContext(); applicationContext.getEnvironment().setActiveProfiles("dev", "test"); applicationContext.register(MainConfigOfProfile.class); applicationContext.refresh(); printNames(); applicationContext.close(); } ``` ### **配置类上** **当标注类上时, 只有当配置类生效时, 此类中的所有的配置才会生效** ![](https://fp1.fghrsh.net/2020/08/21/c2114515166779cf7a39c7f8c8e22a76.png) # 组件注入 ## Spring常用的三种注入方式 构造方法注入,setter注入,基于注解的注入。 ## @Autowired|自动注入 `自动注入:Spring利用依赖注入(DI),完成IoC容器中各个组件的依赖关系赋值` autowire主要有三个属性值:constructor,byName,byType。 - onstructor:通过构造方法进行自动注入,spring会匹配与构造方法参数类型一致的bean进行注入,如果有一个多参数的构造方法,一个只有一个参数的构造方法,在容器中查找到多个匹配多参数构造方法的bean,那么spring会优先将bean注入到多参数的构造方法中。 - byName:被注入bean的id名必须与set方法后半截匹配,并且id名称的第一个单词首字母必须小写,这一点与手动set注入有点不同。 - byType:查找所有的set方法,将符合符合参数类型的bean注入。 ### 运行规则 优**先按照类型**查找组件 ​ 类似效果: applicationContext.getBean(BookDao.class); 相同类型组件,再根据**组件id**查找 ​ 类似效果: applicationContext.getBean("bookDao"); ### @Autowired使用位置 > @Autowired:构造器、参数、方法、属性;都是从容器中获取参数组件的值 #### 标注在方法位置 @Bean+方法参数,参数从容器中获取,默认不用在参数写@Autowired,自动装配 #### 标注在构造器 如果组件只有一个有参构造器,则@Autowired可以省略,参数位置的组件从容器中获取 #### 参数位置 ### @Qualifier|指定注入组件id **有多个组件,指定需要装配的组件id** ```java @Qualifier("boyService") //指定bean的id private BoyService boyService2; //将已有的Service类装配到Controller ``` ### @Autowired(required = false)|组件可不注入 **组件不一定存在,设置不一定注入,防止报错(默认一定要将属性赋值好)** ```java @Autowired(required = false) @Qualifier("boyService3") //指定bean的id private BoyService boyService2; //将已有的Service类装配到Controller ``` ### @Primary|首选bean **多个同类型组件,首选一个bean** ```java @Bean("boyService2") @Primary //首选 public BoyService boyService(){ BoyService boyService = new BoyService(); boyService.setLable("2"); return boyService; } ``` `注意:使用@Primary需要关闭@Qualifier` ## 其他自动注入方式 ### @Resource 可以和@Autowired一样实现自动装配功能, 默认是按照组件名称进行装配 没有能支持@Primary功能、没有支持@Autowired(reqiured=false) **用法:** **与@Autowired相似, 参数 [\*name\* = \*id\*], 当使用name参数时, 其功能与 \*@Qualifier\* 相似, 也是按 \*id\* 匹配** ### @Inject 需要导入javax.inject的包,和Autowired的功能一样 没有@Autowired(reqiured=false)功能 ### 区别 @Autowired:Spring定义的, @Resource、@Inject都是java的规范 > AutowiredAnnotationBeanPostProcessor:解析完成自动装配功能 # AOP 面向切面编程 ## 一、什么是AOP **动态代理 :** 指在程序运行期间动态地将某段代码切入带指定位置进行运行的编程方式 ### 相关概念 - ***Aspects***: 切面, 通常是一个类, 即所谓的切面类,切面类中定义了切入点和通知 - ***JointPoint***: 连接点, 执行的方法(需要通知的方法), 即下面的 ***div()***方法 - ***Advice***: 通知, 即什么(类型)通知, *eg: @Before, @After, @AfterReturning…* - ***Pointcut***: 切入点, 对连接点进行拦截的定义, *即下面的 logStart(), logEnd()* 等等 ### 使用步骤 1. 导入 *aspects* 模块 2. 定义业务逻辑类, 定义一个实验方法 3. 定义一个切面类 4. 定义相关通知和切入点 通知方法 - 前置通知: 在目标方法(切点) 运行之前运行 - 后置通知: 在目标方法(切点) 运行之后运行 - 返回通知: 在目标方法(切点) 正常返回之后运行 - 异常通知: 在目标方法(切点) 运行出现异常之后运行 - 环绕通知: 动态代理, 目标方法运行前后运行 5. 将切面和业务逻辑类交由容器管理 6. 给切面类添加 *@Aspect* 注解, 指明其是切面类 7. 在配置类中添加 *@EnableAspectJAutoProxy* 注解, 开启注解版的 AOP 功能 ### 要点 - 通知(*@Before(), @After()*) 中填入的是切点表达式: *@Before(“execution(public int com.lymboy.spring.annotation.aop.MathCalculator.\*(…))”)* - 为了避免在通知中重复写相同的切点表达式可以使用 *@PointCut* 注解 ## 二、使用注解实现Spring AOP 首先是业务逻辑 ```java public class MathCalculator { public int div(int i,int j){ System.out.println("MathCalculator...div..."); return i/j; } } ``` 然后是切面类 ```java /** * 切面类 * * @Aspect: 告诉Spring当前类是一个切面类 * */ @Aspect public class LogAspects { //抽取公共的切入点表达式 //1、本类引用 //2、其他的切面引用 @Pointcut("execution(public int com.atguigu.aop.MathCalculator.*(..))") public void pointCut(){}; //@Before在目标方法之前切入;切入点表达式(指定在哪个方法切入) @Before("pointCut()") public void logStart(JoinPoint joinPoint){ Object[] args = joinPoint.getArgs(); System.out.println(""+joinPoint.getSignature().getName()+"运行。。。@Before:参数列表是:{"+Arrays.asList(args)+"}"); } @After("com.atguigu.aop.LogAspects.pointCut()") public void logEnd(JoinPoint joinPoint){ System.out.println(""+joinPoint.getSignature().getName()+"结束。。。@After"); } //JoinPoint一定要出现在参数表的第一位 @AfterReturning(value="pointCut()",returning="result") public void logReturn(JoinPoint joinPoint,Object result){ System.out.println(""+joinPoint.getSignature().getName()+"正常返回。。。@AfterReturning:运行结果:{"+result+"}"); } @AfterThrowing(value="pointCut()",throwing="exception") public void logException(JoinPoint joinPoint,Exception exception){ System.out.println(""+joinPoint.getSignature().getName()+"异常。。。异常信息:{"+exception+"}"); } /*@Around(value="pointCut()") public void myAround(ProceedingJoinPoint joinPoint) { // 方法之前,前置通知 System.out.println("《方法之前:前置通知"); try { // 方法执行时 joinPoint.proceed(); // 执行方法 // 方法执行之后:后置通知 System.out.println("《方法执行之后:后置通知"); } catch (Throwable e) { // 发生异常时 System.out.println("《发生异常时:异常通知"); } finally { // 最终通知 System.out.println("《最终通知"); } }*/ } ``` 接着是配置类 ```java /** * AOP:【动态代理】 * 指在程序运行期间动态的将某段代码切入到指定方法指定位置进行运行的编程方式; * * 1、导入aop模块;Spring AOP:(spring-aspects) * 2、定义一个业务逻辑类(MathCalculator);在业务逻辑运行的时候将日志进行打印(方法之前、方法运行结束、方法出现异常,xxx) * 3、定义一个日志切面类(LogAspects):切面类里面的方法需要动态感知MathCalculator.div运行到哪里然后执行; * 通知方法: * 前置通知(@Before):logStart:在目标方法(div)运行之前运行 * 后置通知(@After):logEnd:在目标方法(div)运行结束之后运行(无论方法正常结束还是异常结束) * 返回通知(@AfterReturning):logReturn:在目标方法(div)正常返回之后运行 * 异常通知(@AfterThrowing):logException:在目标方法(div)出现异常以后运行 * 环绕通知(@Around):动态代理,手动推进目标方法运行(joinPoint.procced()) * 4、给切面类的目标方法标注何时何地运行(通知注解); * 5、将切面类和业务逻辑类(目标方法所在类)都加入到容器中; * 6、必须告诉Spring哪个类是切面类(给切面类上加一个注解:@Aspect) * [7]、给配置类中加 @EnableAspectJAutoProxy 【开启基于注解的aop模式】 * 在Spring中很多的 @EnableXXX; * * 三步: * 1)、将业务逻辑组件和切面类都加入到容器中;告诉Spring哪个是切面类(@Aspect) * 2)、在切面类上的每一个通知方法上标注通知注解,告诉Spring何时何地运行(切入点表达式) * 3)、开启基于注解的aop模式;@EnableAspectJAutoProxy */ @EnableAspectJAutoProxy @Configuration public class MainConfigOfAOP { //业务逻辑类加入容器中 @Bean public MathCalculator calculator(){ return new MathCalculator(); } //切面类加入到容器中 @Bean public LogAspects logAspects(){ return new LogAspects(); } } ``` 最后是测试类 ```java public class IOCTest_AOP { @Test public void test01(){ AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfigOfAOP.class); //1、不要自己创建对象 // MathCalculator mathCalculator = new MathCalculator(); // mathCalculator.div(1, 1); MathCalculator mathCalculator = applicationContext.getBean(MathCalculator.class); mathCalculator.div(1, 1); applicationContext.close(); } } ```