【Spring注解驱动】(二)AOP及一些扩展原理

2023-06-25,,

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注解驱动】(二)AOP及一些扩展原理.doc》

下载本文的Word格式文档,以方便收藏与打印。