【Spring注解驱动】(二)AOP及一些扩展原理
1 AOP动态代理简介及功能实现
1.1 简介
指在程序运行期间动态地将某段代码切入到指定方法的指定位置进行运行的方式。
1.2 功能实现测试
功能
:实现在业务逻辑运行的时候将日志打印
①导入aop模块:Spring aop
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>5.3.1</version> </dependency>
②创建一个业务逻辑类
MathCalculator.java
public class MathCalculator { public int div(int a, int b) { return a / b; } }
③创建一个切面类
,并添加通知注解
和@Aspect注解
,@Aspect用于告诉Spring容器这是一个切面类。
LogAspect.java
/** * 日志切面类 * */ @Aspect public class LogAspect { // 抽取公共的切入表达式 @Pointcut("execution(public int com.hikaru.aop.MathCalculator.div(int, int))") public void pointCut() {} @Before("pointCut()") public void logStart() { System.out.println("除法运行,参数列表是{}"); } @After("pointCut()") public void logEnd() { System.out.println("除法运行结束"); } @AfterReturning("pointCut()") public void logReturn() { System.out.println("除法正常返回,返回值是:"); } @AfterThrowing("pointCut()") public void logException() { System.out.println("除法异常,异常信息为:"); } }
④将切面类
和业务逻辑类
添加到容器
中
⑤添加@EnableAspectJAutoProxy注解
,开启基于注解的aop模式
。
MainConfigOfAop.java
@EnableAspectJAutoProxy @Configuration public class MainConfigOfAop { @Bean public MathCalculator mathCalculator() { return new MathCalculator(); } @Bean public LogAspect logAspect() { return new LogAspect(); } }
1.3 切面类的通知注解
注解 | 说明 | |
---|---|---|
前置通知 | @Before | 在目标方法执行之前执行注解的代码 |
后置通知 | @After | 在目标方法执行之后执行注解的代码 |
返回通知 | @AfterReturning | 在目标方法正常返回之后执行注解的代码 |
异常通知 | @AfterThrowing | 在目标方法发生异常之后执行注解的代码 |
环绕通知 | @Around | 动态代理,手动推进目标方法的执行 |
1.4 @PointCut切入点表达式提取
对公用的切入点表达式进行提取并添加到注解@PointCut
的value中,同一切面类中的切面方法想要使用只需在原切入点表达式书写注解所在的方法名
,其他切面类想要使用则需要书写方法的全路径名
即可。
// 抽取公共的切入表达式 @Pointcut("execution(public int com.hikaru.aop.MathCalculator.div(int, int))") public void pointCut() {}
同一、不同切面类的引用:
@Before("pointCut()") public void logStart() { System.out.println("除法运行,参数列表是{}"); } @After("com.hikaru.aop.LogAspect.pointCut()") public void logEnd() { System.out.println("除法运行结束"); }
1.5 JoinPoint获取切入点信息
1.5.1 JoinPoint.getSignature().getName()获取切入点函数名
1.5.2 JoinPoint.getArgs()获取切入点函数的参数
LogAspect.java
测试:
@Before("pointCut()") public void logStart(JoinPoint joinPoint) { System.out.println(joinPoint.getSignature().getName() + "运行,参数列表是" + Arrays.asList(joinPoint.getArgs())); }
结果:
div运行,参数列表是{}[1, 2]
1.5.3 接收切入点方法的返回值
在切面方法中使用一个参数接收返回值,并在注解中声明这个变量,让切面知道这个变量是用来接收返回值的。
@AfterReturning(value = "pointCut()", returning = "value") public void logReturn(Object value) { System.out.println("除法正常返回,返回值是:" + value); }
测试结果:
@AfterReturning(value = "pointCut()", returning = "value") public void logReturn(Object value) { System.out.println("除法正常返回,返回值是:" + value); }
1.5.4 接收切入点方法的异常
与1.5.3同理。
@AfterThrowing(value = "pointCut()", throwing = "exception") public void logException(Exception exception) { System.out.println("除法异常,异常信息为:" + exception); }
测试结果:
除法异常,异常信息为:java.lang.ArithmeticException: / by zero
2 AOP原理
2.1 @EnableAspectJAutoProxy原理
略了。。有空回来再看(一定
3 声明式事务
3.1 导入依赖
数据源
c3p0
<dependency> <groupId>com.mchange</groupId> <artifactId>c3p0</artifactId> <version>0.9.5.2</version> </dependency>
数据库驱动
mysql-connector-java
<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.16</version> </dependency>
spring jdbc模块
导入jdbc和tx,包括spring对jdbc以及事务操作的简化
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>${spring-version}</version> </dependency>
3.2 配置数据源
TxConfig.java
@Configuration @PropertySource(value = "classpath:jdbc.properties") public class TxConfig { @Value("${db.username}") private String username; @Value("${db.password}") private String password; @Value("${db.url}") private String url; @Value("${db.driver}") private String driver; @Bean public DataSource dataSource() throws PropertyVetoException { DruidDataSource dataSource = new DruidDataSource(); dataSource.setUsername(username); dataSource.setPassword(password); dataSource.setUrl(url); dataSource.setDriverClassName(driver); return dataSource; } @Bean public JdbcTemplate jdbcTemplate(DataSource dataSource) { return new JdbcTemplate(dataSource); } }
注意:
这里使用了@PropertySource注解引入了外部Property文件读入了环境变量,在使用环境变量的时候只能通过@Value注解的值"${}"的形式进行获取:
①其他地方是不能使用这种形式(也包括#{}的Spring表达式形式)的(我在下面方法内直接写的结果找了半个多小时错,从mysql-connector版本驱动到切换c3p0数据源到druid,c3p0一直在显示找不到驱动,而看到druid的错误信息找不到四个值才恍然大悟。。
②配置文件的值应该使用二级变量的形式,防止读入变量环境之后与已有的系统变量发生冲突
3.3 编写一个插入用户业务
UserDao
@Repository public class UserDao { @Autowired JdbcTemplate jdbcTemplate; public int insert(String name) { String sql = "insert into t_user(name) values(?)"; int result = jdbcTemplate.update(sql, name); return result; } }
UserService
public interface UserService { public int addUser(); }
UserServiceImpl
@Service public class UserServiceImpl implements UserService{ @Autowired UserDao userDao; @Override public int addUser() { String name = UUID.randomUUID().toString().substring(0, 5); return userDao.insert(name); } }
在配置类TxConfig
中开启注解扫描
@Configuration @ComponentScan(basePackages = "com.hikaru.tx") @PropertySource(value = "classpath:jdbc.properties") public class TxConfig { @Value("${db.username}") private String username; @Value("${db.password}") private String password; @Value("${db.url}") private String url; @Value("${db.driver}") private String driver; @Bean public DataSource dataSource() throws PropertyVetoException { DruidDataSource dataSource = new DruidDataSource(); dataSource.setUsername(username); dataSource.setPassword(password); dataSource.setUrl(url); dataSource.setDriverClassName(driver); return dataSource; } @Bean public JdbcTemplate jdbcTemplate(DataSource dataSource) { return new JdbcTemplate(dataSource); } }
测试
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = TxConfig.class) public class TxTest { @Autowired UserService userService; @Test public void test1() { int result = userService.addUser(); System.out.println(result); } }
存在的问题
:当插入完成的下一秒出现了异常的时候,程序终止但是插入仍能成功。
解决方案
:使用事务,出现异常会自动进行数据库回滚操作。
@Repository public class UserDao { @Autowired JdbcTemplate jdbcTemplate; public int insert(String name) { String sql = "insert into t_user(name) values(?)"; int result = jdbcTemplate.update(sql, name); System.out.println("插入完成"); int i = 10 / 0; return result; } }
3.4 对插入业务进行事务管理
①在持久层方法上标注@Transactional注解
,表明当前方法是一个事务方法
@Repository public class UserDao { @Autowired JdbcTemplate jdbcTemplate; @Transactional public int insert(String name) { String sql = "insert into t_user(name) values(?)"; int result = jdbcTemplate.update(sql, name); System.out.println("插入完成"); int i = 10 / 0; return result; } }
②在配置类开启事务扫描@EnableTransactionManagement
③注册事务管理器来控制事务
@Configuration @ComponentScan(basePackages = "com.hikaru.tx") @PropertySource(value = "classpath:jdbc.properties") @EnableTransactionManagement public class TxConfig { @Value("${db.username}") private String username; @Value("${db.password}") private String password; @Value("${db.url}") private String url; @Value("${db.driver}") private String driver; @Bean public DataSource dataSource() throws PropertyVetoException { DruidDataSource dataSource = new DruidDataSource(); dataSource.setUsername(username); dataSource.setPassword(password); dataSource.setUrl(url); dataSource.setDriverClassName(driver); return dataSource; } @Bean public JdbcTemplate jdbcTemplate(DataSource dataSource) { return new JdbcTemplate(dataSource); } @Bean public PlatformTransactionManager transactionManager(DataSource dataSource) { return new DataSourceTransactionManager(dataSource); } }
DataSourceTransactionManager
继承抽象类AbstractPlatformTransactionManager
,而抽象类实现了PlatformTransactionManager
测试结果显示出现异常事务进行了回滚。
3.5 声明式事务原理
同样先略过了。。
4 BeanFactoryProcessor
BeanPostProcessor
:bean后置处理器,bean创建对象初始化前后进行拦截工作。
BeanFactoryProcessor
:beanfactory的后置处理器,在BeanFactory标准初始化之后调用,所有的Bean定义已经保存加载到BeanFactory。
①首先自定义一个BeanFactoryPostProcessor的实现类
import java.util.Arrays; public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor { @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { System.out.println("MyBeanFactoryPostProcessor...postProcessBeanFactory"); int count = beanFactory.getBeanDefinitionCount(); System.out.println("BeanFactory中总共有" + count + "个Bean,分别是:"); String[] beanDefinitionNames = beanFactory.getBeanDefinitionNames(); System.out.println(Arrays.asList(beanDefinitionNames)); } }
②加入IOC容器
@Configuration public class ExtConfig { @Bean public Color color() { return new Color(); } @Bean public MyBeanFactoryPostProcessor beanFactoryPostProcessor() { return new MyBeanFactoryPostProcessor(); } }
③输出结果
MyBeanFactoryPostProcessor...postProcessBeanFactory BeanFactory中总共有8个Bean,分别是: [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, extConfig, color, beanFactoryPostProcessor]
5 BeanDefinitionRegistryPostProcessor
BeanDefinitionRegistryPostProcessor
:继承于BeanFactoryPostProcessor
。在所有的Bean信息即将被加载,Bean实例还没有被创建的时候执行其方法。因此,它在BeanFactoryPostProcessor执行前执行。 它包含两个实例方法:
1.postProcessBeanFactory:来自于父类BeanFactoryPostProcessor
2.postProcessBeanDefinitionRegistry:
①首先自定义一个BeanDefinitionRegistryPostProcessor的实现类
public class MyBeanDefinitionRegisterPostProcessor implements BeanDefinitionRegistryPostProcessor { @Override public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException { System.out.println("postProcessBeanDefinitionRegistry...bean的数量为:" + registry.getBeanDefinitionCount()); AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(Color.class).getBeanDefinition(); registry.registerBeanDefinition("color", beanDefinition); } @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { System.out.println("MyBeanDefinitionRegisterPostProcessor...bean的数量为:" + begetBeanDefinitionCountessorCount()); } }
②加入IOC容器
@Configuration public class ExtConfig { @Bean public BeanDefinitionRegistryPostProcessor registryPostProcessor() { return new MyBeanDefinitionRegisterPostProcessor(); } }
③测试结果
postProcessBeanDefinitionRegistry...bean的数量为:7 七月 18, 2022 4:11:51 下午 org.springframework.context.annotation.ConfigurationClassPostProcessor enhanceConfigurationClasses 信息: Cannot enhance @Configuration bean definition 'extConfig' since its singleton instance has been created too early. The typical cause is a non-static @Bean method with a BeanDefinitionRegistryPostProcessor return type: Consider declaring such methods as 'static'. MyBeanDefinitionRegisterPostProcessor...bean的数量为:8
6 ★ApplicationListener 应用监听器
Spring基于事件驱动开发的功能,监听器通过监听容器中发生的事件,当容器事件发发布就会触发监听器的回调。如下便是监听ApplicationEvent及其下面的子事件。
interface ApplicationListener<E extends ApplicationEvent> extends EventListener
①创建一个自定义ApplicationListener的实现类,实现类能够监听泛型ApplicationEvent。
public class MyApplicationListener implements ApplicationListener<ApplicationEvent> { @Override public void onApplicationEvent(ApplicationEvent applicationEvent) { System.out.println("收到事件:" + applicationEvent); } }
②将Myapplication加入IOC容器。
@Import(MyApplicationListener.class) @Configuration public class ExtConfig { @Bean public BeanDefinitionRegistryPostProcessor registryPostProcessor() { return new MyBeanDefinitionRegisterPostProcessor(); } }
③测试:当容器创建和关闭的时候,会触发监听器方法。
public class ExtTest { @Test public void test1() { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ExtConfig.class); context.close(); } }
测试结果:
收到事件:org.springframework.context.event.ContextRefreshedEvent[source=org.springframework.context.annotation.AnnotationConfigApplicationContext@1055e4af, started on Tue Jul 19 15:49:08 CST 2022] 收到事件:org.springframework.context.event.ContextClosedEvent[source=org.springframework.context.annotation.AnnotationConfigApplicationContext@1055e4af, started on Tue Jul 19 15:49:08 CST 2022]
如下图所示(快捷键F4),ContextCloseEvent容器关闭事件,ContextStatedEvent容器开始事件都是Application的继承实现类,因此在容器开启和关闭的时候会触发事件监听器。
6.1 自定义事件发布 context.publishEvent
public class ExtTest { @Test public void test1() { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ExtConfig.class); context.publishEvent(new ApplicationEvent(new String("我自定义的事件")){ }); context.close(); } }
输出结果
收到事件:org.springframework.context.event.ContextRefreshedEvent[source=org.springframework.context.annotation.AnnotationConfigApplicationContext@1055e4af, started on Tue Jul 19 16:06:51 CST 2022] 收到事件:com.hikaru.test.ExtTest$1[source=我自定义的事件] 收到事件:org.springframework.context.event.ContextClosedEvent[source=org.springframework.context.annotation.AnnotationConfigApplicationContext@1055e4af, started on Tue Jul 19 16:06:51 CST 2022]
7 通过@EventListener注解实现普通逻辑组件的事件监听
@Service public class UserServiceImpl implements UserService{ @Autowired UserDao userDao; @Override public int addUser() { String name = UUID.randomUUID().toString().substring(0, 5); return userDao.insert(name); } @EventListener(classes = {ApplicationEvent.class}) public void listen(ApplicationEvent event) { System.out.println("UserService 监听到的事件:" + enent); } }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步