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或该异常的子类异常不回滚,其他异常则回滚。