本篇博客内容:
- Spring AOP
- JDBC Template
- 声明式事务实现
Spring AOP
JDK 动态代理实现 AOP
使用JDK动态代理需要动态代理的该类是一个接口的实现
- Service 接口
package cyt.proxy;
public interface UserService {
public void register();
public void login();
public void changePasswd();
}
- Service 接口的实现 ServiceImpl
package cyt.proxy;
public class UserServiceImpl implements UserService {
@Override
public void register() {
System.out.println("register");
}
@Override
public void login() {
System.out.println("login");
}
@Override
public void changePasswd() {
System.out.println("changePasswd");
}
}
- Aspect 封装的用于横向插入系统功能(如事务、日志等)的类
package cyt.proxy;
public class MyAspect {
public void check() {
System.out.println("===>权限控制功能");
}
public void logging() {
System.out.println("===>日志功能");
}
public void monitoring() {
System.out.println("===>监测系统功能");
}
}
- 动态代理类
package cyt.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class UserServiceDynamicProxy implements InvocationHandler{
UserService service;
public UserService creatUserService(UserService userService) {
service = userService;
ClassLoader loader = UserServiceDynamicProxy.class.getClassLoader();
Class<?>[] interfaces = service.getClass().getInterfaces();
return (UserService)Proxy.newProxyInstance(loader, interfaces, this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
MyAspect myAspect = new MyAspect();
myAspect.check();
myAspect.logging();
Object result = method.invoke(service, args);
myAspect.monitoring();
return result;
}
}
- 测试
@Test
void test() {
UserService service = new UserServiceImpl();
UserServiceDynamicProxy proxy = new UserServiceDynamicProxy();
UserService serviceProxy = proxy.creatUserService(service);
serviceProxy.register();
System.out.println("==============================");
serviceProxy.login();
System.out.println("==============================");
serviceProxy.changePasswd();
}
结果
===>权限控制功能
===>日志功能
register
===>监测系统功能
==============================
===>权限控制功能
===>日志功能
login
===>监测系统功能
==============================
===>权限控制功能
===>日志功能
changePasswd
===>监测系统功能
AspectJ开发
- 环绕通知:在目标方法执行前后实施增强,可以应用于日志、事务管理等功能。
- 前置通知:在目标方法执行前实施增强,可以应用于权限管理等功能。
- 返回通知:在目标方法成功执行后实施增强,可以应用于关闭流、上传文件、删除临时文件等功能。
- 后置(最终)通知:在目标方法执行后实施增强,不论是否发生异常,该通知都要执行,该类通知可用于释放资源。
- 异常抛出通知:在方法抛出异常后实施增强,可以应用于处理异常记录日志等功能。
前置通知
- 先导入相应的 jar 包
- 相关类的创建
package cyt.aspectj;
public interface Calculator {
public int add(int x,int y);
public int sub(int x,int y);
public int mul(int x,int y);
public int div(int x,int y);
}
package cyt.aspectj;
import org.springframework.stereotype.Component;
@Component
public class CalculatorImpl implements Calculator {
@Override
public int add(int x, int y) {
return x+y;
}
@Override
public int sub(int x, int y) {
return x-y;
}
@Override
public int mul(int x, int y) {
return x*y;
}
@Override
public int div(int x, int y) {
return x/y;
}
}
- Aspect 切面类
@Component
@Aspect
public class MyAspect {
@Before("execution(* cyt.aspectj.CalculatorImpl.add(..))")
public void beforeAnnouncement() {
System.out.println("Before Announcement===>");
}
}
切入点表达式:execution(* com.dgut.aspect..(…))
第一个参数*表示任意返回值,cyt.aspectj.CalculatorImpl表示类,add表示要切入的方法,及参数任意有(…)
- 配置 xml
<context:component-scan base-package="cyt.aspectj"></context:component-scan>
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
注意这里在获取bean时,
Calculator calculator = context.getBean("calculatorImpl", Calculator.class);
其中 Calculator.class 不能写实现类的class!
- 如果要获取方法名和方法参数
在 Aspect 类中添加 JoinPoint jp
@Component
@Aspect
public class MyAspect {
@Before("execution(* cyt.aspectj.CalculatorImpl.add(..))")
public void beforeAnnouncement(JoinPoint jp) {
String methond = jp.getSignature().getName();
Object[] args = jp.getArgs();
System.out.println("Before Announcement===>method name: " + methond + " args: " + Arrays.asList(args));
}
}
返回通知 / 后置(最终)通知 / 异常抛出通知
@AfterReturning(value = "execution(* cyt.aspectj.CalculatorImpl.*(..))", returning = "result")
public void afterReturningAnnouncement(JoinPoint jp, Object result) {
String methond` = jp.getSignature().getName();
System.out.println("AfterReturing Announcement===>method name: " + methond + " return: " + result);
}
@After("execution(* cyt.aspectj.CalculatorImpl.*(..))")
public void afterAnnouncement(JoinPoint jp) {
String methond = jp.getSignature().getName();
System.out.println("After Announcement===>method name: " + methond + " excute. ");
}
@AfterThrowing(value = "execution(* cyt.aspectj.CalculatorImpl.*(..))",throwing = "ex")
public void exceptionAnnouncement(JoinPoint jp,Exception ex) {
String methond = jp.getSignature().getName();
System.out.println("Exception Announcement===>method name: " + methond + " throwing exception: " + ex.getMessage());
}
其中异常抛出通知可以设定只对一种异常作出通知,直接在函数参数中社会组,例如把
exceptionAnnouncement(JoinPoint jp,Exception ex)
改为exceptionAnnouncement(JoinPoint jp,ArithmeticException ex)
就是只对ArithmeticException类数学错误作出通知
环绕通知
@Around("execution(* cyt.aspectj.CalculatorImpl.*(..))")
public Object aroundAnnouncement(ProceedingJoinPoint pjp) {
Object result = null;
try {
//前置通知
System.out.println("前置通知");
result = pjp.proceed();
// 返回通知
System.out.println("返回通知");
} catch (Throwable e) {
// 异常通知
System.out.println("异常通知");
e.printStackTrace();
} finally {
// 后置通知
System.out.println("后置通知");
}
return result;
}
多切面
创建另一个切面类,对同一个函数add切入,可以设置优先级 @Order(1)
@Component
@Aspect
@Order(1)
public class OtherAspect {
@Before("execution(* cyt.aspectj.CalculatorImpl.add(..))")
public void beforeAnnouncement(JoinPoint jp) {
String methond = jp.getSignature().getName();
Object[] args = jp.getArgs();
System.out.println("【OtherAspect】Before Announcement===>method name: " + methond + " args: " + Arrays.asList(args));
}
}
非接口实现类的 AspectJ
在getbean时用该类即可
CalculatorImpl calculator = context.getBean("calculatorImpl", CalculatorImpl.class);
该 CalculatorImpl 没有实现 Calculator 接口
基于XML的AspectJ
<bean id="myAspect" class="com.dgut.aspectj.xml.MyAspect" />
<aop:config>
<aop:aspect id="aspect" ref="myAspect">
<aop:pointcut expression="execution(* com.dgut.apsect.*.*(..))“ id="myPointCut" />
<aop:before method="myBefore" pointcut-ref="myPointCut" />
<aop:after-returning method="myAfterReturning“ pointcut-ref="myPointCut" returning="returnVal" />
<aop:around method="myAround" pointcut-ref="myPointCut" />
<aop:after-throwing method="myAfterThrowing“ pointcut-ref="myPointCut" throwing="e" />
<aop:after method="myAfter" pointcut-ref="myPointCut" />
</aop:aspect>
</aop:config>
JDBC Template
- 定义 bean
package cyt.spring.jdbc.bean;
public class Member {
private Integer memberId;
private String name;
private Integer balance;
public Integer getMemberId() {
return memberId;
}
public void setMemberId(Integer memberId) {
this.memberId = memberId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getBalance() {
return balance;
}
public void setBalance(Integer balance) {
this.balance = balance;
}
@Override
public String toString() {
return "Member [memberId=" + memberId + ", name=" + name + ", balance=" + balance + "]";
}
}
- 创建相应的mysql数据表
- 创建 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"
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-4.3.xsd">
<context:property-placeholder location="classpath:db.properties"/>
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driver}"></property>
<property name="jdbcUrl" value="${jdbc.url}"></property>
<property name="user" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<!-- Jdbc Template -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
</beans>
- 创建配置文件
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/ssm?serverTimezone=Hongkong
jdbc.username=root
jdbc.password=123456
- 测试
class SpringJDBCTest {
private ConfigurableApplicationContext context;
private JdbcTemplate template;
@BeforeEach
void init() {
context = new ClassPathXmlApplicationContext("applicationContext.xml");
template = context.getBean("jdbcTemplate",JdbcTemplate.class);
}
@Test
void testConfig() throws Exception {
System.out.println(template);
Connection connection = template.getDataSource().getConnection();
System.out.println(connection);
}
@Test
void testCRUD() throws Exception {
String sql = "update tbl_member set name=?, balance=? where member_id=2";
template.update(sql, "Noel",300);
String sql1 = "select * from tbl_member where member_id=?";
RowMapper<Member> rowMapper = new BeanPropertyRowMapper<Member>(Member.class);
Member member = template.queryForObject(sql1, rowMapper, 1);
System.out.println(member);
}
@AfterEach
void closeContext() {
context.close();
}
}
声明式事务实现
创建ShopDao,ShopDapImpl及ShopService
ShopDao
package cyt.spring.jdbc.dao;
public interface ShopDao {
public int getGoodsPriceById(int goodsId);
public void decreaseGoodsQuantity(int goodsId);
public void updateBalance(int memberId, int charge);
}
ShopDapImpl
@Repository
public class ShopDaoImpl implements ShopDao {
@Autowired
JdbcTemplate template;
@Override
public int getGoodsPriceById(int goodsId) {
String sql = "select price from tbl_goods where goods_id=?;";
Integer price = template.queryForObject(sql, Integer.class,goodsId);
return price;
}
@Override
public void decreaseGoodsQuantity(int goodsId) {
String sql = "update tbl_goods set quantity=quantity-1 where goods_id=?;";
template.update(sql, goodsId);
String sql2 = "select quantity from tbl_goods where goods_id=?;";
Integer quantity = template.queryForObject(sql2, Integer.class, goodsId);
if (quantity<0) {
throw new QuantityNotEnoughException("Quantity is not enough.");
}
}
@Override
public void updateBalance(int memberId, int charge) {
// TODO Auto-generated method stub
String sql = "update tbl_member set balance = balance-? where member_id=?;";
template.update(sql, charge, memberId);
String sql2 = "select balance from tbl_member where member_id=?";
Integer balance = template.queryForObject(sql2, Integer.class, memberId);
if (balance<0) {
throw new BalanceNotEnoughException("Balance is not enough.");
}
}
}
ShopService
@Service
public class ShopService {
@Autowired
private ShopDao dao;
public void sellGoods(int goodsId,int memberId) {
Integer price = dao.getGoodsPriceById(goodsId);
dao.decreaseGoodsQuantity(goodsId);
dao.updateBalance(memberId, price);
}
}
此例子还需两个异常类,这里给出声明
public class BalanceNotEnoughException extends RuntimeException{}
public class QuantityNotEnoughException extends RuntimeException{}
在xml中加入配置
<!-- config transactionManager -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<tx:annotation-driven transaction-manager="transactionManager"/>
在需要添加事务的方法前加注解 @Transactional
@Transactional
public void sellGoods(int goodsId,int memberId) {
Integer price = dao.getGoodsPriceById(goodsId);
dao.decreaseGoodsQuantity(goodsId);
dao.updateBalance(memberId, price);
}
进行以上操作后,则两个方法的调用:decreaseGoodsQuantity 和 updateBalance 会共同通过,或一个出错两个都回滚!
事务的传递
propagation属性
当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。
事务传播属性可以在@Transactional注解的propagation属性中定义
REQUIRED 传播属性
注意以下 @Transactional(propagation = Propagation.REQUIRED)
@Service
public class ShopService {
@Autowired
private ShopDao dao;
@Transactional(propagation = Propagation.REQUIRED)
public void sellGoods(int goodsId,int memberId) {
Integer price = dao.getGoodsPriceById(goodsId);
dao.decreaseGoodsQuantity(goodsId);
dao.updateBalance(memberId, price);
}
@Transactional
public void buySomeGoods(ArrayList<Integer> goodsIds,int memberId) {
for (Integer goodsId : goodsIds) {
this.sellGoods(goodsId, memberId);
}
}
}
使用 REQUIRED 则不会新创建一个事务,依旧是 buySomeGoods 这个事务,故如果同时买多件商品,中间有一件不能正确购买,则全部不能购买!!!
REQUIRES_NEW 传播属性
要注意一定要有一个另外的bean容器,不可以用 this 去调用sellGoods方法!!!
@Service
public class ShopService {
@Autowired
private ShopDao dao;
@Autowired
private ShopService service;
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void sellGoods(int goodsId,int memberId) {
Integer price = dao.getGoodsPriceById(goodsId);
dao.decreaseGoodsQuantity(goodsId);
dao.updateBalance(memberId, price);
}
@Transactional
public void buySomeGoods(ArrayList<Integer> goodsIds,int memberId) {
for (Integer goodsId : goodsIds) {
service.sellGoods(goodsId, memberId);
// this.sellGoods(goodsId, memberId);
// 如果用上面这个this语句则会失效,sellGoods 的事务注解会失效,原因参考 aop 的原理
}
}
}
如果用上面这个this语句则会失效,sellGoods 的事务注解会失效,原因参考 aop 的原理,解决办法则是创建一个另外的对象应用并生成容器
private ShopService service;
重点重点重点重点重点重点重点重点重点重点重点
====== Spring 끝나다 !======