[длинная статья из 4 слов] Spring AOP легко начать поэтапно!

Java

Введение в АОП (традиционные процедуры)

Советы: если вы хотите быстро проверить друзей, вы можете сразу перейти к разделу «Знакомство с АОП (программа Spring)».

Предыдущее содержание в основном основано на постепенном внедрении знаний о Java и JavaWeb в АОП в прошлом. Настоящий контент Spring AOP включает в себя следующие разделы, вы можете перейти к ним

(1) Терминология АОП

(2) Случай ввода АОП: XML, метод аннотации

(3) Управление транзакциями, полностью основанное на Spring: XML, метод аннотации, чистый метод аннотации.

(1) Краткий анализ и введение АОП

В индустрии программного обеспечения,AOPАббревиатура аспектно-ориентированного программирования, что означает:Аспектно-ориентированное программирование, как предварительно скомпилированный, так и во время выполненияДинамический проксиТехнология для достижения унифицированного обслуживания программных функций. АОП является продолжением ООП, горячей точки в разработке программного обеспечения и важной частью среды Spring, производной парадигмы функционального программирования. Использование АОП можетЧасти бизнес-логики изолированы, так что связь между различными частями бизнес-логикиУменьшенная муфта, улучшить возможность повторного использования программы и повысить эффективность разработки.

-- Энциклопедия Байду

В начале я прямо читал описание энциклопедии в Spring AOP. Лично мне оно кажется очень туманным. Когда я оглядываюсь на это введение, я вдруг понимаю, что смысл этого предложения, грубо говоря, означает, что мы ставим запрограммировать некоторые изповторяющийся кодВыньте его, и когда вам нужно будет его выполнить, вы можете пройтипредварительно скомпилированоилиДинамические прокси во время выполнениявыполнитьНе перемещайте исходный кодИ динамически к программеУлучшить или добавить функциональностьТехнологии

Придумать дублирующий код? Какой код вы придумали? Например!

В следующем методе мы имитируем управление транзакциями в программе.AB в следующем коде можно рассматривать как некоторые сценарии транзакций "открытие транзакции" и "совершение транзакции".Эти коды можно рассматривать как выше- упомянутый вид повторяющегося кода

И есть дублирующийся код, в основном оуправление полномочиямиИлижурнал журналХотя это влияет на «чистоту» бизнес-логики нашего кода, она должна существовать.Если есть какой-либо способ извлечь эти методы, чтобы сделать наш бизнес-код более кратким, мы, естественно, можем больше сосредоточиться на нашем бизнесе, что способствует развитию. , на чем мы хотим сегодня сосредоточиться

Наконец, я должен упомянуть, что АОП, являясь одним из основных компонентов среды Spring, очевидно, применяет множествоШаблоны проектирования, шаблоны проектирования, в конце концов, предназначены дляразъединение, и увеличитьгибкость,Масштабируемость, а некоторые из фреймворков, которые мы изучили, напрямую инкапсулируют эти вещи и позволяют вам использовать их напрямую. Грубо говоря, это просто делает вас ленивыми, чтобы вы могли поддерживать хорошую структуру кода, не написав его самостоятельно. Эти сложные данные структуры,Повышение эффективности разработки

Как только я подошел, я говорил прямо о терминологии АОП, лицом к лицу и т. д. Это явно не очень подходило. Одно только услышав название, всегда может вызвать у людей робость. Название любой технологии — это просто существительное. Что нам нужно больше понять, так это то, что, сравнивая традиционные программы с программами, использующими технологии, связанные со Spring AOP, какие проблемы или требования может помочь нам решить АОП Поэтому, если вы непосредственно изучите его базовое использование, у вас будет больше душ!

(2) Демонстрационный случай (традиционный способ)

Примечание: пример в первой части ниже является улучшением программы из предыдущей статьи.Чтобы позаботиться обо всех моих друзьях, я упомяну все, от зависимости до написания класса, чтобы каждый мог попрактиковаться и взглянуть на Программа в целом Структура также полезна для следующего описания.

(1) Добавьте необходимые зависимости

  • spring-context
  • mysql-connector-java
  • c3p0 (пул соединений с базой данных)
  • commons-dbutils (инструменты для упрощения JDBC) — будут кратко представлены позже
  • junit (модуль самотестирования)
  • spring-test

Примечание. Поскольку здесь я создаю проект Maven, я могу изменить pom.xml, чтобы добавить сюда некоторые необходимые координаты зависимостей.

Если вы не используете зависимого друга при его создании, просто скачайте нужный нам jar-пакет и импортируйте его

<packaging>jar</packaging>
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>

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

        <dependency>
            <groupId>c3p0</groupId>
            <artifactId>c3p0</artifactId>
            <version>0.9.1.2</version>
        </dependency>

        <dependency>
            <groupId>commons-dbutils</groupId>
            <artifactId>commons-dbutils</artifactId>
            <version>1.4</version>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
    </dependencies>

Просто взгляните, импортируются некоторые зависимости ядра Spring, а также зависимости, связанные с базой данных, а также модульные тесты и другие зависимости.

(2) Создание учетных записей и сущностей

Первый случай, который будет использоваться ниже, включает симулированную транзакцию перевода между двумя учетными записями, поэтому мы создаем таблицу с такими полями, как имя и баланс.

A: Создать таблицу учетных записей

-- ----------------------------
-- Table structure for account
-- ----------------------------
CREATE TABLE `account`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(32),
  `balance` float,
  PRIMARY KEY (`id`)
)

B: Создайте класс Account

Нечего сказать, что соответствует нашей форме для создания объекта

public class Account implements Serializable {
    private  Integer id;
    private String name;
    private Float balance;
    ......补充 get set toString 方法

(3) Создание служения и Дао

Далее мы продемонстрируем проблему с транзакцией. Самое главное - использовать метод передачи передачи. Конечно, есть также некоторые методы добавления, удаления, изменения и проверки. Я оставляю только один метод для запроса всех методов. В то время вы можете увидеть повторение и сложность некоторых кодов в традиционном методе.

A: Интерфейс AccountService

public interface AccountService {
    /**
     * 查询所有
     * @return
     */
    List<Account> findAll();

    /**
     * 转账方法
     * @param sourceName    转出账户
     * @param targetName    转入账户
     * @param money
     */
    void transfer(String sourceName,String targetName,Float money);
}

B: Класс реализации AccountServiceImpl

@Service("accountService")
public class AccountServiceImpl implements AccountService {
    @Autowired
    private AccountDao accountDao;

    public List<Account> findAll() {
        return accountDao.findAllAccount();
    }
    
    public void transfer(String sourceName, String targetName, Float money) {
        //根据名称分别查询到转入转出的账户
        Account source = accountDao.findAccountByName(sourceName);
        Account target = accountDao.findAccountByName(targetName);

        //转入转出账户加减
        source.setBalance(source.getBalance() - money);
        target.setBalance(target.getBalance() + money);
        //更新转入转出账户
        accountDao.updateAccount(source);
        accountDao.updateAccount(target);
    }
}

C: Интерфейс AccountDao

public interface AccountDao {

    /**
     * 更细账户信息(修改)
     * @param account
     */
    void updateAccount(Account account);

    /**
     * 查询所有账户
     * @return
     */
    List<Account> findAllAccount();

    /**
     * 通过名称查询
     * @param accountName
     * @return
     */
    Account findAccountByName(String accountName);
}

D: Класс реализации AccountDaoImpl

Мы представили инструмент для DBUTILS для работы с базой данных. Его роль заключается в инкапсуляции кода для достижения цели упрощения операций JDBC, благодаря входящей структуре SSM, постоянный уровень может быть передан mybatis, и сегодня мы сосредоточимся на объяснении знания в Spring, поэтому эта часть будет использовать их.

Основное объяснение используемого содержимого:

QueryRunner предоставляет API (вставка удаления обновления) для работы с операторами sql.

Интерфейс ResultSetHander, который определяет, как инкапсулировать набор результатов после запроса (только то, что мы используем)

  • BeanHander: инкапсулировать первую запись в результирующем наборе в указанный JavaBean
  • BeanListHandler: инкапсулирует все записи результирующего набора в указанный JavaBean и инкапсулирует каждый JavaBean в список.
@Repository("accountDao")
public class AccountDaoImpl implements AccountDao {

    @Autowired
    private QueryRunner runner;

    public void updateAccount(Account account) {
        try {
            runner.update("update account set name=?,balance=? where id=?", account.getName(), account.getBalance(), account.getId());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public List<Account> findAllAccount() {
        try {
            return runner.query("select * from account", new BeanListHandler<Account>(Account.class));
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public Account findAccountByName(String accountName) {
        try {
            List<Account> accounts = runner.query("select * from account where name = ?", new BeanListHandler<Account>(Account.class), accountName);

            if (accounts == null || accounts.size() == 0) {
                return null;
            }
            if (accounts.size() > 1) {
                throw new RuntimeException("结果集不唯一,数据存在问题");
            }
            return accounts.get(0);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

(4) Файл конфигурации

A:bean.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       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="cn.ideal"></context:component-scan>

    <!--配置 QueryRunner-->
    <bean id="runner" class="org.apache.commons.dbutils.QueryRunner">
        <!--注入数据源-->
        <constructor-arg name="ds" ref="dataSource"></constructor-arg>
    </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:3306/ideal_spring"></property>
        <property name="user" value="root"></property>
        <property name="password" value="root99"></property>
    </bean>
</beans>

B: jdbcConfig.properties

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/ideal_spring
jdbc.username=root
jdbc.password=root99

(5) Тестовый код

A: АккаунтСервисТест

Здесь мы используем тесты Spring и Junit.

Описание: используйте аннотацию @RunWith, чтобы заменить исходный бегун, затем используйте @ContextConfiguration, чтобы указать расположение файла конфигурации spring, а затем используйте @Autowired для ввода данных в переменные.

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:bean.xml")
public class AccountServiceTest {

    @Autowired
    private AccountService as;

    @Test
    public void testFindAll() {
        List<Account> list = as.findAll();
        for (Account account : list) {
            System.out.println(account);
        }
    }

    @Test
    public void testTransfer() {
        as.transfer("李四", "张三", 500f);
    }

}

(6) Эффект исполнения

Сначала выполните запрос all:

Затем выполните метод симуляции передачи:

Метод заключается в том, чтобы передать Джо Смиту Джону Доу 500, см. следующие результаты, проблем нет.

(3) Предварительный анализ и решение

(1) Анализ транзакционных проблем

Прежде всего, давайте проанализируем, что мы явно не управляем транзакциями, но и не отрицаем, что транзакции должны существовать.Если транзакция не отправлена, очевидно, что функция запроса не может быть успешно протестирована.Наш код транзакции неявно автоматически контролируется. , используйте setAutoCommit(true) объекта подключения, то есть автоматически фиксируйте

Тогда посмотрите файл конфигурации, мы только ввели источник данных, что это значит?

<!--配置 QueryRunner-->
<bean id="runner" class="org.apache.commons.dbutils.QueryRunner">
	<!--注入数据源-->
	<constructor-arg name="ds" ref="dataSource"></constructor-arg>
</bean>

То есть каждый оператор является независимо транзакционным:

Чтобы сильно поставить, это означает, что нет связи друг с другом. Например, в способе передачи сервиса, оператор в положении 1 2 3 4 5 отмечен ниже. Каждый раз, когда он называется, новый Объект QueryRunner будет создан и получить соединение из источника данных, но когда проблема внезапно возникает на определенном шаге, предыдущее утверждение все еще будет выполнено, но последнее утверждение будет прекращено из-за исключения, что мы сказал в начале, отношения между друг другом независимы

public void transfer(String sourceName, String targetName, Float money) {
        //根据名称分别查询到转入转出的账户
        Account source = accountDao.findAccountByName(sourceName); // 1
        Account target = accountDao.findAccountByName(targetName); // 2

        //转入转出账户加减
        source.setBalance(source.getBalance() - money); // 3
        target.setBalance(target.getBalance() + money);
        //更新转入转出账户
        accountDao.updateAccount(source); // 4
        //模拟转账异常
        int num = 100/0; // 异常
        accountDao.updateAccount(target); //5
}

Очевидно, что это очень неуместно, даже смертельно, поскольку мы написали код, переданный из информационной информации об учетной записи учетной записи, но информация об учетной записи в партию, но поскольку предыдущее возникновение аномалии, а не причина успешной реализации, Джон Доу от 2500 стал 2000 лет, но Джо Смит не добился успеха и получить передачу

(2) Предварительное решение бизнес-задач

Вышеупомянутая проблема, в конечном счете, заключается в том, что метод на нашем уровне персистентности не зависит от транзакции, поэтому невозможно добиться полного контроля над транзакцией (вопреки согласованности транзакции). решение проблемы?

Первое, что нам нужно сделать, это использовать объект ThreadLocal для привязки Connection к текущему потоку, чтобы был только один объект, управляющий транзакцией в потоке.

Краткое упоминание о Threadlocal:

Threadlocal — это класс хранения внутри потока, который может хранить данные в указанном потоке, что эквивалентно тому, что данные привязаны к этому потоку, и нужные данные могут быть получены только через этот указанный поток.

Вот официальное описание:

This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its get or set method) has its own, independently initialized copLy of the variable. ThreadLocal instances are typically private static fields in classes that wish to associate state with a thread (e.g., a user ID or Transaction ID).

То есть ThreadLoacl предоставляет способ хранения локальных переменных в потоке, которые являются специальными переменными, каждый полученный поток является независимыми переменными, значение полученных данных должно быть получено и установлено

A: Класс инструмента ConnectionUtils

Создайте пакет utils, а затем создайте класс инструмента ConnectionUtils. Самая важная часть - написать простое суждение. Если в этом потоке уже есть соединение, он вернется напрямую. Если соединения нет, он получит данные из источника данных. ссылка, затем депозит и возврат

@Component
public class ConnectionUtils {
    private ThreadLocal<Connection> threadLocal = new ThreadLocal<Connection>();

    @Autowired
    private DataSource dataSource;

    public Connection getThreadConnection() {

        try {
            // 从 ThreadLocal获取
            Connection connection = threadLocal.get();
            //先判断是否为空
            if (connection == null) {
                //从数据源中获取一个连接,且存入 ThreadLocal
                connection = dataSource.getConnection();
                threadLocal.set(connection);
            }
            return connection;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public void removeConnection(){
        threadLocal.remove();
    }
}

B: Класс инструментов TransactionManager

Затем вы можете создать служебный класс для управления транзакциями, включая открытие, фиксацию, откат транзакций и освобождение соединений.

@Component
public class TransactionManager {

    @Autowired
    private ConnectionUtils connectionUtils;

    /**
     * 开启事务
     */
    public void beginTransaction() {
        try {
            connectionUtils.getThreadConnection().setAutoCommit(false);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 提交事务
     */
    public void commit() {
        try {
            connectionUtils.getThreadConnection().commit();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 回滚事务
     */
    public void rollback() {
        try {
            System.out.println("回滚事务" + connectionUtils.getThreadConnection());
            connectionUtils.getThreadConnection().rollback();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 释放连接
     */
    public void release() {
        try {
            connectionUtils.getThreadConnection().close();//还回连接池中
            connectionUtils.removeConnection();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

C: Добавьте код транзакции на бизнес-уровень

Добавьте код управления транзакциями в метод, выполните открытую транзакцию при нормальных обстоятельствах, выполните операцию (ваш бизнес-код), зафиксируйте транзакцию, выполните операцию отката транзакции после перехвата исключения и, наконец, выполните соединение освобождения

В этом случае, даже если на каком-то шаге возникнет нештатная ситуация, это не приведет к фактическим изменениям данных, поэтому вышеуказанная проблема изначально решена.

@Service("accountService")
public class AccountServiceImpl implements AccountService {
    @Autowired
    private AccountDao accountDao;

    @Autowired
    private TransactionManager transactionManager;

    public List<Account> findAll() {
        try {
            //开启事务
            transactionManager.beginTransaction();
            //执行操作
            List<Account> accounts = accountDao.findAllAccount();
            //提交事务
            transactionManager.commit();
            //返回结果
            return accounts;
        } catch (Exception e) {
            //回滚操作
            transactionManager.rollback();
            throw new RuntimeException(e);
        } finally {
            //释放连接
            transactionManager.release();
        }
    }

    public void transfer(String sourceName, String targetName, Float money) {

        try {
            //开启事务
            transactionManager.beginTransaction();
            //执行操作

            //根据名称分别查询到转入转出的账户
            Account source = accountDao.findAccountByName(sourceName);
            Account target = accountDao.findAccountByName(targetName);

            //转入转出账户加减
            source.setBalance(source.getBalance() - money);
            target.setBalance(target.getBalance() + money);

            //更新转出转入账户
            accountDao.updateAccount(source);
            //模拟转账异常
            int num = 100 / 0;
            accountDao.updateAccount(target);

            //提交事务
            transactionManager.commit();

        } catch (Exception e) {
            //回滚操作
            transactionManager.rollback();
            e.printStackTrace();
        } finally {
            //释放连接
            transactionManager.release();
        }
    }
}

(4) Методы мышления и совершенствования

Хотя мы реализовали управление транзакциями на бизнес-уровне выше, очевидно, что у нас слишком много повторяющегося кода в каждом методе, и появляется связь между бизнес-уровнем и управлением транзакциями.Например, если какое-либо имя метода в транзакции изменяется класс управления, это напрямую приведет к тому, что соответствующий метод не будет найден на бизнес-уровне, и все они должны быть изменены.Если на бизнес-уровне много методов, это, очевидно, очень хлопотно.

В этом случае мы можем улучшить вышеуказанную программу, используя статический прокси.Перед улучшением, чтобы позаботиться обо всех наших друзьях, давайте рассмотрим введение динамического прокси и его базовое использование.

(5) Обзор динамических агентов

(1) Что такое динамический прокси

Динамическое агентство, которое является объектом для предоставления прокси-объекта для управления доступом к этому объекту.

Простой пример: покупать билеты на поезда, самолеты и т. д. мы можем прямо из кассы вокзала, то есть пользователи покупают напрямую у официальных лиц, но многие наши магазины или некоторые придорожные киоски могут покупать билеты на поезд , Прокси-продажи, пользователи могут напрямую покупать билеты в прокси-точках, эти места являются прокси-объектами.

(2) Каковы преимущества использования прокси-объектов?

  • функции, предоставляемые этим классом(Билетная касса железнодорожного вокзала), может больше сосредоточиться на реализации основных функций, таких как организация поездов и изготовление железнодорожных билетов и т. д.
  • прокси-класс(Точка продажи) может быть добавлена ​​на основе метода, предоставляемого классом, обеспечивающим функцию, для достижения большего количества функций.

Преимущество этого динамического агента, приносящее нам много удобства, может помочь нам реализоватьРасширение кода без навязчивости, то есть безИзмените исходный код с помощьюна основеМетод улучшения

Существует два типа динамических прокси: ① Динамические прокси на основе интерфейса ② Динамические прокси на основе подкласса

(3) Два способа динамических агентов

A: Динамический прокси на основе интерфейса

A: Создать официальную кассу (класс и интерфейс)

Интерфейс RailwayTicketProducer

/**
 * 生产厂家的接口
 */
public interface RailwayTicketProducer {

    public void saleTicket(float price);

    public void ticketService(float price);

}

Класс RailwayTicketProducerImpl

В классе реализации мы только улучшаем способ продажи билетов позже, а послепродажное обслуживание не предполагает

/**
 * 生产厂家具体实现
 */
public class RailwayTicketProducerImpl implements RailwayTicketProducer{

    public void saleTicket(float price) {
        System.out.println("销售火车票,收到车票钱:" + price);
    }

    public void ticketService(float price) {
        System.out.println("售后服务(改签),收到手续费:" + price);
    }
}

Класс клиента

Этот класс является классом клиентов, в котором через прокси-объект реализуется спрос на покупку билетов.

Во-первых, как создать прокси-объект. Ответ:Метод newProxyInstance в классе Proxy

Примечание: Поскольку он называется интерфейсным динамическим прокси, это означает, что проксируемый класс, то есть класс, официально продающий билеты в тексте, должен реализовывать хотя бы один интерфейс, что необходимо!

public class Client {

    public static void main(String[] args) {
        RailwayTicketProducer producer = new RailwayTicketProducerImpl();

        //动态代理
        RailwayTicketProducer proxyProduce = (RailwayTicketProducer) Proxy.newProxyInstance(producer.getClass().getClassLoader(),
                producer.getClass().getInterfaces(),new MyInvocationHandler(producer));

        //客户通过代理买票
        proxyProduce.saleTicket(1000f);
    }
}

newProxyInstanceобщийтри параметраОбъяснять:

  • ClassLoader: загрузчик классов

    • Прокси-объект для загрузки Bytecode и тот же класс класса объекта прокси-объекта
  • Класс []: массив байт-кода

    • Чтобы прокси-объект и прокси-объект имели один и тот же метод и реализовывали один и тот же интерфейс, его можно рассматривать как фиксированный метод записи.
  • InvocationHandler: как проксировать, то есть так, как вы хотите улучшить

    • То есть нам в основном нужно создать новый InvocationHandler, а затем написать его класс реализации, писать ли его как анонимный внутренний класс мы можем сами.

    • Например, в приведенном выше коде new MyInvocationHandler(producer) создает экземпляр класса MyInvocationHandler, который я написал сам. На самом деле, я могу напрямую создать новый InvocationHandler, а затем переписать его метод. Суть также усиливается за счет реализации метода вызова InvocationHandler. .

Класс MyInvocationHandler

Этот метод перехватил вызов функции, любой метод прокси-объекта выполняется, будет вызываться через

public class MyInvocationHandler implements InvocationHandler {

    private  Object implObject ;

    public MyInvocationHandler (Object implObject){
        this.implObject=implObject;
    }

    /**
     * 作用:执行被代理对象的任何接口方法都会经过该方法
     * 方法参数的含义
     * @param proxy   代理对象的引用
     * @param method  当前执行的方法
     * @param args    当前执行方法所需的参数
     * @return        和被代理对象方法有相同的返回值
     * @throws Throwable
     */
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object returnValue = null;
        //获取方法执行的参数
        Float price = (Float)args[0];
        //判断是不是指定方法(以售票为例)
        if ("saleTicket".equals(method.getName())){
            returnValue = method.invoke(implObject,price*0.8f);
        }
        return returnValue;
    }
}

Здесь мы получаем сумму покупки билета клиентом.Поскольку мы используем агента для покупки билета, агент будет взимать определенную плату за обработку, поэтому пользователь отправил 1000 юаней, но чиновник получил только 800 юаней.Вот как это прокси реализован, а результат такой

Продать билеты на поезд, получить деньги за билеты: 800.0

B: Метод динамического прокси на основе подкласса

Вышеприведенный метод несложно реализовать просто, но единственным критерием является то, что прокси-объект должен предоставлять интерфейс, а тот, который поясняется сейчас, представляет собой способ прямого проксирования обычных классов Java, и в то же время при демонстрации я буду писать метод прокси прямо в виде внутреннего класса, вместо того, чтобы создавать отдельный класс, всем удобно сравнивать с приведенным выше

Добавить зависимые координаты cglib

<dependencies>
	<dependency>
		<groupId>cglib</groupId>
		<artifactId>cglib</artifactId>
        <version>3.2.4</version>
    </dependency>
</dependencies>

Класс TicketProducer

/**
 * 生产厂家
 */
public class TicketProducer {

    public void saleTicket(float price) {
        System.out.println("销售火车票,收到车票钱:" + price);
    }

    public void ticketService(float price) {
        System.out.println("售后服务(改签),收到手续费:" + price);
    }
}

Метод create в классе Enhancer используется для создания прокси-объектов.

Метод create имеет два параметра

  • Класс: байт-код
    • Указывает байт-код проксируемого объекта
  • Обратный вызов: предоставляет расширенные методы
    • Это в основном то же самое, что и предыдущая функция вызова
    • Обычно написано в классах интерфейса интерфейса: MethodivereCeptor
public class Client {

    public static void main(String[] args) {
        // 由于下方匿名内部类,需要在此处用final修饰
        final TicketProducer ticketProducer = new TicketProducer();

        TicketProducer cglibProducer =(TicketProducer) Enhancer.create(ticketProducer.getClass(), new MethodInterceptor() {

            /**
             * 前三个三个参数和基于接口的动态代理中invoke方法的参数是一样的
             * @param o
             * @param method
             * @param objects
             * @param methodProxy   当前执行方法的代理对象
             * @return
             * @throws Throwable
             */
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                Object returnValue = null;
                //获取方法执行的参数
                Float price = (Float)objects[0];
                //判断是不是指定方法(以售票为例)
                if ("saleTicket".equals(method.getName())){
                    returnValue = method.invoke(ticketProducer,price*0.8f);
                }
                return returnValue;
            }
        });
        cglibProducer.saleTicket(900f);
    }

(6) Повторное улучшение программы динамического агента

Здесь мы пишем фабрику для создания объектов бизнес-уровня

В этом коде мы используем основанный на интерфейсеРежим динамического прокси, До и после метода выполнения записываются методы управления транзакциями, такие как открытие транзакции, фиксация транзакции и откат транзакции.В это время бизнес-уровень может удалить повторяющийся код о бизнесе, написанный ранее.

@Component
public class BeanFactory {
    @Autowired
    private AccountService accountService;
    @Autowired
    private TransactionManager transactionManager;

    @Bean("proxyAccountService")
    public AccountService getAccountService() {
        return (AccountService) Proxy.newProxyInstance(accountService.getClass().getClassLoader(),
                accountService.getClass().getInterfaces(),
                new InvocationHandler() {
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

                        Object returnValue = null;
                        try {
                            //开启事务
                            transactionManager.beginTransaction();
                            //执行操作
                            returnValue = method.invoke(accountService, args);
                            //提交事务
                            transactionManager.commit();
                            //返回结果
                            return returnValue;
                        } catch (Exception e) {
                            //回滚事务
                            transactionManager.rollback();
                            throw new RuntimeException();
                        } finally {
                            //释放连接
                            transactionManager.release();
                        }
                    }
                });
    }
}

Класс AccountServiceTest

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:bean.xml")

public class AccountServiceTest {
    @Autowired
    @Qualifier("proxyAccountService")
    private AccountService as;

    @Test
    public void testFindAll() {
        List<Account> list = as.findAll();
        for (Account account : list) {
            System.out.println(account);
        }
    }

    @Test
    public void testTransfer() {
        as.transfer("李四", "张三", 500f);
    }

}

До сих пор был преобразован относительно полный случай. Поскольку мы обычно используем описанный выше метод аннотации, мы не используем XML для конфигурации. Если мы используем XML для конфигурации, конфигурация является относительно громоздкой, поэтому мы прокладываем путь для многих Фактически, содержание состоит в том, чтобы представить концепцию АОП в Spring, от корня, шаг за шагом, в соответствии с проблемой, чтобы вывести технологию, которую необходимо изучить.

Давайте посмотрим вместе!

Знакомство с АОП (весенняя программа)

В начале мы подробно объяснили, как мы можем шаг за шагом улучшать и решать такие проблемы, как транзакции в традиционных программах, а технология АОП в Spring может помочь нам решить существующие проблемы без изменения исходного кода. улучшены, а обслуживание также очень удобно, что значительно повышает эффективность разработки.Теперь мы начинаем формально внедрять знания АОП.Получив определенные знания, мы можем использовать АОП, чтобы продолжать улучшать предыдущую программу.!

(1) Терминология АОП

Любая технология будет иметь свои определенные термины, которые на самом деле являются просто некоторыми конкретными названиями.На самом деле, когда я изучал раньше, я чувствовал, что некоторые термины АОП были относительно абстрактными и не отражали его интуитивно.Смысл АОП, но эти термины были широко известны разработчикам и стали некоторыми понятиями, известными по умолчанию в этой родственной технологии.Хотя более важно понять идею и использование АОП, нам все же нужно говорить о таком своего рода «консенсусе»

«Весна настоящая» в этом предложении, выделив:

Прежде чем мы войдем в определенное поле, мы должны научиться говорить в этом поле.

Совет

  • Определите безопасность, транзакцию или ведение журнала для выполнения некоторых уведомлений, расширенной обработки до и после метода.
  • То есть: уведомление относится к тому, что необходимо сделать после перехвата **Joinpoint**
  • Существует пять типов уведомлений:
    • Перед уведомлением (Before): вызывается перед выполнением целевого метода.
    • После уведомления (After): используется после завершения целевого метода, результат вывода не имеет к этому никакого отношения.
    • После возврата: вызывается после успешного выполнения целевого метода.
    • Уведомление об исключении (после выбрасывания): вызывается после того, как целевой метод выдает исключение
    • Вокруг уведомления (Around): уведомление оборачивает уведомленный метод и выполняет настраиваемое поведение до и после вызова уведомленного метода (очевидно, это отражено в аннотации, вы можете обратить внимание позже).

Точка соединения (Joinpoint)

  • это точка, в которую аспекты могут быть вставлены во время выполнения приложения. Эта точка может быть при вызове метода, при возникновении исключения или даже при изменении поля. Код аспекта может использовать эти точки для вставки в обычный поток приложения и добавления нового поведения.
  • Например, мы добавили управление транзакциями в методы в Сервисе, а методы в слое транзакций будут перехватываться динамическим прокси.Эти методы можно рассматривать как эту точку подключения.До и после этих методов мы можем добавить некоторые Уведомление
  • Одним словом: переднюю и заднюю часть метода можно рассматривать как точки соединения

Точечная резка

  • Иногда в классе есть много методов, но мы не хотим добавлять уведомления до и после всех методов, мы просто хотим передать указанный метод, что является концепцией точки входа.
  • Одним предложением: точка входа — это фильтрация точек подключения и выбор конечного использования.

Аспект

  • Pointcut сообщает программе, где выполнять улучшение или обработку, а уведомление сообщает программе, что делать в этот момент и когда это делать, поэтому pointcut + уведомление ≈ аспект
  • Фактически, аспект заключается в том, чтобы разделить и увеличить повторяющиеся части бизнес-модуля.Вы можете сравнить предыдущие шаги, где мы напрямую добавляли повторяющиеся коды транзакций к каждому методу на бизнес-уровне, чтобы понять
  • Одним словом: разрезточка входаиУведомлениеСвязывание

Введение

  • Это специальное уведомление, которое может динамически добавлять некоторые методы или свойства в класс во время выполнения без изменения исходного кода.

Ткачество

  • Процесс применения аспекта (улучшения) к целевому объекту и создания нового прокси-объекта
  • По сути, это похоже на предыдущий процесс улучшения метода с помощью динамического прокси и добавления методов транзакций.

(2) Случай входа АОП

Прежде всего, на очень простом примере, чтобы продемонстрировать, как выполнить метод печати журнала до выполнения определенных методов и просто смоделировать его как вывод предложения, Мы все знакомы с предыдущими шагами, и нам нужно обратить внимание на метод bean, настроенный в .xml, я подробно объясню его ниже кода

(1) подход на основе XML

A: Зависимые координаты

аспект jweaver, эта зависимость используется для поддержки выражений pointcut и т.д. Это знание будет упомянуто в конфигурации позже

<packaging>jar</packaging>
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.8.7</version>
        </dependency>
    </dependencies>

B: бизнес-уровень

Интерфейс AccountService

public interface AccountService {
    /**
     * 保存账户
     */
    void addAccount();
    /**
     * 删除账户
     * @return
     */
    int  deleteAccount();
    /**
     * 更新账户
     * @param i
     */
    void updateAccount(int i);
}

Класс реализации CounterviceImpl

public class AccountServiceImpl implements AccountService {
    public void addAccount() {
        System.out.println("这是增加方法");
    }

    public int deleteAccount() {
        System.out.println("这是删除方法");
        return 0;
    }

    public void updateAccount(int i) {
        System.out.println("这是更新方法");
    }
}

C: класс журнала

public class Logger {
    /**
     * 用于打印日志:计划让其在切入点方法执行之前执行(切入点方法就是业务层方法)
     */
    public void printLog(){
        System.out.println("Logger类中的printLog方法执行了");
    }
}

D: файл конфигурации

bean.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: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="cn.ideal.service.impl.AccountServiceImpl"></bean>

    <!--配置 Logger 进来-->
    <bean id="logger" class="cn.ideal.utils.Logger"></bean>

    <!--配置 AOP-->
    <aop:config>
        <!--配置切面-->
        <aop:aspect id="logAdvice" ref="logger">
            <!--通知的类型,以及建立通知方法和切入点方法的关联-->
            <aop:before method="printLog" pointcut="execution(* cn.ideal.service.impl.*.*(..))"></aop:before>
        </aop:aspect>
    </aop:config>
</beans>

(2) Анализ конфигурации 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: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">
</beans>

Первое, что необходимо ввести, это заголовочный файл этого XML, некоторые ограничения, которые вы можете скопировать сюда напрямую, или вы можете перейти на официальный сайт, чтобы найти соответствующие ограничения, как и раньше.

Далее настройка через Service и bean tag Logger

B: базовая конфигурация АОП

aop:config: указывает на начало конфигурации aop, и весь код конфигурации записан в этом теге.

aop:aspect: Указывает на начало настройки аспекта

  • атрибут id: укажите уникальный идентификатор аспекта
  • Атрибут ref: используется для ссылки на настроенный bean-компонент класса уведомлений, заполните идентификатор класса уведомлений.

Внутри тега aop:aspect настройте тип уведомления через соответствующий тег

<aop:config>
	<!--配置切面-->
	<aop:aspect id="logAdvice" ref="logger">
    	<!--通知的类型,以及建立通知方法和切入点方法的关联-->
	</aop:aspect>
</aop:config>

C: Четыре общих конфигурации уведомлений AOP

В заголовке мы выполняем уведомление до выполнения метода, поэтому используется предварительное уведомление

aop:before: используется для настройки предварительного уведомления, указывающего, что расширенный метод выполняется до метода pointcut.

aop:after-returning: используется для настройки почтовых уведомлений, и может быть выполнено только одно уведомление об исключении.

aop:after-throwing: используется для настройки уведомления об исключении, уведомление об исключении может выполнять только одно из них.

aop:after: Используется для настройки конечного совета, он будет выполняться после метода pointcut независимо от того, есть ли исключение при выполнении.

параметр:

  • Метод: используется для указания улучшенного имени метода в классе уведомлений, который является методом printLog в нашем классе регистратора выше

  • poinitcut: используется для указания выражения pointcut (используется в тексте), указывает, какие методы на бизнес-уровне улучшаются.

  • ponitcut-ref: ссылка на выражение, используемое для указания pointcut (когда количество вызовов слишком много, используйте это чаще, уменьшая повторяющийся код)

Как написать выражение pointcut:

  • Во-первых, добавьте ключевое слово execute() в кавычки атрибута poinitcut и напишите выражение в круглых скобках.

  • Базовый формат: модификатор доступа Возвращаемое значение имя пакета.имя пакета.имя пакета...имя класса.имя метода (параметр метода)

    • Описание: Некоторые имена пакетов определяются в соответствии со структурой пакета, в котором расположены их собственные классы.

    • Метод полного соответствия

      • public void cn.ideal.service.impl.AccountServiceImpl.addAccount()
    • Модификаторы доступа, такие как public, можно опустить, а для возвращаемых значений можно использовать подстановочные знаки, указывающие на любое возвращаемое значение.

      • void cn.ideal.service.impl.AccountServiceImpl.addAccount()
    • Имя пакета может использовать подстановочные знаки для представления любого пакета.Если есть несколько уровней пакетов, вам нужно написать несколько *.

      • * *.*.*.*.AccountServiceImpl.addAccount()
    • Имя пакета может использовать .. для обозначения текущего пакета и его подпакетов.

      • cn..*.addAccount()
    • И имена классов, и имена методов могут использовать * для получения подстановочных знаков, а следующее означает, что все подстановочные знаки

      • * *..*.*(..)
  • параметры метода

    • Типы данных могут быть записаны напрямую: например, int

    • Способ написания имени пакета и имени класса для ссылочного типа java.lang.String

    • Подстановочные знаки могут использоваться для представления любого типа, но должны быть параметры

    • Вы можете использовать .., чтобы указать, что параметров нет, и любые параметры могут быть любого типа.

На практике более рекомендуемым способом написания является тот, который приведен в приведенном выше коде, с указанием структуры пакета (как правило, улучшения бизнес-уровня) и использованием подстановочных знаков для других.

pointcut="execution(* cn.ideal.service.impl.*.*(..))"

После указания типа уведомления в 4 необходимо написать это врезное выражение несколько раз, поэтому мы можем использовать параметр pointcut-ref для решения проблемы повторяющегося кода, что фактически эквивалентно его абстрагированию, что удобно для будущих звонков

ponitcut-ref: ссылка на выражение, используемое для указания pointcut (когда количество вызовов слишком много, используйте это чаще, уменьшая повторяющийся код)

Расположение помещено в конфигурацию, просто за пределами аспекта

<aop:pointcut id="pt1" 
expression="execution(* cn.ideal.service.impl.*.*(..))"></aop:pointcut>

При вызове:

<aop:before method="PrintLog" pointcut-ref="pt1"></aop:before>

D: объемное уведомление

Далее, среда Spring предоставляет нам способ вручную контролировать, когда в коде выполняется расширенный код, то есть объемные уведомления.

В конфигурации нужно такое предложение, pt1 такое же, как и раньше

<aop:around method="aroundPrintLog" pointcut-ref="pt1"></aop:around>

Настроено так в классе Logger

public Object aroundPrintLog(ProceedingJoinPoint proceedingJoinPoint) {
    Object returValue = null;
    try {
        Object[] args = proceedingJoinPoint.getArgs();
        System.out.println("这是Logger类中的aroundPrintLog前置方法");

        returValue = proceedingJoinPoint.proceed(args);

        System.out.println("这是Logger类中的aroundPrintLog后置方法");

        return returValue;
    } catch (Throwable throwable) {
        System.out.println("这是Logger类中的aroundPrintLog异常方法");
        throw new RuntimeException();
    } finally {
        System.out.println("这是Logger类中的aroundPrintLog最终方法");
    }
}

Объяснять:

Spring предоставляет интерфейс: ProceedingJoinPoint, у которого есть метод с именем continue(args), что эквивалентно явному вызову метода pointcut.Метод continue() подобен вызову в предыдущем динамическом прокси, и этот интерфейс можно использовать как объемное уведомление Параметры метода , поэтому он похож на предыдущий динамический прокси.

(3) Подход на основе аннотаций

Методы зависимостей и бизнес-уровня, мы используем тот же XML, что и XML, но для удобства демонстрации здесь оставлен только один метод add.

О: Файл конфигурации

Один из конфигурационных файлов предназначен для введения новых ограничений, а другойВключить сканированиеа такжеВключить поддержку аннотаций AOP

<?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"
       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/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

    <!-- 配置spring创建容器时要扫描的包-->
    <context:component-scan base-package="cn.ideal"></context:component-scan>

    <!-- 配置spring开启注解AOP的支持 -->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>

</beans>

Б: добавить заметки

Первый — внедрить сервис на бизнес-уровень.

@Service("accountService")
public class AccountServiceImpl implements AccountService {
    public void addAccount() {
        System.out.println("这是增加方法");
    }
}

Затем находится последнее место в классе Logger, сначала введите этот класс целиком через @Component("logger")

Затем используйте @Aspect, чтобы указать, что это класс аспекта.

Ниже я использовал четыре типа уведомлений, а также объемные типы уведомлений, на которые нужно обратить внимание в аннотациях.

Впервые я протестировал четыре типа уведомлений: первый комментарий вокруг совета, отпустить первые четыре комментария.

@Component("logger")
@Aspect//表示当前类是一个切面类
public class Logger {

    @Pointcut("execution(* cn.ideal.service.impl.*.*(..))")
    private void pt1(){}


//    @Before("pt1()")
    public void printLog1(){
        System.out.println("Logger类中的printLog方法执行了-前置");
    }

//    @AfterReturning("pt1()")
    public void printLog2(){
        System.out.println("Logger类中的printLog方法执行了-后置");
    }

//    @AfterThrowing("pt1()")
    public void printLog3(){
        System.out.println("Logger类中的printLog方法执行了-异常");
    }

//    @After("pt1()")
    public void printLog4(){
        System.out.println("Logger类中的printLog方法执行了-最终");
    }


    @Around("pt1()")
    public Object aroundPrintLog(ProceedingJoinPoint proceedingJoinPoint) {
        Object returValue = null;
        try {
            Object[] args = proceedingJoinPoint.getArgs();
            System.out.println("这是Logger类中的aroundPrintLog前置方法");

            returValue = proceedingJoinPoint.proceed(args);

            System.out.println("这是Logger类中的aroundPrintLog后置方法");

            return returValue;
        } catch (Throwable throwable) {
            System.out.println("这是Logger类中的aroundPrintLog异常方法");
            throw new RuntimeException();
        } finally {
            System.out.println("这是Logger类中的aroundPrintLog最终方法");
        }
    }

}

Результаты тестирования четырех типов уведомлений:

Видно, что произошла очень странная вещь.Есть проблема с положением пост-уведомления и финального уведомления.Такая же проблема будет и в нештатных ситуациях.Здесь действительно проблема,поэтому в основном используем накрутку в нашей аннотации способ уведомления

Результаты теста объемного уведомления:

(4) Метод чистой аннотации

Чистая аннотация относительно проста, просто добавьте @EnableAspectJAutoProxy

@Configuration
@ComponentScan(basePackages="cn.ideal")
@EnableAspectJAutoProxy//主要是这个注解
public class SpringConfiguration {
}

На этом базовое использование двух методов XML и аннотаций закончено.Далее мы поговорим о том, как полностью реализовать управление транзакциями на основе Spring.

(C) полный контроль Spring на основе транзакций

Внедрение знаний АОП в Spring выше, но на самом деле Spring, как мощная структура, рассматривалась для обработки транзакций на нашем бизнес-уровне.Он предоставляет нам набор интерфейсов для управления транзакциями, основанный на фундаменте АОП. В приведенном выше примере управление транзакциями может быть эффективно выполнено. Давайте представим эту часть контента через случай. В этой части контент, который мы выбираем, например, модульный тест уровня сохраняемости, использует Spring. Особое внимание: сохраняемость Слой мы используем Spring JdbcTemplate, незнакомые друзья могут перейти к краткому пониманию, в этом случае основное внимание уделяется изучению управления транзакциями, здесь это не окажет большого влияния

(1) Подготовьте код

Примечание: После подготовки кода первое, что нужно продемонстрировать, — это форма на основе XML, поэтому при ее подготовке мы не использовали аннотации.

A: Импорт зависимых координат

<packaging>jar</packaging>

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

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

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>

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

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

        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.8.7</version>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
    </dependencies>

B: Создание таблиц учетных записей и сущностей

Создать таблицу учетных записей

-- ----------------------------
-- Table structure for account
-- ----------------------------
CREATE TABLE `account`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(32),
  `balance` float,
  PRIMARY KEY (`id`)
)

Создайте класс учетной записи

Нечего сказать, что соответствует нашей сущности создания таблицы

public class Account implements Serializable {
    private  Integer id;
    private String name;
    private Float balance;
    ......补充 get set toString 方法

C: Создать Службу и Дао

Для уменьшения места дан класс реализации, а интерфейс не выложен, очень просто

Бизнес-уровень

package cn.ideal.service.impl;

import cn.ideal.dao.AccountDao;
import cn.ideal.domain.Account;
import cn.ideal.service.AccountService;

public class AccountServiceImpl implements AccountService {

    private AccountDao accountDao;

    public void setAccountDao(AccountDao accountDao) {
        this.accountDao = accountDao;
    }

    public Account findAccountById(Integer accountId) {
        return accountDao.findAccountById(accountId);

    }

    public void transfer(String sourceName, String targetName, Float money) {
        System.out.println("转账方法执行");
        //根据名称分别查询到转入转出的账户
        Account source = accountDao.findAccountByName(sourceName);
        Account target = accountDao.findAccountByName(targetName);

        //转入转出账户加减
        source.setBalance(source.getBalance() - money);
        target.setBalance(target.getBalance() + money);
        //更新转入转出账户
        accountDao.updateAccount(source);

        int num = 100/0;

        accountDao.updateAccount(target);
    }
}

слой сохраняемости

public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao {

    public Account findAccountById(Integer accountId) {
        List<Account> accounts = super.getJdbcTemplate().query("select * from account where id = ?",new BeanPropertyRowMapper<Account>(Account.class),accountId);
        return accounts.isEmpty()?null:accounts.get(0);
    }


    public Account findAccountByName(String accountName) {
        List<Account> accounts = super.getJdbcTemplate().query("select * from account where name = ?",new BeanPropertyRowMapper<Account>(Account.class),accountName);
        if(accounts.isEmpty()){
            return null;
        }
        if(accounts.size()>1){
            throw new RuntimeException("结果集不唯一");
        }
        return accounts.get(0);
    }


    public void updateAccount(Account account) {
        super.getJdbcTemplate().update("update account set name=?,balance=? where id=?",account.getName(),account.getBalance(),account.getId());
    }
}

D: Создайте файл конфигурации bean.xml

Следует отметить одну вещь: если вы не использовали JdbcTemplate, вам может быть интересно узнать, что представляет собой следующий DriverManagerDataSource — встроенный источник данных Spring.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- 配置业务层-->
    <bean id="accountService" class="cn.ideal.service.impl.AccountServiceImpl">
        <property name="accountDao" ref="accountDao"></property>
    </bean>

    <!-- 配置账户的持久层-->
    <bean id="accountDao" class="cn.ideal.dao.impl.AccountDaoImpl">
        <property name="dataSource" ref="dataSource"></property>
    </bean>


    <!-- 配置数据源-->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql://localhost:3306/ideal_spring"></property>
        <property name="username" value="root"></property>
        <property name="password" value="root99"></property>
    </bean>
</beans>

Э: тест

/**
 * 使用Junit单元测试:测试我们的配置
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:bean.xml")
public class AccountServiceTest {

    @Autowired
    private AccountService as;

    @Test
    public void testTransfer() {
        as.transfer("张三", "李四", 500f);
    }

(2) подход на основе XML

Первое, что нужно сделать, это изменить файл конфигурации, здесь необходимо ввести два пространства имен aop и tx.

О настройке бизнес-уровня, уровня сохраняемости и источника данных говорить нечего, просто скопируйте их напрямую.

A: Настройте диспетчер транзакций

Объект, который фактически управляет транзакцией, Spring уже предоставил нам.

Может использоваться при сохранении данных с использованием Spring JDBC или iBatis. org.springframework.jdbc.datasource.DataSourceTransactionManager

При использовании Hibernate для сохранения данных вы можете использовать org.springframework.orm.hibernate5.HibernateTransactionManager.

где находится источник данных

<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
	<property name="dataSource" ref="dataSource"></property>
</bean>

B: Настройка уведомлений о транзакциях

При выполнении уведомлений о транзакциях и настройке атрибутов вам необходимо ввести ограничения транзакций, пространства имен и ограничения tx и aop.

Здесь может быть представлен менеджер транзакций

<!-- 配置事务的通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">

</tx:advice>

C: Настройка свойств транзакции

существует<tx:advice></tx:advice>Вы можете настроить свойства транзакции в разделе Проблема требует глубокого понимания, здесь мы больше сосредоточены на том, как ее настроить и использовать.

  • name: Укажите имя метода, который вам нужен для добавления определенной транзакции, вы можете использовать подстановочные знаки, например * представляет все find * представляет метод, имя которого начинается с find, второй приоритет выше

  • isolation: используется для указания уровня изоляции транзакции, что означает использование уровня изоляции базы данных по умолчанию, значение по умолчанию — ПО УМОЛЧАНИЮ.

    • Читать незафиксированные

      • Идентификация Spring: ISOLATION_READ_UNCOMMITTED

      • Указывает, что грязное чтение разрешено, но обновления не могут быть потеряны. То есть, если одна транзакция начала записывать данные, другая транзакция не может одновременно записывать данные, но другим транзакциям разрешено читать эту строку данных.

    • Чтение зафиксировано

      • Идентификация Spring: ISOLATION_READ_COMMITTED

      • Только зафиксированные данные могут быть прочитаны, что решает проблему грязного чтения. Транзакции, которые читают данные, позволяют другим транзакциям продолжать доступ к строке, но незафиксированные транзакции записи не позволяют другим транзакциям получить доступ к строке.

    • Повторяемое чтение

      • Идентификация Spring: ISOLATION_REPEATABLE_READ
      • Следует ли читать измененные данные, представленные другими транзакциями, решить проблему неповторяющегося чтения и грязного чтения, но иногда могут возникать фантомные данные чтения. Транзакция, которая читает данные, запрещает транзакцию записи (но разрешает транзакцию чтения), а транзакция записи запрещает любую другую транзакцию.
    • Сериализуемый

      • Идентификация Spring: ISOLATION_SERIALIZABLE.
      • Обеспечивает строгую изоляцию транзакций. Для решения проблемы фантомного чтения требуется сериализованное выполнение транзакций.Транзакции могут выполняться только одна за другой и не могут выполняться одновременно.
  • propagation: Используется для указания атрибута распространения транзакции. Значение по умолчанию — ТРЕБУЕТСЯ, что означает, что транзакция должна быть. Обычно используется для добавления, удаления и изменения. Метод запроса может выбрать использование ПОДДЕРЖКИ

  • read-only: используется, чтобы указать, доступна ли транзакция только для чтения. Значение по умолчанию — false для чтения и записи, а для общего метода запроса установлено значение true.

  • timeout: Используется для указания тайм-аута транзакции. Значение по умолчанию равно -1, что означает никогда тайм-аута. Если указано значение в секундах, это свойство обычно не используется.

  • rollback-for: Используется для указания исключения, когда возникает исключение, транзакция откатывается, а когда генерируются другие исключения, транзакция не откатывается. Там нет значения по умолчанию. Указывает, что любое исключение откатывается

  • no-rollback-for: Используется для указания исключения.Когда возникает исключение, транзакция не будет откатываться, а транзакция будет откатываться при создании других исключений. Там нет значения по умолчанию. Указывает, что любое исключение откатывается

<tx:advice id="txAdvice" transaction-manager="transactionManager">
    <!-- 配置事务的属性 -->
    <tx:attributes>
        <tx:method name="*" propagation="REQUIRED" read-only="false"/>
        <tx:method name="find*" propagation="SUPPORTS" read-only="true"/>
    </tx:attributes>
</tx:advice>

D: Настройка выражений pointcut AOP

<!-- 配置aop-->
<aop:config>
     !-- 配置切入点表达式-->
    <aop:pointcut id="pt1" expression="execution(* cn.ideal.service.impl.*.*(..))"></aop:pointcut>
</aop:config>

E: установление соответствия между точкой входа и уведомлением о транзакции выражения

существует<aop:config></aop:config>сделать этот шаг в

<!-- 配置aop-->
<aop:config>
     !-- 配置切入点表达式-->
    <aop:pointcut id="pt1" expression="execution(* cn.ideal.service.impl.*.*(..))"></aop:pointcut>
    <!--建立切入点表达式和事务通知的对应关系 -->
    <aop:advisor advice-ref="txAdvice" pointcut-ref="pt1"></aop:advisor>
</aop:config>

F: все коды конфигурации

<?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"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- 配置业务层-->
    <bean id="accountService" class="cn.ideal.service.impl.AccountServiceImpl">
        <property name="accountDao" ref="accountDao"></property>
    </bean>

    <!-- 配置账户的持久层-->
    <bean id="accountDao" class="cn.ideal.dao.impl.AccountDaoImpl">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!-- 配置数据源-->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql://localhost:3306/ideal_spring"></property>
        <property name="username" value="root"></property>
        <property name="password" value="root99"></property>
    </bean>

    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    
	<!-- 配置事务的通知-->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <!-- 配置事务的属性 -->
        <tx:attributes>
            <tx:method name="*" propagation="REQUIRED" read-only="false"/>
            <tx:method name="find*" propagation="SUPPORTS" read-only="true"/>
        </tx:attributes>
    </tx:advice>

    <!-- 配置aop-->
    <aop:config>
        <!-- 配置切入点表达式-->
        <aop:pointcut id="pt1" expression="execution(* cn.ideal.service.impl.*.*(..))"></aop:pointcut>
        <!--建立切入点表达式和事务通知的对应关系 -->
        <aop:advisor advice-ref="txAdvice" pointcut-ref="pt1"></aop:advisor>
    </aop:config>
    
</beans>

(3) Подход на основе аннотаций

Это все еще базовый код, но требуется небольшая модификация уровня сохраняемости.Для упрощения настройки мы напрямую использовали метод наследования JdbcDaoSupport, но его можно использовать только в режиме XML, а аннотации в этом использовать нельзя. путь. , поэтому нам по-прежнему нужно использовать традиционный способ, который заключается в определении JdcbTemplate в Dao

A: Измените файл конфигурации bean.xml.

Общие операционные аннотации, открытые заметки, здесь у нас есть источник данных и настроил JDBCTEMPLATE

<?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"
       xmlns:tx="http://www.springframework.org/schema/tx"
       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/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

    <!-- 配置spring创建容器时要扫描的包-->
    <context:component-scan base-package="cn.ideal"></context:component-scan>

    <!-- 配置JdbcTemplate-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!-- 配置数据源-->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql://localhost:3306/ideal_spring"></property>
        <property name="username" value="root"></property>
        <property name="password" value="root99"></property>
    </bean>

</beans>

B: Добавьте базовые аннотации на бизнес-уровень и уровень сохраняемости.

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

    @Autowired
    private AccountDao accountDao;
    
    //下面是一样的
}
@Repository("accountDao")
public class AccountDaoImpl implements AccountDao {

    @Autowired
    private JdbcTemplate jdbcTemplate;
    
    //下面基本是一样的
    //只需要将原来的 super.getJdbcTemplate().xxx 改为直接用  jdbcTemplate 执行
}

C: Настройте диспетчер транзакций в bean.xml

<!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"></property>
</bean>

D: включить поддержку аннотационных транзакций в bean.xml

<!-- 开启spring对注解事务的支持-->
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>

E: Добавьте аннотацию @Transactional на бизнес-уровень.

Эта аннотация может появляться на интерфейсах, классах и методах.

  • Появится интерфейс, указывающий, что все класс реализации интерфейса имеет поддержку транзакций

  • Появляется в классе, указывая на то, что все методы в классе поддерживают транзакции.

  • Появляется в методе, указывая на то, что метод поддерживает транзакции.

Например, в следующем примере наш класс указывает, что транзакция доступна только для чтения, но следующая передача также включает операции записи, поэтому мы добавляем к методу аннотацию только для чтения со значением false

@Service("accountService")
@Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
public class AccountServiceImpl implements AccountService {
	.... 省略
	@Transactional(readOnly=false,propagation=Propagation.REQUIRED)
    public void transfer(String sourceName, String targetName, Float money) {
    	...... 省略
    }
}

F: тестовый код

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:bean.xml")
public class AccountServiceTest {

    @Autowired
    private AccountService as;

    @Test
    public void testTransfer() {
        as.transfer("张三", "李四", 500f);
    }
}

(4) На основе метода чистой аннотации

Ниже приведен метод чистой аннотации, bean.xml можно удалить, этот метод не сложен

A: Аннотации класса конфигурации

@Configuration
  • Указывает, что текущий класс является классом конфигурации Spring, эквивалентным файлу bean.xml в XML.

Вам необходимо использовать следующую форму при получении контейнера

private ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfiguration.class);

При использовании весенних модульных тестов

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes= SpringConfiguration.class)
public class AccountServiceTest {
	......
}

B: Укажите аннотацию пакета сканирования

@ComponentScan

@Configuration эквивалентно помощи в создании файла bean.xml.Согласно нашим обычным шагам, мы должны указать отсканированный пакет, который является ролью нашей аннотации.

  • Указывает пакеты, которые Spring будет сканировать при инициализации контейнера, эквивалентно в XML:

  • <!--开启扫描-->
    <context:component-scan base-package="cn.ideal"></context:component-scan>
    
  • Среди них basePackages используется для указания пакета для сканирования, что согласуется с атрибутом value в этой аннотации.

C: Файл свойств конфигурации

@PropertySource

Раньше при создании источника данных информация о конфигурации записывалась напрямую.

  • Используется для загрузки конфигурации в файл .properties
  • значение [] указывает расположение файла свойств.В пути к классам вам нужно добавить путь к классам

Класс SpringConfiguration (эквивалент bean.xml)

/**
 * Spring 配置类
 */
@Configuration
@ComponentScan("cn.ideal")
@Import({JdbcConfig.class,TransactionConfig.class})
@PropertySource("jdbcConfig.properties")
@EnableTransactionManagement
public class SpringConfiguration {

}

Д: создать объект

@Bean

После написания класса конфигурации и указания пакета для сканирования, следующее, что нужно сделать, это настроить jdbcTemplate и источник данных, а затем создать объект диспетчера транзакций.В XML мы настроим его, написав теги bean-компонентов, и Spring предоставляет нам с аннотацией @Bean заменяет исходный тег

  • Напишите аннотацию к методу (только метод), что означает, что объект создается с помощью этого метода, а затем помещается в контейнер Spring.
  • Дайте этому методу имя через атрибут имени, который является идентификатором компонента в нашем XML.
  • Таким образом, данные в файле конфигурации считываются.

JdbcConfig (класс конфигурации JDBC)

/**
 * 和连接数据库相关的配置类
 */
public class JdbcConfig {

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

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

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

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

    /**
     * 创建JdbcTemplate
     * @param dataSource
     * @return
     */
    @Bean(name="jdbcTemplate")
    public JdbcTemplate createJdbcTemplate(DataSource dataSource){
        return new JdbcTemplate(dataSource);
    }

    /**
     * 创建数据源对象
     * @return
     */
    @Bean(name="dataSource")
    public DataSource createDataSource(){
        DriverManagerDataSource ds = new DriverManagerDataSource();
        ds.setDriverClassName(driver);
        ds.setUrl(url);
        ds.setUsername(username);
        ds.setPassword(password);
        return ds;
    }
}

jdbcConfig.properties

Настройте файл конфигурации отдельно

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/ideal_spring
jdbc.username=root
jdbc.password=root99

TransactionConfig

/**
 * 和事务相关的配置类
 */
public class TransactionConfig {
    /**
     * 用于创建事务管理器对象
     * @param dataSource
     * @return
     */
    @Bean(name="transactionManager")
    public PlatformTransactionManager createTransactionManager(DataSource dataSource){
        return new DataSourceTransactionManager(dataSource);
    }
}

Суммировать:

① Эта статья написана здесь. Чтобы изучить любую технологию, вы можете понять ее, только если вы ее знаете. Многие люди были погружены в определенную техническую область в течение многих лет, и, естественно, имеют особое мышление и понимание. Если вы находитесь за дверью , или иметь мало контактов с этим аспектом, вам нужно понять причины и последствия технологии, но какой анализ исходного кода, различные шаблоны проектирования, это тоже Позже, наша первоочередная задача - использовать его для выполнения каких-либо действий, чтобы заставить его запустить, думаю я не слишком умный, а сразу учить кучу конфигураций, кучу аннотаций, кучу имен собственных, это пусто., сложно понять.

② Мы часто попадаем в своего рода,состояние обучения,Возможно, все знают SSM и я тоже его изучаю.Все говорят, что SpringBoot прост и удобен, поэтому я тоже его изучаю.Конечно, из-за каких-то потребностей в работе или учебе нет возможности, но я все равно чувствую, что когда смотрю в технологии снова в частном порядке, я могу использовать некоторые статьи или материалы или найти некоторые видеоресурсы, чтобы увидеть, что принесла эта дисциплина.Его превосходство должно решать проблемы, с которыми мы сталкивались раньше или не рассматривали.Такого рода A Пошаговый подход к обучению может помочь нам получить общее представление о некоторых технологиях и понять связи между ними.

③ В этой статье я имею в виду «Весенний бой», видео с определенной лошадью и некоторый справочный контент на Baidu Google, начиная с очень простого случая добавления, удаления, изменения и проверки, анализируя ее бизнес-проблемы, шаг шаг за шагом от динамического прокси, АОП был улучшен много раз, что требует некоторых знаний, таких как динамический прокси или JdcbTemplate.Может быть, некоторые друзья не знакомы с ним.Я также использовал некоторое пространство, чтобы объяснить, что написание такой длинной статьи действительно сложно Если вы хотите узнать о знаниях, связанных со Spring AOP, вы можете взглянуть на него или использовать его в качестве простого справочника, который можно использовать в качестве справочника, когда вы рождаетесь вручную

Я очень надеюсь, что смогу помочь вам, еще раз спасибо за вашу поддержку, спасибо!

Советы: В то же время нуждающиеся друзья могут прочитать мою предыдущую статью

[Статья из 40 слов] Среда Spring легко приступает к работе слой за слоем (IOC и DI)

nuggets.capable/post/684490…

конец

Если в статье есть какие-либо недостатки, пожалуйста, оставьте сообщение для обмена, спасибо за вашу поддержку!

Если это может вам помочь, то следуйте за мной! Если вы предпочитаете читать статьи WeChat, вы можете подписаться на мой публичный аккаунт.

Мы не знаем друг друга здесь, но мы все усердно работаем над своей мечтой ❤

Публичный аккаунт, настаивающий на размещении оригинальных технических статей о разработке: в идеале — более двух дней.