阶段六模块二 AOP

内容输出来源:拉钩教育Java就业训练营

1 转账案例

使用spring框架整合DBUtils技术,实现用户转账功能

1.1 基础功能

1)创建java项目,导入坐标

<dependencies>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.47</version>
    </dependency>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.1.15</version>
    </dependency>
    <dependency>
        <groupId>commons-dbutils</groupId>
        <artifactId>commons-dbutils</artifactId>
        <version>1.6</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.1.5.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>5.1.5.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
    </dependency>
</dependencies>

2) 编写Account实体类

public class Account {
    private Integer id;
    private String name;
    private Double money;
    // setter getter....
}

3)编写AccountDao接口和实现类

public interface AccountDao {
    // 转出操作
    public void out(String outUser, Double money);
    // 转入操作
    public void in(String inUser, Double money);
}
@Repository
public class AccountDaoImpl implements AccountDao {
    @Autowired
    private QueryRunner queryRunner;
    @Override
    public void out(String outUser, Double money) {
        try {
            queryRunner.update("update account set money=money-? where name=?",
            money, outUser);
        } catch (SQLException e) {
        	e.printStackTrace();
        }
    }
    @Override
    public void in(String inUser, Double money) {
        try {
            queryRunner.update("update account set money=money+? where name=?",
            money, inUser);
        } catch (SQLException e) {
        	e.printStackTrace();
        }
    }
}

4)编写AccountService接口和实现类

public interface AccountService {
	public void transfer(String outUser, String inUser, Double money);
}
@Service
public class AccountServiceImpl implements AccountService {
    @Autowired
    private AccountDao accountDao;
    @Override
    public void transfer(String outUser, String inUser, Double money) {
        accountDao.out(outUser, money);
        accountDao.in(inUser, money);
    }
}

5)编写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:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
    <!--开启组件扫描-->
    <context:component-scan base-package="com.lagou"/>
    <!--加载jdbc配置文件-->
    <context:property-placeholder location="classpath:jdbc.properties"/>
    <!--把数据库连接池交给IOC容器-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${jdbc.driver}"></property>
        <property name="url" value="${jdbc.url}"></property>
        <property name="username" value="${jdbc.username}"></property>
        <property name="password" value="${jdbc.password}"></property>
    </bean>
    <!--把QueryRunner交给IOC容器-->
    <bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner">
    	<constructor-arg name="ds" ref="dataSource"></constructor-arg>
    </bean>
</beans>

6)编写测试代码

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class AccountServiceTest {
    @Autowired
    private AccountService accountService;
    @Test
    public void testTransfer() throws Exception {
    	accountService.transfer("tom", "jerry", 100d);
    }
}

上面的代码事务在dao层,转出转入操作都是一个独立的事务,但实际开发,应该把业务逻辑控制在一个事务中,所以应该将事务挪到service层。

1.2 传统事务

1)编写线程绑定工具类

/**
* 连接工具类,从数据源中获取一个连接,并将实现和线程的绑定
*/
@Component
public class ConnectionUtils {
    private ThreadLocal<Connection> threadLocal = new ThreadLocal<>();
    @Autowired
    private DataSource dataSource;
    /**
    * 获取当前线程上的连接
    *
    * @return Connection
    */
    public Connection getThreadConnection() {
        // 1.先从ThreadLocal上获取
        Connection connection = threadLocal.get();
        // 2.判断当前线程是否有连接
        if (connection == null) {
            try {
                // 3.从数据源中获取一个连接,并存入到ThreadLocal中
                connection = dataSource.getConnection();
                threadLocal.set(connection);
            } catch (SQLException e) {
            	e.printStackTrace();
            }
        }
        return connection;
    }
    /**
    * 解除当前线程的连接绑定
    */
    public void removeThreadConnection() {
    	threadLocal.remove();
    }
}

2)编写事务管理器

/**
* 事务管理器工具类,包含:开启事务、提交事务、回滚事务、释放资源
*/
@Component
public class TransactionManager {
    @Autowired
    private ConnectionUtils connectionUtils;
    public void beginTransaction() {
        try {
        	connectionUtils.getThreadConnection().setAutoCommit(false);
        } catch (SQLException e) {
        	e.printStackTrace();
        }
    }
    public void commit() {
        try {
        	connectionUtils.getThreadConnection().commit();
        } catch (SQLException e) {
        	e.printStackTrace();
        }
    }
    public void rollback() {
        try {
        	connectionUtils.getThreadConnection().rollback();
        } catch (SQLException e) {
        	e.printStackTrace();
        }
    }
    public void release() {
        try {
        	connectionUtils.getThreadConnection().setAutoCommit(true); // 改回自动提交事务
            connectionUtils.getThreadConnection().close();// 归还到连接池
            connectionUtils.removeThreadConnection();// 解除线程绑定
        } catch (SQLException e) {
        	e.printStackTrace();
        }
    }
}

3)修改service层代码

@Service
public class AccountServiceImpl implements AccountService {
    @Autowired
    private AccountDao accountDao;
    @Autowired
    private TransactionManager transactionManager;
    @Override
    public void transfer(String outUser, String inUser, Double money) {
        try {
            // 1.开启事务
            transactionManager.beginTransaction();
            // 2.业务操作
            accountDao.out(outUser, money);
            int i = 1 / 0;
            accountDao.in(inUser, money);
            // 3.提交事务
            transactionManager.commit();
        } catch (Exception e) {
        	e.printStackTrace();
            // 4.回滚事务
            transactionManager.rollback();
        } finally {
            // 5.释放资源
            transactionManager.release();
        }
    }
}

4)修改dao层代码

@Repository
public class AccountDaoImpl implements AccountDao {
    @Autowired
    private QueryRunner queryRunner;
    @Autowired
    private ConnectionUtils connectionUtils;
    @Override
    public void out(String outUser, Double money) {
        try {
            queryRunner.update(connectionUtils.getThreadConnection(), "update
            account set money=money-? where name=?", money, outUser);
        } catch (SQLException e) {
        	e.printStackTrace();
        }
    }
    @Override
    public void in(String inUser, Double money) {
        try {
            queryRunner.update(connectionUtils.getThreadConnection(), "update
            account set money=money+? where name=?", money, inUser);
        } catch (SQLException e) {
        	e.printStackTrace();
        }
    }
}

2 Proxy优化转账案例

常用的动态代理技术
JDK 代理 : 基于接口的动态代理技术·:利用拦截器(必须实现invocationHandler)加上反射机制生成一个代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理,从而实现方法增强

CGLIB代理:基于父类的动态代理技术:动态生成一个要代理的子类,子类重写要代理的类的所有不是final的方法。在子类中采用方法拦截技术拦截所有的父类方法的调用,顺势织入横切逻辑,对方法进行增强

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uCvrFLuB-1615964488121)(AOP.assets/任务二:AOP.jpg)]

2.1 JDK动态代理方式

Jdk工厂类

@Component
public class JdkProxyFactory {
    @Autowired
    private AccountService accountService;
    @Autowired
    private TransactionManager transactionManager;
    public AccountService createAccountServiceJdkProxy() {
        AccountService accountServiceProxy = null;
        accountServiceProxy = (AccountService)
        Proxy.newProxyInstance(accountService.getClass().getClassLoader(),
        accountService.getClass().getInterfaces(), new
        InvocationHandler() {
            @Override  //proxy:当前代理对象引用 method:被调用的目标方法的引用 args:被调用的目标方法所用到的参数
            public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
                Object result = null;
                try {
                    // 1.开启事务
                    transactionManager.beginTransaction();
                    // 2.业务操作
                    result = method.invoke(accountService, args);
                    // 3.提交事务
                    transactionManager.commit();
                } catch (Exception e) {
                	e.printStackTrace();
                    // 4.回滚事务
                    transactionManager.rollback();
                } finally {
                    // 5.释放资源
                    transactionManager.release();
                }
                return result;
            }
        });
        return accountServiceProxy;
    }
}
2.2 CGLIB动态代理方式

Cglib工厂类

@Component
public class CglibProxyFactory {
    @Autowired
    private AccountService accountService;
    @Autowired
    private TransactionManager transactionManager;
    public AccountService createAccountServiceCglibProxy() {
        AccountService accountServiceProxy = null;
        /*
        * 参数一:目标对象的字节码对象
        * 参数二:动作类,实现增强功能
        * */
        accountServiceProxy = (AccountService)
        Enhancer.create(accountService.getClass(), new MethodInterceptor() {
            @Override
            public Object intercept(Object o, Method method, Object[] objects,
            MethodProxy methodProxy) throws Throwable {
                Object result = null;
                try {
                    // 1.开启事务
                    transactionManager.beginTransaction();
                    // 2.业务操作
                    result = method.invoke(accountService, objects);
                    // 3.提交事务
                    transactionManager.commit();
                } catch (Exception e) {
                    e.printStackTrace();
                    // 4.回滚事务
                    transactionManager.rollback();
                } finally {
                    // 5.释放资源
                    transactionManager.release();
                }
                return result;
            }
        });
        return accountServiceProxy;
    }
}

3 AOP

AOP 为 Aspect Oriented Programming 的缩写,意思为面向切面编程

AOP 是 OOP(面向对象编程) 的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高
程序的可重用性,同时提高了开发的效率。

优势:

  1. 在程序运行期间,在不修改源码的情况下对方法进行功能增强
  2. 逻辑清晰,开发核心业务的时候,不必关注增强业务的代码
  3. 减少重复代码,提高开发效率,便于后期维护
3.1 AOP相关术语
* Target(目标对象):代理的目标对象

* Proxy (代理):一个类被 AOP 织入增强后,就产生一个结果代理类

* Joinpoint(连接点):所谓连接点是指那些可以被拦截到的点。在spring中,这些点指的是方法,因为spring只支持方法类型的连接点

* Pointcut(切入点):所谓切入点是指我们要对哪些 Joinpoint 进行拦截的定义

* Advice(通知/ 增强):所谓通知是指拦截到 Joinpoint 之后所要做的事情就是通知
	分类:前置通知、后置通知、异常通知、最终通知、环绕通知
	
* Aspect(切面):是切入点和通知(引介)的结合

* Weaving(织入):是指把增强应用到目标对象来创建新的代理对象的过程。spring采用动态代理织入,而AspectJ采用编译期织入和类装载期织入
3.2 基于XML的AOP开发
3.2.1 创建java项目,导入AOP相关坐标
<dependencies>
    <!--导入spring的context坐标,context依赖aop-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.1.5.RELEASE</version>
    </dependency>
    <!-- aspectj的织入(切点表达式需要用到该jar包) -->
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.8.13</version>
    </dependency>
    <!--spring整合junit-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>5.1.5.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
    </dependency>
</dependencies>
3.2.2 创建目标接口和目标实现类
public interface AccountService {
	public void transfer();
}
public class AccountServiceImpl implements AccountService {
    @Override
    public void transfer() {
    	System.out.println("转账业务...");
    }
}
3.2.3 创建通知类
public class MyAdvice {
    public void before() {
    	System.out.println("前置通知...");
    }
}
3.2.4 将目标类和通知类对象创建权交给spring
<!--目标类交给IOC容器-->
<bean id="accountService" class="com.lagou.service.impl.AccountServiceImpl">
</bean>
<!--通知类交给IOC容器-->
<bean id="myAdvice" class="com.lagou.advice.MyAdvice"></bean>
3.2.5 在核心配置文件中配置织入关系,及切面
<?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">
    <!--目标类交给IOC容器-->
    <bean id="accountService" class="com.lagou.service.impl.AccountServiceImpl">
    </bean>
    <!--通知类交给IOC容器-->
    <bean id="myAdvice" class="com.lagou.advice.MyAdvice"></bean>
    <aop:config>
        <!--引入通知类-->
        <aop:aspect ref="myAdvice">
            <!--配置目标类的transfer方法执行时,使用通知类的before方法进行前置增强-->
            <aop:before method="before"
                        pointcut="execution(public void
                        com.lagou.service.impl.AccountServiceImpl.transfer())"></aop:before>
        </aop:aspect>
    </aop:config>
</beans>
3.2.6 编写测试代码
@RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration("classpath:applicationContext.xml")
    class AccountServiceTest {
    @Autowired
    private AccountService accountService;
    @Test
    public void testTransfer() throws Exception {
    	accountService.transfer();
    }
}
3.3 切点表达式
execution([修饰符] 返回值类型 包名.类名.方法名(参数))
	访问修饰符可以省略
    返回值类型、包名、类名、方法名可以使用星号 * 代替,代表任意
    包名与类名之间一个点 . 代表当前包下的类,两个点 .. 表示当前包及其子包下的类
    参数列表可以使用两个点 .. 表示任意个数,任意类型的参数列表
execution(public void com.lagou.service.impl.AccountServiceImpl.transfer())
execution(void com.lagou.service.impl.AccountServiceImpl.*(..))
execution(* com.lagou.service.impl.*.*(..))
execution(* com.lagou.service..*.*(..))

切点表达式抽取

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

<aop:config>
    <!--抽取的切点表达式-->
    <aop:pointcut id="myPointcut" expression="execution(* com.lagou.service..*.*
    (..))"> </aop:pointcut>
    <aop:aspect ref="myAdvice">
        <aop:before method="before" pointcut-ref="myPointcut"></aop:before>
    </aop:aspect>
</aop:config>
3.4 通知类型
<aop:通知类型 method=“通知类中方法名” pointcut=“切点表达式"></aop:通知类型>
名称标签说明
前置通知aop:before用于配置前置通知。指定增强的方法在切入点方法之前执行
后置通知aop:afterReturning用于配置后置通知。指定增强的方法在切入点方法之后执行
异常通知aop:afterThrowing用于配置异常通知。指定增强的方法出现异常后执行
最终通知aop:after用于配置最终通知。无论切入点方法执行时是否有异常,都会执行
环境通知aop:around用于配置环绕通知。开发者可以手动控制增强代码在什么时候执行

4 基于注解的AOP开发

4.1 创建java项目,导入AOP相关坐标
<dependencies>
    <!--导入spring的context坐标,context依赖aop-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.1.5.RELEASE</version>
    </dependency>
    <!-- aspectj的织入 -->
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.8.13</version>
    </dependency>
    <!--spring整合junit-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>5.1.5.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
    </dependency>
</dependencies>
4.2 创建目标接口和目标实现类
public interface AccountService {
	public void transfer();
}
public class AccountServiceImpl implements AccountService {
    @Override
    public void transfer() {
    	System.out.println("转账业务...");
    }
}
4.3 创建通知类
public class MyAdvice {
    public void before() {
    	System.out.println("前置通知...");
    }
}
4.4 将目标类和通知类对象创建权交给spring
@Service
public class AccountServiceImpl implements AccountService {}
@Component
public class MyAdvice {}
4.5 在通知类中使用注解配置织入关系,升级为切面类
@Component
@Aspect
public class MyAdvice {
    @Before("execution(* com.lagou..*.*(..))")
    public void before() {
    	System.out.println("前置通知...");
    }
}
4.6 在配置文件中开启组件扫描和 AOP 的自动代理
<!--组件扫描-->
<context:component-scan base-package="com.lagou"/>
<!--aop的自动代理  spring会采用动态代理完成植入增强,并且生成代理 proxy-target-class="true":强制使用动态代理-->
<aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy>
4.7 编写测试代码
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class AccountServiceTest {
    @Autowired
    private AccountService accountService;
    @Test
    public void testTransfer() throws Exception {
    	accountService.transfer();
    }
}
4.8 切点表达式

切点表达式的抽取

@Component
@Aspect
public class MyAdvice {
    @Pointcut("execution(* com.lagou..*.*(..))")
    public void myPoint(){}
    @Before("MyAdvice.myPoint()")
    public void before() {
    	System.out.println("前置通知...");
    }
}
4.9 通知类型
名称标签说明
前置通知@Before用于配置前置通知。指定增强的方法在切入点方法之前执行
后置通知@AfterReturning用于配置后置通知。指定增强的方法在切入点方法之后执行
异常通知@AfterThrowing用于配置异常通知。指定增强的方法出现异常后执行
最终通知@After用于配置最终通知。无论切入点方法执行时是否有异常,都会执行
环境通知@Around用于配置环绕通知。开发者可以手动控制增强代码在什么时候执行
4.10 纯注解配置
@Configuration
@ComponentScan("com.lagou")
@EnableAspectJAutoProxy //替代 <aop:aspectj-autoproxy />
public class SpringConfig {
}

) {
System.out.println(“前置通知…”);
}
}


#### 4.9 通知类型

| 名称     | 标签            | 说明                                                       |
| -------- | --------------- | ---------------------------------------------------------- |
| 前置通知 | @Before         | 用于配置前置通知。指定增强的方法在切入点方法之前执行       |
| 后置通知 | @AfterReturning | 用于配置后置通知。指定增强的方法在切入点方法之后执行       |
| 异常通知 | @AfterThrowing  | 用于配置异常通知。指定增强的方法出现异常后执行             |
| 最终通知 | @After          | 用于配置最终通知。无论切入点方法执行时是否有异常,都会执行 |
| 环境通知 | @Around         | 用于配置环绕通知。开发者可以手动控制增强代码在什么时候执行 |

#### 4.10 纯注解配置

```java
@Configuration
@ComponentScan("com.lagou")
@EnableAspectJAutoProxy //替代 <aop:aspectj-autoproxy />
public class SpringConfig {
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值