Spring新手指南(下)
AOP:
概念:(Aspect Oriented Programing)指在程序运行期间,将某段代码动态的切入到指定方法的指定位置进行运行的编程方式
动态代理
注意点: 被代理的对象必须实现某个接口(即代理对象与被代理对象均实现了同一个接口)
专业术语
初体验
使用步骤:
- 导包
基础包+增强包 - 写配置
- 将目标类和切面类(封装了通知方法)加入到容器中
- 使用@Aspect注解告诉Spring这个一个切面类
- 告诉Spring,切面类里面的每一个方法,都是何时何地的运行
package pers.lele.utils; import org.aspectj.lang.annotation.*; import org.springframework.stereotype.Component; /** * @Before: 前置通知, 在方法执行之前执行 * @After: 后置通知, 在方法执行之后执行 * @AfterRunning:返回通知, 在方法成功执行返回结果之后执行 * @AfterThrowing: 异常通知, 在方法抛出异常之后 * @Around: 环绕通知, 围绕着方法执行 */ //标识该类是一个切面类 @Component @Aspect public class LogUtils { //定义通知方法 @Before("execution(public int pers.lele.service.MyMathCalculator.*(int, int))") public void LogBefore() { System.out.println("LogBefore方法被执行了..."); } @After("execution(public int pers.lele.service.MyMathCalculator.*(int, int))") public void LogAfter() { System.out.println("LogAfter方法被执行了..."); } @AfterReturning("execution(public int pers.lele.service.MyMathCalculator.*(int, int))") public void LogAfterReturning() { System.out.println("LogAfterReturning方法被执行了..."); } @After("execution(public int pers.lele.service.MyMathCalculator.*(int, int))") public void LogAfterThrowing() { System.out.println("LogAfterThrowing方法被执行了..."); } }
- 开启基于注解的AOP功能
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
- 测试
@SpringJUnitConfig(locations = "classpath:applicationContext.xml") @RunWith(SpringJUnit4ClassRunner.class) public class CalculatorTest { @Autowired Calculator calculator; // MyMathCalculator myMathCalculator; @Test public void test01() { /** * org.springframework.beans.factory.UnsatisfiedDependencyException: * Error creating bean with name 'CalculatorTest': * Unsatisfied dependency expressed through field 'myMathCalculator'; * nested exception is org.springframework.beans.factory.BeanNotOfRequiredTypeException: * Bean named 'myMathCalculator' is expected to be of type 'pers.lele.service.MyMathCalculator' * but was actually of type 'com.sun.proxy.$Proxy23' */ /*只能通过接口来获取bean*/ // int result = myMathCalculator.add(1, 2); // System.out.println(result); } @Test public void test02() { ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); Calculator bean = context.getBean(Calculator.class); // System.out.println(bean.getClass()); // int result = bean.add(1, 2); // System.out.println(result); } }
细节
-
若目标类实现了接口,则若从容器中通过类型来拿到目标对象,要用接口类型来获取,若从容器中通过id来拿到对象,要用接口来接受该对象
-
AOP的底层是动态代理,容器中保存的组件是它的代理对象
-
即使目标类不实现接口,也可以获取到代理对象(通过cglib),用目标类接收就可以
-
切入点表达式:execution(访问权限符 返回值类型 方法全类名(参数表))
通配符* :
1. 匹配一个或多个字符
2. 匹配任意一个参数
3. 只能匹配一层路径
4. 权限位置*不能表示任意,当不写时表示任意,
通配符有…:
1. 匹配任意多个参数
2. 匹配任意多层路径最模糊的 execution(* .(…))
&&:切入的位置同时满足
|| :
!: -
当程序正常运行时,执行顺序 before after afterReturning,当程序异常运行时,执行顺序 before after adterthrowing
try{ @before div(); @AfterReturning }catch(){ @AfterThrowing }final { @After }
-
在通知方法的参数列表上写一个参数 JoinPoint 来获取方法的详细信息(获取连接点的详细信息)
```java
@Before("execution(public int pers.lele.service.MyMathCalculator.*(int, int))")
public void LogBefore(JoinPoint joinPoint) {
Signature signature = joinPoint.getSignature();
String name = signature.getName();
System.out.println(name+" : "+ Arrays.asList(joinPoint.getArgs()));
System.out.println("LogBefore方法被执行了...");
}
```
-
在标有注解@AfterReturning的方法上,通过returning来指定接收方法的返回值
@AfterReturning(value = "execution(public int pers.lele.service.MyMathCalculator.*(int, int))", returning = "result") public void LogAfterReturning(Object result) { System.out.println("结果是:" + result); System.out.println("LogAfterReturning方法被执行了..."); }
-
在标有注解@AfterThrowing,通过throwing来指定接收方法的返回值
@AfterThrowing(value = "execution(public int pers.lele.service.MyMathCalculator.*(int, int))",
throwing = "result")
public void LogAfterThrowing(Throwable result) {
System.out.println("错误信息:" + result);
System.out.println("LogAfterThrowing方法被执行了...");
}
- (Spring对通知方法的约束)通知方法的参数不能随便写,必须要Spring清楚
//因为方法参数的返回值是doubel类型,而真正的返回值是int类型,当类型不匹配时就不会执行响应的通知方法
@AfterReturning(value = "execution(public int pers.lele.service.MyMathCalculator.*(int, int))",
returning = "result")
public void LogAfterReturning(double result) {
System.out.println("结果是:" + result);
System.out.println("LogAfterReturning方法被执行了...");
}
-
抽取可重用的切入点表达式
- 随便声明一个没有实现的返回void的空方法
- 给方法上标注@Pointcut注解
@Pointcut("execution(public int pers.lele.service.MyMathCalculator.*(int, int))") public void myPointCut(){ } //定义通知方法 @Before(value = "myPointCut()") public void LogBefore(JoinPoint joinPoint) { Signature signature = joinPoint.getSignature(); String name = signature.getName(); System.out.println(name + " : " + Arrays.asList(joinPoint.getArgs())); System.out.println("LogBefore方法被执行了..."); }
-
环绕通知:@Around:本身就是一个动态代理
//引用可重用切入点 @Around("myPointCut()") public Object myAround(ProceedingJoinPoint pjp) throws Throwable { Object result = null; //获取参数 Object[] args = pjp.getArgs(); //执行方法 try { System.out.println("前置通知"); result = pjp.proceed(args); System.out.println("返回通知"); } catch (Exception e) { e.printStackTrace(); System.out.println("异常通知"); } finally { System.out.println("后置通知"); return result; } } //顺序 前置通知 返回通知 后置通知
-
环绕通知和普通通知一起作用的执行顺序
- 正常情况下:环绕前置 普通前置 方法执行 环绕返回 环绕后置 普通后置 普通返回
- 异常情况下:环绕前置 普通前置 方法执行 环绕异常 环绕后置 普通后置 普通返回
try{ 普通通知:LogBefore方法被执行了... { //环绕通知 try{ 环绕通知:前置通知 方法执行 环绕通知:返回通知 }catch(){ 环绕通知:异常通知 }final{ 环绕通知:后置通知 } } 普通通知:LogAfterReturning方法被执行了... }catch(Exception e){ 普通通知:LogAfterThrowing方法被执行了... }final { 普通通知:LogAfter方法被执行了... }
- 为了使得普通的异常能捕获到环绕异常,则需要在环绕异常内部进行抛出异常
@Around("myPointCut()") public Object myAround(ProceedingJoinPoint pjp) throws Throwable { Object result = null; //获取参数 Object[] args = pjp.getArgs(); //执行方法 try { System.out.println("环绕通知:前置通知"); result = pjp.proceed(args); System.out.println("环绕通知:返回通知"); } catch (Exception e) { System.out.println("环绕通知:异常通知"); throw new RuntimeException(e); } finally { System.out.println("环绕通知:后置通知"); } return result; }
- 多切面运行顺序
- 多切面的运行顺序
- 对于多个切面而言,执行顺序根据字母的大小进行排序
- AOP使用场景:
- AOP做日志保存到数据库
- 权限验证
- AOP做安全检查
- 事务控制
基于注解的AOP配置步骤
- 将目标类和切面类都加入到ioc容器中
- 告诉Spring哪个是切面类@Aspect
- 在切面类中使用注解来配置切面类中哪些方法何时何地执行
- 开启注解的AOP功能
基于配置文件的AOP配置步骤
<!--1.将目标类与切面类都加入到ioc容器中-->
<bean id="userServlet" class="pers.lele.servlet.UserServlet"></bean>
<bean id="logUtils" class="pers.lele.utils.LogUtils"></bean>
<bean id="timeUtils" class="pers.lele.utils.TimeUtils"></bean>
<!--2.告诉Spring哪个是切面类-->
<aop:config>
<!--切入点-->
<aop:pointcut id="deleteUserById"
expression="execution(public * pers.lele.servlet.UserServlet.deleteUserById(Integer))"/>
<!--2.告诉Spring哪些是切面类-->
<aop:aspect ref="logUtils">
<!--3.告诉Spring通知方法何地运行-->
<aop:before method="LogBefore" pointcut-ref="deleteUserById"/>
<aop:after-returning method="LogAfterReturning" pointcut-ref="deleteUserById" returning="result"/>
<aop:after-throwing method="LogAfterThrowing" pointcut-ref="deleteUserById" throwing="e"/>
<aop:after method="LogAfter" pointcut-ref="deleteUserById"/>
<aop:around method="LogAround" pointcut-ref="deleteUserById"/>
</aop:aspect>
<aop:aspect ref="timeUtils">
<aop:before method="LogBefore" pointcut-ref="deleteUserById"/>
<aop:after-returning method="LogAfterReturning" pointcut-ref="deleteUserById" returning="result"/>
<aop:after-throwing method="LogAfterThrowing" pointcut-ref="deleteUserById" throwing="e"/>
<aop:after method="LogAfter" pointcut-ref="deleteUserById"/>
</aop:aspect>
</aop:config>
注解与配置文件比较
重要的用配置,不重要的用注解
声明式事务
事务的特征:
- 原子性
- 一致性
- 隔离性
- 持久性
Spring事务的使用
- 配置事务管理器,让其进行事务控制
<!--1.配置事务管理器,让其进行事务控制-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--控制住数据源-->
<property name="dataSource" ref="dataSource"></property>
</bean>
- 开启基于注解的事务控制模式,依赖tx名称空间 需要aop包
<!--2.开启基于注解的事务控制模式,依赖tx名称空间 需要aop包-->
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
- 给事务方法加注解即可
@Transactional
public void checkOut(String username, String ISBN) {
//根据SBN查出价格
Float price = bookMapper.queryPriceByISBN(ISBN);
Account account = accountMapper.queryAccountByUsername(username);
account.setBalance(account.getBalance() - price);
//修改用户价格
accountMapper.updateBalance(account);
//修改库存
BookStock bookStock = bookStockMapper.queryBookStockByISBN(ISBN);
bookStock.setStock(bookStock.getStock() - 1);
bookStockMapper.updateBookStock(bookStock);
}
ApplicationContext与BeanFactory的区别
ApplicationContext是BeanFactory的子接口,
BeanFactory:bean的工厂接口,负责创建bean实例,保存所有单例的bean其实是一个map 最底层的接口
ApplicationContext是容器接口,更多负责容器功能的实现,留给程序员使用的
事务细节
-
timeout() 超出指定时长后自动终止并回滚,以秒为单位,这里的超时指的是方法的内sql语句在指定的时间内全部执行完才
超时,如一下情况不会超时@Transactional(timeout = 3) public void updateBalance(Float price) { Account account = new Account("Jerry", price); accountMapper.updateBalance(account); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } // accountMapper.updateBalance(account); // int i = 1 / 0; }
-
readOnly() 设置事务为只读事务,加快查询速度
-
异常
- 运行时异常(非检查异常):可以不用处理,默认都回滚
- 编译时异常(检查异常):可以进行try-catch或抛出,默认不回滚
-
Class<? extends Throwable>[] noRollbackFor() default {};哪些异常事务可以不回滚
@Transactional(noRollbackFor = {ArithmeticException.class}) public void updateBalance(Float price) { Account account = new Account("Jerry", price); accountMapper.updateBalance(account); int i = 1 / 0; }
-
Class<? extends Throwable>[] RollbackFor() default {};哪些异常事务可以回滚
@Transactional(rollbackFor = FileNotFoundException.class) public void updateBalance(Float price) throws FileNotFoundException { Account account = new Account("Jerry", price); accountMapper.updateBalance(account); //对于事务中的编译时异常,不要进行try catch new FileInputStream("123"); }
-
事务的隔离级别 transaction isolation level:数据库系统必须具有隔离并发运行各个事务的能力,使得他们不会相互影响,避免各种并发问题
- 读未提交 (出现脏读)
- 读已提交
- 可重复读
- 串行化
- 事务的传播行为(事务的传播+事务的行为)
如果有多个事务进行嵌套,子事务是否要和大事务一起回滚,当子事务不设置传播行为时,默认应该是REQUIRED的@Transactional public void mulOperator(Account account, Book book) { accountService.updateBalance(account); bookService.updatePrice(book); System.out.println("exe..."); int i = 1 / 0; } //与大事务同一车 使用之前的connection @Transactional(propagation = Propagation.REQUIRED) public void updateBalance(Account account){ accountMapper.updateBalance(account); } //自己开自己的车,不与大事务开一条车 新创建一个connection @Transactional( propagation = Propagation.REQUIRES_NEW) public void updatePrice(Book book) { bookMapper.updatePriceByISBN(book); }
- REQUIRED事务属性继承于大事务,而propagation-Propagation.REQUESTS_NEW可以调整
- 若上面的mulOperator与(updateBalance或updatePrice)在同一个类中,
当大事务出错时会直接回滚,无论只事务是否是EQUIRES_NEW(本类方法的嵌套调用就只是一个事务)
基于xml配置事务
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<tx:advice id="myAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!--name:方法名-->
<tx:method name="" propagation=""/>
</tx:attributes>
</tx:advice>
<aop:config>
<!--配置哪些方法是切面方法-->
<aop:pointcut id="pointcut" expression=" execution(*.*())"/>
<aop:advisor advice-ref="myAdvice" pointcut-ref="pointcut"></aop:advisor>
</aop:config>