1 案例
需求: 完成转账业务, 事务的支持:
1.1开发数据库表:Account id name money、
开发实体类: Account
public class Account {
private Integer id;
private String name;
private double money;
}
1.2开发AccountDao ; 查询, 更新方法:
package com.yidongxueyuan.spring.dao;
import com.yidongxueyuan.spring.pojo.Account;
public interface AccountDao {
/**
* 查询用户:
* @param accountName
* @return
*/
Account findAccountByName(String accountName);
/**
* 更新操作:
* @param newAccount
*/
void updateAccount(Account newAccount);
}
1.3开发AccountDaoImpl ; DBUtils链接数据库。
package com.yidongxueyuan.spring.dao.impl;
import java.sql.SQLException;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import com.yidongxueyuan.spring.dao.AccountDao;
import com.yidongxueyuan.spring.pojo.Account;
import com.yidongxueyuan.spring.utils.TransactionManager;
public class AccountDaoImpl implements AccountDao {
//获得对象, 不需要给出数据源:
private QueryRunner qr= new QueryRunner();
@Override
public Account findAccountByName(String accountName) {
try {
String sql ="select * from account where name =?";
Account account = qr.query(TransactionManager.getConnection(), sql, new BeanHandler<Account>(Account.class),accountName);
return account;
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
@Override
public void updateAccount(Account newAccount) {
try {
String sql ="update account set money = ? where name =?";
qr.update(TransactionManager.getConnection(), sql, newAccount.getMoney(),newAccount.getName());
} catch (SQLException e) {
e.printStackTrace();
}
}
}
1.4开发AccountService ; 定义业务方法: transfer
package com.yidongxueyuan.spring.service;
public interface AccountService {
/**
* 业务方法, 实现转账
* @param sourceAccount 来源账户
* @param targetAccount 目标账户
* @param money 转账金额
*/
public void transfer(String sourceAccount,String targetAccount,float money);
}
1.5开发AccountServiceImpl实现类:
package com.yidongxueyuan.spring.service.impl;
import com.yidongxueyuan.spring.dao.AccountDao;
import com.yidongxueyuan.spring.dao.impl.AccountDaoImpl;
import com.yidongxueyuan.spring.pojo.Account;
import com.yidongxueyuan.spring.service.AccountService;
public class AccountServiceImpl implements AccountService{
private AccountDao dao= new AccountDaoImpl();
@Override
public void transfer(String sourceAccount, String targetAccout, float money) {
//开启事务:
//根据来源账户:
Account sourceAcc = dao.findAccountByName(sourceAccount);//aaa
//查询目标账户:
Account targetAcc = dao.findAccountByName(targetAccout);// ccc
//来源账户减钱
sourceAcc.setMoney(sourceAcc.getMoney()-money);
//目标账户增钱:
targetAcc.setMoney(targetAcc.getMoney()+money);
//调用dao层的方法: 将变化后的数据保存哎数据库当中:
dao.updateAccount(sourceAcc);
//模拟异常的发生:
int i=1/0;
dao.updateAccount(targetAcc);
}
}
1.6工具类: 获得数据源的工具类
package com.yidongxueyuan.spring.utils;
import java.sql.Connection;
import java.sql.SQLException;
import javax.sql.DataSource;
import com.mchange.v2.c3p0.ComboPooledDataSource;
public class C3P0Util {
/**
* 初始化一个对象:
* ComboPooledDataSource 是DataSource类的子类对象:
*/
private static DataSource ds = new ComboPooledDataSource();
/**
* 返回一个数据源:
* @return
*/
public static DataSource getDataSource(){
return ds;
}
/**
* 获得一个链接对象:
* @return Connection链接对象:
*/
public static Connection getConnection(){
try {
return ds.getConnection();
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
}
1.7工具类依赖的配置文件
<?xml version="1.0" encoding="UTF-8"?> com.mysql.jdbc.Driver jdbc:mysql:///customer root root 10 30 100 10 2001.8测试
public class TestAccountService {
@Test
public void test1() {
//直接获得被代理对象:
AccountService service = new AccountServiceImpl();
service.transfer(“aaa”, “ccc”, 100);
}
}
测试总结: 在没有加入事务的前提下, 转账的过程当中: aaa 用户-100 但是ccc用户钱并没有增加: 不符合实际情况。 所以加入事务的支持。 保证转账的一个逻辑单元要么都成功, 要么都失败。
1.9 使用aop的方式事务
获得UserServiceImpl类的代理类对象; 在执行业务方法的时候, 在动态的加入事务的支持。
package com.yidongxueyuan.spring.utils;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import com.yidongxueyuan.spring.service.AccountService;
import com.yidongxueyuan.spring.service.impl.AccountServiceImpl;
/**
- 定义一个工厂: 获得对象的代理对象:
- @author Mrzhang
*/
public class BeanFactory {
/**
* 定义了一个方法: 能够获得业务对象的代理对象:
* @return
*/
public static AccountService getProxy(){
//被代理的对象:
final AccountService service = new AccountServiceImpl();//只专注于转账:
//获得代理类:
AccountService proxy= (AccountService)Proxy.newProxyInstance(
//代理类的类加载器
service.getClass().getClassLoader(),
//代理类的接口当中所有的方法:
service.getClass().getInterfaces(),
//InvocationHandler 接口: 定义代理类和被代理类的具体的代理策略:
new InvocationHandler() {
//proxy:代理类引用:
// method:当前执行的方法:
// args:执行方法的参数
//Object 调用方法的返回值: :
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
Object obj =null;
try {
//事务开启:
TransactionManager.startTransaction();
//调用业务方法:
obj = method.invoke(service, args);
//事务的提交:
TransactionManager.commit();
} catch (Exception e) {
TransactionManager.rollback();
e.printStackTrace();
} finally{
//释放资源:
}
return obj;
}
});
return proxy;
}
}
1.10测试:
@Test
public void test1() {
//直接获得被代理对象:
AccountService service = new AccountServiceImpl();
//获得service类的代理类对象:
AccountService proxy = BeanFactory.getProxy();
proxy.transfer(“aaa”, “ccc”, 100);
}
2AOP
2.1 aop概述
Aop 面向切面编程,底层是动态代理实现的。 是oop的延伸。 解决了oop开发过程当中遇到的一些问题。
Aop可以解决: 事务管理 日志的记录 权限的校验 性能的监控。
2.2 代理模式
代理模式: 分类静态代理&动态代理:
静态代理:
动态代理:分类:
基于接口的动态代理: JDK
基于普通Bean的代理: CGLIB (第三方,使用时候导入jar)
2.3 spring当中AOP
Aop 面向切面编程的思想, 这思想并不是spring提出的。 spring当中引用了aop。
Spring框架: aop的实现:
(1)spring当中对aop进行了具体的实现: (废弃)
(2)Spring引用了 AspectJ 对aop进行了很好的实现。(广泛使用)
2.4 aop相关的数据
public interface UserDao {
public User findUserById(int id);
public void saveUser(User user);
public void deleteUser(int id);
public void updateUser(User user) ;
}
连接点(join point ): 可以被拦截到的点就是连接点。
增删改查这些方法都有机会可以被进行功能性的增强, 这些方法都是连接点。
切入点(PonitCut): 真正被拦截到的点。
真正被增强的方法, 就称之为切入点。 在实际开发过程当中, saveUser方法被增强了, 此时saveUser这个方法就是切入点
通知(advice) :增强(方法层面的增强)
在执行saveUser方法之前, 要执行checkPri这个方法,此时checkPri这个方法就称之为通知, 也叫作增强。
引介: (Introduction) : 类层面的增强。
目标:(Target) : 被增强的对象:
在开发当中, UserDao 需要被增强, UserDao就是增强的对象。
织入:(Weaving) :将通知(advice)应用到目标(target)的过程
saveUser方法需要在执行之前加入checkPri()进行权限的校验,这个过程就是织入。
代理对象(Proxy): 目标被增强后,就是一个代理对象。
切面: (aspect): 多个通知和多个切入点的集合。 多个通知的集合。
2.5 aop的入门案例
2.5.1 引入aop相关的jar包:
IOC: 4个核心+ 2个日志:
注解式开发: spring3.x 不需要导入其他的》
Spring4.x 引来了aop
Aop的开发: 4个:
第一个jar: aop联盟
第二个jar: aspectweaving
第三个jar: aop
第四个jar: spring 整合aspect
Spring当中自己提供了一个套实现: 开发只需要两个: aop , aopalliance
Spring引入了aspect后: aspectJ spring-aspect :
2.5.2 核心配置文件当中引入相关的约束:
<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:aop="http://www.springframework.org/schema/aop"
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/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
</bean>
2.5.3 定义接口 UserDao
public interface UserDao {
public User findUserById(int id);
public void saveUser(User user);
public void deleteUser(int id);
public void updateUser(User user) ;
}
2.5.4 定义接口的实现:
UserDaoImpl
package com.yidongxueyuan.spring.dao.impl;
import org.springframework.stereotype.Repository;
import com.yidongxueyuan.spring.dao.UserDao;
import com.yidongxueyuan.spring.pojo.User;
public class UserDaoImpl implements UserDao {
@Override
public User findUserById(int id) {
System.out.println("根据id 进行用户的查询:");
return null;
}
@Override
public void saveUser(User user) {
System.out.println("save...");
}
@Override
public void deleteUser(int id) {
System.out.println("delete...");
}
@Override
public void updateUser(User user) {
System.out.println("update...");
}
}
2.5.5 定义切面类 并且常见对象: (增强的集合)
package com.yidongxueyuan.spring.pojo;
/**
- 定义一个切面: (通知的集合)
- @author Mrzhang
*/
public class MyAspectXml {
/**
* 定义了一个通知: (增强)
*/
public void checkPri() {
System.out.println("权限的校验");
}
}
2.5.6 通过配置的方法, 实现对UserDaoImpl当中的方法进行功能的增强。
ApplicationContext.xml当中进行配置L:
<!-- 配置切面类: -->
<bean id="myAspectXml" class="com.yidongxueyuan.spring.pojo.MyAspectXml"></bean>
<!-- 目标类: -->
<bean id="userDao" class="com.yidongxueyuan.spring.dao.impl.UserDaoImpl"></bean>
<!-- aop的配置:
aop: config 标签代表aop配置标签:
aop:aspect: 开始配置切面: 通知和切点集合:
id: 配置该切面的唯一标识: 自定义的:
ref:引入切面类:
aop:before: 在切点之前执行:
method: 指定增强的方法:
pointCut: 定义切点: 基于execution编写:
-->
<aop:config>
<!-- 配置一个切点: -->
<!-- 配置切面: 通知和切入点的集合 -->
<aop:aspect id="myAspect" ref="myAspectXml">
<aop:before method="checkPri" pointcut="execution( * com.yidongxueyuan.spring.dao.impl.UserDaoImpl.saveUser(..))" />
</aop:aspect>
</aop:config>·
总结配置: 将切面类当中的增强应用在目标类的切点上。
2.5.7测试 (spring整合Junit后)
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class TestUserDao {
//整合后的单元测试:
@Resource(name="userDao")
private UserDao userDao;
@Test
public void test2() throws Exception {
userDao.saveUser(new User());
}
}
2.6 spring整合junit
(1)加入单元测试的包:
(2)使用JUnit:
package com.yidongxueyuan.spring.test;
import javax.annotation.Resource;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.yidongxueyuan.spring.dao.UserDao;
import com.yidongxueyuan.spring.pojo.User;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class TestUserDao {
//整合后的单元测试:
@Resource(name="userDao")
private UserDao userDao;
@Test
public void test2() throws Exception {
userDao.saveUser(new User());
}
//传统的单元测试:
@Test
public void test1() throws Exception {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserDao dao = (UserDao)context.getBean("userDao");
dao.saveUser(new User());
}
}
2.7 spring当中提供的通知的类型:
五种通知类型:
2.7.1前置通知:
<!-- 配置切面: 通知和切入点的集合 -->
<aop:aspect id="myAspect" ref="myAspectXml">
<aop:before method="checkPri" pointcut="execution( * com.yidongxueyuan.spring.dao.impl.UserDaoImpl.saveUser(..))" />
</aop:aspect>
2.7.2 后置增强:
总结: 当异常通知执行了, 后置通知就不再执行了。
2.7.3 最终通知
2.7.4 异常抛出通知
总结: 如果最后通知执行, 异常抛出通知不执行。
异常抛出通知, 能够获得异常信息:
public void afterThrow(Throwable e) {// e的名称必须和配置文件当中抛出的名称一致。
System.out.println("异常抛出通知: "+e.getMessage());//获得抛出的异常信息。
}
总结: 前置, 后置, 最终, 异常抛出通知, 以上四个通知不可能通知执行。 后置通知和异常抛出通知只能执行一个。
当没有遇到异常的时候, 执行后置通知。
遇到了异常信息, 执行异常抛出通知。
2.7.5 环绕通知
切面当中增强的定义:
/**
* 测试了代码: 环绕通知执行了, 但是业务代码没有执行:
*
* spring当中提供了一个对象: ProceedingJoinPoint 放行业务代码: 方法有返回值:
*/
public Object around(ProceedingJoinPoint joinPoint) {
//放行业务代码:
Object obj= null;
try {
System.out.println("前置通知。");
obj= joinPoint.proceed();
System.out.println("后置通知。");
} catch (Throwable e) {
System.out.println("异常抛出通知: ");
e.printStackTrace();
} finally {
System.out.println("最终通知");
}
return obj;
}
核心配置文件当中配置环绕通知:
<aop:around method="around" pointcut="execution( * com.yidongxueyuan.spring.dao.impl.UserDaoImpl.saveUser(..))" />
测试。。。
总结: 环绕通知可以模拟前置,后置, 异常, 最终通知。
2.8 通用化切点的配置
<aop:config>
<!-- 配置一个切点: 任何的切面都可以引用该切点: -->
<aop:pointcut expression="execution( * com.yidongxueyuan.spring.dao.impl.UserDaoImpl.saveUser(..))" id="ponit1"/>
<!-- 配置切面: 通知和切入点的集合 -->
<aop:aspect id="myAspect" ref="myAspectXml">
<!-- 前置通知 -->
<aop:before method="checkPri" pointcut-ref="ponit1"/>
<!-- 后置通知: -->
<aop:after-returning method="writeLogger" pointcut-ref="ponit1" />
<!-- 最终通知: -->
<aop:after method="after" pointcut-ref="ponit1" />
<!-- 异常抛出通知 -->
<aop:after-throwing method="afterThrow" throwing="e" pointcut-ref="ponit1" />
</aop:aspect>
</aop:config>
2.9 execution 表达式的书写
<aop:pointcut expression="execution( void com.yidongxueyuan.spring.dao.impl.UserDaoImpl.saveUser(..))" id="ponit1"/>
Expression: 基于execution的书写:
语法:
访问权限修饰符 返回值 包名.类名.方法的名称(方法参数的类型)
访问权限可以省略的:
void com.yidongxueyuan.spring.dao.impl.UserDaoImpl.saveUser(…))
返回值: 可以写成具体的返回值类型: void
返回值类型可以使用通配符: * :带表任何的返回值。
包: 可以使用统配符: * 每一个就代表一级包。
execution( * .....UserDaoImpl.saveUser(…))"
*… 代表当前当前包,
expression=“execution( * *…UserDaoImpl.saveUser(…))”
类: 可以使用统配: * 代表所有的类。
方法: 可以同时通配符: * 代表任意的方法。
方法的参数:
如果写具体的类型: 基本数据类型: int double
如果是引用类型: 必须写全限定类型: java.lang.String.class Java.lang.Integer.class
方法的参数可以使用通配符:
*:带表有参数:
() 匹配不带参数;
(…) 匹配带参数的和不带参数的。
总结: 在实际开发当中, 不需要写统配方法:
统配的写法:
expression=“execution( * ….*(…))”
建议:
- com.yidongxueyuan.xx.impl..(…)
业务包下的所有的实现类。