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及一些扩展原理的更多相关文章

  1. 【spring 注解驱动开发】扩展原理

    尚学堂spring 注解驱动开发学习笔记之 - 扩展原理 扩展原理 1.扩展原理-BeanFactoryPostProcessor BeanFactoryPostProcessor * 扩展原理: * ...

  2. 【Spring注解驱动开发】二狗子让我给他讲讲@EnableAspectJAutoProxy注解

    写在前面 最近,二狗子入职了新公司,新入职的那几天确实有点飘.不过慢慢的,他发现他身边的人各个身怀绝技啊,有Spring源码的贡献者,有Dubbo源码的贡献者,有MyBatis源码的贡献者,还有研究A ...

  3. Spring 注解驱动(二)Servlet 3.0 注解驱动在 Spring MVC 中的应用

    Spring 注解驱动(二)Servlet 3.0 注解驱动在 Spring MVC 中的应用 Spring 系列目录(https://www.cnblogs.com/binarylei/p/1019 ...

  4. 【spring 注解驱动开发】Spring AOP原理

    尚学堂spring 注解驱动开发学习笔记之 - AOP原理 AOP原理: 1.AOP原理-AOP功能实现 2.AOP原理-@EnableAspectJAutoProxy 3.AOP原理-Annotat ...

  5. 0、Spring 注解驱动开发

    0.Spring注解驱动开发 0.1 简介 <Spring注解驱动开发>是一套帮助我们深入了解Spring原理机制的教程: 现今SpringBoot.SpringCloud技术非常火热,作 ...

  6. 【Spring注解驱动开发】聊聊Spring注解驱动开发那些事儿!

    写在前面 今天,面了一个工作5年的小伙伴,面试结果不理想啊!也不是我说,工作5年了,问多线程的知识:就只知道继承Thread类和实现Runnable接口!问Java集合,竟然说HashMap是线程安全 ...

  7. 【spring 注解驱动开发】spring ioc 原理

    尚学堂spring 注解驱动开发学习笔记之 - Spring容器创建 Spring容器创建 1.Spring容器创建-BeanFactory预准备 2.Spring容器创建-执行BeanFactory ...

  8. 你真的知道Spring注解驱动的前世今生吗?这篇文章让你豁然开朗!

    本篇文章,从Spring1.x到Spring 5.x的迭代中,站在现在的角度去思考Spring注解驱动的发展过程,这将有助于我们更好的理解Spring中的注解设计. Spring Framework ...

  9. Spring 注解驱动(一)基本使用规则

    Spring 注解驱动(一)基本使用规则 Spring 系列目录(https://www.cnblogs.com/binarylei/p/10198698.html) 一.基本使用 @Configur ...

  10. 【Spring注解驱动开发】面试官:如何将Service注入到Servlet中?朋友又栽了!!

    写在前面 最近,一位读者出去面试前准备了很久,信心满满的去面试.没想到面试官的一个问题把他难住了.面试官的问题是这样的:如何使用Spring将Service注入到Servlet中呢?这位读者平时也是很 ...

随机推荐

  1. OSIDP-并发:死锁和饥饿-06

    死锁原理 死锁:一组相互竞争系统资源或者进行通信的进程间"永久"阻塞的现象. 资源分为两类:可重用资源和可消耗资源. 可重用资源:一次只能被一个进程使用且不会被耗尽的资源.如处理器 ...

  2. win10启动和安装nacos服务

    https://blog.csdn.net/tbmingzhao/article/details/113276845

  3. Bad Request - Invalid Hostname HTTP Error 400. The request hostname is invalid.

    VS 调试 显示 如下错误 解决办法: 1 打开调试运行中的 IIS Express 2 点击其中一条记录 3  点击配置 4 找到对应项目站点 .添加 <binding protocol=&q ...

  4. Python MySQLdb连接被多线程共享引发的内核segfault段错误

    Python celery Worker exited prematurely: signal 11 (SIGSEGV) --一种解决方案 Python libmysqlclient segfault ...

  5. SQLServer数据库执行时间

    with kk AS( SELECT TOP 2000 total_worker_time/1000 AS [总消耗CPU 时间(ms)],execution_count [运行次数], qs.tot ...

  6. MySql5.7基础配置

    MySql5.7基础配置 [client] #设置mysql客户端的字符集 default-character-set=utf8 [mysqld] #设置mysql端口为3306 port = 330 ...

  7. 什么是js严格模式?

    (use strict)严格模式是一种将更好的错误检查引入代码中的方法. 在使用严格模式时,无法使用隐式声明的变量.将值赋给只读属性或将属性添加到不可扩展的对象等. 1. 严格模式的目的 1) 消除J ...

  8. 随笔:for in 和 for of的区别

    百度前端面试题:for in 和 for of的区别详解以及为for in的输出顺序 - 知乎 以该页面为例,我稍微总结一点东西: 在这⾥我们把对象中的数字属性称为 「排序属性」,在V8中被称为 el ...

  9. 网络安全(中职组)-B模块:Web隐藏信息获取

    Web隐藏信息获取任务环境说明:服务器场景名:web20200604服务器场景用户名:未知(关闭链接) 1.    通过本地PC中渗透测试平台Kali使用Nmap扫描目标靶机HTTP服务子目录,将扫描 ...

  10. DAMA数据管理知识体系指南-V1

    注:只摘抄了部分个人认为需要记录的笔记,如果想完整了解请看原文 中文版序 数据管理是把业务和信息技术融合起来所必须的一整套技术.方法及相应的管理和治理过程. 它的特殊定位决定了它涉及的知识体系面广且度 ...