AOP思想和重要术语
需求问题
在开发中,为了给业务方法中增加日志记录,权限检查,事务控制等功能,此时我们需要去修改业务方法代码,考虑到代码的重用性,我们可以考虑使用OOP的继承或组合关系来消除重复,但是无论怎么样,我们都会在业务方法中纵向地增加这些功能方法的调用代码。此时,既不遵循开闭原则,也会为后期系统的维护带来很大的麻烦。(即不管怎样都得修改到原来的代码)
为了解决该问题,OOP 思想是不行了,得使用 AOP 思想。
AOP是什么
AOP(Aspect Oriented Programming),是面向切面编程的技术,把一个个的横切关注点放到某个模块中去,称之为切面。那么每一个的切面都能影响业务的某一种功能,切面的目的就是功能增强,如日志切面就是一个横切关注点,应用中许多方法需要做日志记录的只需要插入日志的切面即可。(动态代理就可以实现 AOP),这种面向切面编程的思想就是 AOP 思想,不修改现有代码的前提下给某些类中方法添加功能,符合开闭原则,提供项目可维护性。
把业务方法中与业务无关的操作抽离到不同的对象中,最后使用动态代理的方式组合起来,动态地为类增加功能。
AOP术语
Joinpoint:连接点,一般指需要被增强的方法。where:去哪里做增强。
Pointcut:切入点,哪些包中的哪些类中的哪些方法,可认为是连接点的集合。where:去哪些地方做增强。
Advice:增强,当拦截到 Joinpoint 之后,在方法执行的什么时机(when)做什么样(what)的增强。根据时机分为:前置增强、后置增强、异常增强、最终增强、环绕增强。
Aspect:切面,Pointcut + Advice,去哪些地方 + 在什么时候 + 做什么增强。
Target:被代理的目标对象。
Weaving:织入,把 Advice 加到 Target 上之后,创建出 Proxy 对象的过程。
Proxy:一个类被 AOP 织入增强后,产生的代理类。
AspectJ切入点Pointcut表达式
AspectJ 是一个面向切面的框架,它扩展了Java 语言(即使用 Java 对 AOP 进行了实现)。
AspectJ切入点语法
怎么表示Pointcut,即怎么表示哪些包中的哪些类中的哪些方法?AspectJ提供了表示切入点的语法
-
切入点语法通配符
*:匹配任何部分,只能表示一个单词
… : 可用于全限定名中和方法参数中,分别表示子包和 0 到 N 个参数
-
切入点语法案例
execution(* cn.wolfcode.ssm.service.impl.*ServiceImpl.*(..))
注意第一个星符号后面有空格。
使用XML配置AOP
需求
想给业务方法加模拟的事务功能,又不想修改原来的代码
使用AOP
-
添加依赖
<dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.13</version> </dependency>
-
XML文件添加AOP约束,编写配置
-
编写单元测试
变更使用CGLIB
Spring AOP 不做配置的话且目标对象实现接口的话,默认使用 JDK 的动态代理。若想强制使用 CGLIB,则在applicationContext.xml配置如下:
<aop:config proxy-trage-calss="true">
使用注解配置AOP
对比XML配置来使用注解的方式来配置
变更使用CGLIB
在applicationContext.xml配置如下
<aop:aspectj-autoproxy proxy-trage-calss="true">
集成
集成作用及本质
使用框架,在别人的基础上开发,提高效率;
集成MyBatis和业务层,即业务对象、Mapper对象等都交由Spring容器管理,使用Spring IoC 和DI来完成对象创建及其属性注入,后面再使用AOP来配置事务。
项目搭建熟练集成
新建项目添加依赖
利用 IoC DI
关联 db.properties
配置数据源 DruidDataSource
配置 SqlSessionFactory bean SqlSessionFactoryBean
批量配置 Mapper 接口的实现类对象 MapperScannerConfigurer
配置业务对象
利用 AOP 配置事务
XML(最低要求拷贝能改)
注解配置
配置事务管理器,给其注入数据源 DataSourceTransactionManager
配置事务注解解析器,关联事务管理器
在想加事务功能方法或者类上添加注解 @Transactional
Transactional注解使用
Transactional注解可以贴在接口或实现类上,即类或接口上的事务的配置是通用与整个类或接口的的方法;而也可以贴方法上,即方法上的的事务的配置仅限于被贴的方法。
若想强制使用CGLIB动态代理,则修改tx:annotation-drive上的属 proxy-target-class修改为true即可。
项目源码
需求
新建Maven项目,打包方式是war,为后面项目做铺垫。需求:做个转账功能。
mop.xml 依赖和插件
<dependencies>
<!-- web 项目相关的-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.20</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.0.1</version>
</dependency>
<!-- spring 相关-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.0.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.0.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.13</version>
</dependency>
<!-- 数据库相关-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.45</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.9</version>
</dependency>
<!-- MyBatis 相关-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.5</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.12</version>
</dependency>
<!-- Spring集成MyBatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.1</version>
</dependency>
<!--页面标签-->
<dependency>
<groupId>org.apache.taglibs</groupId>
<artifactId>taglibs-standard-spec</artifactId>
<version>1.2.5</version>
</dependency>
<dependency>
<groupId>org.apache.taglibs</groupId>
<artifactId>taglibs-standard-impl</artifactId>
<version>1.2.5</version>
</dependency>
</dependencies>
<build>
<finalName>ssm</finalName>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.1</version>
<configuration>
<port>8080</port>
<path>/</path>
<uriEncoding>UTF-8</uriEncoding>
</configuration>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
数据库及实体对象
CREATE TABLE `account` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`balance` decimal(10,2) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
@Setter
@Getter
public class Account {
private Long id;
private BigDecimal balance;
}
Mapper接口和Mapper配置文件
public interface AccountMapper {
void addBalance(@Param("inId")Long inId, @Param("amount") BigDecimal amount);
void subtractBalance(@Param("outId") Long outId, @Param("amount")BigDecimal amount);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.wolfcode.mapper.AccountMapper">
<update id="addBalance">
UPDATE account SET balance = balance + #{amount} WHERE id = #{inId}
</update>
<update id="subtractBalance">
UPDATE account SET balance = balance - #{amount} WHERE id = #{outId}
</update>
</mapper>
applicationContext.xml 配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
<!--关联db.properties-->
<context:property-placeholder location="db.properties"/>
<!--配置dataSource-->
<bean class="com.alibaba.druid.pool.DruidDataSource" id="dataSource" init-method="init" destroy-method="close">
<property name="driverClassName" value="${db.driverClassName}"/>
<property name="url" value="${db.url}"/>
<property name="username" value="${db.username}"/>
<property name="password" value="${db.password}"/>
</bean>
<!--配置sqlSessionFactory对象-->
<bean class="org.mybatis.spring.SqlSessionFactoryBean" id="sqlSessionFactory">
<!--数据源-->
<property name="dataSource" ref="dataSource"/>
<!--配置别名-->
<property name="typeAliasesPackage" value="cn.wolfcode"/>
<!--mapper 配置文件-->
<property name="mapperLocations" value="classpath:cn/wolfcode/mapper/*Mapper.xml"/>
</bean>
<!--配置Mapper接口扫描器-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="cn.wolfcode.mapper"/>
</bean>
<!--Ioc ID 注解解析器-->
<context:component-scan base-package="cn.wolfcode.service.impl"/>
<!--配置事务管理器-->
<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="transactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--事务注解解析器-->
<tx:annotation-driven transaction-manager="transactionManager"/>
</beans>
service实现类
@Service
@Transactional
public class AccountServiceImpl implements IAccountService {
@Autowired
private AccountMapper accountMapper;
@Override
public void transfer(Long outId, Long inId, BigDecimal amount) {
accountMapper.addBalance(inId,amount);
int a = 1/0;
accountMapper.subtractBalance(outId,amount);
}
}
单元测试类
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class AccountServiceTest {
@Autowired
private IAccountService accountService;
@Test
public void transfer() {
accountService.transfer(1L,2L,new BigDecimal("10"));
}
}