Spring(三)
一.AOP
AOP思想
AOP术语
AspectJ
AOP实现
1.使用XML配置
2.使用AOP注解配置
二.Spring对事务支持
1.使用XMl配置事务
2.使用注解配置事务
三.集成
需求,模拟转账功能,配合事务
1.使用XML配置
2.使用注解配置
模板方法设计模式:把一些相同操作定义父类中,把不同操作定义子类中,一般可以覆盖的方法都以do开头
AOP
AOP思想
在开发中,我们会给业务方法增加日志功能,事务控制等,如果业务类过多,不便于维护系统,OOP的继承可以消除重复,但是还是会在纵向的增加功能方法的调用代码不遵循开闭原则,所以有了AOP思想。
AOP,面向切面编程技术,把业务方法中与业务方法无关的操作抽离到不同对象中,使用动态代理组合起来,动态为类增加功能。
AOP本质是使用动态代理,AOP会根据我给的条件实现invocationHandle接口并重写invoke(),还会使用动态代理API帮你把代理类及对象创建好
public class XxxinvocationHandler implements InvocationHandler{
//AOP实现会根据我们给的条件从此额invoke方法
public void invoke(..){}
}
//使用动态代理API帮我们把代理类以及对象创建好
public class $Proxy数值 extends Proxy implements..{}
AOP术语
- Joinpoint:连接点,想加功能的方法
- Pointcut:切入点,多个连接点集合的表达式,开发者知道哪里想加功能,但是别人AOP实现不知道(在哪里加),可以通过切入点告诉它(where)
- Advice:增强,什么时候加(when),加具体什么功能(what)
- Aspect:切面:切入点+增强(Pointcut+Advice),就是where,when,what的集合
- Target:被代理的目标对象
- Weaving:织入,把增强(
Advice
)加到 目标对象(Target
)后,创建代理类(Proxy
)的过程 - Proxy:一个类被AOP织入后增强后,产生的代理类
AspectJ
AspectJ 是一个面向切面的框架,它扩展了Java 语言(即使用 Java 对 AOP 进行了实现)。
切入点语法通配符
execution(* cn.wolfcode.ssm.service.impl.*ServiceImpl.*(..)) //注意第一个星符号有空格
配置依赖
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.13</version>
</dependency>
AOP实现
使用XML配置
给业务方法添加模拟事务功能
- service层
public interface IUserService {
void save(String username,String password);
}
----------------------------------------
public class UserServiceImpl implements IUserService {
@Override
public void save(String username, String password) {
System.out.println("保存了"+username+""+password);
}
}
- 模拟事务管理类 tx
//封装事务相关的操作,
public class MyTransactionManager {
public void begin(){
System.out.println("开启事务");
}
public void commit(){
System.out.println("提交事务");
}
public void rollback(){
System.out.println("回滚事务");
}
}
- 配置applicationContext.xml
//头省略
<!--配置事务管理器-->
<bean id="tx" class="cn.k.tx.MyTransactionManager"/>
<!--配置真实对象或者业务对象-->
<bean id="userService" class="cn.k.service.impl.UserServiceImpl"/>
<!--配置AOP一定记住是否配置三要素 where when what-->
<aop:config>
<!--配置切面-->
<aop:aspect ref="tx">
<!--配置切入点where * cn.k.service.impl.*ServiceImpl.*(..)-->
<aop:pointcut id="txPointcut" expression="execution(* cn.k.service.impl.*ServiceImpl.*(..))"/>
<!--切入点表示到的方法,在方法执行之前调用 tx.begin()-->
<aop:before pointcut-ref="txPointcut" method="begin"/>
<!--切入点表示到的方法,在方法正常执行完调用 tx.commit()-->
<aop:before pointcut-ref="txPointcut" method="commit"/>
<!--切入点表示到的方法,在方法执行抛出异常调用 tx.rollback()-->
<aop:before pointcut-ref="txPointcut" method="rollback"/>
</aop:aspect>
</aop:config>
- 测试类
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class UserServiceTest {
@Autowired
private IUserService userService;
@Test
public void save() {
System.out.println(userService.getClass());
userService.save("qqq","465");
}
}
注意:使用AOP相当于拿到了代理对象,把AOP去掉拿到了真实对象
使用AOP注解配置
- 修改applicationContext.xml配置文件
<!--配置事务管理器-->
<!--配置真实对象或者业务对象-->
<!--IoC DI注解解析
让spring帮我们创建事务管理器对象,真实对象或者业务对象
-->
<context:component-scan base-package="cn.k"/>
<!--AOP注解解析器-->
<aop:aspectj-autoproxy/>
- 修改事务管理类
@Component @Aspect
public class MyTransactionManager {
@Pointcut("execution(* cn.k.service.impl.*ServiceImpl.*(..))")
public void txPointcut() {}
@Before("txPointcut()")
public void begin() {
System.out.println("开启事务");}
@AfterReturning("txPointcut()")
public void commit() {
System.out.println("提交事务");}
@AfterThrowing("txPointcut()")
public void rollback() {
System.out.println("回滚事务");}}
这里使用的是JDK代理,如果要使用CGLIB,修改为
<aop:aspectj-autoproxy proxy-target-class="true"/>
Spring对事务支持
Spring事务管理器,不同持久化技术的不同事务管理器
- JDBC和Mybatis 使用DataSourceTransactionManager
- Hibernate 使用 HibernateTransactionManager
- JPA 使用 JpaTransactionManager。
事务增强的属性配置
常用 :
read-only:若为 true,开启一个只读事务,只读事务的性能较高,但是不能在只读事务中操作 DML
Spring默认情况下,业务方法会抛出RuntimeException 及其子类的异常才会去回滚,抛出 Exception 和 Thowable 的异常不会回滚,如果是自定义,一般都会继承运行时异常
使用XMl配置事务
<!--配置事务管理器对象-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--指定数据源-->
<property name="dataSource" ref="dataSource"/>
</bean>
<!--定义事务增强when what when看不出来-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<!--针对方法的差异化配置-->
<tx:attributes>
<!--Xxx*前代表会匹配Xxx*开头的-->
<tx:method name="select*" read-only="true"/>
<tx:method name="query*" read-only="true"/>
<tx:method name="list*" read-only="true"/>
<tx:method name="count*" read-only="true"/>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
<!--
切入点表示的到方法执行之前,调用transactionManager 提交事务方法getTransaction
切入点表示的到方法执行正常完,调用transactionManager 提交事务方法commit
切入点表示的到方法执行抛出异常时,调用transactionManager 提交事务方法rollback
-->
<!--配置AOP-->
<aop:config>
<!--配置切入点 where-->
<aop:pointcut id="txPointcut" expression="execution(* cn.k.service.impl.*ServiceImpl.*(..))"/>
<!--关联切入点和增强,构成切面-->
<aop:advisor pointcut-ref="txPointcut" advice-ref="txAdvice" />
</aop:config>
使用注解配置事务
- 给业务类贴上注解
// 贴事务注解
@Transactional
public class XxxServiceImpl implements IXxxService {}
- 修改配置
<!--配置事务管理器对象-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--指定数据源-->
<property name="dataSource" ref="dataSource"/>
</bean>
<!--配置事务注解解析器-->
<tx:annotation-driven/>
Transactional 注解
- 贴业务类或业务接口上,事务的配置是通用与整个类或接口的的方法;
- 贴业务方法上,即方法上的的事务的配置仅限于被贴的方法;
- 同时存在时,后者覆盖前者;
若要使用 CGLIB 动态代理,则修改事务注解解析器的配置
<tx:annotation-driven proxy-target-class="true" transactionmanager="transactionManager"/>
集成
使用框架,集成MyBatis和业务层,将业务对象,Mapper对象等都交给Spring容器管理
注意打包方式是war(因为要建WEB项目),我们建立一个maven项目
需求,模拟转账功能,配合事务
这里思路:
集成Mybatis和业务层,首先需要定义mapper接口,和mapper.xml文件,db.properties文件完成持久层文件基本定义,其他交给Spring容器.
- 首先我们需要再applicationContext.xml 关联db.properties
- 我们需要获取数据源对象
- 获取完数据源对象,我们需要获取SqlSessionFactory对象,并设置数据源,设置别名(一般都整个别名)
- 获取SqlSessionFactory对象的目的是为了拿到mapper对象(mapper接口的代理对象可能会有多个,所有使用批量获取)
- 拿到mapper对象,我们就可以配置业务对象
- 配置事务管理器对象,指定数据源
- 配置事务注解解析器
使用XML配置
- 定义实体类
@Data
public class Account {
private Long id;
private BigDecimal balance;}
- 定义mapper接口
public interface AccountMapper {
void addBalance(@Param("inId") Long inId, @Param("amount") BigDecimal amount);
void subtractBalance(@Param("outId") Long outId, @Param("amount") BigDecimal amount);
}
- 编写mapper.xml文件
<mapper namespace="cn.k.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>
- 编写service层
接口
public interface IAccountService {
//转账
void transfer(Long outId, Long inId, BigDecimal amount);
}
----------------------------------
实现类
public class AccountServiceImpl implements IAccountService {
private AccountMapper accountMapper;
@Autowired
public void setAccountMapper(AccountMapper accountMapper) {
this.accountMapper = accountMapper;}
@Override
public void transfer(Long outId, Long inId, BigDecimal amount) {
accountMapper.subtractBalance(outId,amount);
//int i=1/0;
accountMapper.addBalance(inId,amount); }}
- 这里主要写applicationContext.xml配置文件
1. <!--关联db.properties-->
<context:property-placeholder location="classpath:db.properties"/>
<!--配置数据源对象-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
init-method="init" destroy-method="close">
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
2.<!--配置sqlSessionFactory对象-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!--设置数据源-->
<property name="dataSource" ref="dataSource"/>
<!--设置别名-->
<property name="typeAliasesPackage" value="cn.k.domain"/>
<!--关联Mapper XML 把mapper接口路径与resources路径下的mapper.xml建立相同层级就可以省略不写,建立相同层级字节码文件是编译在一起的-->
</bean>
3.<!--批量配置Mapper 对象-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!--指定Mapper接口所在包-->
<property name="basePackage" value="cn.k.mapper"/>
</bean>
4.<!--配置业务对象 AccountService对象-->
<context:component-scan base-package="cn.k.service.impl"/>
5.<!--配置事务管理器对象-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--指定数据源-->
<property name="dataSource" ref="dataSource"/>
</bean>
6. <!--定义事务增强when what when看不出来-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<!--针对方法的差异化配置-->
<tx:attributes>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
7.<!--配置AOP-->
<aop:config>
<!--配置切入点 where-->
<aop:pointcut id="txPointcut" expression="execution(* cn.k.service.impl.*ServiceImpl.*(..))"/>
<!--关联切入点和增强,构成切面-->
<aop:advisor pointcut-ref="txPointcut" advice-ref="txAdvice" />
</aop:config>
编写测试类(省略…)
使用注解配置
- 修改service实现类
@Service
@Transactional
public class AccountServiceImpl implements IAccountService {
private AccountMapper accountMapper;
@Autowired
public void setAccountMapper(AccountMapper accountMapper) {
this.accountMapper = accountMapper;}
@Override
public void transfer(Long outId, Long inId, BigDecimal amount) {
accountMapper.subtractBalance(outId,amount);
accountMapper.addBalance(inId,amount);}}
- 修改applicationContext.xml配置文件
1.<!--关联db.properties-->
<context:property-placeholder location="classpath:db.properties"/>
2. <!--配置数据源对象-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
init-method="init" destroy-method="close">
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
3. <!--配置sqlSessionFactory对象-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!--设置数据源-->
<property name="dataSource" ref="dataSource"/>
<!--设置别名-->
<property name="typeAliasesPackage" value="cn.k.domain"/>
<!--关联Mapper XML-->
</bean>
4. <!--批量配置Mapper 对象-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!--指定Mapper接口所在包-->
<property name="basePackage" value="cn.k.mapper"/>
</bean>
5.<!--配置业务对象 AccountService对象-->
<context:component-scan base-package="cn.k.service.impl"/>
6.<!--配置事务管理器对象-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--指定数据源-->
<property name="dataSource" ref="dataSource"/>
</bean>
7.<!--配置事务注解解析器-->
<tx:annotation-driven/>
</beans>
原理解读
批量配置Mapper对象
-
在指定包路径下获取所有Mapper接口对象,Class[] clzes;
-
根据类型到容器中获取对应的对象SqlSessionFactory factory =(SqlSessionFactory)容器对象.getBean(SqlSessionFactory.class)
-
for(Class clz :clzes){ factory.openSession().getMapper(clz) //把方法返回对象存到容器中,名称对应接口类名首字母小写 }