【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中呢?这位读者平时也是很 ...
随机推荐
- CentOS7安装 Redis5 单实例
1.下载redis下载地址在:redis.io比如把Redis安装到/usr/local/soft/ cd /usr/local/soft/ wget http://download.redis.io ...
- Web_ServletContext主要方法
ServletContext:联系上下文,一个项目通用一个context,作用域:整个项目 用法:Servlet里面直接应用,tomcat帮我们自动创建. 获取ServletContext:getSe ...
- NOIP2014普及组
T2]比例简化 其实比较简单,主要是比较的方法以前没看过吧 要学会知识迁移啊! #include<iostream> #include<cstring> #include< ...
- 实验八-Web部署
进入华为云中购置的虚拟机 配置openEuler cd /etc/yum.repos.d vi openEuler_x86_64.repo 安装LAMP 在shell中 通过下面命令安装Apache: ...
- 转载·Charles4.2.8 开启macOS Proxy ,MacOS10.15 Catalina版本提示APP权限为只读
转载地址:https://superuser.com/questions/1490116/charles-4-2-8-cannot-configure-your-proxy-settings-whil ...
- Adaboost分类器
Adaboost分类器 2019-08-31 非集成的机器学习算法就像古代皇帝一样,一个人说了算:集成学习算法类似于现在的国会,需要听取在会所有人的意见. Adaboost是一个集成学习算法,下面将会 ...
- 第四章 快速排序 分而治之(divide an conquer)
def quicksort(array): if len(array) < 2: return array else: flag = array[0] less = [] greater = [ ...
- 鲁迅文集 第3卷 而已集 华盖集续编 华盖集 热风\四十一.md
目录 导读 正文 导读 本篇首次发表于1919年1月15日<新青年>第六卷第一号.署名唐俟. 文章以生物进化的事实,驳斥旧势力对改革者的嘲讽,号召青年蔑视反改革者的冷笑和暗箭,&quo ...
- python学习记录(一)-基础
交换变量值 a,b = 10,20 print(a,b) #10 20 a,b = b,a print(a,b) #20 10 大字符串 str = '''最近在看的动漫: 黑之契约者.咒术回战... ...
- Nginx配置ThinkPHP3.1的PATHINFO模式
location / { if (!-e $request_filename) { rewrite ^/(.*)$ /index.php?$1 last; bre ...