Spring 基础知识笔记【下】

本篇博客内容:

  1. Spring AOP
  2. JDBC Template
  3. 声明式事务实现

Spring AOP

JDK 动态代理实现 AOP

使用JDK动态代理需要动态代理的该类是一个接口的实现

  1. Service 接口
package cyt.proxy;

public interface UserService {
	public void register();
	public void login();
	public void changePasswd();
}
  1. 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");
	}

}
  1. 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("===>监测系统功能");
	}
}
  1. 动态代理类
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;
	}
}
  1. 测试
@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开发

  • 环绕通知:在目标方法执行前后实施增强,可以应用于日志、事务管理等功能。
  • 前置通知:在目标方法执行前实施增强,可以应用于权限管理等功能。
  • 返回通知:在目标方法成功执行后实施增强,可以应用于关闭流、上传文件、删除临时文件等功能。
  • 后置(最终)通知:在目标方法执行后实施增强,不论是否发生异常,该通知都要执行,该类通知可用于释放资源。
  • 异常抛出通知:在方法抛出异常后实施增强,可以应用于处理异常记录日志等功能。
前置通知
  1. 先导入相应的 jar 包
  2. 相关类的创建
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;
	}

}
  1. 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表示要切入的方法,及参数任意有(…)

  1. 配置 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!

  1. 如果要获取方法名和方法参数
    在 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

  1. 定义 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 + "]";
	}

}
  1. 创建相应的mysql数据表
  2. 创建 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>
  1. 创建配置文件
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/ssm?serverTimezone=Hongkong
jdbc.username=root
jdbc.password=123456
  1. 测试
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 끝나다 !======

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值