Spring系列第7篇:Spring中的AOP

一、AOP概念

1、Aop是什么?

面向切面编程,在程序中具有公共特性的某些类,某些方法上进行拦截,在方法的执行前,执行后,执行结果返回后增加执行一些方法。
作用:在程序运行期间,不修改源码的情况下对已有的方法增强

2、Spring中Aop一些概念

1.目标对象(target):指将要被增强的对象,包含主业务逻辑类的对象。
2.连接点(JoinPoint):程序执行过程中明确的点,连接点由2个信息确定:方法,表示程序执行点,既在哪个目标方法 。 相对点:表示方位,即目标方法的什么位置,比如调用前,后等。
所以连接点就是被拦截到的程序执行点。因为spring只支持方法类型的连接点,所以在spring中连接点就是被拦截到的方法。(业务层所有的方法都是连接点)
3.代理对象 (proxy): spring会通过代理的方式,对目标对象生成代理对象,代理对象中会加入一些增强的功能,通过代理对象间接的对目标对象起到增强的效果。
4.通知(advice):需要在目标对象中增强的功能,在方法的什么地方,执行什么操作。
5.切入点(pointcut):用来指定需要将通知使用在哪些地方,比如用在哪些类的哪些方法上,切入点就是做这些配置的。(业务层中被增强的方法就是切入点)
6.切面(aspect):通知advice和切入点pointcut的组合,切面用来定义在哪些地方pointcut,执行什么操作advice。
7.顾问,通知器(advisor):顾问就是将通知和切入点组合起来了,通知是要增强的逻辑,而增强的逻辑在什么地方执行由切入点来指定的,所以通知必须和切入点组合在一起,所以就产生了这个advisor这个类。spring aop提供了advisor这个接口将通知和切入点组合起来

二、动态代理

动态代理:
特点:字节码随用随创建,随用随加载
作用:不修改源码的基础上对方法增强
分类:基于接口的动态代理 ,基于子类的动态代理

1.基于接口的动态代理

涉及的类:proxy
提供者:jdk官方
如何创建代理对象:
使用proxy类中的newProxyInStance方法
创建代理对象的要求:被代理类至少实现一个接口,如果没有则不能使用。
newProxyInsatnce方法的参数:
classloader:类加载器,它是用于加载代理对象字节码的,和被代理对象使用相同的的类加载器
class[]:它是用于让代理对象和被代理对象具有相同的方法。
InvocationHandler:用于提供增强的代码,它是让我们写如何代理,我们一般都是写一个该接口的实现类,通常情况下都是匿名内部类
InvocationHandler的Invoke()方法作用:执行被代理对象的任何接口方法都会经过该方法
参数:proxy代理对象的引用,method:当前执行的方法 agrs:当前执行方法需要的参数,return返回值:和被代理对象方法具有相同的返回值

通过实现InvocationHandler接口的方式:

package com.ming.test;

import com.ming.service.IUserService;
import com.ming.service.impl.UserServiceImpl;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * 基于接口的动态代理JDk
 */
public class UserJDKProxy implements InvocationHandler {
    private Object target;//定义代理的目标对象
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        String name = args[0].toString();
                        Object result = null;
                        if ("work".equals(method.getName())) {
                            System.out.println(name + "工作前先吃饭。。");
                            result = method.invoke(target, name);
                        }
                        return result;
    }

    //生成代理对象
    public Object getJDkProxy(Object target){
        this.target = target;
        //JDK动态代理只能针对实现了接口的类进行代理,newProxyInstance 函数所需参数就可看出
        return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
    }

    public static void main(String[] args) {
        UserJDKProxy userJDKProxy = new UserJDKProxy();
       IUserService userService= (IUserService) userJDKProxy.getJDkProxy(new UserServiceImpl());
       userService.work("jdkProxy");
    }

//    @Test
//    public void JDKIntefaceProxyUser() {
//        //1.被代理的对象,要求至少实现一个接口,匿名内部内访问外部成员要求是最终的用final
//        final UserServiceImpl user = new UserServiceImpl();
//        //2.Proxy.newInstance()生成代理对象
//        IUserService iUserService = (IUserService) Proxy.newProxyInstance(user.getClass().getClassLoader(), user.getClass().getInterfaces(),
//                new InvocationHandler() {
//                    //3.InvocationHandler的invoke()需要增强的代码
//                    @Override
//                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//                        String name = args[0].toString();
//                        Object result = null;
//                        if ("work".equals(method.getName())) {
//                            System.out.println(name + "工作前先吃饭。。");
//                            result = method.invoke(user, name);
//                        }
//                        return result;
//                    }
//                });
//
//        iUserService.work("jdkProxy代理");
//    }
}

2.基于子类的动态代理

涉及的类:enhancer
提供者:第三方cglib库
如何创建代理对象:使用Enhancer类中的一个create方法
创建代理对象的要求:被代理类不是最终类,必须保证类不含有final关键字,否则是无法代理的
create方法参数
class:用于指定一个字节码,被代理对象的字节码
callback:用于提供增强的代码,我们一般写改接口的子接口实现类,MethodInterceptor方法拦截

package com.ming.test;

import com.ming.service.IUserService;
import com.ming.service.impl.UserServiceImpl;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
 * 基于子类的动态代理
 */
public class CglibProxyUser implements MethodInterceptor {
    //被代理的目标对象
    private Object tagert;
    //创建代理对象
    public Object createProxy(Object tagert) {
        //指定被代理的父类
        this.tagert = tagert;
        //创建代理对象
        Enhancer enhancer = new Enhancer();
        //设置父类,因为Cglib是针对指定的类生成一个子类,所以需要指定父类
        enhancer.setSuperclass(this.tagert.getClass());
        //设置回调方法,传入该对象的所有字段和方法信息
        enhancer.setCallback(this);
        //返回代理对象的信息
        return enhancer.create();
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        String name = objects[0].toString();
        Method work = this.tagert.getClass().getMethod("work", String.class);
        Object resultValue = null;
        if (work != null) {
            //执行代理的方法
            resultValue = method.invoke(this.tagert, name + "先吃饭!!");
        } else {
            System.out.println("cglib动态代理执行失败!!");
        }
//        String  name = objects[0].toString();
//        if("work".equals(method.getName())){
//            System.out.println("cglib基于子类的动态代理");
//            resultValue = method.invoke(this.tagert,name);
//        }
        return resultValue;
    }


    public static void main(String[] args) {
        CglibProxyUser proxyUser = new CglibProxyUser();
        IUserService userService = (IUserService) proxyUser.createProxy(new UserServiceImpl());
        userService.work("cglibProxy");
    }
//       @Test
//       public void userCglibTest(){
//           UserCglib userCglib = new UserCglib();
//
//           UserCglib user = (UserCglib) Enhancer.create(userCglib.getClass(), new MethodInterceptor() {
//               @Override
//               public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
//                   Object result = null;
//                   String name = objects[0].toString();
//                   if("work".equals(method.getName())){
//                       System.out.println(name+"吃完饭");
//                       result = method.invoke(userCglib,name);
//                   }
//                   return result;
//               }
//           });
//
//           user.work("cglibProxy");
//       }
}

三、Spring中基于xml的aop配置步骤

1、Spring中基于xml的aop配置步骤

1.把通知的bean交给spring来管理(就是抽取的公共类,被增强的功能)
2.使用aop:config:标签表示开始aop的配置
3.使用aop:aspect标签在config标签里面表示配置切面
id属性:表示给切面提供一个唯一标识
ref:指定通知的bean的id
4.在aop:aspect标签的内部使用对应的标签来配置通知类型
aop:befor:表示前置通知 method属性:用于指定通知类中的哪个方法时前置通知
pointcut属性:用于指定切入点表达式,该表达式的含义指的是对业务层中哪些方法增强

<?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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">
    <bean id="accountProxy" factory-method="getAccountService" factory-bean="proxyAccountService"></bean>
    <!--用动态代理Proxy将accoutService被代理对象-->
    <bean id="proxyAccountService" class="com.ming.util.aop.AccountFactory">
        <property name="accountService" ref="accountService"></property>
        <property name="tx" ref="tx"></property>
    </bean>
    <!-- 配置dao对象-->
    <bean id="accountDao" class="com.ming.dao.impl.AccountDaoImpl">
        <!-- 注入queryRunner对象-->
        <property name="queryRunner" ref="runner"></property>
        <property name="conn" ref="connUtile"></property>
    </bean>
    <bean id="connUtile" class="com.ming.util.ConnectionThredUtil">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!-- 配置QueryRunner,它是单例对象所以配置作用域scope-->
    <bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
    </bean>
    <!--注入数据源-->
    <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:3307/test"></property>
        <property name="user" value="liming"></property>
        <property name="password" value="liming"></property>
    </bean>
    <!-- =========================aop====================================-->
    <!-- 配置service对象,目标对象,将要被增强的对象,也是被代理对象-->
    <bean id="accountService" class="com.ming.service.impl.AccountServiceImpl">
        <!-- 注入dao对象-->
        <property name="accountDao" ref="accountDao"></property>
    </bean>
     <!--把通知的bean交给spring来管理,增强的功能-->
    <bean id="tx" class="com.ming.util.TransacationManger">
        <property name="connectionUtil" ref="connUtile"></property>
    </bean>
    <!--配置aop-->
    <aop:config>
        <!--配置切面-->
        <aop:aspect id="txManager" ref="tx">
            <!-- 配置切入点-->
            <aop:pointcut id="pointTx" expression="execution(* com.ming.service.impl.*.*(..))"/>
            <!-- 配置通知的类型,并建立通知方法和切入点方法的关联-->
            <aop:before method="createTransaction" pointcut-ref="pointTx"></aop:before>
            <aop:after-returning method="commitTransaction" pointcut-ref="pointTx"></aop:after-returning>
            <aop:after-throwing method="rollbackTransaction" pointcut-ref="pointTx"></aop:after-throwing>
            <aop:after method="releaseTransaction" pointcut-ref="pointTx"></aop:after>
        </aop:aspect>
    </aop:config>

</beans>

2、切入点表达式写法

关键字:execution(表达式)
全通配写法:* .(…)
表达式:访问修饰符 返回值 包名.包名.包名.包名…类名.方法名(参数列表)
标准的表达式写法: public void com.ming.service.impl.AccountServiceImpl.saveAccount()
访问修饰符可以省略:void com.ming.service.impl.AccountServiceImpl.saveAccount()
返回值可以使用通配符,表示任意返回值:
com.ming.service.impl.AccountServiceImpl.saveAccount()
包名可以使用通配符,表示任意包,但是有几级包,就需要几个*. 写法:* ...…AccountServiceImpl.saveAccount()
包名可以使用…表示当前包及其子包 * …AccountServiceImpl.saveAccount()
类名和方法名都可以使用
来实现通配 * .()
参数列表:可以直接写数据类型1.基本数据类型直接写名称 int 2.引用类型写包名.类名的方式 java.lang.String
可以使用通配符表示任意类型,但是必须要有参数
可以使用…表示有无参数均可,有参数可以是任意类型
实际开发中写法:切到业务层实现类下的所有方法:
com.ming.service.impl..(…)
aspectweaver 负责解析切入点表达式

3、通知

1.前通知:在切入点方法(就是业务方法)之前执行
2.后置通知:在切入点方法执行之后通知,它和异常通知永远只能执行一个
3.异常通知:在切入点方法产生异常后执行 它和后置通知永远只能执行一个
4.最终通知:无论切入点方法正常执行它都会在其后面执行
5.环绕通知:
动态代理(jdk&cglib)的环绕通知就是整个invoke方法的执行,在环绕通知中有明确的切入点方法调用
问题:切入点方法没有执行,环绕通知方法执行了
分析:比较动态代理的环绕通知发现invoke(accountService,agrs)有明确的切入点调用也就是业务层调用,而spring aop的环绕通知没有调用产生的
解决:spring为我们提供了一个接口proceedingJoinPoint改接口有一个proceed()方法,此方法相当于明确调用切入点方法
该接口可以作为环绕通知的方法参数,在程序执行时,spring提供该接口的实现类供我们使用( 对象调用proceed(pjp.getArgs()))
spring环绕通知:spring为我们提供的一种在代码中可以手动控制增强的代码何时执行的方式
比如在pjp.proceed()方法执行前执行就是前置 执行后添加增强的代码就是后置,异常中就是异常通知,finally就是最终通知

四、基于注解的Aop配置

基于注解的Aop配置:
配置spring开启aop注解的支持bean.xml : <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
在配置类上添加@EnableAspectJAutoProxy注解,开启注解版的AOP功能
@Aspect表示当前类是一个切面类
通知:
@Before 表示当前方法是前置通知
@AfterReturning 表示是后置通知
@AfterThrowing 异常通知
@After 最终通知
使用注解时出现异常:java.sql.SQLException: Can’t call commit when autocommit=true
因为他们的调用通知的顺序不同,所以会出现SQLException
前四个通知用注解方法的执行顺序回出现问题,而环绕通知没有
@Around环绕通知

    /**
     * @Before和@Around的执行顺序 : 若是基于XML 则 其结果 受影响于 配置的先后顺序。
     * 
     * @AfterReturning和@After的区别 :
     * (1) @AfterReturning 被代理的方法执行完成之后 要执行的代码。
     * (2) @After 新生成的代理方法执行完成之后 要执行的代码,是放在finally块中的。
     * 注: 若是基于XML 则 两者的执行顺序结果 受影响于 配置的先后顺序。
     * @param proxy
     * @param method
     * @param args
     * @return
     * @throws Throwable
     */

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object result;
        try {
            //@Before
            result = method.invoke(target, args);
            //@AfterReturning
            return result;
        } catch (InvocationTargetException e) {
            Throwable targetException = e.getTargetException();
            //@AfterThrowing
            throw targetException;
        } finally {
            //@After
        }
    }
package com.ming.util;

import org.apache.log4j.Logger;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
 * 事物管理
 */
@Component
@Aspect
public class TransacationManger{
   private static Logger logger = Logger.getLogger(TransacationManger.class);
    //注入当前线程的连接
    @Autowired
    private  ConnectionThredUtil connectionUtil;

    @Pointcut("execution(* com.ming.service.impl.*.*(..))")
    public void pointCut(){

    }
    /**
     * 开启事物
     */
    //@Before("pointCut()")
    public  void createTransaction() {
        try {
            connectionUtil.getThredConnection().setAutoCommit(false);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 提交事物
     */
   // @AfterReturning("pointCut()")
    public  void commitTransaction() {
        try {
            connectionUtil.getThredConnection().commit();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 回滚事物
     */
    //@AfterThrowing("pointCut()")
    public  void rollbackTransaction() {
        try {
            connectionUtil.getThredConnection().rollback();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 释放资源
     */
    //@After("pointCut()")
    public  void releaseTransaction() {
        try {
            connectionUtil.getThredConnection().close();//关闭连接,释放资源到连接池
            connectionUtil.remove();//解绑线程与连接
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 环绕通知
     * @param point
     *
     */
    @Around("pointCut()")
    public Object aroundMethod(ProceedingJoinPoint point) throws Throwable {
        try {
            logger.info("环绕通知的前置通知执行了...");
            connectionUtil.getThredConnection().setAutoCommit(false);
            Object object = point.proceed(point.getArgs());
            logger.info("环绕通知的后置通知执行了...");
            connectionUtil.getThredConnection().commit();
            return object;
        } catch (Throwable throwable) {
            logger.info("环绕通知的异常通知执行了...");
            connectionUtil.getThredConnection().rollback();
            throw new RuntimeException(throwable);
        }finally {
            logger.info("环绕通知的最终通知执行了...");
            connectionUtil.getThredConnection().close();
        }
    }
}

五、 Spring事物

1.控制事物的方法:connection的手动提交改成自动提交开启事物,提交事物,回滚事物,释放资源
连接池的好处:把消耗时间获取连接的这一部分放到我们的应用一加载开始就获取连接
使用ThredLocal对象把connection和当前线程绑定,从而使一个线程中只有一个能控制事物的对象。
2.事物控制应该在业务层:
1.开启事物 2.执行操作 3.提交事物 4.返回结果5在catch回滚事物 6释放资源
事物代码

package com.ming.util;

/**
 * 事物管理
 */
public class TransacationManger {
    
    //注入当前线程的连接
    private static ConnectionThredUtil connectionUtil;

    public void setConnectionUtil(ConnectionThredUtil connectionUtil) {
        this.connectionUtil = connectionUtil;
    }

    /**
     * 开启事物
     */
    public static void createTransaction() {
        try {
            connectionUtil.getThredConnection().setAutoCommit(false);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 提交事物
     */
    public static void commitTransaction() {
        try {
            connectionUtil.getThredConnection().commit();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 回滚事物
     */
    public static void rollbackTransaction() {
        try {
            connectionUtil.getThredConnection().rollback();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 释放资源
     */
    public static void releaseTransaction() {
        try {
            connectionUtil.getThredConnection().close();//关闭连接,释放资源到连接池
            connectionUtil.remove();//解绑线程与连接
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

获取当前线程的连接

package com.ming.util;
import javax.sql.DataSource;
import java.sql.Connection;
/**
 * 获取数据库连接
 */
public class ConnectionThredUtil {
    //将连接与当前线程绑定
    private ThreadLocal<Connection> connectionThreadLocal = new ThreadLocal<Connection>();

    private DataSource dataSource;

    public void setDataSource(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    /**
     * 返回当前线程的连接
     *
     * @return
     */
    public Connection getThredConnection() {
        try {
            Connection connection = connectionThreadLocal.get();
            if (connection == null) {
                connection = dataSource.getConnection();
                connectionThreadLocal.set(connection);
            }
            return connection;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 释放资源
     */
    public void remove() {
        connectionThreadLocal.remove();
    }

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值