AOP核心概念-基于XML配置实现(附详细源码)

2023-10-22 09:44 李志远 735

AOP目标

- AOP核心概念

- Spring - ProxyFactory

- 目标对象- 基于XML配置实现AOP

- 基于注解配置实现AOP

1.注解

- Spring IOC基础注解

- @Component - 类 --创建该类的对象存入到Spring容器中,默认使用类名(首字母小写)

- @Controller

- @Service

- @Repository

- 属性-@Resource(name=""),@Autowired(required=true|false),@Qualifier("")

- 为属性注入值的@Value注解

- -String或数值 [基本简单类型的值]

- Properties属性文件中的值

- 整合MyBatis-基于 XML配置

- DataSource

- SqlSessionFactoryBean

- 造Dao接口代理子类对象的配置

- Spring新注解

- @Configuration - 配置类[使用java类来取代xml配置]

- @ComponentScan({"包名"})

- @Bean -<bean>

2.Spring注解

## 2-1 @PropertySource注解

- 用于加载.properties 文件中的配置

- 我们配置数据源时,可以把连接数据库的信息写到properties 配置文件中,就可以使用此注解指定 properties 配置文件的位置

属性:value[]:用于指定 properties 文件位置

- 示例:

jdbc.driverClass=com.mysql.cj.jdbc.Driver 
jdbc.url=jdbc:mysql://localhost:3306/smbms?serverTimezone=Asia/Shanghai&characterEncoding=UTF-8 
jdbc.username=root jdbc.password=root 
 
#Druid连接池的配置 
druid.initalSize=4 
druid.maxActive=20  
druid.minIdel=5

创建配置类:JdbcConfig.java

@Configuration
@PropertySource({"classpath:db.properties"}) //ctrl+p   读取属性配置文件*.properties
public class JdbcConfig {
    @Value("{{jdbc.driverClass}")
    private String driverClass;

    @Value("{{jdbc.url}")
    private String url;

    @Value("{{jdbc.username}")
    private String username;

    @Value("{{jdbc.password}")
    private String password;

    @Bean("dataSource")
    public DataSource getDataSource(){
        DruidDataSource dataSource = new DruidDataSource();
        System.out.println("driverClass:"+driverClass);
        dataSource.setDriverClassName(driverClass);
        System.out.println("url:"+url);
        dataSource.setUrl(url);
        dataSource.setUsername(username);
        dataSource.setPassword(password);
        dataSource.setInitialSize(4);
        dataSource.setMaxActive(20);
        dataSource.setMinIdle(20);
        return dataSource;
    }
}

## 2-2 @Import注解

- 用于导入其他配置类,在引入其他配置类时,可以不用再写@Configuration 注解

- 将 @Value 属性 (甚至包括数据库的配置) 拆分出去,再通过 @Import 引入回来,变相强迫 Spring 在

 MapperScannerConfigurer 进行包扫描前先处理它们

@Configuration
@ComponentScan(basePackages = "com.woniu")
@Import(value = {JdbcConfig.class})
public class SpringConfig {
}

## 2-3 Spring整合JUnit

- 如果没有整合JUnit,则每次在测试方法中都需要手动创建Spring工厂对象[扫描包,创建相应对象放入容器中]

- 整合的目的就是当我们启动JUnit运行器方法时,自动的创建Spring容器

- 具体的实现步骤:

step1: 添加整合Junit的依赖包
 <!--整合JUnit-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.2.5.RELEASE</version>
        </dependency>
    
step2: 在JUnit 测试类上添加注解@RunWith 
         使用Spring框架提供的运行器替代JUnit运行器,就会在启动时自动创建spring工厂
    import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

   
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes={SpringConfig.class})  //指定运行器需要读取的 Java配置类
//@ContextConfiguration(locations={"classpath:applicationContext.xml"}) //运行器创建工厂需要读取的XML配置文件
public class UserServiceTest {
  ...
}


step3: 在Test类中为UserService属性注入@Autowired

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes={SpringConfig.class}) 
public class UserServiceTest {

    @Autowired
    private UserService userService;
   
    @Test
    public void testFindUserList(){ //main方法启动器
        List<User> userList = userService.findUserList();
        for (User user : userList) {
            System.out.println(user);
        }
    }
}

3 使用Spring+Mybatis整合实现转账操作

步骤1:添加依赖

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.2.5.RELEASE</version>
    </dependency>

    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.6</version>
    </dependency>

    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.23</version>
    </dependency>

    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis-spring</artifactId>
        <version>2.0.6</version>
    </dependency>

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
        <version>5.2.5.RELEASE</version>
    </dependency>

    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-classic</artifactId>
        <version>1.2.3</version>
    </dependency>

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>5.2.5.RELEASE</version>
    </dependency>

    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.13</version>
        <scope>test</scope>
    </dependency>
    
     <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.2.5</version>
        </dependency>
</dependencies>

步骤2: 准备相关的配置文件

applicationContext.xml
db.properties
logback.xml

步骤 3: 创建包dao,entity,添加实体类Account

public class Account {
    private Integer id;
    private String userName;
    private Integer money;
    ...
}

 步骤4:编写AccountService,AccountDao,JUnit整合后的测试类

/**
 * 业务测试类-模拟表示层
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:applicationContext.xml"})  //启动时自动基于XML配置文件创建 Spring工厂
public class AccountServiceTest {
    @Autowired
    private AccountService accountService;

    @Test
    public void testTransfer(){
        Integer result = accountService.transfer("jack", "tom", 100);
        System.out.println(result==1 ? "OK" :"Error");
    }

}
---------------------------------------------------------------------------------

/**
 * 业务实现类
 */
@Service("accountService")
public class AccountServiceImpl implements AccountService {
    @Autowired
    private AccountDao accountDao;

    /**
     * 转账业务方法实现
     * @param sourceName
     * @param targetName
     * @param money
     * @return
     */
    public Integer transfer(String sourceName, String targetName, Integer money) {
        try {
            //根据帐户名查询sourceAccount和targetAccount对象
            Account sourceAccount = accountDao.getAccountByName(sourceName);
            Account targetAccount = accountDao.getAccountByName(targetName);

            //根据转账金额修改余额
            sourceAccount.setMoney(sourceAccount.getMoney()-money);
            targetAccount.setMoney(targetAccount.getMoney()+money);

            //更新两个账户
            accountDao.updateAccount(sourceAccount);
            int i=3/0;
            accountDao.updateAccount(targetAccount);

            return 1;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }
}
---------------------------------------------------------------------------------

import com.woniu.entity.Account;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;

public interface AccountDao {

    @Select("select * from account where userName=#{userName}")
    Account getAccountByName(String userName);

    @Update("update account set money=#{money} where userName=#{userName}")
    void updateAccount(Account account);
}

结论:使用Spring整合mybatis之后,默认是sql语句自动提交,也就没有开启事务处理的,会导致一个转账内部出现数据不一致.

4 转账案例-问题解决

- 让业务层来控制事务的提交和回滚

- 先使用手动控制事务

 - 手动控制连接对象

 - 添加Dao实现类

步骤1:将applicationContext.xml仅保留数据源

<?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: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">

    <context:component-scan base-package="com.woniu" />

    <!--加载db.properties属性文件 -->
    <context:property-placeholder location="classpath:db.properties"/>

    <!--数据源配置-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="{{jdbc.driverClass}"/>
        <property name="url" value="{{jdbc.url}"/>
        <property name="username" value="{{jdbc.username}"/>
        <property name="password" value="{{jdbc.password}"/>
        <!--关于连接池相关配置-->
        <property name="initialSize" value="{{druid.initalSize}"/>
        <property name="maxActive" value="{{druid.maxActive}" />
        <property name="minIdle" value="{{druid.minIdel}" />
    </bean>
</beans>

步骤2: 添加AccountDao接口手动实现类

/**
 * 持久层实现类
 */
@Repository("accountDao")
public class AccountDaoImpl implements AccountDao {
    @Autowired
    private ConnectionUtil connectionUtil;

    public Account getAccountByName(String userName) {
        Account account = null;
        try {
            String sql = "select * from account where userName=?";
            Connection conn = connectionUtil.getConnection();
            PreparedStatement pstmt = conn.prepareStatement(sql);
            pstmt.setString(1, userName);
            ResultSet rs = pstmt.executeQuery();
            if (rs.next()) {
                account = new Account();
                account.setId(rs.getInt(1));
                account.setUserName(rs.getString(2));
                account.setMoney(rs.getInt(3));
            }
            return account;
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
        return null;
    }

    public int updateAccount(Account account) {
        try {
            String sql = "update account set money=? where userName=?";

            Connection conn = connectionUtil.getConnection();
            PreparedStatement pstmt = conn.prepareStatement(sql);
            pstmt.setInt(1, account.getMoney());
            pstmt.setString(2, account.getUserName());
            int rows = pstmt.executeUpdate();
            return rows;
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
        return 0;
    }
}

步骤3:定制ConnectionUtil工具类,并定义ThreadLocal本地线程变量,确保同一个业务操作获取Connection是同一个

/**
 * 管理Connection连接对象
 */
@Component
public class ConnectionUtil {
    @Autowired
    private DataSource dataSource;
    private ThreadLocal<Connection> conns = new ThreadLocal<>();

    /**
     * 获取连接对象
     * @return
     */
    public Connection getConnection() {
        Connection conn = conns.get();
        if (conn == null) {
            try {
                conn = dataSource.getConnection();
                conns.set(conn); //设置到ThreadLocal本地变量中的Map中
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
        return conn;
    }

    /**
     * 关闭连接
     * @param conn
     */
    public void closeConnection(Connection conn){
        if(conn!=null){
            try {
                conn.close();
                conns.remove(); //移除Map中的变量conn
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
    }
}

步骤4: 添加事务管理器TransactionManager类

/**
 * 事务管理器
 */
@Component
public class TransactionManager {
    @Autowired
    private ConnectionUtil connectionUtil;

    //开启事务
    public void beginTran(){
        try {
            System.out.println("begin Trans...");
            connectionUtil.getConnection().setAutoCommit(false);
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
    }

    //提交事务
    public void commit(){
        try {
            System.out.println("commit Trans...");
            connectionUtil.getConnection().commit();
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
    }

    //回滚事务
    public void rollback(){
        try {
            System.out.println("rollback Trans...");
            connectionUtil.getConnection().rollback();
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
    }

    //释放资源
    public void release(){
        System.out.println("release...");
        connectionUtil.closeConnection(connectionUtil.getConnection());
    }
}

步骤5:更新业务层代码,添加事务管理

/**
 * 业务实现类
 */
@Service("accountService")
public class AccountServiceImpl implements AccountService {
    @Autowired
    private AccountDao accountDao;

    @Autowired
    private TransactionManager transactionManager;

    /**
     * 转账业务方法实现
     * @param sourceName
     * @param targetName
     * @param money
     * @return
     */
    public Integer transfer(String sourceName, String targetName, Integer money) {
        try {
            //开启事务
            transactionManager.beginTran();

            //根据帐户名查询sourceAccount和targetAccount对象
            Account sourceAccount = accountDao.getAccountByName(sourceName);
            Account targetAccount = accountDao.getAccountByName(targetName);

            //根据转账金额修改余额
            sourceAccount.setMoney(sourceAccount.getMoney()-money);
            targetAccount.setMoney(targetAccount.getMoney()+money);

            //更新两个账户
            accountDao.updateAccount(sourceAccount);
            int i=3/0;  //发生异常
            accountDao.updateAccount(targetAccount);

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

5 新的问题解决--动态代理

- 事务处理问题 解决了,但是业务层代码过于臃肿,每个业务方法需要去处理事务[开启,提交,回滚,释放]

- 动态代理常用的有两种方式:

 - JDK

   - 被代理类必须是基于接口设计的目标类

 - CGLIB - cglib.jar

   - 没有实现接口的目标类

- 这里我们采用JDK动态代理

ServiceProxy:
@Component
public class ServiceProxy {
    @Autowired
    private AccountService accountService;

    @Autowired
    private TransactionManager transactionManager;

    /**
     * 获取AccountService代理对象
     * @return
     */
    @Bean("accountServiceProxy")
    public AccountService getJdkProxy(){
        return (AccountService)Proxy.newProxyInstance(accountService.getClass().getClassLoader(), accountService.getClass().getInterfaces(),
                new InvocationHandler() {
                    /**
                     * 代理对象调用业务方法时的统一处理
                     * @param proxy
                     * @param method
                     * @param args
                     * @return
                     * @throws Throwable
                     */
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        Object returnVal = null; //用于接收目标对象真实方法的返回值
                        try{
                            System.out.println("代理对象的开启...");
                            transactionManager.beginTran(); //开启
                            returnVal = method.invoke(accountService,args);
                            transactionManager.commit(); //提交
                        }catch(Exception e){
                            e.printStackTrace();
                            transactionManager.rollback();
                        }finally {
                            transactionManager.release();
                        }
                        return returnVal;
                    }
                });
    }
}

- 业务层去掉事务管理代码

public Integer transfer(String sourceName, String targetName, Integer money) {
    //根据帐户名查询sourceAccount和targetAccount对象
    Account sourceAccount = accountDao.getAccountByName(sourceName);
    Account targetAccount = accountDao.getAccountByName(targetName);

    //根据转账金额修改余额
    sourceAccount.setMoney(sourceAccount.getMoney()-money);
    targetAccount.setMoney(targetAccount.getMoney()+money);

    //更新两个账户
    accountDao.updateAccount(sourceAccount);
    int i=3/0;  //发生异常
    accountDao.updateAccount(targetAccount);

     return 1;
 }

- 测试类使用业务代理对象执行transfer()

/**
 * 业务测试类-模拟表示层
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:applicationContext.xml"})  //启动时自动基于XML配置文件创建 Spring工厂
public class AccountServiceTest {
    @Autowired
    @Qualifier("accountServiceProxy")
    private AccountService accountService;

    @Test
    public void testTransfer(){
        Integer result = accountService.transfer("jack", "tom", 100);
        System.out.println(result==1 ? "OK" :"Error");
    }
}