spring进阶

spring Ioc注解式开发

注解就是用来标注一些结构的,它就是用来标注的

注解都是配合反射机制使用的,如果有这个注解就做一些事,没有就略过

组件扫描原理

public class ComponentScan {
    public static void main(String[] args){
        Map<String,Object> beanMap = new HashMap<>();
        // 目前只知道一个包的名字,扫描这个包下所有的类,当这个类上有@Component注解的时候,实例化该对象,然后放到Map集合中。
        String packageName = "com.powernode.bean";
        // 开始写扫描程序。
        // . 这个正则表达式代表任意字符。这里的"."必须是一个普通的"."字符。不能是正则表达式中的"."
        // 在正则表达式当中怎么表示一个普通的"."字符呢?使用 \. 正则表达式代表一个普通的 . 字符。
        String packagePath = packageName.replaceAll("\\.", "/");
        //System.out.println(packagePath);
        // com是在类的根路径下的一个目录。
        URL url = ClassLoader.getSystemClassLoader().getResource(packagePath);
        String path = url.getPath();
        //System.out.println(path);
        // 获取一个绝对路径下的所有文件
        File file = new File(path);
        File[] files = file.listFiles();
        Arrays.stream(files).forEach(f -> {
            try {
                //System.out.println(f.getName());
                //System.out.println(f.getName().split("\\.")[0]);
                String className = packageName + "." + f.getName().split("\\.")[0];
                //System.out.println(className);
                // 通过反射机制解析注解
                Class<?> aClass = Class.forName(className);
                // 判断类上是否有这个注解
                if (aClass.isAnnotationPresent(Component.class)) {
                    // 获取注解
                    Component annotation = aClass.getAnnotation(Component.class);
                    String id = annotation.value();
                    // 有这个注解的都要创建对象
                    Object obj = aClass.newInstance();
                    beanMap.put(id, obj);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }

        });

        System.out.println(beanMap);
    }
}

声明Bean的注解

负责声明Bean的注解,常见的包括四个:

  • @Component

  • @Controller

  • @Service

  • @Repository

源码如下:

package com.powernode.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(value = {ElementType.TYPE})
@Retention(value = RetentionPolicy.RUNTIME)
public @interface Component {
    String value();
}
package org.springframework.stereotype;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Controller {
    @AliasFor(
        annotation = Component.class
    )
    String value() default "";
}
package org.springframework.stereotype;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Service {
    @AliasFor(
        annotation = Component.class
    )
    String value() default "";
}
package org.springframework.stereotype;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Repository {
    @AliasFor(
        annotation = Component.class
    )
    String value() default "";
}

通过源码可以看到,@Controller、@Service、@Repository这三个注解都是@Component注解的别名。

也就是说:这四个注解的功能都一样。用哪个都可以。

只是为了增强程序的可读性,建议:

  • 控制器类上使用:Controller

  • service类上使用:Service

  • dao类上使用:Repository

他们都是只有一个value属性。value属性用来指定bean的id,也就是bean的名字。

选择性实例化Bean

<!--
        第一种解决方案:
        use-default-filters="false"
        如果这个属性是false,表示edu.gdpi.bean包下所有的带有声明Bean的注解全部失效。@Component @Controller @Service @Repository全部失效
    -->
    <context:component-scan base-package="edu.gdpi.bean" use-default-filters="false">
        <!--只有@Repository被包含进来,生效-->
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Repository"/>
    </context:component-scan>

<!--第二种解决方案:
        use-default-filter="true"
        如果这个属性是true,表示edu.gdpi.bean包下所有的带有声明Bean的注解全部生效
    -->
    <context:component-scan base-package="edu.gdpi.bean">
        <!--@Repository注解失效-->
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Repository"/>
    </context:component-scan>

@Autowired

@Autowired注解使用的时候,不需要指定任何属性,直接使用这个注解即可。
这个注解的作用是根据类型byType进行自动装配
如果想根据名称注入,需要配合@Qualifier注解一起使用,在@Qualifier中指定需要注入的名称
如果一个类当中构造方法只有一个,并且构造方法上的参数和属性都能对应上,@Autowire可以省略,但是最好别省,可读性差

@Resource

@Resource注解默认是按名称装配,如果没有名称就使用属性名当做名称去找,找不到才会通过类型去找
这个注解是JDK扩展包的一部分,不属于spring,所以需要引入其他依赖javax.annotation-api

全注解开发

全注解开发需要写一个类代替配置文件,需要使用@Configuration注解去标注那个类,表明这是一个配置类,组件扫描的注解,@ComponentScan

@Configuration
@ComponentScan({"包名","包名"})
public class SpringConfig{
	//配置类
}

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(类名)

AOP注解实现步骤

//1.先编写目标类
//2.编写切面类(切面类中需要编写切面方法)  切面 = 通知 + 切点
//3.在配置文件中编写组件扫描器
//4.在配置文件中开启自动代理(开启aspectj的自动代理)
//<aop:aspectj-autoproxy proxy-target-class="true" />
//proxy-target-class默认是false,true表示使用一定使用cglib动态代理,
//false表示有接口使用jdk动态代理,否则使用cglib动态代理
//配置了这个后spring容器在扫描类的时候查看该类上是否有Aspect注解,如果有,则给这个类生成代理对象

//通知就是增强,就是具体要编写的增强代码
//这是通知Advice以方法的形式出现。(因为方法中可以下代码)
//通知就是一段代码,但是我们需要告诉spring这段代码是我们的增强代码,即通知
//通知有5大类:
//前置通知  后置通知  环绕通知  异常通知  最终通知
//前置通知:在目标对象的目标方法执行之前执行

/**
	标注这是一个前置通知  @Before(切点表达式)
*/
@Componen("LogAspect")
@Aspect  //标注这是一个切面类
public class LogAspect{
    @Before("execution(* edu.gdpi.service.UserService.*(..))")
    public void log(){
        System.out.println("我是一个通知,我是一段增强代码...");
    }
}
总结:
做AOP一共就几个步骤:
1.创建目标类,因为需要给目标类增强功能
2.创建切面类,通过切面类来实现增强功能
3.把这两个类都交给spring容器来管理
4.开启自动代理,让spring来实现代理类
	<aop:aspectj-autoproxy proxy-target-class="false" />

切面顺序

//两个切面的执行顺序由@Order注解来控制,谁的数字越小谁就先执行
@Component
@Aspect
@Order(1)
public class LogAspect{
    
}

连接点

//每个通知的方法里都可以写一个JoinPoint类型的参数,表示连接点
//这个JoinPoint,在spring容器调用这个方法的时候自动传过来
//我们可以直接用,用这个JoinPoint方法干啥?
//joinPoint.getSignature()可以得到目标方法的签名
//通过方法的签名可以获取到一个方法的具体信息

AOP基于注解的全注解开发

//标注这是一个配置类
@Configuration
//配置组件扫描器
@ComponentScan("edu.gdpi.service")
//配置自动代理
@EnableAspectJAutoProxy(proxyTargetClass = true)  //true表示使用cglib动态代理
public class SpringConfig {
}

AOP的实际应用

/**
 * 编程式事务解决方案
 */
@Component
@Aspect
public class TransactionAspect {

    @Around("execution(* edu.gdpi.service..*(..))")
    public void aroundAdvice(ProceedingJoinPoint joinPoint){
        try {
            //前环绕
            System.out.println("开启事务");
            //执行目标
            joinPoint.proceed();
            //后环绕
            System.out.println("提交事务");
        } catch (Throwable e) {
            System.out.println("回滚事务");
        }
    }
//需要增强功能的类(目标类)
@Service
public class AccountService {

    //转账的业务方法
    public void transfer(){
        System.out.println("银行账户正在完成转账操作");
    }

    //取款的业务方法
    public void withdraw(){
        System.out.println("正在取款,请稍后");
    }
    
//执行结果
    //开启事务
	//银行账户正在完成转账操作
	//提交事务

事务

spring对于事务的支持是使用AOP来实现的,基于AOP又进行了二次封装

声明式事务

spring在事务管理中提供了一个非常重要的接口,PlatformTransactionManager接口,事务管理器接口

spring还提供了这个接口不同的实现

提供这个接口是给了其他ORM框架集成的机会,只要你想用spring的事务,那你就实现这个接口

在Spring6中它有两个实现:
● DataSourceTransactionManager:支持JdbcTemplate、MyBatis、Hibernate等事务管理。
● JtaTransactionManager:支持分布式事务管理。
注意:这是spring6版本的,之前的版本不是这样的

mybatis的事务管理器实现类spring已经实现了,我们直接用就行了,但是肯定是需要配置的,我们需要创建一个事务管理器对象,有了事务管理器对象才能帮我们管理事务

管理事务就是conn.setAutocommit(false)  需要Connection对象,也就是需要连接对象,也就是需要数据源

注解创建事务的步骤:
1.创建事务管理器对象
<bean id="transactionManager" class="...DataSourceTransactionManager">
	<property name="dataSource" ref="数据源对象"/>
</bean>
2.开启事务注解驱动器,开启事务注解,告诉spring框架,采用注解的方式去控制事务,开启后可以使用Transactional注解去标注哪一些方法使用事务
<tx:annotation-driven transaction-manager="transactionManager" />
3.在需要使用事务的方法或类上添加@Transactional注解
//控制事务,因为在这个方法中要完成所有的转账业务
    @Override
    public void transfer(String fromAccount, String toAccount, Double money) {
        //查询转出账户的余额是否充足
        Account fromAct = accountDao.selectByAct(fromAccount);
        if(fromAct.getBalance() < money){
            throw new RuntimeException("余额不足...");
        }

        Account toAct = accountDao.selectByAct(toAccount);

        //将内存中两个对象的余额先修改
        fromAct.setBalance(fromAct.getBalance() - money);
        toAct.setBalance(toAct.getBalance() + money);

        //数据库更新
        int count = accountDao.updateByAct(fromAct);

        //模拟异常
//        String s = null;
//        s.toString();

        count  += accountDao.updateByAct(toAct);

        if(count != 2){
            throw new RuntimeException("转账失败,联系银行...");
        }
    }

事务的传播行为

@Transactional(Propagation.REQUIRED)
什么是事务的传播行为?
在service类中有a()方法和b()方法,a()方法上有事务,b()方法上也有事务,当a()方法执行过程中调用了b()方法,事务是如何传递的?合并到一个事务里?还是开启一个新的事务?这就是事务传播行为。
Propagation.REQUIRED:一个事务,没有就新建,有就加入

事务的隔离级别

@Transactional(Isolation.REPEATABLE_READ)

事务的超时时间

@Transactional(timeout=10)
表示超过10秒如果该事务中所有的DML语句还没有执行完毕的话,最终结果会选择回滚。
默认值-1,表示没有时间限制。
这里有个坑,事务的超时时间指的是哪段时间?
在当前事务当中,最后一条DML语句执行之前的时间。如果最后一条DML语句后面很有很多业务逻辑,这些业务代码执行的时间不被计入超时时间。

只读事务

@Transactional(readOnly=true)
将当前事务设置为只读事务,在该事务执行过程中只允许select语句执行,delete insert update均不可执行。
该特性的作用是:启动spring的优化策略。提高select语句执行效率。
如果该事务中确实没有增删改操作,建议设置为只读事务。

设置哪些异常回滚事务

@Transactional(rollbackFor=RuntimeException.class)
表示只有发生RuntimeException异常或该异常的子类异常才回滚。

设置哪些异常不回滚事务

@Transactional(noRollbackFor=NullPointerException.class)
表示发生NullPointerException或该异常的子类异常不回滚,其他异常则回滚。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值