Spring中的aop和基于XML以及注解的AOP配置
完善account案例
E:\JAVAworkspace\spring_account
分析案例中的问题
-
各个数据库操作应该只获取一个connection连接,把这些此操作放到一个事务中,避免产生脏读、不可重复读、幻读等问题。
- ConnectionUtils类:连接的工具类,用于从数据源中获取一个连接,并且实现和线程的绑定。使用ThreadLocal对象把Connection和当前线程绑定,从而使一个线程中只有一个能控制事务的对象
private ThreadLocal<Connection> tl = new ThreadLocal<>(); private DataSource dataSource; public void setDataSource(DataSource dataSource) { this.dataSource = dataSource; } /** * 获取当前线程上的连接 * @return */ public Connection getThreadConnection(){ //1.先从ThreadLocacl上获取 Connection conn = tl.get(); try{ //2.判断当前线程上是否有连接 if (conn == null){ //3.从数据源中获取一个连接,并存入ThreadLocal中 conn = dataSource.getConnection(); tl.set(conn); } //4.返回当前线程上的连接 return conn; }catch (Exception e){ throw new RuntimeException(e); } } /** * 把连接和线程解绑 */ public void removeConnection(){ tl.remove(); }
- TransactionManager类:和事务管理相关的工具类,他包含:开启事务、提交事务、回滚事务、释放连接
private ConnectionUtils connectionUtils; public void setConnectionUtils(ConnectionUtils connectionUtils) { this.connectionUtils = connectionUtils; } /** * 开启事务 */ public void beginTransaction(){ try { connectionUtils.getThreadConnection().setAutoCommit(false); } catch (SQLException e) { e.printStackTrace(); } } /** * 提交事务 */ public void commit(){ try { connectionUtils.getThreadConnection().commit(); } catch (SQLException e) { e.printStackTrace(); } } /** * 回滚事务 */ public void rollback(){ try { connectionUtils.getThreadConnection().rollback(); } catch (SQLException e) { e.printStackTrace(); } } /** * 释放连接 */ public void release(){ try { connectionUtils.getThreadConnection().close();//还回连接池 connectionUtils.removeConnection();//把连接和线程解绑 } catch (SQLException e) { e.printStackTrace(); } }
-
在AccountDaoImpl中定义一个ConnectionUtil对象,避免QueryRunner从配置文件bean.xml的数据源注入中拿连接,而是在配置文件中注入ConnectionUtil,从而保证各项操作在一个事务中。
回顾之前的一个技术:动态代理
-
特点:字节码随用随创建,随用随加载
-
作用:不修改源码的基础上对该方法增强
-
分类:
-
基于接口的动态代理
-
涉及的类:Proxy
-
提供者:JDK官方
-
创建:使用Proxy类中的newProxyInstance方法
-
创建代理对象的要求:被代理对象至少实现一个接口,如果没有则不能使用
-
newProxyInstance方法的参数:
ClassLoader:类加载器。用于加载代理对象字节码的,和被代理对象使用相同的类加载器,固定写法。
Class[]:字节码数组。用于让代理对象和被代理对象有相同的方法。固定写法。
InvocationHandler:用于提供增强的代码。它是让我们写如何代理,我们一般都是写一个该接口的实现类,通常情况下都是匿名内部类,但不是必须的。此接口的实现类都是谁用谁写。
final Producer producer = new Producer(); //生成代理对象 IProducer proxyProducer = (IProducer) Proxy.newProxyInstance(producer.getClass().getClassLoader(), producer.getClass().getInterfaces(), new InvocationHandler() { /** * 作用:执行被代理对象的任何接口方法都会经过该方法 * 方法的参数含义 * @param proxy 代理对象的引用 * @param method 当前执行的方法 * @param args 当前执行方法所需的参数 * @return 和被代理对象方法有相同的返回值 * @throws Throwable */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object returnValue = null; //提供增强的代码 //1.获取方法执行的参数 Float money = (Float) args[0]; //2.判断当前方法是不是销售、 if ("saleProduct" == method.getName()){ returnValue = method.invoke(producer,money*0.8f); } return returnValue; } }); proxyProducer.saleProduct(10000f);
-
-
基于子类的动态代理
-
涉及的类:Enhancer
-
提供者:第三方cglib库
-
创建:使用Enhancer类中的create方法
-
要求:被代理类不能是最终类(final修饰的)
-
create方法的参数:
Class:字节码。用于指定被代理对象的字节码。
Callback:用于提供增强代码的。我们一般写的都是该接口的子接口的实现类:MethondInterceptor(方法拦截)
final Producer producer = new Producer(); //生成代理对象 Producer cglibProducer = (Producer) Enhancer.create(producer.getClass(), new MethodInterceptor() { /** * 指定被代理对象的任何方法都会经过该方法 * @param o * @param method * @param objects * 以上三个从参数和基于接口的动态代理中的invoke方法的参数是一样的 * @param methodProxy 当前执行方法的代理对象 * @return * @throws Throwable */ @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { Object returnValue = null; //提供增强的代码 //1.获取方法执行的参数 Float money = (Float) objects[0]; //2.判断当前方法是不是销售、 if ("saleProduct" == method.getName()){ returnValue = method.invoke(producer,money*0.8f); } return returnValue; } }); cglibProducer.saleProduct(12000f);
-
-
AOP的概念
- AOP: Aspect Oriented Programming 即:面向切面编程。通过预编译的方式和运行期动态代理实现程序功能统一维护的一种技术。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,提高开发效率。
- 作用:在程序运行期间,不修改源码对已有方法进行增强。
- 优点:减少重复代码,提高开发效率,维护方便。
- 实现方式:基于接口的动态代理。
spring 中的AOP 相关术语
-
Joinpoint(连接点):是指那些被拦截到的点,这些点指的是方法,因为spring只支持方法类型的连接点。
-
Pointcut(切入点):是指我们要对哪些Joinpoint进行拦截的定义。(被增强的连接点)
-
Advice(通知/增强):是指拦截到JoinPoint之后要做的事情。通知的类型:前置通知,后置通知,异常通知,最终通知,环绕通知。
-
Introduction(引介):是一种特殊的通知,在不修改类代码的前提下,Introduction可以在运行期为类动态的添加一些方法或Field。
-
Target(目标对象):代理的目标对象(accountService)
-
Weaving(织入):是指把增强应用到目标对象来创建新的代理对象的过程。spring采用动态代理织入。
-
Proxy(代理):一个类被AOP织入增强后,就产生一个结果代理类。
-
Aspect(切面):是切入点和通知(引介)的结合。
spring中基于XML的AOP配置
-
第一步:把通知的bean也交给Spring来管理
-
第二步:使用aop:config标签表明开始AOP的配置
-
第三步:使用aop:aspect标签表明配置切面
- id属性:给切面提供一个唯一标识
- ref属性:指定通知类bean的id
-
第四步:在aop:aspect标签内部使用标签来配置通知的类型
-
aop:before:表示配置前置通知
-
method:用于指定Logger类中哪个方法是前置通知
-
pointcut属性:用于指定切入点表达式,该表达式的含义指的是对业务层中哪些方法增强。
切入点表达式的写法:
关键字:execution(表达式)
表达式: 访问修饰符 返回值 包名.包名.包名…类名.方法名(参数列表)
标准的表达式写法:public void com.ssm.service.impl.AccountService.saveAccount()
访问修饰符可以省略;返回值可以使用通配符,表示任意返回值;
包名可以使用通配符,表示任意包。但是有几级包,就需要写几个
*
; 包名可以使用
..
表示当前包及其子包; 类名和方法名都可以使用
*
实现通配; 参数列表:
可以直接写数据类型:基本类型直接写名称(int);引用类型写 包名.类名的方式(java.lang.String);
可以使用通配符表示任意类型,但是必须有参数;
可以使用
..
表示有无参数都可以,有参数可以是任意类型全通配写法:
* *..*.*(..)
实际开发中切入点表达式的通常写法:切到业务层实现类下的所有方法:
* com.ssm.service.impl.*.*(..)
-
<?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" 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"> <!--配置spring的ioc,把service对象配置进来--> <bean id="accountService" class="com.ssm.service.impl.AccountServiceImpl"></bean> <!--spring中基于xml的AOP配置步骤--> <!--配置Logger类--> <bean id="logger" class="com.ssm.utils.Logger"></bean> <!--配置Aop--> <aop:config> <!--配置切面--> <aop:aspect id="logAdvice" ref="logger"> <!--配置通知的类型,并且建立通知方法和切入点方法的关联--> <aop:before method="printLog" pointcut="execution(* com.ssm.service.impl.*.*(..))"></aop:before> </aop:aspect> </aop:config> </beans>
-
-
通知类型:
<!--配置Aop--> <aop:config> <aop:pointcut id="pt1" expression="execution(* com.ssm.service.impl.*.*(..))"></aop:pointcut> <!--配置切面--> <aop:aspect id="logAdvice" ref="logger"> <!--配置前置通知--> <aop:before method="beforePrintLog" pointcut-ref="pt1"></aop:before> <!--配置后置通知--> <aop:after-returning method="afterReturningPrintLog" pointcut-ref="pt1"></aop:after-returning> <!--配置异常通知--> <aop:after-throwing method="afterThrowingPrintLog" pointcut-ref="pt1"></aop:after-throwing> <!--配置最终通知--> <aop:after method="afterPrintLog" pointcut-ref="pt1"></aop:after> <!--配置切入点表达式: 该标签写在aop:aspect标签内部只能当前切面使用, 他还可以写在aop:aspect标签外面,此时就变成了所有切面可用 --> <!--<aop:pointcut id="pt1" expression="execution(* com.ssm.service.impl.*.*(..))"></aop:pointcut>--> </aop:aspect> </aop:config>
环绕通知:
<!--配置环绕通知--> <aop:around method="aroundPrintLog" pointcut-ref="pt1"></aop:around>
- 问题:当我们配置了环绕通知之后,切入点方法没有执行,而通知方法执行了。
- 分析:对比动态代理中的环绕通知代码,动态代理的环绕通知有明确的切入点方法,而我们的代码中没有。
- 解决:Spring框架为我们提供了一个接口:ProceedJoinPoint。该接口有一个方法proceed(),此方法就相当于明确调用切入点方法,该接口可以作为环绕通知的方法参数,在程序执行时,spring框架会为我们提供该接口的实现类供我们使用。
public Object aroundPrintLog(ProceedingJoinPoint pjp){ Object rtValue; try{ Object[] args = pjp.getArgs();//得到方法执行所需要的参数 System.out.println("Logger类中的aroundPrintLog方法开始记录日志了----前置"); rtValue = pjp.proceed(args);//明确调用业务层方法(切入点方法) System.out.println("Logger类中的aroundPrintLog方法开始记录日志了----后置"); return rtValue; }catch (Throwable t){ System.out.println("Logger类中的aroundPrintLog方法开始记录日志了----异常"); throw new RuntimeException(t); }finally { System.out.println("Logger类中的aroundPrintLog方法开始记录日志了----最终"); } }
spring环绕通知:他是spring框架为我们提供的一种可以在代码中手动控制增强方法何时执行的方式。
spring中基于注解的AOP配置
-
bean.xml
<!--配置spring创建容器时要扫描的包--> <context:component-scan base-package="com.ssm"></context:component-scan> <!--配置spring 开启注解AOP的支持--> <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
-
Logger.java
@Component("logger") @Aspect//表示当前类是一个切面类
@Pointcut("execution(* com.ssm.service.impl.*.*(..))") private void pt1(){}
@Before("pt1()") @AfterReturning("pt1()") @AfterThrowing("pt1()") @After("pt1()") @Around("pt1()")
-
不使用XML(纯注解)的配置方式
@Configuration @ComponentScan(basePackages = "com.ssm")//配置spring创建容器时要扫描的包 @Import(Logger.class)//引入其他配置类 @EnableAspectJAutoProxy//配置spring 开启注解AOP的支持 public class SpringConfiguration { }