SpringAOP

SpringAOP,第1张

SpringAOP里的用的技术相当于是动态代理一样,但是和动态代理不一样的是,反射是需要父接口的,而CGLIb可以 *** 作没有接口的类,代码例子如下:
       

动态代理Proxy代码:

public class JdkProxyTest {
    @Test
    public void testProxy() {
        //创建对象
        AccountServiceImpl accountService = new AccountServiceImpl();
        
        //类加载器
        ClassLoader classLoader = JdkProxyTest.class.getClassLoader();
        //父接口
        Class[] interfaces = AccountServiceImpl.class.getInterfaces();
        //处理器
        InvocationHandler handler = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Object result = null;//方法执行完后的返回值

                long beginTime = System.currentTimeMillis();//开时时间

                result = method.invoke(accountService, args);//执行方法

                long endTime = System.currentTimeMillis();//结束时间

                System.out.println("执行时间:" + (endTime - beginTime) + "毫秒");

                return result;
            }
        };

        //使用jdk的Proxy获取代理对象
        IAccountService accountServiceProxy = (IAccountService) Proxy.newProxyInstance(classLoader, interfaces, handler);

        //使用代理对象调用方法
        List accountList = accountServiceProxy.queryAllAccount();
    }
}

AOP的代码(CGLIB):
    

@Service("accountService2")
public class AccountServiceClass {  //类没有实现接口
    public Account queryAccountById(Integer id) {
        System.out.println("AccountServiceClass  =>  queryAccountById方法");
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return null;
    }

    public List queryAllAccount() {
        System.out.println("AccountServiceClass  =>  queryAllAccount方法");

        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        return null;
    }
}

CGLIB底层是会根据这个类去创建一个他的子类继承他创建出新的代理类对象。而使用cglib是需要导包的,Spring-context已经整合了他的包。
在jdk1.8之前cglib的速度快于jdk的动态代理的,而1.8开始cglib由于没有优化低于了jdk动态代理


Aop开发基础概念:
         是面向切面,也就是说,有一个切面类,然后有一个切入点也就是原有的方法,然后前置通知代码放这个方法的前边,后置通知放在后面,中间是在不改变代理类原有的方法的基础上去进行前置增强和后置增强;

  1. Target(目标对象)

    • 要被增强的对象(被代理类的对象)

  2. Proxy(代理对象)

    • 对目标对象的增强对象(生成的代理类对象)

  3. Joinpoint(连接点)

    • 目标对象中的可被增强的方法(被代理类中的方法)

      • 不可被增强的方法:

        • Proxy中被代理类不可被增强方法(父接口中没有的方法)

        • CGlib中被代理类不可被增强方法(final修饰的方法)

  4. Pointcut(切入点)

    • 要被增强的方法(被代理类中要增强的方法)

      • 切入点一定是连接点;但连接点不一定是切入点

  5. Advice(通知)

    • 通知是增强的那段代码形成的方法

    • 通知的分类:

      1. 前置通知 在方法之前进行增强

      2. 后置通知 在方法之后进行增强

      3. 异常通知 在方法异常进行增强

      4. 最终通知 最终执行的方法进行增强

      5. 环绕通知 单独使用(以上所有通知)

  6. Aspect(切面)

    • 切面 = 切入点+通知

      • 目标方法和增强代码合到一起叫做切面

  7. Weaving(织入)

    • 在运行过程中spring底层将通知和切入点进行整合的过程称为织入

       

     


Spring中AOP开发步骤:
           1.导入相关坐标(Spring、切入点表达式):             



    org.springframework
    spring-context
    5.2.10.RELEASE




    org.aspectj
    aspectjweaver
    1.9.4

      2:开启aop注解支持

@Configurable
@ComponentScan("com.itheima")
@EnableAspectJAutoProxy  //开启aop注解支持
public class SpringConfig {
}

3.编写切面类
   

@Aspect  //配置当前类为切面类(切入点+通知)
@Component    //这个也是要交给Spring管理,所以需要这个注解让Spring扫描到
public class ClassName{
    //配置切入点 : @Pointcut("切入点表达式")
    //定义切入点表达式:com.itheima包及子包下的AccountServiceImpl类中所有方法 
    /*
       举例1:针对AccountServiceImpl类中的queryAllAccount()方法进行增强
       切入点表达式: com.itheima.service.impl.AccountServiceImpl.queryAllAccount()
       
       举例2:针对AccountServiceImpl类中的queryAccountById(Integer id)方法进行增强
       切入点表达式: com.itheima.service.impl.AccountServiceImpl.queryAccountById(Integer)
       
        举例3:针对AccountServiceImpl类中的所有方法进行增强
        切入点表达式:com.itheima.service.impl.AccountServiceImpl.*(..)
       
    */
    @Pointcut("execution(* com.itheima..AccountServiceImpl.*(..))")
    public void pt(){
        //切入点,必须配置在三无方法上(无参、无返回值、空方法体)
    }
    
    //配置通知类型
    /* @Before: 前置通知
       @AfterReturning:后置通知
       @AfterThrowing :异常通知
       @After :最终通知
       @Around:环绕通知
    */
    @Before("pt()") //指定切点入(原因:明确要对哪个方法进行前置增强)
    public void 方法名(){
        //增强功能的代码
    }
    @AfterReturning("pt()")
    public void 方法名(){
        //增强功能的代码
    }
}
这是一个切面类,当他的切入点中的表达式中的类如果被final修饰的方法,又因为cglib生成的代理类是需要增强的类的子类,所以他不会重写这个方法也就意味着这个方法不会被增强也就是说使用这个方法的时候还是被代理类的,如果这个被代理类实现了一个接口那么就会优先使用jdk的动态代理,这样的话就不会被final修饰符影响了
Spring的AOP详解-切入点

      在上边的切面中有一个注解@pointcut这个注解,他上边是规定的切入点,当要写这个切入点的时候,也就是规定要将什么进行增强他有一个公式
   示例:
execution(public void com.itheima.service.impl.AccountServiceImpl.deleteAccount(Integer))
上边这个是最完整的,其中前边两个public  void是说明寻找方法为public的无返回值的,然后如果有返回值就要写这个返回值的类的路径,下边的寻找的类也是如此需要这个类的包名,后面是要增强的方法后面是返回值,只要符合这个要求的都会被增强

也可以使用通配符    

  • *可以代替:返回值类型、类名、方法名、一个包名

  • ..可以代替:任意个包、任意的方法参数类型

示例:
execution(public * com.itheima..AccountServiceImpl.*(..))


              

Spring中AOP的通知:

1:前置通知:@Before

/*
     原始方法(切入点)执行前执行,如果通知中抛出异常,阻止原始方法运行
     应用场景:数据校验

*/

@Before("配置了切入点的方法名()")
public void beforeMethod(){
    //前增强功能(在目标方法执行前运行)
}

2:后置通知:@AfterReturning
 

/*
      原始方法执行后执行,原始方法中出现异常,不再执行
      应用场景:返回值相关数据处理

*/
@AfterReturning("配置了切入点的方法名()")
public void afterMethod(){
    //后增强功能(在目标方法执行后运行)
}

3:抛出异常后通知:@AfterThrowing

/*
    原始方法抛出异常后执行,如果原始方法没有抛出异常,无法执行
    应用场景:对原始方法中出现的异常信息进行处理

*/
@AfterThrowing("配置了切入点的方法名()")
public void throwMethod(){
    //在目标方法执行有异常时运行
}

4:最终通知:@After

/*
    无论如何最终都执行(不论是否有异常发生,都会执行)
    应用场景:清理资源

*/
@After("配置了切入点的方法名()")
public void finallyMethod(){
    //不论是否有异常,在目标方法执行完后都会运行
}

5:环绕通知:@Around
 

/*
        环绕通知是在原始方法的前后添加功能,在环绕通知中,存在对原始方法的显式调用
*/
@Around("配置了切入点的方法名()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
    Object ret = pjp.proceed();//调用原始方法
    return ret;
}

环绕通知方法相关说明:
            方法须设定Object类型的返回值,否则会拦截原始方法的返回。如果原始方法返回值类型为void,通知方也可以设定返回值类型为void,最终返回null
            方法需在第一个参数位置设定ProceedingJoinPoint对象(代表切入点),通过该对象调用proceed()方法,实现对原始方法的调用。如省略该参数,原始方法将无法执行。
                     ProceedingJoinPoint相当于Method,但是比Method的封装度更高
             使用proceed()方法调用原始方法时,因无法预知原始方法运行过程中是否会出现异常,强制抛出Throwable对象,封装原始方法中可能出现的异常信息

事务:

也就是一组sql执行要吗全成功要么全失败,例如如果我两张表多对多肯定要有中间表简历他们的外键,当一个表中添加数据肯定要给中间表添加外键,如果外键添加失败了而主表添加成功了,这时候又没有事务,这条数据将不会在中间表中记录

事物的 *** 作步骤:
       1:开启事务
        2:业务 *** 作
       3:提交事务/回滚事务


事务的特性(ACID):

  • 原子性(Atomicity)

    • 指事务是一个不可分割的整体,其中的 *** 作要么全执行或全不执行

  • 一致性(Consistency)

    • 事务前后数据的完整性必须保持一致

  • 隔离性(Isolation)

    • 事务的隔离性是多个用户并发访问数据库时,数据库为每一个用户开启的事务,不能被其他事务的 *** 作数据所干扰,多个并发事务之间要相互隔离

  • 持久性(Durability)

    • 持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响

事务的隔离级别:
      

 

事务的隔离级别由高到低是:
        READ_UNCOMMITTED->READ_COMMITTED->REPEATABLE->SERIALIZABLE
MySQL的默认隔离级别是REPEARABLE(可重复读)

Spring管理事务:
      MySQL的事务管理对象是:DataSourceTranctionManager适用于SpringJDBC或者MyBatis

事物的隔离级别:
      spring默认使用和数据库一样的隔离级别,不设置就是数据库默认的

DataSourceTransactionManager datasource = new DataSourceTransactionManager();
datasource.setdatasource(datasource);   //这个参数可以注解得到
DefaultTransactionDefinition td = new DefaultTransactionDefinition();
//设置事务的隔离级别
td.setIsolationLevel(TransactionDefinition.ISOLATION_DEFAULT);
//mysql默认隔离级别是REPEATABLE_READ
//oracle默认隔离级别是READ_COMMITTED

事务设置是否只读:

//设置是否为只读事务
td.setReadOnly(false);
//false, 表示可读可写(适合增删改 *** 作)【默认设置】
//true, 表示只读(适合查,效率高)

事务超时时间(单位:秒):

//设置超时时间
td.setTimeout(10);//超时时间为10秒
//默认值是-1, 表示永不超时

事务传播行为:

//设置事务传播行为 (这个比较复杂,后续详解)
td.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);

示例代码:

DefaultTransactionDefinition td = new DefaultTransactionDefinition();
//设置事务隔离级别
td.setIsolationLevel(TransactionDefinition.ISOLATION_DEFAULT);
//设置是否为只读事务
td.setReadOnly(false);
//设置超时时间
td.setTimeout(10);
//设置事务传播行为 
td.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);



TransactionStatus接口(事务状态):
   定义了事务在执行过程中某个时间点上事务对象的状态信息

示例代码:
    

获取事务是否处于新开启事务状态
boolean isNewTransaction();

获取事务是否处于完成状态
boolean isCompleted();

//获取事务是否处于回滚状态
boolean isRollbackOnly();

//获取事务是否具备回滚存储点
boolean hasSavepoint();

//设置事务处于回滚状态
void setRollbackOnly()

//刷新事务状态
void flush();




上边的完整代码示例:
   

/* Spring提供的事务管理对象:
     平台事务管理器对象 :PlatformTransactionManager接口
     事务定义对象:TransactionDefinition接口
     事务状态对象:TransactionStatus接口
*/
//1、创建事务管理器对象(Mybatis专用)
DataSourceTransactionManager dstm = new DataSourceTransactionManager();
//为事务管理器设置数据源
dstm.setDataSource(dataSource);//数据库连接池对象和事务管理器绑定了

//2、创建事务定义对象
DefaultTransactionDefinition td = new DefaultTransactionDefinition();
//设置事务隔离级别
td.setIsolationLevel(TransactionDefinition.ISOLATION_DEFAULT);
//设置是否为只读事务
td.setReadOnly(false);
//设置超时时间
td.setTimeout(10);
//设置事务传播行为 
td.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);

//3、创建事务状态对象   (用于控制事务执行[相当于开启事务]) 
TransactionStatus ts = dstm.getTransaction(td);

//省略:业务层调用dao层功能代码

// 提交事务
dstm.commit(ts); 
// 回滚事务
dstm.rollback(ts);




使用注解处理事务:
    首先先要给Spring配置类中开启事务支持:
                @EnableTransactionManagement
第2步:配置事务管理器
      

@Bean
public DataSourceTransactionManager getTxManager(DataSource dataSource){
    //创建事务管理器
    DataSourceTransactionManager manager = new DataSourceTransactionManager();
    manager.setDataSource(dataSource);//给事务管理器设置数据源
    return manager;
}

第3步:配置需要事务支持的切入点
       

@Transactional(
    isolation = Isolation.DEFAULT,  //使用数据库默认的隔离级别
    readOnly = false,  //不是只读事务(增删改查都可以)
    timeout = 10,  //超时时间 
    propagation = Propagation.REQUIRED //设置事务传播行为
)
void transfer(int outId,int inId,double money);

@Transaction注解写的位置:

  1. 可以直接放在接口上,表示该接口的所有方法都是切入点 (推荐)

  2. 可以放在接口方法上,表示该方法的所有重写方法都是切入点 (推荐)

  3. 放在实现类上,表示该实现类所有方法都是切入点

  4. 放在实现类的方法上,表示该方法是切入点

     

    Spring事务传播行为 在一个方法中可能需要调用多个方法,而每个方法又可能有多个事务,这个时候就需要规定他的事务态度,可以在方法上加上注解来规定,
       

事务传播行为Propagation的取值:

  • REQUIRED (默认的传播行为)

    • 支持当前事务,如果不存在,就新建一个

  • SUPPORTS

    • 支持当前事务,如果不存在,就不使用事务

  • REQUIRES_NEW

    • 不论当前事务是否存在,都创建一个新的事务

  • NOT_SUPPORTED

    • 以非事务方式运行,如果有事务存在,挂起当前事务

  • MANDATORY

    • 支持当前事务,如果不存在,抛出异常

  • NEVER

    • 以非事务方式运行,如果有事务存在,抛出异常

  • NESTED

    • 如果当前事务存在,则嵌套事务执行(一个事务, 在A事务调用B过程中,B失败了,回滚事务到之前SavePoint ,用户可以选择提交事务或者回滚事务)

 

常用事务传播行为:
    

一般增删改:加事务 REQUIRED  (默认取值)
一般查询:不加事务 SUPPORTS
非主要业务不对主业务造成影响:REQUIRES_NEW

举例:
   

  • REQUIRED (默认的传播行为)

    • 支持当前事务,如果不存在,就新建一个事务
       

这个事务的设置可以这样理解:当一个方法A调用加了事务,然后方法A调用方法B,那么方法B又有两个调用方法,相当于上百年图片的transfer这个方法调用inAndOut这个方法一样,因为transfer他声明的事务,所以inAndOut来了就会看transfer有没有事务,如果有,那么就不创建事务了,如果transfer事务没有的话,那么inAndOut就会给inAndOut这个方法创建事务防止他调用的两个方法中一个成功另外一个失败导致没有回滚什么的。

例子二:
 

  • SUPPORTS

    • 支持当前事务,如果不存在,就不使用事务

就是说如果调用的方法没有声明事务,那么被调用的来了看到没有事务,那么被调用的这个方法也就没有事务,如果调用方有事务,那么被调用的一方也就有事务

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/924208.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-16
下一篇 2022-05-16

发表评论

登录后才能评论

评论列表(0条)

保存