AOP
1)AOP(Aspect-Oriented Programming,面向切面编程):是一种新的方法论,是对传 统 OOP(Object-Oriented Programming,面向对象编程)的补充。 面向对象 纵向继承机制 面向切面 横向抽取机制
2)AOP编程操作的主要对象是切面(aspect),而切面用于模块化横切关注点(公共功能)。
3)在应用AOP编程时,仍然需要定义公共功能,但可以明确的定义这个功能应用在哪里,以什么方式应用,并且不必修改受影响的类。这样一来横切关注点就被模块化到特殊的类里——这样的类我们通常称之为“切面”。
4)AOP的好处: ① 每个事物逻辑位于一个位置,代码不分散,便于维护和升级 ② 业务模块更简洁,只包含核心业务代码 ③ AOP图解
AspectJ
AspectJ:Java社区里最完整最流行的AOP框架。 在Spring2.0以上版本中,可以使用基于AspectJ注解或基于XML配置的AOP。
在Spring中启用AspectJ注解支持 1)导入JAR包 com.springsource.net.sf.cglib-2.2.0.jar com.springsource.org.aopalliance-1.0.0.jar com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar spring-aop-4.0.0.RELEASE.jar spring-aspects-4.0.0.RELEASE.jar 2)引入aop名称空间 3)配置
<aop:aspectj-autoproxy>
当Spring IOC容器侦测到bean配置文件中的<aop:aspectj-autoproxy> 元素时,会自动为 与AspectJ切面匹配的bean创建代理
用AspectJ注解声明切面
用AspectJ注解声明切面 1)要在Spring中声明AspectJ切面,只需要在IOC容器中将切面声明为bean实例。
2)当在Spring IOC容器中初始化AspectJ切面之后,Spring IOC容器就会为那些与 AspectJ切面相匹配的bean创建代理。
3)在AspectJ注解中,切面只是一个带有@Aspect注解的Java类,它往往要包含很多通知。
4)通知是标注有某种注解的简单的Java方法。
5)AspectJ支持5种类型的通知注解: ① @Before:前置通知,在方法执行之前执行 ② @After:后置通知,在方法执行之后执行 ③ @AfterRunning:返回通知,在方法返回结果之后执行 ④ @AfterThrowing:异常通知,在方法抛出异常之后执行 ⑥ @Around:环绕通知,围绕着方法执行
切入点表达式
通过表达式的方式定位一个或多个具体的连接点。
1)切入点表达式的语法格式
execution([权限修饰符] [返回值类型] [简单类名/全类名] [方法名]([参数列表]))
2)在AspectJ中,切入点表达式可以通过 “&&”、“||”、“!”等操作符结合起来。
execution (* *.add(int,..)) || execution(* *.sub(int,..))
含义 任意类中第一个参数为int类型的add方法或sub方法
前置通知
1)前置通知:在方法执行之前执行的通知 2)使用@Before注解
后置通知
1)后置通知:后置通知是在连接点完成之后执行的,即连接点返回结果或者抛出异常的时候 2)使用@After注解
返回通知
1)返回通知:无论连接点是正常返回还是抛出异常,后置通知都会执行。如果只想在连接点返回的时候记录日志,应使用返回通知代替后置通知。
2)使用@AfterReturning注解,在返回通知中访问连接点的返回值 ①在返回通知中,只要将returning属性添加到@AfterReturning注解中,就可以访问连接点的返回值。该属性的值即为用来传入返回值的参数名称 ②必须在通知方法的签名中添加一个同名参数。在运行时Spring AOP会通过这个参数传递返回值 ③原始的切点表达式需要出现在pointcut属性中
异常通知
1)异常通知:只在连接点抛出异常时才执行异常通知
2)将throwing属性添加到@AfterThrowing注解中,也可以访问连接点抛出的异常。Throwable是所有错误和异常类的顶级父类,所以在异常通知方法可以捕获到任何错误和异常。
3)如果只对某种特殊的异常类型感兴趣,可以将参数声明为其他异常的参数类型。然后通知就只在抛出这个类型及其子类的异常时才被执行
环绕通知
1)环绕通知是所有通知类型中功能最为强大的,能够全面地控制连接点,甚至可以控制是否执行连接点。
2)对于环绕通知来说,连接点的参数类型必须是ProceedingJoinPoint。它是 JoinPoint的子接口,允许控制何时执行,是否执行连接点。
3)在环绕通知中需要明确调用ProceedingJoinPoint的proceed()方法来执行被代理的方法。如果忘记这样做就会导致通知被执行了,但目标方法没有被执行。
4)注意:环绕通知的方法需要返回目标方法执行之后的结果,即调用 joinPoint.proceed();的返回值,否则会出现空指针异常。
指定切面的优先级
1)在同一个连接点上应用不止一个切面时,除非明确指定,否则它们的优先级是不确定的。 2)切面的优先级可以通过实现Ordered接口或利用@Order注解指定。 3)实现Ordered接口,getOrder()方法的返回值越小,优先级越高。 4)若使用@Order注解,序号出现在注解中
演示案例:
aop.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:aop="http://www.springframework.org/schema/aop"
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/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.spring.aop"/>
<aop:aspectj-autoproxy/>
</beans>
MathImpl
package com.spring.aop;
import org.springframework.stereotype.Component;
@Component
public class MathImpl implements MathI {
@Override
public int add(int i, int j) {
int result = i + j;
return result;
}
@Override
public int sub(int i, int j) {
int result = i - j;
return result;
}
@Override
public int mul(int i, int j) {
int result = i * j;
return result;
}
@Override
public int div(int i, int j) {
int result = i / j;
return result;
}
}
MyloggerAspect切面
package com.spring.aop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.util.Arrays;
@Component
@Aspect
@Order(1)
public class MyloggerAspect {
@Pointcut(value="execution(* com.spring.aop.*.*(..))")
public void test() {}
@Before(value = "test()")
public void brforeMethod(JoinPoint joinPoint){
Object[] args = joinPoint.getArgs();
String methodName = joinPoint.getSignature().getName();
System.out.println("前置通知:method:"+methodName+",arguments:"+ Arrays.toString(args));
}
@After(value = "execution(* com.spring.aop.*.*(..))")
public void afterMethod(){
System.out.println("后置通知");
}
@AfterReturning(value="execution(* com.spring.aop.*.*(..))", returning="result")
public void afterReturningMethod(JoinPoint joinPoint, Object result) {
String methodName = joinPoint.getSignature().getName();
System.out.println("返回通知:method:"+methodName+",result:"+result);
}
@AfterThrowing(value = "execution(* com.spring.aop.*.*(..))",throwing = "ex")
public void afterThrowingMethod(Exception ex){
System.out.println("有异常,message:"+ex);
}
}
TestHandler切面
package com.spring.aop;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
@Component
@Aspect
@Order(0)
public class TestHandler {
@Before(value="execution(* com.spring.aop.*.*(..))")
public void before() {
System.out.println("TestHandler==>前置通知");
}
}
public class TestAOP {
public static void main(String[] args) {
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("aop.xml");
MathI mathImpl = applicationContext.getBean("mathImpl", MathI.class);
mathImpl.add(1,2);
System.out.println("------------------------");
mathImpl.sub(4,2);
System.out.println("------------------------");
mathImpl.div(1,0);
}
}
测试结果:
以XML方式配置切面
除了使用AspectJ注解声明切面,Spring也支持在bean配置文件中声明切面。这种声明是通过aop名称空间中的XML元素完成的。 正常情况下,基于注解的声明要优先于基于XML的声明。通过AspectJ注解,切面可以与AspectJ兼容,而基于XML的配置则是Spring专有的。由于AspectJ得到越来越多的 AOP框架支持,所以以注解风格编写的切面将会有更多重用的机会。
<?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"
xmlns:aop="http://www.springframework.org/schema/aop"
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">
<context:component-scan base-package="com.spring.aopxml"></context:component-scan>
<aop:config>
<aop:aspect ref="myLogger">
<aop:pointcut id="cut" expression="execution(* com.spring.aopxml.*.*(..))"/>
<aop:before method="before" pointcut-ref="cut"/>
</aop:aspect>
</aop:config>
</beans>
package com.spring.aopxml;
import org.springframework.stereotype.Component;
@Component
public class MyLogger {
public void before(){
System.out.println("前置通知:");
}
}
package com.spring.aopxml;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class test {
public static void main(String[] args) {
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("aopxml.xml");
MathI mathImpl = applicationContext.getBean("mathImpl", MathI.class);
System.out.println(mathImpl.add(12,12));
}
}
JdbcTemplate
为了使JDBC更加易于使用,Spring在JDBC API上定义了一个抽象层,以此建立一个JDBC存取框架。 作为Spring JDBC框架的核心,JDBC模板的设计目的是为不同类型的JDBC操作提供模板方法,通过这种方式,可以在尽可能保留灵活性的情况下,将数据库存取的工作量降到最低。 可以将Spring的JdbcTemplate看作是一个小型的轻量级持久化层框架,和我们之前使用过的DBUtils风格非常接近。
环境准备 导入JAR包 1)IOC容器所需要的JAR包 commons-logging-1.1.1.jar spring-beans-4.0.0.RELEASE.jar spring-context-4.0.0.RELEASE.jar spring-core-4.0.0.RELEASE.jar spring-expression-4.0.0.RELEASE.jar
2)JdbcTemplate所需要的JAR包 spring-jdbc-4.0.0.RELEASE.jar spring-orm-4.0.0.RELEASE.jar spring-tx-4.0.0.RELEASE.jar
3)数据库驱动和数据源 druid-1.1.9.jar mysql-connector-java-5.1.7-bin.jar
持久化操作
首先配置文件:
<context:property-placeholder location="db.properties"></context:property-placeholder>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driver}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
通过IOC容器获得jdbctemplate对象
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("jdbc.xml");
JdbcTemplate jdbcTemplate = applicationContext.getBean("jdbcTemplate", JdbcTemplate.class);
增删改
增删改:
@Test
public void test(){
String sql="insert into emp values(null,?,?,?)";
jdbcTemplate.update(sql,"李四",24,"女");
String deletesql="delete from emp where eid in (?)";
String eids="1,2,3";
jdbcTemplate.update(deletesql,eids);
}
批量增删改
@Test
public void testBatchUpdate(){
String sql="insert into emp values(null,?,?,?)";
ArrayList<Object[]> list = new ArrayList<>();
list.add(new Object[]{"a1",1,"男"});
list.add(new Object[]{"a2",2,"男"});
list.add(new Object[]{"a3",3,"男"});
jdbcTemplate.batchUpdate(sql,list);
}
查询
@Test
public void testqueryforobject() {
String sql = "select ename,age,sex,eid from emp where eid=?";
BeanPropertyRowMapper<Emp> rowMapper = new BeanPropertyRowMapper<>(Emp.class);
Emp emp = jdbcTemplate.queryForObject(sql, new Object[]{1}, rowMapper);
System.out.println(emp);
String sql2 = "select count(*) from emp";
Integer count = jdbcTemplate.queryForObject(sql2, Integer.class);
System.out.println(count);
String sql3 = "select ename,age,eid,sex from emp";
List<Emp> list = jdbcTemplate.query(sql3, rowMapper);
for (Emp emplist : list) {
System.out.println(emplist);
}
}
事务问题
事务就是一组由于逻辑上紧密关联而合并成一个整体(工作单元)的多个数据库操作,这些操作要么都执行,要么都不执行。
Spring事务管理
编程式事务管理
1)使用原生的JDBC API进行事务管理 ①获取数据库连接Connection对象 ②取消事务的自动提交 ③执行操作 ④正常完成操作时手动提交事务 ⑤执行失败时回滚事务 ⑥关闭相关资源
2)评价 使用原生的JDBC API实现事务管理是所有事务管理方式的基石,同时也是最典型的编程式事务管理。编程式事务管理需要将事务管理代码嵌入到业务方法中来控制事务的提交和回滚。在使用编程的方式管理事务时,必须在每个事务操作中包含额外的事务管理代码。相对于核心业务而言,事务管理的代码显然属于非核心业务,如果多个模块都使用同样模式的代码进行事务管理,显然会造成较大程度的代码冗余。
声明式事务管理
大多数情况下声明式事务比编程式事务管理更好:它将事务管理代码从业务方法中分离出来,以声明的方式来实现事务管理。
事务管理代码的固定模式作为一种横切关注点,可以通过AOP方法模块化,进而借助Spring AOP框架实现声明式事务管理。 Spring在不同的事务管理API之上定义了一个抽象层,通过配置的方式使其生效,从而让应用程序开发人员不必了解事务管理API的底层实现细节,就可以使用Spring的事务管理机制。 Spring既支持编程式事务管理,也支持声明式的事务管理。
Spring提供的事务管理器
Spring从不同的事务管理API中抽象出了一整套事务管理机制,让事务管理代码从特定的事务技术中独立出来。开发人员通过配置的方式进行事务管理,而不必了解其底层是如何实现的。 Spring的核心事务管理抽象是它为事务管理封装了一组独立于技术的方法。无论使用Spring的哪种事务管理策略(编程式或声明式),事务管理器都是必须的。
事务管理器可以以普通的bean的形式声明在Spring IOC容器中。 事务管理器的主要实现: 1)DataSourceTransactionManager:在应用程序中只需要处理一个数据源,而且通过JDBC存取。 2)JtaTransactionManager:在JavaEE应用服务器上用JTA(Java Transaction API)进行事务管理 3)HibernateTransactionManager:用Hibernate框架存取数据库
事务的传播行为
当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。 事务的传播行为可以由传播属性指定。Spring定义了7种类传播行为。
事务传播属性可以在@Transactional注解的propagation属性中定义。 说明 ①REQUIRED传播行为 当bookService的purchase()方法被另一个事务方法checkout()调用时,它默认会在现有的事务内运行。这个默认的传播行为就是REQUIRED。因此在checkout()方法的开始和终止边界内只有一个事务。这个事务只在checkout()方法结束的时候被提交,结果用户一本书都买不了。
②REQUIRES_NEW传播行为 表示该方法必须启动一个新事务,并在自己的事务内运行。如果有事务在运行,就应该先挂起它。
事务的隔离级别
数据库事务并发问题 假设现在有两个事务:Transaction01和Transaction02并发执行。 1)脏读 ①Transaction01将某条记录的AGE值从20修改为30。 ②Transaction02读取了Transaction01更新后的值:30。 ③Transaction01回滚,AGE值恢复到了20。 ④Transaction02读取到的30就是一个无效的值。 2)不可重复读 ①Transaction01读取了AGE值为20。 ②Transaction02将AGE值修改为30。 ③Transaction01再次读取AGE值为30,和第一次读取不一致。 3)幻读 ①Transaction01读取了STUDENT表中的一部分数据。 ②Transaction02向STUDENT表中插入了新的行。 ③Transaction01读取了STUDENT表时,多出了一些行。
事务的超时和只读属性
由于事务可以在行和表上获得锁,因此长事务会占用资源,并对整体性能产生影响。 如果一个事务只读取数据但不做修改,数据库引擎可以对这个事务进行优化。
超时事务属性:事务在强制回滚之前可以保持多久。这样可以防止长期运行的事务占用资源。 只读事务属性: 表示这个事务只读取数据但不更新数据, 这样可以帮助数据库引擎优化事务。
事务总结(源代码测试)
通过aspectj注解方式配置事务
book.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" 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/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
<context:component-scan base-package="com.spring.book"></context:component-scan>
<context:property-placeholder location="db.properties"></context:property-placeholder>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driver}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="dataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<tx:annotation-driven transaction-manager="dataSourceTransactionManager"/>
</beans>
BookDaoImpl
package com.spring.book.dao;
import com.spring.book.MyException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
@Repository
public class BookDaoImpl implements BookDao {
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public Integer selectPrice(String bid) {
Integer price = jdbcTemplate.queryForObject("select price from book where bid=?", new Object[]{bid}, Integer.class);
return price;
}
@Override
public void updateSt(String bid) {
Integer integer = jdbcTemplate.queryForObject("select st from stock where sid=?", new Object[]{bid}, Integer.class);
if (integer<=0){
throw new RuntimeException();
}else {
jdbcTemplate.update("update stock set st=st-1 where sid=?",bid);
}
}
@Override
public void updateBalance(String uid ,Integer price) {
Integer integer = jdbcTemplate.queryForObject("select balance from customer where uid=?", new Object[]{uid}, Integer.class);
if (integer<price){
throw new MyException();
}else{
jdbcTemplate.update("update customer set balance=balance-? where uid=? ",price,uid);
}
}
}
BookServiceImpl
package com.spring.book.service;
import com.spring.book.MyException;
import com.spring.book.dao.BookDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Service
public class BookServiceImpl implements BookService {
@Autowired
private BookDao bookDao;
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW,timeout = 3,rollbackFor = {NullPointerException.class, MyException.class})
public void buyBook(String bid, String uid) {
Integer price = bookDao.selectPrice(bid);
bookDao.updateSt(bid);
bookDao.updateBalance(uid,price);
}
}
CashierServiceImpl
package com.spring.book.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Service
@Transactional
public class CashierServiceImpl implements Cashier {
@Autowired
private BookService service;
@Override
public void checkOut(String uid, List<String> bids) {
for (String bid :
bids) {
service.buyBook(bid,uid);
}
}
}
BookController
package com.spring.book.controller;
import com.spring.book.service.BookService;
import com.spring.book.service.Cashier;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import java.util.ArrayList;
@Controller
public class BookController {
@Autowired
private BookService bookService;
@Autowired
private Cashier cashier;
public void buybook(){
bookService.buyBook("1","1001");
}
public void checkOut(){
ArrayList<String> bids = new ArrayList<>();
bids.add("1");
bids.add("2");
cashier.checkOut("1001",bids);
}
}
public class test {
public static void main(String[] args) {
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("book.xml");
BookController controller = applicationContext.getBean("bookController", BookController.class);
controller.checkOut();
}
}
通过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"
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-4.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd">
<context:component-scan base-package="com.atguigu.book_xml"></context:component-scan>
<context:property-placeholder location="db.properties"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driver}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<tx:advice id="tx" transaction-manager="dataSourceTransactionManager">
<tx:attributes>
<tx:method name="buyBook"/>
<tx:method name="checkOut"/>
<tx:method name="select*" read-only="true"/>
<tx:method name="insert*"/>
<tx:method name="update*"/>
<tx:method name="delete*"/>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut expression="execution(* com.atguigu.book_xml.service.impl.*.*(..))" id="pointCut"/>
<aop:advisor advice-ref="tx" pointcut-ref="pointCut"/>
</aop:config>
</beans>
|