3.1 AOP简介【常见面试】
AOP:全称:Aspect Oreinted Programming:面向切面编程
OOP:全称:Object Oreinted Programming:面向对象编程
Aop 含义:Aop 和 OOP 不存在谁取代谁的关系,它们是相互促进,相互补充,Aop 是在不改变原来 OOP 类代码的基础之上,对原来类的功能进行拓展。
AOP 作用:解决了软件工程中的关注点分离问题。可以让系统变得高内聚、低耦合、便于项目后期的维护和拓展。
Aop 底层原理:动态代理。
3.2 问题提出
需求:我们现在需要实现一个计算器功能,可以进行加、减、乘、除运算。还需要在加减乘除之前或者之后打印一条日志信息。
具体实现:
基本实现:让项目后期变得难以维护。
动态代理:
AOP 编程:
3.3 AOP中的几个概念
通知[Advice]:表示拓展的功能,本质:方法
切面[Aspect]:通知所在的类,称之为切面,本质是一个类
切入点[PointCut]:表示通知对谁进行拓展,本质:表达式
连接点[JoinPoint]:通知和目标方法的交点
目标对象[Target]:被拓展的类对象,称为目标对象
织入[Weaving]:将通知应用到目标方法的过程
3.4 AOP对于问题的实现
第一步:创建动态 web 工程,导入 jar 包
第二步:需要将被拓展的类和拓展的类加入到容器中:扫描包+注解
第三步:在 spring 配置文件中开启基于注解的切面支持及在拓展的类上加@Aspect 注解
第四步:在拓展的类的方法上指定切入点表达式
applicationContext.xml:
<context:component-scan base-package="com.atguigu"/>
<!--开启基于注解的切面支持
-->
<aop:aspectj-autoproxy/>
实现类 CaculatorImpl:
@Component
public class CaculatorImpl implements Caculator {
//签名:包名+类名+方法名(参数列表)
@Override
public int add(int i, int j) {
int result = i+j;
System.out.println("add方法执行了......");
return
result;
}
@Override
public int
substract(int i, int j) {
int result = i-j;
return
result;
}
@Override
public int
multiply(int i, int j) {
int result = i*j;
return
result;
}
@Override
public int
divide(int i, int j) {
int result = i/j;
return result;
}
}
切面类:
@Component
@Aspect //该注解标记的类表示当前类是一个切面类
public class LogAspect {
@Before(value = "execution(public int com.atguigu.aop.CaculatorImpl.add(int,int))")
public void beforeLog(){
System.out.println("在目标方法执行之前打印........");
}
@After(value = "execution(public int com.atguigu.aop.CaculatorImpl.add(int,int))")
public void afterLog(){
System.out.println("在目标方法执行之后打印........");
}
}
第五步:测试
public void test01(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
Caculator bean = context.getBean(Caculator.class);
bean.add(3,5);
}
3.5 图解 AOP 中涉及到的几个概念
3.6 AOP底层原理
3.7 代理模式
静态代理:指的只能代理某一个接口的实现类对象
Payment:
public interface Payment {
public void pay(double money);
}
真实用户 RealPayment:
public class RealPayment implements Payment{
@Override
public void pay(double money) {
System.out.println("作为真实用户,我们只关注付了"+money+"钱");
}
}
支付宝代理 AliPay:
public class AliPay
implements Payment {
private Payment
payment;
public AliPay(Payment payment){
this.payment=payment;
}
public void beforePay(){
System.out.println("支付宝代理真实用户把钱从银行取出来....");
}
@Override
public void pay(double money) {
beforePay();
this.payment.pay(money);
afterPay();
}
public void afterPay(){
System.out.println("支付宝代理真实用户把钱付给商家....");
}
}
测试类:
public void testPay(){
AliPay aliPay = new AliPay(new RealPayment());
aliPay.pay(32.9);
}
注意:静态代理只能代理某个特定接口的实现类对象
。
WeiPayImpl 实现类:
public class WeiPayImpl implements WeiPay {
@Override
public void pay(double money) {
System.out.println("微信付款"+money);
}
}
动态代理:指的只能代理任意接口的实现类对象
测试类:
//万能动态代理
@Test
public void test02(){
// RealPayment realPayment = new RealPayment();
WeiPayImpl weiPay = new WeiPayImpl();
/************************************************************
newProxyInstance()参数:
第一个参数:被代理类的类加载器
第二个参数:被代理类实现的接口
第三个参数:
InvocationHandler接口的实现类对象,咱们可以在InvocationHandler
接口的实现类中对被代理类进行拓展。
newProxyInstance()返回值:表示代理类对象
************************************************************/
MyInvocationHandler<WeiPay> result = new MyInvocationHandler<WeiPay>(weiPay);
WeiPay proxy = result.proxy();
proxy.pay(32.8);
}
InvocationHandler 实现类:
public class MyInvocationHandler<T> implements InvocationHandler {
private T target;
public MyInvocationHandler(T target){
this.target = target;
}
public T proxy(){
T o = (T)Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(),this);
return o;
}
//method:表示被代理类的目标方法,
//args:当调用被代理类方法的时候传递的参数
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws
Throwable {
System.out.println("把钱从银行取出来...");
//调用被代理类的目标方法
Object result = method.invoke(target, args);
System.out.println("把钱付给商家...");
return result;
}
}
两者区别:
相同点:
1. 被代理类和代理类都实现相同的接口
2. 都是在不改变被代理类代码的基础上对被代理类功能进行拓展
不同点:
1. 静态代理只能代理某个接口的实现类对象
2. 动态代理可以代理任意接口的实现类对象。
3.8 切面中的五种通知
前置通知[@Before]:在目标方法执行之前执行的功能。
后置通知[@After]:无论目标方法是否执行成功,在目标方法执行之后,后置通知都会执行。
返回通知[@AfterReturning]:目标方法执行成功之后,返回通知才会执行。
异常通知[@AfterThrowing]:目标方法在执行出现异常的时候,异常通知才会执行。
环绕通知[@Around]:以一抵四。
@Component
@Aspect //该注解标记的类表示当前类是一个切面类
public class LogAspect {
//前置通知
@Before(value = "execution(public int com.atguigu.aop.CaculatorImpl.*(int,int))")
public void beforeLog(){
System.out.println("日志切面前置通知");
}
//后置通知
@After(value = "execution(public int com.atguigu.aop.CaculatorImpl.*(int,int))")
public void afterLog(){
System.out.println("日志切面后置通知");
}
//返回通知
@AfterReturning(value = "execution(public int com.atguigu.aop.CaculatorImpl.*(int,int))")
public void afterReturningLog(){
System.out.println("日志切面 返回通知");
}
//异常通知
@AfterThrowing(value = "execution(public int com.atguigu.aop.CaculatorImpl.*(int,int))")
public void afterThrowingLog(){
System.out.println("日志切面 异常通知");
}
}
2.9 通知的底层结构
//spring 4.x版本 底层结构
try{
try{
//1.前置通知
//目标方法
}finally {
//2.后置通知
}
//3.返回通知
}catch (Throwable ex){
//4.异常通知
}
//spring 5.x版本 底层结构
try{
//1.前置通知
//目标方法
//2.返回通知
}catch (Throwable ex){
//3.异常通知
}finally {
//4.后置通知
}
3.10 切入点表达式
作用:用来确定对谁进行拓展
语法:execution([权限修饰符] [返回值类型] [全类名] [参数列表])
最简洁[最模糊]:
execution(* *.*(..))
最复杂[最精确]:
execution(public int com.atguigu.CaculatorImpl.add(int,int))
切入点表达式重用:
@Component
@Aspect //该注解标记的类表示当前类是一个切面类
public class LogAspect {
@Pointcut(value = "execution(public int com.atguigu.aop.CaculatorImpl.*(..))")
public void myPointCut(){}
//前置通知
@Before(value = "myPointCut()")
public void beforeLog(){
System.out.println("日志切面 前置通知");
}
}
切入点表达式支持 与、或、非操作:
3.11 从通知中获取目标方法信息
try{
try{
//1.前置通知: 获取目标方法名和传递给目标方法的参数值信息
//目标方法:
}finally {
//2.后置通知
}
//3.返回通知: 获取目标方法的返回结果
}catch (Throwableex){
//4.异常通知:获取目标方法的异常信息
}
前置通知:获取目标方法名和传递目标方法的参数值信息
//前置通知
@Before(value = "myPointCut()")
public void beforeLog(JoinPoint joinPoint){
//方法签名:全类名.方法名(参数列表)
//获取方法名
String methodName = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
System.out.println("日志切面 前置通知,目标方法名为:"+methodName+",参数值
为:"+ Arrays.asList(args));
}
返回通知:获取目标方法返回结果
//返回通知
//在返回通知中获取目标方法的返回结果,有两点要求:
1:返回通知的参数类型最好是Object
2.方法的参数名必须和returning属性值保持一致
@AfterReturning(value = "myPointCut()",returning = "result")
public void afterReturningLog(Object result){
System.out.println("日志切面返回通知,返回结果为:"+result);
}
异常通知:获取目标方法的异常信息
//异常通知
//在异常通知中获取目标方法的异常信息,有两点要求:
1.要求参数的类型必须为 Throwing,
2要求异常通知的参数名必须和@AfterThrowing注解的throwing属性值保持一致
@AfterThrowing(value = "myPointCut()",throwing = "ex")
public void afterThrowingLog(Throwable ex){
System.out.println("日志切面异常通知,异常信息为:"+ex.getMessage());
}
3.12 使用环绕通知实现事物切面
编程式事务:
Connection conn=JdbcUtils.getConnection();
try{
conn.setAutoCommit(false); //1.取消事务的自动提交功能
//多条增删改 sql // 2.执行多次增删改操作
conn.commit(); //3.提交事务
}catch(Exception ex){
conn.rollback() //4.回滚操作
}finally{
conn.close(); //5.关闭连接
}
@Component
@Aspect
public class TransactionAspect {
/************************************************************
环绕通知:
1.要求环绕通知标记的方法返回值类型必须为Object类型
2.要求将目标方法的返回值返回。
************************************************************/
@Around(value = "com.atguigu.aop.LogAspect.myPointCut()")
public Object around(ProceedingJoinPoint joinPoint){
Object proceed = null;
try {
try{
//1.前置通知
System.out.println("事务切面前置通知");
//放行请求:执行目标方法
proceed = joinPoint.proceed();
}finally {
System.out.println("事务切面后置通知");
}
System.out.println("事务切面返回通知");
} catch (Throwable throwable) {
System.out.println("事务切面异常通知");
}
return proceed;
}
}
3.13 多切面的情况下,切面中的通知的执行顺序问题
try{
try{
//1.前置通知
//目标方法
}finally {
//2.后置通知
}
//3.返回通知
}catch (Throwable ex){
//4.异常通知
}
三种情况:
-
当目标方法不出现异常的时候
-
当目标方法出现异常的时候,内层切面不抛出异常
-
当目标方法出现异常的时候,内层切面抛出异常
3.14 多切面的情况下,切面的执行顺序
多切面情况下,切面的执行顺序是由标记在切面类上的@Order 决定的,@Order 注解的value 属性值越小,切面的优先级越高。
3.15 基于xml配置的AOP
<bean id="caculator" class="com.atguigu.aop.CaculatorImpl"></bean>
<bean id="logAspect" class="com.atguigu.aop.LogAspect"></bean>
<bean id="transactionAspect" class="com.atguigu.aop.TransactionAspect"></bean>
<aop:config>
<aop:pointcut id="myPointCut" expression="execution(public int
com.atguigu.aop.CaculatorImpl.add(int, int))"/>
<aop:aspect ref="logAspect" order="10">
<aop:before method="beforeLog" pointcut-ref="myPointCut"></aop:before>
<aop:after method="afterLog" pointcut-ref="myPointCut"></aop:after>
<aop:after-returning method="afterReturningLog" pointcut-ref="myPointCut"
returning="result"></aop:after-returning>
<aop:after-throwing method="afterThrowingLog" pointcut-ref="myPointCut"
throwing="ex"></aop:after-throwing>
</aop:aspect>
<aop:aspect ref="transactionAspect" order="8">
<aop:around method="around" pointcut-ref="myPointCut"></aop:around>
</aop:aspect>
</aop:config>
4.事务
4.1 JdbcTemplate
Spring 的 JdbcTemplate 实际上就是 Spring 框架对原生 JDBC 的简单封装,类似于Javaweb 部分学过 DbUtils 工具类,主要是对数据表的数据进行增删改查操作。
具体使用:
第一步:创建动态Web工程,导入jar包
第二步:在spring的配置文件配置JdbcTemplate
<!-- 1.加载properties文件 -->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!-- 2.配置数据源信息 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
destroy-method="close">
<property name="username" value="${jdbc.userName}"/>
<property name="password" value="${jdbc.password}"/>
<property name="driverClassName" value="${jdbc.driverClass}"/>
<property name="url" value="${jdbc.jdbcUrl}"/>
</bean>
<bean class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
第三步:在dao层使用JdbcTemplate
@Repository
public class EmployeeDao{
@Autowired
private JdbcTemplate jdbcTemplate;
/************************************************************
JdbcTemplate的使用:
增删改操作:update()方法
************************************************************/
public void insert(){
String sql="insert into employee values(null,?,?)";
jdbcTemplate.update(sql,"董阳",13000);
}
}
JdbcTemplate的使用:
增删改操作:update()方法
批量增删改:batchUpdate()方法
查询操作:
1.查询一个pojo对象:queryForObject()
2.查询一个单值:queryForObject()
3.查询一个对象列表:query()方法