美文网首页Java
Spring泛览二(AOP)

Spring泛览二(AOP)

作者: 强某某 | 来源:发表于2019-02-22 09:27 被阅读0次

    AOP

    面向切面编程,通过预编译和运行期动态代理,实现程序功能,是函数式编程的一个衍生范型

    AOP实现方式


    1. 动态代理Proxy:接口+实现类
    2. cglib字节码: 实现类 (通过创建目标类的子类来实现功能)

    JDK动态代理

    该方式必须有接口和实现类,原因是对应回调传参的参数是接口类型

    /**
     * 切面类:增加代码 与 切入点 结合
     */
    public class MyAspect2 {
    
        public void before(){
            System.out.println("开启事务...");
        }
    
        public void after(){
            System.out.println("提交事务...");
        }
    }
    
    /**
    * 接口
    */
    public interface IUserService {
    
        public void add();
    
        public void add(User user);
    
        //切面编程
        public void addUser();
        public void updateUser();
        public void deleteUser();
        public int deleteUser(int id);
    }
    
    /**
    * 接口实现类
    */
    @Service("myUserService")
    public class UserServiceImpl implements IUserService {
    
        @Autowired //spring会自动注入userDao赋值
        private IUserDao userDao;
    
        @Override
        public void add(User user) {
            System.out.println("service 添加用户:" + user);
            //调用dao
            userDao.add(user);
        }
    
        @Override
        public void addUser() {
            System.out.println("添加用户。。。。");
        }
    
        @Override
        public void updateUser() {
            System.out.println("更新用户。。。。");
        }
    
        @Override
        public void deleteUser() {
            System.out.println("删除用户。。。。");
        }
        @Override
        public int deleteUser(int id) {
            System.out.println("通过id删除用户");
            return 1;
        }
        private String name;
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
        @Override
        public void add() {
            System.out.println("创建用户...." + name);
        }
        public UserServiceImpl() {
            //System.out.println("UserServiceImpl()调用了");
        }
    }
    
    /**
    * 工厂类
    */
    public class MyBeanFactory {
    
        /**
         * JDK实现代理
         * @return
         */
        public static IUserService createUserService(){
            //1.创建目标对象target
            final IUserService userService = new UserServiceImpl();
    
            //2.声明切面类对象
            final MyAspect2 aspect = new MyAspect2();
    
            //3.把切面类2个方法 应用 目标类
            //3.1 创建JDK代理,拦截方法
            /*newProxyInstance(
                    ClassLoader loader, 类加载器,写当前类(记住即可)
                    Class<?>[] interfaces, 接口,接口的方法会被拦截
                    InvocationHandler h) 处理
                    */
            IUserService seriviceProxy = (IUserService) Proxy.newProxyInstance(
                    MyBeanFactory.class.getClassLoader(),
                    //因为此处必须传接口,所以原生jdk动态代理必有有接口
                    userService.getClass().getInterfaces(),
                    new InvocationHandler() {
                        @Override
                        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                            //开启事务
                            aspect.before();
                            //方法返回值是 业务方法的返回值
                            Object retObj = method.invoke(userService,args);
                            //System.out.println("拦截返回值:" + retObj);
                            //提交事务
                            aspect.after();
                            return retObj;
                        }
                    }
            );
            return seriviceProxy;
        }
    }
    
    
    /**
    * 测试方法
    */
    @Test
        public void test1() throws Exception {
    
            //实现AOP编程,使用cglib代理来实现
    
            StudentService ss = MyBeanFactory.createStudentService();
    
            ss.delete();
           /* ss.update();
            ss.add();*/
    
        }
    

    cglib字节码实现AOP

    只需要实现类(当然有接口和实现类也可)即可,当然切面对象也不能少即(MyAspect2.java)

    public class StudentService {
        public void delete(){
            System.out.println("删除学生");
    
        }
        public void add(){
            System.out.println("add学生");
        }
        public void update(){
            System.out.println("update学生");
        }
    }
    
    public class MyBeanFactory {
        /**
         * cglib实现代理
         * @return
         */
        public static StudentService createStudentService(){
            //1.创建目标对象target
            final StudentService studentService = new StudentService();
    
            //2.声明切面类对象
            final MyAspect2 aspect = new MyAspect2();
    
            //3.创建增强对象
            Enhancer enhancer = new Enhancer();
            //设置父类
            enhancer.setSuperclass(studentService.getClass());
            //设置回调【拦截】
            enhancer.setCallback(new MethodInterceptor() {
                @Override
                public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
                    /**
                     * proxy:
                     * proxy代理对象是StudentService的子类
                     */
                    aspect.before();
                    //方式一:放行方法
                    //Object retObj = method.invoke(studentService,args);
    
                    //方式二:等同于方式一,通过代理方法调用父类,好处是解耦了,因为没有引用外部变量
                    Object retObj = methodProxy.invokeSuper(proxy,args);//解耦
                    System.out.println("拦截.....");
                    aspect.after();
                    return retObj;
                }
            });
    
            //创建代理对象
            StudentService serviceProxy = (StudentService) enhancer.create();
            return serviceProxy;
        }
    }
    

    Spring的AOP自动和半自动

    不论自动还是半自动,目的都是不写生成代理对象的相关方法,例如避免书写上面的MyBeanFactory.java文件

    Spring项目中半自动AOP

    需要额外添加AOP联盟(规范)、spring-aop(实现)两个jar包


    1. com.springsource.org.aopalliance-1.0.0.jar AOP联盟
    2. spring-aop-3.2.0.RELEASE.jar AOP实现

    简单案例

    <!--bean.xml-->
    <?xml version="1.0" encoding="UTF-8"?>
    <!--xmlns xml namespace: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"
           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">
        <!--  配置UserService-->
        <bean id="userService" class="com.zengqiang.service.UserServiceImpl"></bean>
    
        <!--  配置切面类对象-->
        <bean id="myAspect" class="com.zengqiang.aspect.MyAspect"></bean>
    
        <!-- 配置代理对象
            默认情况下Spring的AOP生成的代理是JDK的Proxy实现的
        -->
        <bean id="serviceProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
            <!-- 接口 :如果只是一个接口,就写Value,如果是多个接口就写List-->
            <property name="interfaces"  value="com.zengqiang.service.IUserService">
            </property>
            <!-- 目标对象 -->
            <!-- 注意此处和上下不同。用的是ref,而不是value,value是特殊写法,记下即可 -->
            <property name="target" ref="userService"/>
            <!-- 切面类-->
            <property name="interceptorNames" value="myAspect"></property>
            <!-- 配置使用cglib生成-->
            <property name="optimize" value="true"></property>
        </bean>
    </beans>
    
    /**
     * 切面类:增加代码 与 切入点 结合
        务必实现MethodInterceptor
     */
    public class MyAspect implements MethodInterceptor{
    
        @Override
        public Object invoke(MethodInvocation mi) throws Throwable {
            //拦截方法
            System.out.println("开启事务...");
            //放行
            Object retObj = mi.proceed();
            System.out.println("拦截.....");
            System.out.println("提交事务...");
            return retObj;
        }
    }
    

    Spring项目中全自动AOP

    需要com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar(织入)
    需要添加的索引


    1. xmlns:aop ="http://www.springframework.org/schema/aop"
    2. http://www.springframework.org/schema/aop
    3. http://www.springframework.org/schema/aop/spring-aop.xsd

    <!--bean.xml-->
    <?xml version="1.0" encoding="UTF-8"?>
    <!--xmlns xml namespace: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">
        <!--  配置UserService-->
        <bean id="userService" class="com.zengqiang.service.UserServiceImpl"></bean>
        <bean id="studentService" class="com.zengqiang.service.StudentService"></bean>
        <!--  配置切面类对象-->
        <bean id="myAspect" class="com.zengqiang.aspect.MyAspect"></bean>
        <!-- 全自动AOP配置
         1.在bean中配置aop约束
         2.配置aop:conifg内容,把切入点和通知结合
    
         proxy-target-class:使用cglib实现代理
         expression 表达式:*任意
                    execution(*         com.zengqiang.service.*.   *       (..))
                              返回值    包名            类名 方法名  参数
        
        proxy-target-class="true":代表开启cglib
         -->
        <aop:config  proxy-target-class="true">
            <!-- 切入点:
                 expression:表达式
                 每个service的方法前面都开启事务和结束事务
    
                 AOP:用于事务配置&日志记录
             -->
            <aop:pointcut id="myPointcut" expression="execution(* com.zengqing.service.*.*(..))"/>
            <!-- 通知(就是切面类对象) 关联 切入点-->
            <aop:advisor advice-ref="myAspect" pointcut-ref="myPointcut"></aop:advisor>
        </aop:config>
    </beans>
    

    AspectJ

    基本案例

    /**
    * 切面类
    */
    public class MyAspect3{
        public void myBefore(){
            System.out.println("前置通知...");
        }
        public void myAfterReturning(){
            System.out.println("后置通知...");
        }
        public Object myAround(ProceedingJoinPoint pjp) throws Throwable {
            System.out.println("环绕通知...");
            System.out.println(pjp.getSignature().getName());//切入点就方法名
            System.out.println("开启事务...");
            //放行
            Object retObj = pjp.proceed();
            System.out.println("提交事务...");
            return retObj;
        }
    }
    
    <?xml version="1.0" encoding="UTF-8"?>
    <!--xmlns xml namespace: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">
    
    
        <!--  配置UserService-->
        <bean id="userService" class="com.zengqiang.service.UserServiceImpl"></bean>
    
        <!-- 配置切面对象-->
        <bean id="myAspect" class="com.zengqiang.aspect.MyAspect"></bean>
    
        <!-- 配置 aop -->
        <aop:config>
            <!-- aop:指定切面-->
            <aop:aspect ref="myAspect">
                <!--定义一个切入点-->
                <aop:pointcut id="myPointcut" expression="execution(* com.zengqiang.service.UserServiceImpl.*(..))"/>
                <!-- 配置前置通知...-->
                <aop:before method="myBefore" pointcut-ref="myPointcut" />
                <!-- 配置后置通知...-->
                <aop:after-returning method="myAfterReturning" pointcut-ref="myPointcut" returning="retValue"/>
                <!--配置环绕通知-->
                <aop:around method="myAround" pointcut-ref="myPointcut"></aop:around>
                <!-- 配置异常通知
                throwing="e" 值,是方法的参数名
                -->
                <aop:after-throwing method="myAfterThrowing" pointcut-ref="myPointcut" throwing="e"/>
                <!--配置最终通知:不管有没有异常,最终通知都会执行-->
                <aop:after method="myAfter" pointcut-ref="myPointcut"></aop:after>
            </aop:aspect>
        </aop:config>
    </beans>
    

    execution()

    语法:execution(修饰符  返回值  包.类.方法名(参数) throws异常)
    修饰符,一般省略
        public      公共方法
        *           任意
    返回值,不能省略
        void            返回没有值
        String      返回值字符串
        *           任意
    包,[省略]
        com.zengqiang.crm           固定包
        com.zengqiang.crm.*.service crm包下面子包任意 (例如:com.zengqiang.crm.staff.service)
        com.zengqiang.crm..         crm包下面的所有子包(含自己)
        com.zengqiang.crm.*.service..   crm包下面任意子包,固定目录service,service目录任意包
    类,[省略]
        UserServiceImpl         指定类
        *Impl                   以Impl结尾
        User*                   以User开头
        *                       任意
    方法名,不能省略
        addUser                 固定方法
        add*                        以add开头
        *Do                     以Do结尾
        *                       任意
    (参数)
        ()                      无参
        (int)                       一个整型
        (int ,int)                  两个
        (..)                        参数任意
    throws ,可省略,一般不写。
    
    案例1:
    execution(* com.zengqiang.crm.*.service..*.*(..))
    
    案例2:或
    <aop:pointcut expression="execution(* com.zengqiang.crm.service.*.*(..)) || 
                              execution(* com.zengqiang.*Do.*(..))" id="myPointCut"/>
    

    AspectJ基于xml的案例

    package com.zengqiang.aspect;
    
    
    import org.aspectj.lang.JoinPoint;
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.*;
    import org.springframework.stereotype.Component;
    
    
    public class MyAspect {
        public void myPointcut(){}
    
        public void myBefore(JoinPoint jp){
            System.out.println("1.前置通知..." + jp.getSignature().getName());//连接点方法名
        }
    
       
        public void myAfterReturning(JoinPoint jp,Object retValue){
            System.out.println("3.后置通知..." +  jp.getSignature().getName());
            System.out.println("返回值:" + retValue);
        }
    
        public Object myAround(ProceedingJoinPoint pjp) throws Throwable {
            //ProceedingJoinPoint和JoinPoint区别之一就是ProceedingJoinPoint有放行方法
            System.out.println("2.环绕通知...开启事务..." + pjp.getSignature().getName());
            //放行
            Object retObj = pjp.proceed();
            System.out.println("4.环绕通知....提交事务...");
            return retObj;
        }
    
    
        public void myAfterThrowing(JoinPoint jp,Throwable e){
            System.out.println("异常通知..." + jp.getSignature().getName() + "===" + e.getMessage() );
        }
    
        public void myAfter(JoinPoint jp){
            System.out.println("最终通知..." + jp.getSignature().getName());
        }
    }
    
    <?xml version="1.0" encoding="UTF-8"?>
    <!--xmlns xml namespace: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">
    
    
        <!--  配置UserService-->
        <bean id="userService" class="com.zengqiang.service.UserServiceImpl"></bean>
    
        <!-- 配置切面对象-->
        <bean id="myAspect" class="com.zengqiang.aspect.MyAspect"></bean>
    
        <!-- 配置 aop -->
        <aop:config>
            <!-- aop:指定切面-->
            <aop:aspect ref="myAspect">
                <!--定义一个切入点-->
                <aop:pointcut id="myPointcut" expression="execution(* com.zengqiang.service.UserServiceImpl.*(..))"/>
    
                <!-- 其实上面公共的切入点也可以不声明,直接再配置每个通知的时候在每次填写,但是复用性有问题 -->
                <!-- 配置前置通知...-->
                <aop:before method="myBefore" pointcut-ref="myPointcut" />
    
                <!-- 配置后置通知...-->
                <aop:after-returning method="myAfterReturning" pointcut-ref="myPointcut" returning="retValue"/>
    
                <!--配置环绕通知-->
                <aop:around method="myAround" pointcut-ref="myPointcut"></aop:around>
    
                <!-- 配置异常通知
                throwing="e" 值,是方法的参数名
                -->
                <aop:after-throwing method="myAfterThrowing" pointcut-ref="myPointcut" throwing="e"/>
    
                <!--配置最终通知:不管有没有异常,最终通知都会执行-->
                <aop:after method="myAfter" pointcut-ref="myPointcut"></aop:after>
            </aop:aspect>
        </aop:config>
    </beans>
    

    AspectJ基于注解的案例

    //@Aspect对应 bean.xml中的<aop:config><aop:aspect ref="myAspect"></aop:aspect></aop:config>
    @Component
    @Aspect //告诉配置文件这是切入点类
    public class MyAspect {
        //声明一个公共的切入点
       //等效于 <aop:pointcut id="myPointcut" expression="execution(* com.zengqiang.service.UserServiceImpl.*(..))"/>
        @Pointcut("execution(* com.zengqiang.service.UserServiceImpl.*(..))")
        public void myPointcut(){}
    
        @Before("myPointcut()")
        public void myBefore(JoinPoint jp){
            System.out.println("1.前置通知..." + jp.getSignature().getName());//连接点方法名
            //例如此时test中调用的是userService.deleteUser();方法,则上面jp.getSignature().getName()输出
            //就是deleteUser
        }
    
        /**
         * 后置通知获取service方法执行后的返回值
         * Object retValue:service方法执行的返回值,如果写了返回值,需要在xml中配置returning
         * @param jp
         */
        // <aop:after-returning method="myAfterReturning" pointcut-ref="myPointcut" returning="retValue"/>
        @AfterReturning(pointcut = "myPointcut()",returning = "retValue")
        public void myAfterReturning(JoinPoint jp,Object retValue){
            System.out.println("3.后置通知..." +  jp.getSignature().getName());
            System.out.println("返回值:" + retValue);
        }
    
        @Around("myPointcut()")
        public Object myAround(ProceedingJoinPoint pjp) throws Throwable {
            //ProceedingJoinPoint和JoinPoint区别之一就是ProceedingJoinPoint有放行方法
            System.out.println("2.环绕通知...开启事务..." + pjp.getSignature().getName());
            //放行
            Object retObj = pjp.proceed();
            System.out.println("4.环绕通知....提交事务...");
            return retObj;
        }
    
    
        @AfterThrowing(pointcut = "myPointcut()",throwing = "e")
        public void myAfterThrowing(JoinPoint jp,Throwable e){
            System.out.println("异常通知..." + jp.getSignature().getName() + "===" + e.getMessage() );
        }
    
        @After("myPointcut()")
        public void myAfter(JoinPoint jp){
            System.out.println("最终通知..." + jp.getSignature().getName());
        }
    }
    
    <?xml version="1.0" encoding="UTF-8"?>
    <!--xmlns xml namespace: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.zengqiang"/>
    
        <!-- 配置aop注解生效-->
        <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
    
        <!--aop配置-->
        <aop:config>
            <aop:aspect ref="myAspect"></aop:aspect>
        </aop:config>
    </beans>
    

    当AspectJ-xml和注解同时存在的执行顺序

    大部分情况下,这个顺序对业务没影响,主要注意事务提交和资源释放的顺序。该顺序针对上面AspectJ基于注解的案例中的MyAspect.JAVA


    1. service方法有返回值 + 无异常 + XML

    1.前置通知...deleteUser
    2.环绕通知...开启事务...deleteUser
    通过id删除用户
    3.后置通知...deleteUser
    返回值:1
    4.环绕通知....提交事务...
    最终通知...deleteUser


    1. service方法有返回值 + 无异常 + 注解

    2.环绕通知...开启事务...deleteUser
    1.前置通知...deleteUser
    通过id删除用户
    4.环绕通知....提交事务...
    最终通知...deleteUser
    3.后置通知...deleteUser
    返回值:1


    1. service方法没有返回值 + 无异常 + XML

    1.前置通知...deleteUser
    2.环绕通知...开启事务...deleteUser
    删除用户。。。。
    3.后置通知...deleteUser
    返回值:null
    4.环绕通知....提交事务...
    最终通知...deleteUser


    1. service方法没有返回值 + 无异常 + 注解

    2.环绕通知...开启事务...deleteUser
    1.前置通知...deleteUser
    删除用户。。。。
    4.环绕通知....提交事务...
    最终通知...deleteUser
    3.后置通知...deleteUser
    返回值:null



    AOP的事务配置

    基于xml的形式

     <!-- 配置事务管理器,对照下面的图片使用-->
        <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            <!--配置dataSource-->
            <property name="dataSource" ref="dataSource"></property>
        </bean>
    
    图片1.png 图片2.png

    补充:isolation:代表隔离级别,propagation代表传播行为,图上是默认值,其实可以省略不写

    基于注解的形式

    图片3.png 图片4.png

    相关文章

      网友评论

        本文标题:Spring泛览二(AOP)

        本文链接:https://www.haomeiwen.com/subject/ysdsyqtx.html