复用代码和解耦
(1)关注点
- 重复代码就叫做关注点
(2)切入点
- 执行目标对象的方法,动态植入切面代码
- 可以通过切入点表达式,指定拦截哪些类的哪些方法;给指定的类在运行的时候植入切面类的代码
(3)切面
- @Aspect的类
- 关注点形成的类,就叫做切面(类)
- 面向切面编程,就是指对很多功能都有的重复代码进行抽取,再在运行的时候向业务方法上动态植入“切面类的代码”
AOP的原理其实就是依靠静态代理和动态代理进行实现的。其中静态代理需要手动生成目标代理对象(不推荐);动态代理又分为JDK的动态代理(基于反射实现)和CGLIB动态代理(CGLIB底层基于ASM实现的),推荐使用CGLIB因为,CGLIB效率高,直接操作字节码的效率是高于反射的。都是虚拟生成代理类的
(1)java动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法之前调用InvokeHandler来处理。而CGLIB利用了ASM开源包,对代理对象类的class文件做修改和新增,将代理对象类的class文件加载进来,通过修改其字节码生成子类来处理
(2)Spring中,如果目标对象实现了接口,默认情况下采用JDK的动态代理实现AOP
(3)Spring中,如果目标对象实现了接口,可以强制使用CGLIB实现AOP
(4)Spring中,如果目标对象没有实现接口,必须采用CGLIB,spring会自动在JDK和CGLIB之间做转换。
(5)JDK动态代理只能针对实现了接口的类生成代理,而不能针对类。
(6)CGLIB是针对类实现代理,主要是对指定的类生成了一个子类,覆盖其中的方法
因为是继承所以类不要用final修饰,final修饰类,不能被继承
@Before
在方法执行之前执行
(2)后置通知
@After
在方法运行后执行
(3)运行通知
@AfterReturning
(4)异常通知
@AfterThrowing
发生异常后执行,不发生异常就不会执行。
(5)环绕通知
@Around
在方法之前和之后处理
其中有参数ProceedingJoinPoint,proceedingJoinPoint.proceed()调用方法。如果调用方法抛出异常,不会执行环绕通知后面的代码。
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<groupId>com.xiyougroupId>
<artifactId>spring-aopartifactId>
<version>1.0-SNAPSHOTversion>
<dependencies>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-coreartifactId>
<version>3.0.6.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-contextartifactId>
<version>3.0.6.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-aopartifactId>
<version>3.0.6.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-ormartifactId>
<version>3.0.6.RELEASEversion>
dependency>
<dependency>
<groupId>org.aspectjgroupId>
<artifactId>aspectjrtartifactId>
<version>1.6.1version>
dependency>
<dependency>
<groupId>aspectjgroupId>
<artifactId>aspectjweaverartifactId>
<version>1.5.3version>
dependency>
<dependency>
<groupId>cglibgroupId>
<artifactId>cglibartifactId>
<version>2.1_2version>
dependency>
<dependency>
<groupId>com.mchangegroupId>
<artifactId>c3p0artifactId>
<version>0.9.5.2version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>5.1.37version>
dependency>
dependencies>
project>
(3)spring.xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
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">
<aop:aspectj-autoproxy>aop:aspectj-autoproxy>
<context:component-scan base-package="com.xiyou">context:component-scan>
beans>
分别开启了AOP的注解形式,以及扫描包的范围
(4)UserService接口:
package com.xiyou.service;
public interface UserService {
public void add();
}
(5)UserServiceImpl
package com.xiyou.service.impl;
import com.xiyou.service.UserService;
import org.springframework.stereotype.Service;
@Service("userService")
public class UserServiceImpl implements UserService {
public void add() {
System.out.println("给数据库中添加数据。。。");
}
}
(6)AOP
package com.xiyou.aop;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
/**
* 切面类
*/
@Component
@Aspect
public class AopLog {
/**
* 前置通知
*/
@Before("execution(* com.xiyou.service.UserService.add(..))")
public void begin(){
System.out.println("前置通知");
}
/**
* 后置通知
*/
@After("execution(* com.xiyou.service.UserService.add(..))")
public void after(){
System.out.println("后置通知");
}
/**
* 运行通知
*/
@AfterReturning("execution(* com.xiyou.service.UserService.add(..))")
public void returning(){
System.out.println("运行通知");
}
/**
* 异常通知
*/
@AfterThrowing("execution(* com.xiyou.service.UserService.add(..))")
public void afterThrowing(){
System.out.println("异常通知");
}
/**
* 环绕通知
* @param proceedingJoinPoint
*/
@Around("execution(* com.xiyou.service.UserService.add(..))")
public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("环绕通知开始");
proceedingJoinPoint.proceed();
System.out.println("环绕通知结束");
}
}
(7)运行主函数:
package com.xiyou;
import com.xiyou.service.UserService;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TestMain {
public static void main(String[] args) {
ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("spring.xml");
UserService userService = (UserService)classPathXmlApplicationContext.getBean("userService");
userService.add();
}
}
运行结果是:
前置通知
环绕通知开始
给数据库中添加数据。。。
后置通知
运行通知
环绕通知结束
通过上面的运行结果,我们可以看到
(1)先运行前置通知
(2)再运行环绕通知的前置
(3)执行具体的业务方法
(4)执行后置通知
(5)执行运行通知
(6)执行环绕通知的后置
此时因为没有报错,不会执行异常通知。
前置通知
环绕通知开始
后置通知
异常通知
Exception in thread "main" java.lang.ArithmeticException: / by zero
at com.xiyou.service.impl.UserServiceImpl.add(UserServiceImpl.java:10)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:309)
at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:183)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:150)
at org.springframework.aop.aspectj.AspectJAfterAdvice.invoke(AspectJAfterAdvice.java:42)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
at org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint.proceed(MethodInvocationProceedingJoinPoint.java:80)
at com.xiyou.aop.AopLog.around(AopLog.java:53)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs(AbstractAspectJAdvice.java:621)
at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod(AbstractAspectJAdvice.java:610)
at org.springframework.aop.aspectj.AspectJAroundAdvice.invoke(AspectJAroundAdvice.java:65)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
at org.springframework.aop.framework.adapter.AfterReturningAdviceInterceptor.invoke(AfterReturningAdviceInterceptor.java:50)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
at org.springframework.aop.aspectj.AspectJAfterThrowingAdvice.invoke(AspectJAfterThrowingAdvice.java:55)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
at org.springframework.aop.framework.adapter.MethodBeforeAdviceInterceptor.invoke(MethodBeforeAdviceInterceptor.java:50)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:90)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:202)
at com.sun.proxy.$Proxy11.add(Unknown Source)
at com.xiyou.TestMain.main(TestMain.java:10)
Process finished with exit code 1
我们发现其执行完后置通知后执行了异常通知,因为环绕通知中没有捕捉异常,所以方法报错,后续方法无法继续执行。
Spring的事务:
(1)编程事务:需要自己去开启,提交,回滚等操作。
(2)声明事务:本质上就是编程事务+反射实现。
Spring的声明事务相当于是编程事务+反射机制进行包装的。相当于编程事务是手动事务(手动提交和回滚),声明事务是自动事务,自动事务分为注解版本和扫包版本。
事务的特性:ACID
(1)原子性
原子性是指事务包含的所有操作要么全部成功,要么全部失败回滚
(2)一致性
一致性是指事务必须使数据库从一个一致性状态变换到另一个一致性的状态,也就是说一个事务执行之前和执行之后都必须处于一致性的状态
(3)隔离性
隔离性是当多个用户并发访问数据库的时候,比如操作同一张表的时候,数据库为每个用户开启的事务,不能被其他事务的操作而干扰,多个并发事务之间要相互隔离。
(4)持久性
持久性是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即使在数据库系统遇到故障的情况下也不会丢失提交事务的操作。
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-coreartifactId>
<version>3.0.6.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-contextartifactId>
<version>3.0.6.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-aopartifactId>
<version>3.0.6.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-ormartifactId>
<version>3.0.6.RELEASEversion>
dependency>
<dependency>
<groupId>org.aspectjgroupId>
<artifactId>aspectjrtartifactId>
<version>1.6.1version>
dependency>
<dependency>
<groupId>aspectjgroupId>
<artifactId>aspectjweaverartifactId>
<version>1.5.3version>
dependency>
<dependency>
<groupId>cglibgroupId>
<artifactId>cglibartifactId>
<version>2.1_2version>
dependency>
<dependency>
<groupId>com.mchangegroupId>
<artifactId>c3p0artifactId>
<version>0.9.5.2version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>5.1.37version>
dependency>
(2)spring.xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
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.xiyou.mayi.thread5.aop">context:component-scan>
<aop:aspectj-autoproxy>aop:aspectj-autoproxy>
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.jdbc.Driver">property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test">property>
<property name="user" value="root">property>
<property name="password" value="05131004">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>
beans>
这里我们开启了AOP的注解形式,也规定了自动扫包的范围,同时注入了数据源,并且配置了JDBCTemplate和配置了事务
(3)自己实现事务的工具类,提交事务,回滚事务,创建事务,这里我们将事务设置成多例的,避免线程不安全
package com.xiyou.mayi.thread5.aop.transaction;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.stereotype.Component;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.interceptor.DefaultTransactionAttribute;
/**
* 手写事务的实现过程
* 设置成多例避免线程安全问题
*/
@Component
@Scope("prototype")
public class TransactionUtils {
// 获取事务源
@Autowired
private DataSourceTransactionManager dataSourceTransactionManager;
// 开启事务
// 开启默认隔离级别
public TransactionStatus begin(){
TransactionStatus transactionStatus = dataSourceTransactionManager.getTransaction(new DefaultTransactionAttribute());
return transactionStatus;
}
// 提交事务
public void commit(TransactionStatus transactionStatus){
dataSourceTransactionManager.commit(transactionStatus);
}
// 回滚事务
public void rollback(TransactionStatus transactionStatus){
dataSourceTransactionManager.rollback(transactionStatus);
}
}
(4)与数据库交互
package com.xiyou.mayi.thread5.aop.dao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
@Repository
public class UserDao {
@Autowired
private JdbcTemplate jdbcTemplate;
/**
* 给数据库中提交数据
* @param name
* @param age
*/
public void add(String name, int age) {
String sql = "insert into t_users(name, age) values(?, ?)";
int update = jdbcTemplate.update(sql, name, age);
System.out.println("updateResult : " + update);
}
}
(5)Service层
接口:UserService
package com.xiyou.mayi.thread5.aop.service;
public interface UserService {
public void add();
}
实现类:UserServiceImpl
package com.xiyou.mayi.thread5.aop.service.impl;
import com.xiyou.mayi.thread5.aop.dao.UserDao;
import com.xiyou.mayi.thread5.aop.service.UserService;
import com.xiyou.mayi.thread5.aop.transaction.TransactionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service("userService")
public class UserServiceImpl implements UserService {
@Autowired
private UserDao userDao;
@Autowired
private TransactionUtils transactionUtils;
@Override
public void add() {
userDao.add("test001", 20);
// 故意写错,看是否提交
int i = 1/0;
System.out.println("#####");
userDao.add("test002", 30);
}
}
这里的实现调用了两次dao的add方法,我们要达到的效果是,因为调用两次dao的add方法都在一个方法中,所以要么都成功,要么都失败。所以我们在这之间故意写错,看是否能够提交
(6)自己实现AOP切面,使用异常通知进行回滚,环绕通知将其放在一个事务中,负责开启和提交事务
package com.xiyou.mayi.thread5.aop.MyAspect;
import com.xiyou.mayi.thread5.aop.transaction.TransactionUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.interceptor.TransactionAspectSupport;
@Aspect
@Component
public class AopTransaction {
@Autowired
private TransactionUtils transactionUtils;
/**
* 异常通知,用来发现有异常就进行回滚操作
*/
@AfterThrowing("execution(* com.xiyou.mayi.thread5.aop.service.UserService.add(..))")
public void afterThrowing(){
System.out.println("######回滚事务######");
// 获取当前的事务进行回滚
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
/**
* 环绕通知,用来开启事务,提交事务
* 如果出错,不进行后续操作,直接到异常通知中
*/
@Around("execution(* com.xiyou.mayi.thread5.aop.service.UserService.add(..))")
public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("开启事务");
TransactionStatus transactionStatus = transactionUtils.begin();
// 执行调用,如果这里的异常进行try-catch就不会进入异常通知中
proceedingJoinPoint.proceed();
System.out.println("提交事务");
transactionUtils.commit(transactionStatus);
}
}
使用事务的注意事项:
- 事务是程序运行如果没有错误,会自动提交事务,如果程序运行发生异常,则会自动回滚。
- 如果使用了try-catch捕获异常,一定要在catch中进行rollback操作,因为这时候,异常通知因为try-catch的原因不会调用
- 我们在异常通知中没有当前事务对象,得这样回滚:TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
(7)主启动类
package com.xiyou.mayi.thread5.aop;
import com.xiyou.mayi.thread5.aop.service.UserService;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyMain {
public static void main(String[] args) {
ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("spring.xml");
UserService userService = (UserService)classPathXmlApplicationContext.getBean("userService");
userService.add();
}
}
最后的结果是:
开启事务
九月 04, 2019 11:58:06 下午 com.mchange.v2.c3p0.impl.AbstractPoolBackedDataSource
信息: Initializing c3p0 pool... com.mchange.v2.c3p0.ComboPooledDataSource [ acquireIncrement -> 3, acquireRetryAttempts -> 30, acquireRetryDelay -> 1000, autoCommitOnClose -> false, automaticTestTable -> null, breakAfterAcquireFailure -> false, checkoutTimeout -> 0, connectionCustomizerClassName -> null, connectionTesterClassName -> com.mchange.v2.c3p0.impl.DefaultConnectionTester, contextClassLoaderSource -> caller, dataSourceName -> 1hge8sha41y74atp1xb8p4b|46238e3f, debugUnreturnedConnectionStackTraces -> false, description -> null, driverClass -> com.mysql.jdbc.Driver, extensions -> {}, factoryClassLocation -> null, forceIgnoreUnresolvedTransactions -> false, forceSynchronousCheckins -> false, forceUseNamedDriverClass -> false, identityToken -> 1hge8sha41y74atp1xb8p4b|46238e3f, idleConnectionTestPeriod -> 0, initialPoolSize -> 3, jdbcUrl -> jdbc:mysql://localhost:3306/test, maxAdministrativeTaskTime -> 0, maxConnectionAge -> 0, maxIdleTime -> 0, maxIdleTimeExcessConnections -> 0, maxPoolSize -> 15, maxStatements -> 0, maxStatementsPerConnection -> 0, minPoolSize -> 3, numHelperThreads -> 3, preferredTestQuery -> null, privilegeSpawnedThreads -> false, properties -> {user=******, password=******}, propertyCycle -> 0, statementCacheNumDeferredCloseThreads -> 0, testConnectionOnCheckin -> false, testConnectionOnCheckout -> false, unreturnedConnectionTimeout -> 0, userOverrides -> {}, usesTraditionalReflectiveProxies -> false ]
Exception in thread "main" org.springframework.transaction.NoTransactionException: No transaction aspect-managed TransactionStatus in scope
at org.springframework.transaction.interceptor.TransactionAspectSupport.currentTransactionStatus(TransactionAspectSupport.java:111)
at com.xiyou.mayi.thread5.aop.MyAspect.AopTransaction.afterThrowing(AopTransaction.java:27)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs(AbstractAspectJAdvice.java:621)
at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod(AbstractAspectJAdvice.java:603)
at org.springframework.aop.aspectj.AspectJAfterThrowingAdvice.invoke(AspectJAfterThrowingAdvice.java:59)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:90)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:202)
at com.sun.proxy.$Proxy13.add(Unknown Source)
at com.xiyou.mayi.thread5.aop.MyMain.main(MyMain.java:11)
updateResult : 1
######回滚事务######
观察数据库发现并没有进行提交事务,因为出错,所以两次add因为在一个方法中,都会回滚。