AOP核心概念
(Aspect Oriented Programming)面向切面编程,在不改原有代码的前提下对其进行增强 — 代理模式
代理(Proxy):SpringAOP的核心本质是采用代理模式实现的
连接点(JoinPoint):在SpringAOP中,理解为任意方法的执行
切入点(Pointcut):匹配连接点的式子,也是具有共性功能的方法描述
通知(Advice):若干个方法的共性功能,在切入点处执行,最终体现为一个方法
切面(Aspect):描述通知与切入点的对应关系
目标对象(Target):被代理的原始对象成为目标对象
通知类:定义通知的类
入门案例 环境准备
创建项目
添加spring依赖
添加Dao&DaoImpl类
spring的配置类
编写app运行类
新增依赖 <dependency > <groupId > org.aspectj</groupId > <artifactId > aspectjweaver</artifactId > <version > 1.9.4</version > </dependency >
定义通知类和通知 —> 定义切入点 —> 制作切面 —> 将通知类配给容器并标识其为切面类 @Component @Aspect public class MyAdvice { @Pointcut("execution(void com.itheima.dao.BookDao.update())") private void pt () {} @Before("pt()") public void method () { System.out.println(System.currentTimeMillis()); } }
开启注解格式AOP功能 @Configuration @ComponentScan("com.itheima") @EnableAspectJAutoProxy public class SpringConfig {}
运行程序 public class App { public static void main (String[] args) { ApplicationContext ctx = new AnnotationConfigApplicationContext (SpringConfig.class); BookDao bookDao = ctx.getBean(BookDao.class); **bookDao.update();** } }
AOP工作流程 (好像不重要)
Spring容器启动
加载bean
需要被增强的类,如:BookServiceImpl
通知类,如:MyAdvice
读取所有切面配置中的切入点 初始化bean * 匹配失败,创建原始对象
* 匹配成功,创建原始对象(目标对象)的代理对象
获取bean执行方法 AOP配置管理 切入点表达式
*
:单个独立的任意符号,可以独立出现,也可以作为前缀或者后缀的匹配符出现
execution(public * com.itheima.*.UserService.find*(*))
..
:多个连续的任意符号,可以独立出现,常用于简化包名与参数的书写
execution(public User com..UserService.findById(..))
书写技巧 1. 按标准规范 开发 2. 查询操作的返回值建议使用匹配 3. 减少使用..的形式描述包 4. 对接口进行描述 ,使用表示模块名,例如UserService的匹配描述为 Service 5. 方法名书写保留动词,例如get,使用 表示名词,例如getById匹配描述为getBy* 6. 参数根据实际情况灵活调整
AOP通知类型 前置通知 `@Before("pt()")`
后置通知 `@After("pt()")`
环绕通知(重点) `@Around("pt()")`
环绕通知依赖形参ProceedingJoinPoint才能实现对原始方法的调用
环绕通知可以隔离原始方法的调用执行
环绕通知返回值设置为Object类型
环绕通知中可以对原始方法调用过程中出现的异常进行处理
返回后通知(了解) @AfterReturning("pt2()")
抛出异常后通知(了解) @AfterThrowing
业务层接口执行效率 AOP通知获取数据 AOP事务管理
在数据层或业务层 保障一系列的数据库操作同成功同失败
转账案例-需求分析 环境搭建
准备数据库
create database spring_db character set utf8;use spring_db; create table tbl_account(id int primary key auto_increment, name varchar (35 ), money double ); insert into tbl_account values (1 ,'Tom' ,1000 );insert into tbl_account values (2 ,'Jerry' ,1000 );
创建项目导入jar包
<dependencies > <dependency > <groupId > org.springframework</groupId > <artifactId > spring-context</artifactId > <version > 5.2.10.RELEASE</version > </dependency > <dependency > <groupId > com.alibaba</groupId > <artifactId > druid</artifactId > <version > 1.1.16</version > </dependency > <dependency > <groupId > org.mybatis</groupId > <artifactId > mybatis</artifactId > <version > 3.5.6</version > </dependency > <dependency > <groupId > mysql</groupId > <artifactId > mysql-connector-java</artifactId > <version > 5.1.47</version > </dependency > <dependency > <groupId > org.springframework</groupId > <artifactId > spring-jdbc</artifactId > <version > 5.2.10.RELEASE</version > </dependency > <dependency > <groupId > org.mybatis</groupId > <artifactId > mybatis-spring</artifactId > <version > 1.3.0</version > </dependency > <dependency > <groupId > junit</groupId > <artifactId > junit</artifactId > <version > 4.12</version > <scope > test</scope > </dependency > <dependency > <groupId > org.springframework</groupId > <artifactId > spring-test</artifactId > <version > 5.2.10.RELEASE</version > </dependency > </dependencies >
根据表创建模型类
public class Account implements Serializable { private Integer id; private String name; private Double money; }
创建Dao接口
public interface AccountDao {@Update("update tbl_account set money = money + #{money} where name = #{name}") void inMoney (@Param("name") String name, @Param("money") Double money) ;@Update("update tbl_account set money = money - #{money} where name = #{name}") void outMoney (@Param("name") String name, @Param("money") Double money) ;}
创建Service接口和实现类
public interface AccountService { public void transfer (String out,String in ,Double money) ; } @Service public class AccountServiceImpl implements AccountService { @Autowired private AccountDao accountDao; public void transfer (String out,String in ,Double money) { accountDao.outMoney(out,money); accountDao.inMoney(in,money); } }
添加jdbc.properties文件
jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql: jdbc.username=root jdbc.password=root
创建JdbcConfig配置类
public class JdbcConfig { @Value("${jdbc.driver}") private String driver; @Value("${jdbc.url}") private String url; @Value("${jdbc.username}") private String userName; @Value("${jdbc.password}") private String password; @Bean public DataSource dataSource () { DruidDataSource ds = new DruidDataSource (); ds.setDriverClassName(driver); ds.setUrl(url); ds.setUsername(userName); ds.setPassword(password); return ds; } }
创建MybatisConfig配置类
public class MybatisConfig { @Bean public SqlSessionFactoryBean sqlSessionFactory (DataSource dataSource) { SqlSessionFactoryBean ssfb = new SqlSessionFactoryBean (); ssfb.setTypeAliasesPackage("com.itheima.domain" ); ssfb.setDataSource(dataSource); return ssfb; } @Bean public MapperScannerConfigurer mapperScannerConfigurer () { MapperScannerConfigurer msc = new MapperScannerConfigurer (); msc.setBasePackage("com.itheima.dao" ); return msc; } }
创建SpringConfig配置类
@Configuration @ComponentScan("com.itheima") @PropertySource("classpath:jdbc.properties") @Import({JdbcConfig.class,MybatisConfig.class}) public class SpringConfig {}
编写测试类
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = SpringConfig.class) public class AccountServiceTest { @Autowired private AccountService accountService; @Test public void testTransfer () throws IOException { accountService.transfer("Tom" ,"Jerry" ,100D ); } }
最终项目结构如上
实现事务管理具体步骤 需要被事务管理的方法上添加注解@Transactional
public interface AccountService { public void transfer (String out,String in ,Double money) ; } @Service public class AccountServiceImpl implements AccountService { @Autowired private AccountDao accountDao; **@Transactional ** public void transfer (String out,String in ,Double money) { accountDao.outMoney(out,money); int i = 1 /0 ; accountDao.inMoney(in,money); } }
在JdbcConfig类中配置事务管理器 @Bean public PlatformTransactionManager transactionManager (DataSource dataSource) { DataSourceTransactionManager transactionManager = new DataSourceTransactionManager (); transactionManager.setDataSource(dataSource); return transactionManager; }
在SpringConfig的配置类中开启事务注解 @EnableTransactionManagement
@Configuration @ComponentScan("com.itheima") @PropertySource("classpath:jdbc.properties") @Import({JdbcConfig.class,MybatisConfig.class}) @EnableTransactionManagement public class SpringConfig {}
运行测试类 事务配置
转账业务追加日志 创建日志表 create table tbl_log(id int primary key auto_increment, info varchar (255 ), createDate datetime )
添加LogDao接口 public interface LogDao { @Insert("insert into tbl_log (info,createDate) values(#{info},now())") void log (String info) ; }
添加LogService接口与实现类 public interface LogService { void log (String out, String in, Double money) ; } @Service public class LogServiceImpl implements LogService { @Autowired private LogDao logDao; @Transactional public void log (String out,String in,Double money ) { logDao.log("转账操作由" +out+"到" +in+",金额:" +money); } }
在转账的业务中添加记录日志 public interface AccountService { public void transfer (String out,String in ,Double money) throws IOException ; } @Service public class AccountServiceImpl implements AccountService {@Autowired private AccountDao accountDao; @Autowired private LogService logService; @Transactional public void transfer (String out,String in ,Double money) { try { accountDao.outMoney(out,money); accountDao.inMoney(in,money); }finally { logService.log(out,in,money); } } }
事务传播行为
事务协调员对事务管理员所携带事务的处理态度
转账失败后,所有的事务都回滚,导致日志没有记录下来 — 让log方法单独是一个事务
修改logService改变事务的传播行为
@Service public class LogServiceImpl implements LogService { @Autowired private LogDao logDao; @Transactional(propagation = Propagation.REQUIRES_NEW) public void log (String out,String in,Double money ) { logDao.log("转账操作由" +out+"到" +in+",金额:" +money); } }