Личный блог: zhenganwen.top
Обзор АОП
AOP
,СейчасAspect-Oriented Program
Аспектно-ориентированное программирование по сравнению с вертикальным улучшением объектов, таких как наследование и шаблоны декораторов,AOP
Он горизонтальный, неинвазивный, подключаемый и часто используется повторно. Поэтому какSpring
Он широко используется в ведении журналов, управлении транзакциями, управлении разрешениями, обработке исключений и других сценариях.
прокси-режим
из-заAOP
Он основан на динамическом прокси, поэтому в этом разделе кратко представлен режим прокси. Режим прокси делится на статический прокси и динамический прокси.Основная идея состоит в том, чтобы играть роль посредника (прокси-объект) между вызывающим и вызываемым (прокси-объект, также известный как целевой объект), который может достичь разделения , повторное использование, Эффект защиты:
- Все запросы на вызов должны сначала пройти через прокси-объект, поэтому прокси-объект может сначала отфильтровать эти запросы, чтобы заблокировать незаконные запросы, что необходимо для защиты.
- Если вам нужно записывать информацию о вызывающем абоненте перед каждым вызовом, вы можете передать эту задачу прокси-объекту, не добавляя этот код к другим вызываемым объектам, который предназначен для повторного использования.
- Для вызывающей стороны прокси-объект и целевой объект имеют одинаковый внешний вид. Через прокси-объект в качестве посредника вызывающая сторона не знает, кто на самом деле обрабатывает запрос. Вызывающая сторона и целевой объект не имеют прямой зависимости.
Прокси-объект не выполняет фактическую бизнес-обработку запроса на вызов.
статический прокси
Для достижения того же внешнего вида, что и у целевого объекта, путем реализации того же интерфейса, что и у целевого объекта. Выполните фактическую бизнес-обработку запроса, сохранив ссылку на целевой объект.
Пример кода статического прокси-сервера выглядит следующим образом:
public interface UserService {
void add();
}
public class UserReadServiceImpl implements UserService {
public void add() {
System.out.println("illegal invoke");
}
public void query() {
System.out.println("query user");
}
}
public class UserWriteServiceImpl implements UserService {
public void add() {
System.out.println("insert user");
}
public void query() {
System.out.println("illegal invoke");
}
}
public class UserServiceProxy implements UserService {
private UserService userService;
public UserServiceProxy(UserService userService) {
this.userService = userService;
}
public void add() {
userService.add();
}
public void query() {
userService.query();
}
}
Тест выглядит следующим образом:
public class UserServiceProxyTest {
@Test
public void add() {
UserService userService = new UserWriteServiceImpl();
UserServiceProxy userServiceProxy = new UserServiceProxy(userService);
userServiceProxy.add();
}
@Test
public void query() {
UserService userService = new UserReadServiceImpl();
UserServiceProxy userServiceProxy = new UserServiceProxy(userService);
userServiceProxy.query();
}
}
insert user
query user
Недостатки статического прокси:
- Дополнения к каждой сфере деятельности (как здесь
UserService
является доменом), вам нужно вручную написать класс прокси - избыточность кода
- Для каждого прокси-класса логика прокси должна быть написана в каждом прокси-методе, даже если логика одинакова. Например, прокси-класс журнала делает запись в журнале информации о запросе перед каждым вызовом целевого метода.Если это будет добавлено до того, как каждый прокси-метод вызовет целевой метод, он будет казаться избыточным и избыточным.
Чтобы избежать этих недостатков статических прокси, можно использовать динамические прокси.
Динамический прокси
В настоящее время существует две схемы динамического прокси, а именноjdk
прокси иcglib
играет роль.
-
jdk
агент означаетjdk
встроенный, черезjdk api
может быть достигнут,Только методы интерфейса, реализованные целевым объектом, могут быть улучшены. -
cglib
Прокси относятся к использованию сторонних библиотекcglib
реализовать динамический прокси,cglib
также зависит отasm
библиотека,asm
является методом модификации байт-кода, аcglib
Прокси-объект создается путем изменения символической ссылки в байт-коде. Структуру файла байт-кода и ссылки на символы см. в документе «Углубленное понимание виртуальной машины Java (второе издание)» (Чжоу Чжимин). из-заcglib
Динамические прокси основаны на наследовании, поэтомуcglib
Динамические прокси могутУлучшить все наследуемые методы целевого объекта
jdk
Пример кода для прокси выглядит следующим образом:
package cn.tuhu.springaop.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class LogProxy {
private Object target;
private LogInvocationHandler logInvocationHandler = new LogInvocationHandler();
public LogProxy(Object target) {
this.target = target;
}
public Object getProxyObject() {
ClassLoader classLoader = target.getClass().getClassLoader();
Class<?>[] interfaces = target.getClass().getInterfaces();
return Proxy.newProxyInstance(classLoader, interfaces, logInvocationHandler);
}
private class LogInvocationHandler implements InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("log for request");
method.invoke(target, args);
return proxy;
}
}
}
Тест выглядит следующим образом:
package cn.tuhu.springaop.proxy;
import cn.tuhu.springaop.service.UserService;
import cn.tuhu.springaop.service.impl.UserReadServiceImpl;
import org.junit.Test;
import static org.junit.Assert.*;
public class LogProxyTest {
@Test
public void getProxyObject() {
UserService userService = new UserReadServiceImpl();
LogProxy logProxy = new LogProxy(userService);
UserService userServiceProxy = (UserService) logProxy.getProxyObject();
userServiceProxy.query();
userServiceProxy.add();
}
}
log for request
query user
log for request
illegal invoke
Рукописная транзакция на основе SpringAOP
АОП-программирование
Выше описаны основные принципы динамического прокси,Spring
изAOP
Нижний слой представляет собой динамический прокси, который состоит из следующих элементов:
- Точка входа / проблема
pointcut/joinpoint
- Методы, которые необходимо улучшить
- Улучшения/Уведомления
advice
- Требуется расширенная логика поверх целевого метода
- раздел
aspect
- Агрегат, который применяет уведомления через pointcuts
представлятьSpringAOP
Связанные зависимости:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>3.0.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>3.0.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>3.0.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.6.1</version>
</dependency>
<dependency>
<groupId>aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.5.3</version>
</dependency>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.1_2</version>
</dependency>
Добавить к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" xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<context:component-scan base-package="cn.tuhu.springaop"></context:component-scan>
<!-- 开启AOP编程注解,开启后标识为@Aspect的bean的AOP才会生效 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
Бизнес-интерфейс:
package cn.tuhu.springaop.service;
public interface UserService {
void add();
void query();
}
Реализация бизнеса:
package cn.tuhu.springaop.service.impl;
import cn.tuhu.springaop.service.UserService;
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl implements UserService {
public void add() {
System.out.println("insert user");
}
public void query() {
System.out.println("query user");
}
}
Класс аспекта:
package cn.tuhu.springaop;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class TransactionAspect {
//前置通知
@Before(value = "execution(* cn.tuhu.springaop.service.impl.*.*(..))") //切入点:service.impl包下的所有方法
public void before() {
System.out.println("Before:最先执行");
}
//后置通知
@After(value = "execution(* cn.tuhu.springaop.service.impl.*.*(..))")
public void after() {
System.out.println("After:方法执行之后执行");
}
//环绕通知
@Around(value = "execution(* cn.tuhu.springaop.service.impl.*.*(..))")
public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("Around Begin:Before执行之后方法执行之前执行");
proceedingJoinPoint.proceed();
System.out.println("Around End:After执行之后执行");
}
//正常结束通知
@AfterReturning(value = "execution(* cn.tuhu.springaop.service.impl.*.*(..))")
public void afterReturning() {
System.out.println("AfterReturning:最后执行");
}
//异常终止通知
@AfterThrowing(value = "execution(* cn.tuhu.springaop.service.impl.*.*(..))")
public void afterThrowing() {
System.out.println("AfterThrowing:抛出异常后执行");
}
}
Тестовый класс:
package cn.tuhu.springaop.service.impl;
import cn.tuhu.springaop.service.UserService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class UserServiceImplTest {
ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
@Test
public void add() {
UserService userService = context.getBean(UserService.class);
userService.add();
}
}
Before:最先执行
Around Begin:Before执行之后方法执行之前执行
insert user
After:方法执行之后执行
Around End:After执行之后执行
AfterReturning:最后执行
======================
Before:最先执行
Around Begin:Before执行之后方法执行之前执行
query user
After:方法执行之后执行
Around End:After执行之后执行
AfterReturning:最后执行
видимый,AOP
Механизм аспектов является гибким и может быть расширен различными способами, а также гибкая настройка проблем (execution
выражение).
Программные транзакции
Программная транзакция, то есть ручное кодирование бизнес-метода для открытия сеанса транзакции, фиксации транзакции и отката.
Подготовка среды, необходимо подключиться к базе данных и использоватьSpring
который предоставилJdbcTemplate
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.37</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>3.0.6.RELEASE</version>
</dependency>
Настройте источник данных и менеджер транзакций в файле конфигурации:
<!-- 1. 数据源对象: C3P0连接池 -->
<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/test"></property>
<property name="user" value="root"></property>
<property name="password" value="123456"></property>
</bean>
<!-- 2. JdbcTemplate工具类实例 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 3.配置事务 -->
<bean id="dataSourceTransactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
Dao
package cn.tuhu.springaop.dao;
import cn.tuhu.springaop.entity.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
@Repository
public class UserDao {
@Autowired
JdbcTemplate jdbcTemplate;
public void add(User user) {
String sql = "insert into user (id,name) values(" + user.getId() + ",'" + user.getName() + "');";
jdbcTemplate.execute(sql);
}
}
Service
package cn.tuhu.springaop.service.impl;
import cn.tuhu.springaop.dao.UserDao;
import cn.tuhu.springaop.entity.User;
import cn.tuhu.springaop.service.UserService;
import cn.tuhu.springaop.util.TransactionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.TransactionStatus;
@Service
public class UserServiceImpl implements UserService {
@Autowired
UserDao userDao;
@Autowired
TransactionUtils transactionUtils;
public void add() {
TransactionStatus transactionStatus = null;
try {
//begin
transactionStatus = transactionUtils.begin();
User user = new User(1L, "张三");
userDao.add(user);
user = new User(2L, "李四");
int i = 1 / 0;
userDao.add(user);
if (transactionStatus != null) {
transactionUtils.commit(transactionStatus);
}
} catch (Exception e) {
if (transactionStatus != null) {
transactionUtils.rollback(transactionStatus);
}
}
}
}
тестовая транзакция
public class UserServiceImplTest {
ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
@Test
public void add() {
UserService userService = context.getBean(UserService.class);
userService.add();
}
}
обновить базу данныхuser
Таблица обнаружила, что данные не вставлены, и закомментировалаi=1/0
Затем вставьте две части данных, указывающие на то, что транзакция вступит в силу.
Открытие, фиксация и откат вышеупомянутых транзакций — это шаблонные коды, и мы должны извлечь их для повторного использования.В этом случае АОП пригодится.
package cn.tuhu.springaop.proxy;
import cn.tuhu.springaop.util.TransactionUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.TransactionStatus;
@Component
@Aspect
public class TransactionAop {
@Autowired
TransactionUtils transactionUtils;
@Around(value = "execution(* cn.tuhu.springaop.service.impl.*.add*(..))," +
"execution(* cn.tuhu.springaop.service.impl.*.update*(..))," +
"execution(* cn.tuhu.springaop.service.impl.*.delete*(..))")
public void transactionHandler(ProceedingJoinPoint proceedingJoinPoint) {
TransactionStatus transactionStatus = transactionUtils.begin();
try {
proceedingJoinPoint.proceed();
transactionUtils.commit(transactionStatus);
} catch (Throwable throwable) {
throwable.printStackTrace();
transactionUtils.rollback(transactionStatus);
}
}
}
Бизнес-классу нужно сосредоточиться только на бизнес-коде
@Service
public class UserServiceImpl implements UserService {
@Autowired
UserDao userDao;
public void add() {
User user = new User(1L, "张三");
userDao.add(user);
user = new User(2L, "李四");
int i = 1 / 0;
userDao.add(user);
}
}
декларативная сделка
Spring
Декларативная транзакция выполняется через@Transactional
Чтобы добиться этого с помощью аннотаций, в первую очередь мы сначала экранируем то, что было написано в предыдущем разделе.TransactionAop
:
//@Component
//@Aspect
public class TransactionAop {
существуетspring.xml
Открытые декларативные аннотации транзакций в (обратите внимание на введениеtx
пространство имен):
<?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"
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/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<context:component-scan base-package="cn.tuhu.springaop"></context:component-scan>
<aop:aspectj-autoproxy></aop:aspectj-autoproxy> <!-- 开启事物注解 -->
<!-- 1. 数据源对象: C3P0连接池 -->
<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/test"></property>
<property name="user" value="root"></property>
<property name="password" value="123456"></property>
</bean>
<!-- 2. JdbcTemplate工具类实例 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 3.配置事务 -->
<bean id="dataSourceTransactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<tx:annotation-driven transaction-manager="dataSourceTransactionManager"/>
</beans>
Просто добавьте методы и классы@Transactional
Аннотация может добавить контроль транзакций (если она добавлена в класс, это эквивалентно добавлению ее в каждый метод@Transactional
):
package cn.tuhu.springaop.service.impl;
import cn.tuhu.springaop.dao.UserDao;
import cn.tuhu.springaop.entity.User;
import cn.tuhu.springaop.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class UserServiceImpl implements UserService {
@Autowired
UserDao userDao;
@Transactional(rollbackFor = Exception.class)
public void add() {
User user = new User(1L, "张三");
userDao.add(user);
user = new User(2L, "李四");
int i = 1 / 0; //不注释测一次,注释起来再测一次
userDao.add(user);
}
}
Принцип реализации
Spring
При сканировании пакета было обнаружено, что он аннотирован как@Transactional
метод, а затем выполнять программный контроль транзакций с помощью метода АОП.
Напишите примечание о транзакции от руки
-
определить аннотации транзакций
package cn.tuhu.springaop.annotation; public @interface MyTransactional { }
-
Напишите классы аспектов, которые улучшают методы с помощью аннотаций транзакций.
package cn.tuhu.springaop.proxy; import cn.tuhu.springaop.annotation.MyTransactional; import cn.tuhu.springaop.util.TransactionUtils; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.AfterThrowing; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.interceptor.TransactionAspectSupport; import java.lang.reflect.Method; @Component @Aspect public class TransactionAop { @Autowired TransactionUtils transactionUtils; private ProceedingJoinPoint proceedingJoinPoint; @Around(value = "execution(* cn.tuhu.springaop.service.impl.*.*(..))") public void transactionHandler(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { TransactionStatus transactionStatus = null; if (hasTransaction(proceedingJoinPoint)) { transactionStatus = transactionUtils.begin(); } proceedingJoinPoint.proceed(); // 若hasTransaction(proceedingJoinPoint)判断通过,则transactionStatus不为null if (transactionStatus != null) { transactionUtils.commit(transactionStatus); } } /** * 判断切入点是否标注了@MyTransactional注解 * * @param proceedingJoinPoint * @return */ private boolean hasTransaction(ProceedingJoinPoint proceedingJoinPoint) throws NoSuchMethodException { this.proceedingJoinPoint = proceedingJoinPoint; //获取方法名 String methodName = proceedingJoinPoint.getSignature().getName(); //获取方法所在类的class对象 Class clazz = proceedingJoinPoint.getSignature().getDeclaringType(); //获取参数列表类型 Class[] parameterTypes = ((MethodSignature) proceedingJoinPoint.getSignature()).getParameterTypes(); //根据方法名和方法参列各参数类型可定位类中唯一方法 Method method = clazz.getMethod(methodName, parameterTypes); //根据方法对象获取方法上的注解信息 MyTransactional myTransactional = method.getAnnotation(MyTransactional.class); return myTransactional == null ? false : true; } @AfterThrowing(value = "execution(* cn.tuhu.springaop.service.impl.*.*(..))") public void handleTransactionRollback() throws NoSuchMethodException { if (hasTransaction(proceedingJoinPoint)) { //获取当前事务并回滚 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); } } }
Тест пройден!
Поведение распространения транзакции
Spring
аннотация транзакции@Transactional
имеет атрибутpropagation
Указывает поведение распространения текущей транзакции.Необязательные значения:
Распространение транзакций происходит между несколькими транзакциями, значение по умолчанию равноREQUIRED
Propagation propagation() default Propagation.REQUIRED;
Наиболее часто используется толькоREQUIRED
а такжеREQUIRED_NEW
Два варианта, нижеследующее только знакомит со смыслом двух, остальные можете попробовать сами.
Допустим есть такой сценарий, для стола заказовorder
Имеется журнал заказовorder_log
В частности, используется для регистрации запросов для создания заказов. Требуется, чтобы каждый раз, когда запрашивается формирование заказа, запрос регистрировался независимо от того, успешно сформирован заказ или нет, то есть информация о запросе всегда будет вставленаorder_log
скорее, чемOrderService
Влияние контроля транзакций.
Таким образом, мы можем указать операцию вставки журналаREQUIRED_NEW
, чтобы при вызовеaddOrder
вызыватьaddLog
потому чтоaddOrder
Транзакция уже открыта, и транзакция приостановлена наaddLog
Создайте новую транзакцию, подобную этойaddLog
Независимо отaddOrder
Вне транзакции его откат не затронет.
@Service
public class OrderLogService{
@Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRES_NEW)
public void addLog(){
// recode request info
}
}
@Service
public class OrderService{
@Autowire
OrderLogService orderLogService;
@Transactional(rollbackFor = Exception.class)
public void addOrder(){
orderLogService.addLog();
// generate order
// ...
int i = 1 / 0 ;
}
}
иначе, если нетaddLog
Добавьте транзакцию или установите поведение ее распространения по умолчанию.REQUIRED
если,addLog
Логика будет такой же, какaddOrder
Логика в логе та же транзакция.Как только в процессе формирования ордера возникнет исключение, лог вставки тоже будет откатываться вместе.