【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);
    }
}
7 SmartInitialingSingleton
【Spring注解驱动】(二)AOP及一些扩展原理的更多相关文章
- 【spring 注解驱动开发】扩展原理
		
尚学堂spring 注解驱动开发学习笔记之 - 扩展原理 扩展原理 1.扩展原理-BeanFactoryPostProcessor BeanFactoryPostProcessor * 扩展原理: * ...
 - 【Spring注解驱动开发】二狗子让我给他讲讲@EnableAspectJAutoProxy注解
		
写在前面 最近,二狗子入职了新公司,新入职的那几天确实有点飘.不过慢慢的,他发现他身边的人各个身怀绝技啊,有Spring源码的贡献者,有Dubbo源码的贡献者,有MyBatis源码的贡献者,还有研究A ...
 - Spring 注解驱动(二)Servlet 3.0 注解驱动在 Spring MVC 中的应用
		
Spring 注解驱动(二)Servlet 3.0 注解驱动在 Spring MVC 中的应用 Spring 系列目录(https://www.cnblogs.com/binarylei/p/1019 ...
 - 【spring 注解驱动开发】Spring AOP原理
		
尚学堂spring 注解驱动开发学习笔记之 - AOP原理 AOP原理: 1.AOP原理-AOP功能实现 2.AOP原理-@EnableAspectJAutoProxy 3.AOP原理-Annotat ...
 - 0、Spring 注解驱动开发
		
0.Spring注解驱动开发 0.1 简介 <Spring注解驱动开发>是一套帮助我们深入了解Spring原理机制的教程: 现今SpringBoot.SpringCloud技术非常火热,作 ...
 - 【Spring注解驱动开发】聊聊Spring注解驱动开发那些事儿!
		
写在前面 今天,面了一个工作5年的小伙伴,面试结果不理想啊!也不是我说,工作5年了,问多线程的知识:就只知道继承Thread类和实现Runnable接口!问Java集合,竟然说HashMap是线程安全 ...
 - 【spring 注解驱动开发】spring ioc 原理
		
尚学堂spring 注解驱动开发学习笔记之 - Spring容器创建 Spring容器创建 1.Spring容器创建-BeanFactory预准备 2.Spring容器创建-执行BeanFactory ...
 - 你真的知道Spring注解驱动的前世今生吗?这篇文章让你豁然开朗!
		
本篇文章,从Spring1.x到Spring 5.x的迭代中,站在现在的角度去思考Spring注解驱动的发展过程,这将有助于我们更好的理解Spring中的注解设计. Spring Framework ...
 - Spring 注解驱动(一)基本使用规则
		
Spring 注解驱动(一)基本使用规则 Spring 系列目录(https://www.cnblogs.com/binarylei/p/10198698.html) 一.基本使用 @Configur ...
 - 【Spring注解驱动开发】面试官:如何将Service注入到Servlet中?朋友又栽了!!
		
写在前面 最近,一位读者出去面试前准备了很久,信心满满的去面试.没想到面试官的一个问题把他难住了.面试官的问题是这样的:如何使用Spring将Service注入到Servlet中呢?这位读者平时也是很 ...
 
随机推荐
- 基于vue2.0创建vue项目
			
一.安装node环境 1.下载地址为:https://nodejs.org/en/ 2.检查是否安装成功:如果输出版本号,说明我们安装node环境成功 3.为了提高我们的效率,可以使用淘宝的镜像:ht ...
 - ES5及ES6的新增特性
			
介绍 es表示ECMASCript ,他是从es3,es5,es6,es5是2009.12月发布的,es6是2015.6月发布的.vue2完全支持es5的(vue3完全支持es6的),react完全支 ...
 - MMDetection中模型的Checkpoints下载
			
mmdetection中的模型checkpoints是需要自己手动下载的,下载步骤如下: 打开mmdetection, 进入configs目录,可以看到这里面有很多以目标检测模型命名的文件夹,选择你想 ...
 - [Leetcode 701]二叉搜索树BST中插入元素
			
题目 BST二叉搜索树中插入元素 二叉搜索树:左边<root<右边 https://leetcode.com/problems/insert-into-a-binary-search-tr ...
 - WCF使用post 提交
			
[OperationContract] [WebInvoke(RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFor ...
 -  NuGet国内镜像
			
NuGet国内镜像 https://nuget.cdn.azure.cn/v3/index.json
 - Windows10电源选项:睡眠、休眠、启用快速启动
			
参考链接: http://www.dnpz.net/diannaozhishi/2223.html http://www.cfan.com.cn/2018/0118/130151.shtml 在介绍w ...
 - NX二次开发读属性/表达式封装函数
			
int Read_ATTR_Type(int ObjTag, char* Attr_Title); //读取属性返回属性类型 string Read_ATTR_StringValue(int ObjT ...
 - shell多进程并发数控制
			
在批量执行任务时,单进程执行速度太慢,使用&不加数量控制,又担心资源占用过多,导致宕机等问题,因此我们需要控制并发进程的数量,保证效率的同时,保证资源占用不会太高. 其中一个解决思路是利用简单 ...
 - SQLServer游标(Cursor)简单例子
			
DECLARE @username nvarchar(50),@password nvarchar(50),@num int--声明游标变量 DECLARE myCursor CURSOR FOR s ...