栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

65-Spring实战以及AOP介绍

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

65-Spring实战以及AOP介绍

Spring实战以及AOP介绍
转账案例: 需求: 使用spring框架整合DBUtils技术,实现用户转账功能 基础功能: 步骤分析:
 
创建java项目,导入坐标: 

    
        
        mysql
        mysql-connector-java
        5.1.47
    
    
        
        com.alibaba
        druid
        1.1.15
    
    
        
        commons-dbutils
        commons-dbutils
        1.6
    
    
        
        org.springframework
        spring-context
        5.1.5.RELEASE
    
    
        
        org.springframework
        spring-test
        5.1.5.RELEASE
    
    
        
        junit
        junit
        4.12
    


编写Account实体类:
package com.lagou.domain;


public class Account {
    private Integer id;
    private String name;
    private Double money;

    @Override
    public String toString() {
        return "Account{" +
                "id=" + id +
                ", name='" + name + ''' +
                ", money=" + money +
                '}';
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Double getMoney() {
        return money;
    }

    public void setMoney(Double money) {
        this.money = money;
    }
}

编写AccountDao接口和实现类 :
package com.lagou.dao;


public interface AccountDao {

   //转出操作

    public void out(String outUser,double money);

   //转入操作
    public void in(String inUser,double money);

}

package com.lagou.dao.impl;

import com.lagou.dao.AccountDao;
import org.apache.commons.dbutils.QueryRunner;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

import java.sql.SQLException;


@Repository("accountDao")  //生成该类实例到IOC容器中
public class AccountDaoImpl implements AccountDao {

    @Autowired
    private QueryRunner queryRunner;

    @Override
    public void out(String outUser, double money) {

        String sql = "update account set money = money - ? where name = ?";
        try {
            queryRunner.update(sql, money, outUser);
            System.out.println(queryRunner);
            //输出对象,会提示一个小错误,但是不影响程序的运行
            //可以在url后面加上&useSSL=false来解决
            //是因为输出这个,触发了安全机制,需要加上&useSSL=false
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void in(String inUser, double money) {
        String sql = "update account set money = money + ? where name = ?";
        try {
            queryRunner.update(sql, money, inUser);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

编写AccountService接口和实现类:
package com.lagou.service;


public interface AccountService {

    //转账方法
    public void transfer(String out, String in, Double money);


}

package com.lagou.service.impl;

import com.lagou.dao.AccountDao;
import com.lagou.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;


@Service("accountService")
public class AccountServiceImpl implements AccountService {

    @Autowired
    private AccountDao accountDao;

    //转账方法
    //可以知道,service层不只是在dao层的方法的基础上进行单独接口操作(调用)和对应判断(接口外的代码)
    //也可以进行多个接口操作和对应判断
    //实际上本来就可以,只是我们通常这样做
    //即dao层只是提供接口,而service层是对接口的操作和对应判断
    @Override
    public void transfer(String outUser, String inUser, Double money) {

        //调用了减钱方法
        accountDao.out(outUser, money);
        //调用了加钱方法
        accountDao.in(inUser, money);


    }
}

编写spring核心配置文件:



    
    

    


    
    
        
        
        
        
    

    
    
        
    

jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql:///spring_db?characterEncoding=utf8
jdbc.username=root
jdbc.password=123456
编写测试代码:
package com.lagou.test;

import com.lagou.service.AccountService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;


@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class AccountServiceTest {

    @Autowired
    private AccountService accountService;

    @Test //测试方法,基本只能写void返回值,否则报错
    public void textTransfer() {
        accountService.transfer("tom", "jerry", 100d);
        //100d就是这个100是double类型,就如3.14f就是float类型
    }
}

问题分析: 上面的代码事务在dao层,转出转入操作都是一个独立的事务 因为dao层的方法,基本用来编写单个操作的,如转出操作和转入操作 使得service层也只能依次调用,而dao层之所以不合并方法 是为了需要合并中的单个方法时,可以使用(如只需要转入或者转出操作,但这样的业务通常都是一起,虽然也可以多建立方法) 但是方法越少越好(因为实现一次,就要重写一次,为了好维护) 所以实际开发,当有些业务逻辑需要合起来调用时,应该把业务逻辑放在service层,即控制在一个事务中,使得不能分开执行 代码演示:
  @Override
    public void transfer(String outUser, String inUser, Double money) {

        //调用了减钱方法
        accountDao.out(outUser, money);
        
        //int i = 1/0; 
        //若这里报错,后面加钱方法就不会运行
        //由于这两个是分开的,那么在数据库里,可以看到,只有减钱,没有加钱
        //在实际开放中是非常严重的
        
        //调用了加钱方法
        accountDao.in(inUser, money);


    }
从上面的问题出发,看如下解决方式 传统事务: 步骤分析:
 
编写线程绑定工具类: 
package com.lagou.utils;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;


@Component
public class ConnectionUtils {

    @Autowired
    private DataSource dataSource;

    private ThreadLocal threadLocal = new ThreadLocal();

    
    public Connection getThreadConnection(){

        //先从ThreadLocal上获取连接
        Connection conn = threadLocal.get();

        //判断是否为空,即当前线程中是否有连接
        if(conn == null){

            //从数据源中获取一个连接,并且放到ThreadLocal中
            try {
                //不为null
                conn= dataSource.getConnection();

                threadLocal.set(conn);

            } catch (SQLException e) {
                e.printStackTrace();
            }
        }

        return conn;
    }

    //解除当前线程的连接绑定
    public void removeThreadConnection(){
        threadLocal.remove();
        
        
    }
}

连接池和ThreadLocal的区别:
//接下来,我们解释为什么不用数据库连接池,而是使用ThreadLocal线程绑定

ThreadLocal使用图解:

最后得到的都是同一个对象,也就是同一个连接,与引用的不同没有关系(不同引用可以指向同一个对象) 因为执行方法的,从来不是引用,而是对象,只是用引用来代替而已 编写事务管理器:
package com.lagou.utils;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.sql.Connection;


@Component
public class TransactionManager {

    @Autowired
    private ConnectionUtils connectionUtils;

    
    public void beginTransaction(){

            Connection threadConnection = connectionUtils.getThreadConnection();
        try {
            //开启手动事务,也就是关闭自动提交
            threadConnection.setAutoCommit(false);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    public void commit(){

            Connection threadConnection = connectionUtils.getThreadConnection();
        try {
            //提交事务
            threadConnection.commit();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    
    public void rollback(){

            Connection threadConnection = connectionUtils.getThreadConnection();
        try {
            //回滚事务
            threadConnection.rollback();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
     
    public void release(){

        //将手动事务改成自动提交事务
        Connection threadConnection = connectionUtils.getThreadConnection();
        try {
            threadConnection.setAutoCommit(true);

            //将连接归还到连接池中
            connectionUtils.getThreadConnection().close();

            //解除线程绑定,就是删除对应key
            connectionUtils.removeThreadConnection();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }



    }



修改service层代码:
package com.lagou.service.impl;

import com.lagou.dao.AccountDao;
import com.lagou.service.AccountService;
import com.lagou.utils.TransactionManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;


@Service("accountService")
public class AccountServiceImpl implements AccountService {

    @Autowired
    private AccountDao accountDao;

    @Autowired
    private TransactionManager transactionManager;

    @Override
    public void transfer(String outUser, String inUser, Double money) {

        //手动开启事务:调用事务管理器类中的开启事务方法
        transactionManager.beginTransaction();
        try {
            //调用了减钱方法
            accountDao.out(outUser, money);
            
            //int i = 1/0;  这时你就可以通过这个来实验加对应参数连接和不加的区别了

            //调用了加钱方法
            accountDao.in(inUser, money);

            //手动提交事务
            transactionManager.commit();
        } catch (Exception e) {
            e.printStackTrace();
            //手动回滚事务
            transactionManager.rollback();
        }finally{
            //手动释放资源
            transactionManager.release();
        }
        //可以看出,sql的事务操作在实际应用上的重大作用
    }
}

修改dao层代码 :
package com.lagou.dao.impl;

import com.lagou.dao.AccountDao;
import com.lagou.utils.ConnectionUtils;
import org.apache.commons.dbutils.QueryRunner;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

import java.sql.SQLException;


@Repository("accountDao")  //生成该类实例到IOC容器中
public class AccountDaoImpl implements AccountDao {

    @Autowired
    private QueryRunner queryRunner;

    @Autowired
    private ConnectionUtils connectionUtils;

    @Override
    public void out(String outUser, double money) {

        String sql = "update account set money = money - ? where name = ?";
        try {
            queryRunner.update(connectionUtils.getThreadConnection(), sql, money, outUser);
            //指定了连接,那么就不会去连接池里获取连接,使得基本出现不同的事务了(很小几率获得同一连接)
            //因为归还,但就算获得了,基本也是自动提交的,所以还是不要去碰这个运气
            System.out.println(queryRunner);
            //输出对象,会提示一个小错误,但是不影响程序的运行
            //可以在url后面加上&useSSL=false来解决
            //是因为输出这个,触发了安全机制,需要加上&useSSL=false
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void in(String inUser, double money) {
        String sql = "update account set money = money + ? where name = ?";
        try {
            queryRunner.update(connectionUtils.getThreadConnection(), sql, money, inUser);

        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

问题分析 : 上面代码,通过对业务层改造,已经可以实现事务控制了,但是由于我们添加了事务控制 也产生了一个新的问题: 业务层方法变得臃肿了,里面充斥着很多重复代码 并且业务层方法和事务控制方法耦合了,违背了面向对象的开发思想 Proxy优化转账案例: 我们可以将业务代码和事务代码进行拆分,通过动态代理的方式,对业务方法进行事务的增强 这样就不会对业务层产生影响,解决了耦合性的问题啦! 常用的动态代理技术: JDK 代理: 基于接口的动态代理技术·:利用拦截器(必须实现invocationHandler)加上反射机制生成一个代理接口的匿名类 在调用具体方法前调用InvokeHandler来处理,从而实现方法增强 CGLIB代理: 基于父类的动态代理技术:动态生成一个要代理的子类,子类重写要代理的类的所有不是final的方法 不重写也没关系,只是使用的是父类方法的操作然后增强 在子类中采用方法拦截技术拦截所有的父类方法的调用,顺势织入横切逻辑,对方法进行增强

相当于其他类帮你调用方法(也有自己的一些调用,这些调用,就可以说是增强) JDK动态代理方式: Jdk工厂类:
package com.lagou.proxy;

import com.lagou.service.AccountService;
import com.lagou.utils.TransactionManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

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


@Component
public class JDKProxyFactory {

    @Autowired
    private AccountService accountService;

    @Autowired
    private TransactionManager transactionManager;

    
    public AccountService createAccountServiceJdkProxy(){
        

        //getClass()得到的是对应对象的Class对象,因为是对象来调用方法的
        //这点必须理解,后面是以这点为中心
        //而newInstance方法,是创建参数的对象实例,所以不能是接口或者抽象类
        //Class[] getInterfaces(),获取实现的所有接口,Class调用
        //那么就只知道对应对象的实现的接口信息了,一般没去用
        //一般返回格式是,如interface java.lang.Comparable interface java.io.Serializable

        //之所以可以指定对应类型,参数已经指定好了,因为都是对应的对象
        //这就是相当于我们创建了一个一样的对象,帮我们操作原对象的方法
        AccountService o = (AccountService) 
            Proxy.newProxyInstance(accountService.getClass().getClassLoader(), 
                                   accountService.getClass().getInterfaces(), new 
                                   InvocationHandler() {

            //这里参数名称解释
            //proxy:当前的代理对象引用,一般没有去用,反射好的对象,基本是全局的
            //method:被调用的目标方法引用,自己调用时,出现的对应Method对象
            //args:被调用的目标方法所用到的参数,自己调用时,出现的对应参数
            //可以发现,所有的一切操作都在这里执行
            //也就是说proxy是反射出来的实例
            //method就是这个实例的对应Method对象,已经弄好了对应名称,每次调用都是其中一个
            //而args就是对应对象实现的接口格式信息,即包括接口方法的对应参数,每次调用都是其中一组参数
            //之所以没有指定,是进行拦截了自己手动对象调用的信息,而放入的,也就是自己进行调用的方法
            //所以method的名称就是拦截的原信息
            //而args也是拦截的对应参数信息
            //那么对象就可以通过invoke方法,在有methed的情况下
            //通过方法名称,使用invoke来调用对应名称的接口方法了,就使得出现动态代理,即可以增强方法
            //在下面方法的地方可以再次增加其他操作,即增强的意思
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
				
                //手动开启事务:调用事务管理器类中的开启事务方法
                transactionManager.beginTransaction();
                try {
                //System.out.println("目标方法执行之前动态进行了方法增强");
             //被代理的对象的方法执行,我们使用代理对象进行操作时拦截对应信息,然后根据这些信息进行反射执行
                method.invoke(accountService, args);
                //增强的一部分内容
                //System.out.println(1);

                //手动提交事务
                transactionManager.commit();
                } catch (Exception e) {
                    e.printStackTrace();
                    //手动回滚事务
                    transactionManager.rollback();
                }finally{
                    //手动释放资源
                    transactionManager.release();
                }

                return null; //我们主要用到上面的invoke方法,这个返回值是什么,基本可以不用管
            }
        });

        return o;
    }

    //所以说,静态代理,是在类里进行放入其他类,而形成增强,而这里是通过反射进行方法确定,而进行增强
    //静态放入其他类,这里我们自己创建一个类,当作拦截对象(也是对应的类型)
    //再根据拦截信息,来操作反射的真正对象的方法(对应方法中,可以加上其他增强)


    //最后注意:JDK代理只能操作接口,若操作类就会报错,那是因为对应的对象已经继承了Proxy了
    //那么只能实现自己这个接口(在文件里先写好,然后进行运行)
    //因为基本不可能在运行时修改代码
    //所以使得我们强转时可以变成对应接口类型

}

package com.lagou.service.impl;

import com.lagou.dao.AccountDao;
import com.lagou.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;


@Service("accountService")
public class AccountServiceImpl implements AccountService {

    @Autowired
    private AccountDao accountDao;


    @Override
    public void transfer(String outUser, String inUser, Double money) {



            //调用了减钱方法
            accountDao.out(outUser, money);

            //int i = 1/0;

            //调用了加钱方法
            accountDao.in(inUser, money);


    }
}

测试代码:
 
    @Test
    public void testTransferProxyJDK(){

        System.out.println("被代理类的transfer执行了");

        //当前返回的实际上是AccountService的一个代理对象proxy
        AccountService accountServiceJdkProxy = jdkProxyFactory.createAccountServiceJdkProxy();

        //代理对象Proxy调用接口中的任意方法时,都会执行底层的invoke方法
        //因为调用方法的实际操作不是引用,而是对象,所以这里说Proxy对象,因为对应对象继承Proxy类
        accountServiceJdkProxy.transfer("tom", "jerry", 100d);

    }
 
CGLIB动态代理方式: 
Cglib工厂类: 
package com.lagou.proxy;

import com.lagou.service.AccountService;
import com.lagou.utils.TransactionManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;


@Component
public class CglibProxyFactory {

    @Autowired
    private AccountService accountService;

    @Autowired
    private TransactionManager transactionManager;

    public AccountService createAccountServiceCglibProxy(){
        // 编写cglib对应的API来生成代理对象进行返回
        // 参数1 : 目标类的字节码对象
        // 参数2:  动作类,当代理对象调用目标对象中原方法时,那么会执行intercept方法
        AccountService accountServiceproxy = (AccountService) 
            Enhancer.create(accountService.getClass(), new MethodInterceptor() {

            // o : 代表生成的代理对象,一般没去用,反射到的对象一般是全局的
            // method:调用目标方法的引用,拦截到的
            // objects:方法入参,拦截到的
            // methodProxy:代理方法,一般有对应实现接口的信息,一般没去用
            @Override
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy 
                                    methodProxy) throws Throwable {

                try {
                    // 手动开启事务:调用事务管理器类中的开启事务方法
                    transactionManager.beginTransaction();

                    method.invoke(accountService, objects);

                    transactionManager.commit();
                } catch (Exception e) {
                    e.printStackTrace();
                    // 手动回滚事务
                    transactionManager.rollback();
                } finally {
                    // 手动释放资源
                    transactionManager.release();
                }


                return null;
            }
        });
        return accountServiceproxy;


    }



}

测试代码:
    
    @Test
    public void testTransferProxyCglib(){

        //accountServiceCglibProxy:proxy
        AccountService accountServiceCglibProxy = cglibProxyFactory.createAccountServiceCglibProxy();

        accountServiceCglibProxy.transfer("tom", "jerry", 100d);


    }
与JDK动态代理不同,Cglib动态代理的返回值可以是接口 也可以是对应类(需要符号父类指向子类或者本身的规则),即指向规则 而JDK动态代理只可以返回接口,因为JDK动态代理继承了一个类,所以不可再次继承 而Cglib动态代理则没有继承,所以可以继承类 通过分析进行文件添加,但他们都是通过拦截来进行操作的 实际上在文件添加时,由于实现或继承了对应接口或者类,在调用对应方法时 那么就会有对应方法的信息(添加时封装好的),即这些信息就可以说是拦截的信息,然后赋值使用 JDK动态代理由于代理继承了类,那么就要对应需要代理的类实现一个接口 而Cglib动态代理虽然可以返回接口和对应类,但他们对应的final的重写不了 JDK动态代理和Cglib动态代理他们都有父子类关系 初识AOP : 什么是AOP: AOP 为 Aspect Oriented Programming 的缩写,意思为面向切面编程 AOP 是 OOP(面向对象编程) 的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容 利用AOP可以对业务逻辑的各个部分进行隔离 从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率 这样做的好处是: 在程序运行期间,在不修改源码的情况下对方法进行功能增强 逻辑清晰,开发核心业务的时候,不必关注增强业务的代码 减少重复代码,提高开发效率,便于后期维护 AOP底层实现: 实际上,AOP 的底层是通过 Spring 提供的的动态代理技术实现的 在运行期间Spring通过动态代理技术动态的生成代理对象,代理对象方法执行时进行增强功能的介入 在去调用目标对象的方法,从而完成功能的增强 AOP相关术语: Spring 的 AOP 实现底层就是对上面的动态代理的代码进行了封装,封装后我们只需要对需要关注的部分进行代码编写 并通过配置的方式完成指定目标的方法增强 在正式讲解 AOP 的操作之前,我们必须理解 AOP 的相关术语 常用的术语如下:
 
图解: 

对应理解的代码如下:
package com.lagou.proxy;

import com.lagou.service.AccountService;
import com.lagou.utils.TransactionManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

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


@Component
public class JDKProxyFactory {

    @Autowired
    private AccountService accountService;

    @Autowired
    private TransactionManager transactionManager;

    public AccountService createAccountServiceJdkProxy(){

        AccountService o = (AccountService) 
            Proxy.newProxyInstance(accountService.getClass().getClassLoader(), 
                                   accountService.getClass().getInterfaces(), new InvocationHandler() 
                                   {

            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {


                try {
                    //可以通过Method的getName()方法得到对应的方法名称
                    if(method.getName().equals("transfer")) {
                        System.out.println("进行了前置增强");
                        //手动开启事务:调用事务管理器类中的开启事务方法
                        transactionManager.beginTransaction();

                        method.invoke(accountService, args);
                        //手动提交事务
                        transactionManager.commit();
                        System.out.println("进行了后置增强");
                    }else{
                        method.invoke(accountService, args);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                    //手动回滚事务
                    transactionManager.rollback();
                }finally{
                    //手动释放资源
                    transactionManager.release();
                }

                return null;
            }
        });

        return o;
    }


}

package com.lagou.service;


public interface AccountService {

    //转账方法
    public void transfer(String out, String in, Double money);

    public void save();

    public void update();

    public void delete();



}

package com.lagou.service.impl;

import com.lagou.dao.AccountDao;
import com.lagou.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;


@Service("accountService")
public class AccountServiceImpl implements AccountService {

    @Autowired
    private AccountDao accountDao;


    @Override
    public void transfer(String outUser, String inUser, Double money) {



            //调用了减钱方法
            accountDao.out(outUser, money);

            //int i = 1/0;

            //调用了加钱方法
            accountDao.in(inUser, money);


    }

    @Override
    public void save() {
        System.out.println("save方法");
    }

    @Override
    public void update() {

        System.out.println("update方法");
    }

    @Override
    public void delete() {
        System.out.println("delete方法");

    }
}

  
    @Test
    public void testTransferProxyJDK(){

        System.out.println("被代理类的transfer执行了");

        //当前返回的实际上是AccountService的一个代理对象proxy
        AccountService accountServiceJdkProxy = jdkProxyFactory.createAccountServiceJdkProxy();

      
        //accountServiceJdkProxy.transfer("tom", "jerry", 100d);

        accountServiceJdkProxy.save();
    }
AOP开发明确事项: 开发阶段(我们做的) 编写核心业务代码(目标类的目标方法) 切入点 把公用代码抽取出来,制作成通知(增强功能方法) 通知 在配置文件中,声明切入点与通知间的关系,即切面 运行阶段(Spring框架完成的): Spring 框架监控切入点方法的执行 一旦监控到切入点方法被运行,拦截,并使用代理机制,动态创建目标对象的代理对象来进行执行方法 根据通知类别,在代理对象的对应位置,将通知对应的功能织入,完成完整的代码逻辑运行 底层代理实现: 在 Spring 中,框架会根据目标类是否实现了接口来决定采用哪种动态代理的方式 当bean实现接口时,会用JDK代理模式 当bean没有实现接口,用cglib实现 也可以强制使用cglib,在spring配置中加入如下配置(后面会讲到)

知识小结:
 
基于XML的AOP开发: 
快速入门: 
步骤分析: 
 
创建java项目,导入AOP相关坐标: 

    
    
        org.springframework
        spring-context
        5.1.5.RELEASE
    
    
    
        org.aspectj
        aspectjweaver
        1.8.13
    
    
    
        org.springframework
        spring-test 
        5.1.5.RELEASE
    
    
        junit
        junit
        4.12
    


创建目标接口和目标实现类:
package com.lagou.service;


public interface AccountService {

    
    public void transfer();
}

package com.lagou.service.impl;

import com.lagou.service.AccountService;


public class AccountServiceImpl implements AccountService {

    
    @Override
    public void transfer() {
        System.out.println("转账方法执行了");
    }
}

创建通知类:
package com.lagou.advice;


public class MyAdvice {

    public void before() {
        System.out.println("前置通知执行了");
    }
}

将目标类和通知类对象创建权交给spring以及在核心配置文件中配置织入关系,及切面:



    

    


    

    
    
    


    
    
        
         
            
            
        
    


编写测试代码:
package com.lagou.test;

import com.lagou.service.AccountService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;


@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")

public class AccountServiceTest {

    @Autowired
    private AccountService accountService;

    @Test
    public void testTransfer() {
        accountService.transfer();

    }
}

XML配置AOP详解: 切点表达式: 表达式语法:
//execution([修饰符] 返回值类型 包名.类名.方法名(参数))  []包括的是可以省略的
访问修饰符可以省略
 
返回值类型、包名、类名、方法名可以使用星号 * 代替,代表任意 
 
包名与类名之间一个点 . 代表当前包下的类,两个点 … 表示当前包及其子包下的类 
 
参数列表可以使用两个点 … 表示任意个数,任意类型的参数列表 
 
还有如下例子: 
 
最后注意一点:使用@Test的测试方法,public void 方法名称(){}是固定格式,也就是说 
访问权限必须是public,返回值必须是void,方法参数必须是无参,有一个不满足就会报错 
使用后置通知,修改配置文件和加上对应方法: 
   

        
         
            
       

            

        
    
package com.lagou.advice;


public class MyAdvice {

    public void before() {
        System.out.println("前置通知执行了");
    }
    public void after() {
        System.out.println("后置通知执行了");
    }
}

切点表达式抽取: 当多个增强的切点表达式相同时,可以将切点表达式进行抽取 在增强中使用 pointcut-ref 属性代替 pointcut 属性来引用抽取后的切点表达式
  
    
        
        
         
            
    
            -->
            

        
    
通知类型: 通知的配置语法:

注意:通常情况下,环绕通知都是独立使用的 测试代码:
package com.lagou.advice;


public class MyAdvice {

    public void before() {
        System.out.println("前置通知执行了");
    }
    public void after() {
        System.out.println("后置通知执行了");
    }

    public void afterThrowing() {
        System.out.println("异常通知执行了");
    }
    public void afterFinally() {
        System.out.println("最终通知执行了");
    }
}

xml配置文件:
 
    
        
        
        
            
      
            
            
            
        
    

运行结果:
 
使用环绕通知: 

//Proceeding JoinPoint :正在执行的连接点  切点
    //术语中JoinPoint(连接点):所谓连接点是指那些可以被拦截到的点
    //也就是说,我们能够拦截哪些方法的总和,也可以说,对应代理可以执行的方法的总和
    //当我们使用他时,可以知道哪个方法被拦截,得到对应拦截信息
    //那么我们可以通过这个连接点,执行对应方法,那么就相当于执行切入方法
    //一般的在通过监听器监听到切入点之后,然后拦截,自动通过拦截信息进行调用对应方法
    //而由于环绕,使得我们需要手动执行,那么就需要通过自己去使用拦截信息了
    public Object around(ProceedingJoinPoint pjp){
        //切点方法执行
        Object proceed = null;
        try {
            System.out.println("前置通知执行了");
            proceed = pjp.proceed();
            System.out.println("后置通知执行了");
        } catch (Throwable e) {
            e.printStackTrace();
            System.out.println("异常通知执行了");
        }finally {
            System.out.println("最终通知执行了");
        }
        return proceed;


        //可以看出,我们手动操作了通知,但实际上还是放在对应地方的
    }
其实相当于替代执行方法的位置 通过实验
 
    
        
        
         
            
            
            
            
            
        
    
当他们一起时,对应方法
package com.lagou.advice;

import org.aspectj.lang.ProceedingJoinPoint;


public class MyAdvice {

    public void before() {
        System.out.println("前置通知执行了");
    }
    public void after() {
        System.out.println("后置通知执行了");
    }

    public void afterThrowing() {
        System.out.println("异常通知执行了");
    }
    public void afterFinally() {
        System.out.println("最终通知执行了");
    }

    public Object around(ProceedingJoinPoint pjp){
        //切点方法执行
        Object proceed = null;
        try {
            System.out.println("前置通知执行了11");
            proceed = pjp.proceed();
            System.out.println("后置通知执行了11");
        } catch (Throwable e) {
            e.printStackTrace();
            System.out.println("异常通知执行了11");
        }finally {
            System.out.println("最终通知执行了11");
        }
        return proceed;

    }
}

执行结果:
 
知识小结: 
 
基于注解的AOP开发 : 
快速入门: 
步骤分析: 
 
创建java项目,导入AOP相关坐标: 

    
    
        org.springframework
        spring-context
        5.1.5.RELEASE
    
    
    
        org.aspectj
        aspectjweaver
        1.8.13
    
    
    
        org.springframework
        spring-test
        5.1.5.RELEASE
    
    
        junit
        junit
        4.12
    

创建目标接口和目标实现类:
package com.lagou.service;


public interface AccountService {


    public void transfer();
}

package com.lagou.service.impl;

import com.lagou.service.AccountService;
import org.springframework.stereotype.Service;


@Service
public class AccountServiceImpl implements AccountService {


    @Override
    public void transfer() {
        System.out.println("转账方法执行了");


    }
}

创建通知类:
package com.lagou.advice;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;


@Component
@Aspect //升级为切面类:配置切入点和通知的关系,也就是存放通知方法和对应指定的切入方法路径的类
//因为Aspect是是切入点和通知(引介)的结合
public class MyAdvice {

    //很明显,被扫描到时,顺便也有了对应类实例(读取到@Aspect的作用,使得有对应实例)
    //即也通过注解信息,通过了方法添加
    @Before("execution(* com.lagou.service.impl.AccountServiceImpl.*(..))")
    public void before() {
        System.out.println("前置通知执行了");
    }

}

在配置文件中开启组件扫描和 AOP 的自动代理:



    
    

    
    
    


编写测试代码 :
package com.lagou.test;

import com.lagou.service.AccountService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;


@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")

public class AccountServiceTest {

    @Autowired
    private AccountService accountService;

    @Test
    public void testTransfer() {
        accountService.transfer();

    }

}

注解配置AOP详解 : 切点表达式: 切点表达式的抽取
package com.lagou.advice;

import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;


@Component
@Aspect
public class MyAdvice {

    //会先识别这个注解,所以位置不做要求,并讲对应的设置用类名加方法组成
    //后面的注解中,就可以使用这个信息了,当然有的话就会识别
    @Pointcut("execution(* com.lagou.service.impl.AccountServiceImpl.*(..))")
    public void myPoint(){}


    @Before("MyAdvice.myPoint()")
    public void before() {
        System.out.println("前置通知执行了");
    }

    @AfterReturning("MyAdvice.myPoint()")
    public void afterReturning() {
        System.out.println("后置通知执行了");
    }

}

通知类型: 通知的配置语法:@通知注解(“切点表达式")

注意:
 
package com.lagou.advice;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;


@Component
@Aspect
public class MyAdvice {

    //会先识别这个注解,所以位置不做要求,并讲对应的设置用类名加方法组成
    //后面的注解中,就可以使用这个信息了,当然有的话就会识别
    @Pointcut("execution(* com.lagou.service.impl.AccountServiceImpl.*(..))")
    public void myPoint(){}


    @Before("MyAdvice.myPoint()")
    public void before() {
        System.out.println("前置通知执行了");
    }

    @AfterReturning("MyAdvice.myPoint()")
    public void afterReturning() {
        System.out.println("后置通知执行了");
    }

    @AfterThrowing("MyAdvice.myPoint()")
    public void afterThrowing() {
        System.out.println("异常通知执行了");
    }

    @After("MyAdvice.myPoint()")
    public void after() {
        System.out.println("最终通知执行了");
    }

    @Around("MyAdvice.myPoint()")
    public Object around(ProceedingJoinPoint pjp){
        //切点方法执行
        Object proceed = null;
        try {
            System.out.println("前置通知执行了11");
            proceed = pjp.proceed();
            System.out.println("后置通知执行了11");
        } catch (Throwable e) {
            e.printStackTrace();
            System.out.println("异常通知执行了11");
        }finally {
            System.out.println("最终通知执行了11");
        }
        return proceed;

    }

}

运行结果:
 
他们的添加方法,有不同的效果,主要是注解或者环绕通知造成的 
一般的,我们都会单独使用环绕通知,而不会用其他通知和环绕通知一起,若其他通知出现顺序不对,就可以使用环绕通知来操作 
一般开发中,基本都会使用环绕通知来进行,其他通知基本不会使用,因为环绕通知比较方便 
纯注解配置 : 
package com.lagou.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;


@Configuration
@ComponentScan("com.lagou")
@EnableAspectJAutoProxy //开启AOP自动代理 替代了aop:aspectj-autoproxy
public class SpringConfig {
}


package com.lagou.test;

import com.lagou.config.SpringConfig;
import com.lagou.service.AccountService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;


@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
//使用classes指定类,不使用基本指定xml,他们不能互换,否则报错

public class AccountServiceTest {

    @Autowired
    private AccountService accountService;

    @Test
    public void testTransfer() {
        accountService.transfer();

    }

}

知识小结 :
 
AOP优化转账案例 : 
依然使用前面的转账案例,将两个代理工厂对象直接删除!改为spring的aop思想来实现 
xml配置实现: 
在转账案例的配置文件中加入以下配置文件(注意命名空间和约束路径): 
 
    

        
        

        
         
            
            
            
            
            
        

    
注意加上依赖:
  
        
            org.aspectj
            aspectjweaver
            1.8.13
        
执行测试方法:
 @Test
    public void textTransfer() {
        accountService.transfer("tom", "jerry", 100d);
        //100d就是这个100是double类型,就如3.14f就是float类型
    }
注解配置实现 : 在转账案例的配置文件中加入以下配置文件:
 
    
根据下面代码,在转账案例的对应地方加上对应代码:
@Component
@Aspect //先声明为切面类,即通知类
public class TransactionManager {
    @Around("execution(* com.lagou.service.impl.AccountServiceImpl.*(..))")
    //返回值类型也可以是void,或者返回null也可以,会根据返回值类型加上对应的变量的,或者不加
    //所有返回值类型是什么基本无影响
    public Object around(ProceedingJoinPoint pjp){

        Object proceed = null;
        try {
            //开启事务
            connectionUtils.getThreadConnection().setAutoCommit(false);
            proceed = pjp.proceed();
            //提交事务
            connectionUtils.getThreadConnection().commit();
        } catch (Throwable e) {
            e.printStackTrace();
            //回滚事务
            try {
                connectionUtils.getThreadConnection().rollback();
            } catch (SQLException ex) {
                ex.printStackTrace();
            }
        }finally {
            try {
                //释放资源
                connectionUtils.getThreadConnection().setAutoCommit(true);
                //将连接归还到连接池中
                connectionUtils.getThreadConnection().close();

                //解除线程绑定,就是删除对应key
                connectionUtils.removeThreadConnection();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
            return proceed;

    }


}
运行:
 @Test
    public void textTransfer() {
        accountService.transfer("tom", "jerry", 100d);
        //100d就是这个100是double类型,就如3.14f就是float类型
    }
使用注解拦截会先拦截我们写的Proxy类,即在使用注解时,若使用自己的代理类,那么基本会报错 而注解和xml则共生的添加,因为操作是一样的,除非有程序的报错,就如上面的事务,当注解和xml一起使用时 相当于大肠包小肠,即如下
 
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/831369.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号