笔记基于Spring6演示CURD操作，不同于以前JDBC，现在的事务操作时基于注解和Spring技术完成之前有过对于JDBC的介绍和工具类的封装JDBC工具类封装准备工作：父工程中添加依赖<dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>6.0.2</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.30</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.2.15</version> </dependency> </dependencies>创建配置文件jdbc.propertiesjdbc.user=root jdbc.password=abc123 jdbc.url=jdbc:mysql://localhost:3306/spring?characterEncoding=utf8&useSSL=false jdbc.driver=com.mysql.cj.jdbc.Driver在新建的模块类路径下添加bean.xml，调整配置信息引入外部配置文件，创建数据源对象创建 [label color="red"]jdbcTemplate[/label] 对象，注入数据源<?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:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder> <bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="url" value="${jdbc.url}"></property> <property name="driverClassName" value="${jdbc.driver}"></property> <property name="username" value="${jdbc.user}"></property> <property name="password" value="${jdbc.password}"></property> </bean> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="druidDataSource"></property> </bean> </beans>在JDBCTemplateTest 测试类中完成数据库的CRUD，通过整合Junit5通过注解完成xml文件的读取和对象jdbcTemplate的属性注入。通过@SpringJUnitConfig(locations = "classpath:beans.xml") 代替 ApplicationContext context = new ClassPathXmlApplicationContext(".xml"); XXX xxx = context.getBean("xxx", xxx.class); @Autowired用于属性注入@SpringJUnitConfig(locations = "classpath:beans.xml") public class JDBCTemplateTest { @Autowired private JdbcTemplate jdbcTemplate; } 准备数据库表CREATE DATABASE `spring`; use `spring`; CREATE TABLE `t_emp` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(20) DEFAULT NULL COMMENT '姓名', `age` int(11) DEFAULT NULL COMMENT '年龄', `sex` varchar(2) DEFAULT NULL COMMENT '性别', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;在该方法中添加测试类进行CRUD增删改查。使用JdbcTemplate类CRUD@SpringJUnitConfig(locations = "classpath:beans.xml") public class JDBCTemplateTest { @Autowired private JdbcTemplate jdbcTemplate; //添加，修改，删除 @Test public void testUpdate() { /* //1.添加操作 //编写sql语句 String sql = "INSERT INTO t_emp VALUES(null,?,?,?)"; //第二步，调用jdbcTemplate方法,传入相关参数 //Object[] params = {"东方不败", 20, "未知"}; //int rows = jdbcTemplate.update(sql, params); int rows = jdbcTemplate.update(sql, "林平之", 20, "未知"); System.out.println(rows); */ //2.修改操作 /*String sql = "UPDATE t_emp SET NAME =? where id =?"; int rows = jdbcTemplate.update(sql, "林平之atguigu",3); System.out.println(rows);*/ //3.删除操作 String sql = "DELETE FROM t_emp where id=?"; int rows = jdbcTemplate.update(sql, 3); System.out.println(rows); } //4.查询操作 @Test public void testSelectObject() { //写法一 //String sql = "SELECT * FROM t_emp WHERE id=?"; //查询：返回对象 //RowMapper此时采用手动封装,采用lambda表达式封装 //rs表示结果集 //rowNum /* Emp empResult = jdbcTemplate.queryForObject(sql, (rs, rowNum) -> { Emp emp = new Emp(); emp.setId(rs.getInt("id")); emp.setName(rs.getString("name")); emp.setAge(rs.getInt("age")); emp.setSex(rs.getString("sex")); return emp; }, 1); System.out.println(empResult);*/ //写法二 String sql = "SELECT * FROM t_emp WHERE id=?"; Emp emp = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(Emp.class), 1); System.out.println(emp); } //查询：返回list集合 @Test public void testSelectList() { String sql = "SELECT * FROM t_emp"; List<Emp> list = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(Emp.class)); System.out.println(list); } //查询：返回单个值 @Test public void testSelectValue(){ String sql = "SELECT COUNT(*) FROM t_emp"; //参数二：表示返回类型的class //参数三：占位符赋值 Integer count = jdbcTemplate.queryForObject(sql, Integer.class); System.out.println(count); } } [note type="success flat"]这里我要做一定解释，之前学习JDBC的时候写过工具类封装，分为DQL语句和非DQL语句，在使用JdbcTemplate类完成数据库的增删改查的时候我非常激动，因为我看到了JdbcTemplate中query和update方法的形参列表和我当初写的工具类封装几乎一模一样，尤其是query在查询返回list集合的时候我深有体会，传递的是一个字节码对象，当时通过传递一个类的字节码对象，进行反射。对比当初写的JDBC工具类封装和现在使用JdbcTemplate感到非常亲切，翻看源码的时候我却看的不太懂，我认为实现的核心思路没有变，只不过Spring站在了更加全面的角度去编写，代码看上去非常复杂，对比我写的工具类封装可能复用性更好，功能更多，给了我学下去的巨大动力。[/note]基于注解的声明式事务我在之前有写过编程式事务，但是编程式事务不太灵活。这里实现基于注解的声明式事务。通过用户买书的过程演示事务操作。创建表CREATE TABLE `t_book` ( `book_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键', `book_name` varchar(20) DEFAULT NULL COMMENT '图书名称', `price` int(11) DEFAULT NULL COMMENT '价格', `stock` int(10) unsigned DEFAULT NULL COMMENT '库存（无符号）', PRIMARY KEY (`book_id`) ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8; insert into `t_book`(`book_id`,`book_name`,`price`,`stock`) values (1,'斗破苍穹',80,100),(2,'斗罗大陆',50,100); CREATE TABLE `t_user` ( `user_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键', `username` varchar(20) DEFAULT NULL COMMENT '用户名', `balance` int(10) unsigned DEFAULT NULL COMMENT '余额（无符号）', PRIMARY KEY (`user_id`) ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8; insert into `t_user`(`user_id`,`username`,`balance`) values (1,'lucy',500);调整springxml配置文件首先添加一个tx命名空间<?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" xmlns:tx="http://www.springframework.org/schema/tx" 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 http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">添加配置 <tx:annotation-driven transaction-manager="transactionManager" />在对应的Service层类/方法上面添加 [label color="red"]@Transactional[/label] 会有事务影响。如果事务操作失败，基于事务的原子性，方法会有回滚。事务的属性@Transactional()只读：设置只读，只能查询，不能修改添加删除操作 @Transactional(readOnly = true)超时：默认-1，设置超时时间之内没有完成，直接抛出异常，回滚。超时时间单位为秒回滚策略：设置哪些异常不回滚，哪些回滚可以提前设置好对应的异常，在发生异常后控制台报错，但是事务不会回滚。隔离级别：读问题事务之间存在隔离性，即事物之间不产生影响，不考虑隔离性会产生三个读问题。脏读，不可重复读，虚读(幻读)问题解释脏读两个事务没有提交，但是互相修改数据，可以看到不可重复读一个事务没有提交，另一个事务修改已经提交，它能读到事务修改之后的数据。幻读一个事务没有提交，一个事务提交但是做了添加，可以读到添加的数据。设置数据库的隔离级别可以避免，不同数据库对于四种隔离级别支持程度不同（自行百度），MySQL全部支持。各个隔离级别解决并发问题的能力见下表：隔离级别脏读不可重复读幻读READ UNCOMMITTED有有有READ COMMITTED无有有REPEATABLE READ(MySQL默认)无无有SERIALIZABLE无无无[label color="blue"]使用方式[/label]@Transactional(isolation = Isolation.DEFAULT)//使用数据库默认的隔离级别 @Transactional(isolation = Isolation.READ_UNCOMMITTED)//读未提交 @Transactional(isolation = Isolation.READ_COMMITTED)//读已提交 @Transactional(isolation = Isolation.REPEATABLE_READ)//可重复读 @Transactional(isolation = Isolation.SERIALIZABLE)//串行化 事务的传播行为：事务方法之间的调用，事务该如何使用？一共有七种事务的传播行为，这里只关注REQUIRED REQUIRES_NEWREQUIRED：支持当前事务，如果不存在就新建一个(默认) @Transactional(propagation = Propagation.REQUIRED) REQUIRES_NEW：开启一个新的事务，如果一个事务已经存在，则将这个存在的事务挂起。如果lucy想要同时买两本书，而刚好只有买前一本的钱，编写一个买多本书的Service方法调用给买单个书的方法，实际上处于一个事务中调用另一个事务。如果是REQUIRED，那么第二本钱不够交易失败，整个操作都会回滚。也就是说只要有操作失败，整体都会回滚。而REQUIRES_NEW会开启一个新事务，将原先事务挂起，实际上就是能买几本书就会买几本书。而不会整体回滚。全注解配置事务想要实现全注解开发，那么要使用配置类取代配置文件，我们可以添加一个配置类，将原先所有的配置文件中的获取放在类中获取。创建一个SpringConfig类@Configuration //通过配置类替代配置文件 @ComponentScan("com.atguigu.spring6.tx")//开启组件扫描 @EnableTransactionManagement //表示开启事务管理 public class SpringConfig { @Bean public DataSource getDataSource(){ DruidDataSource dataSource = new DruidDataSource(); dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver"); dataSource.setUsername("root"); dataSource.setPassword("abc123"); dataSource.setUrl("jdbc:mysql://localhost:3306/spring?characterEncoding=utf8&useSSL=false"); return dataSource; } @Bean(name = "jdbcTemplate") public JdbcTemplate getJdbcTemplate(DataSource dataSource){ JdbcTemplate jdbcTemplate = new JdbcTemplate(); jdbcTemplate.setDataSource(dataSource); return jdbcTemplate; } @Bean public DataSourceTransactionManager getDataSourceTransactionManager(DataSource dataSource){ DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager(); dataSourceTransactionManager.setDataSource(dataSource); return dataSourceTransactionManager; } } 它与XML文件中的对应关系是这样的基于XML文件实现声明式事务管理环境准备创建Spring配置文件开启组件扫描创建数据源DataSource创建JdbcTemplate,注入数据源创建事务管理器，注入数据源配置事务通知通过切入点表达式，把事务通知添加到方法上，配置事务通知，设置事务中一些相关的属性。<?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" xmlns:tx="http://www.springframework.org/schema/tx" 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/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd"> <context:component-scan base-package="com.atguigu.spring6.xmltx"></context:component-scan> <context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder> <bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="url" value="${jdbc.url}"></property> <property name="driverClassName" value="${jdbc.driver}"></property> <property name="username" value="${jdbc.user}"></property> <property name="password" value="${jdbc.password}"></property> </bean> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="druidDataSource"></property> </bean> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="druidDataSource"></property> </bean> <tx:advice id="txAdvice" transaction-manager="transactionManager"> <tx:attributes> <tx:method name="get*" read-only="true"/> <tx:method name="update*" read-only="false" propagation="REQUIRED"></tx:method> <tx:method name="buy*" read-only="false" propagation="REQUIRED"></tx:method> </tx:attributes> </tx:advice> <aop:config> <aop:pointcut id="pt" expression="execution(* com.atguigu.spring6.xmltx.service.*.*(..))"/> <aop:advisor advice-ref="txAdvice" pointcut-ref="pt"></aop:advisor> </aop:config> </beans>ResourceLoader总结Spring将采用和ApplicationContext相同的策略来访问资源。也就是说，如果ApplicationContext是 [label color="orange"]FileSystemXmlApplicationContext[/label] ，res就是 [label color="orange"]FileSystemResource[/label] 实例；如果ApplicationContext是 [label color="purple"]ClassPathXmlApplicationContext[/label] ，res就是 [label color="purple"]ClassPathResource[/label] 实例当Spring应用需要进行资源访问时，实际上并不需要直接使用Resource实现类，而是调用ResourceLoader实例的getResource()方法来获得资源，ReosurceLoader将会负责选择Reosurce实现类，也就是确定具体的资源访问策略。我们把资源每次写入代码中，我们可以使用配置文件对于资源的引用和代码进行解耦合。配置XML文件<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="resourceBean" class="com.atguigu.spring6.di.ResourceBean"> <property name="resource" value="classpath:atguigu.txt"></property> </bean> </beans>创建对应ResourceBean类，提供resource成员变量，并提供对应get和set方法，最终测试类测试即可。Validation实际开发中会频繁用到数据校验，Spring Validation可以把校验和业务分离出来，降低耦合度。让代码编写更加简单。通过Validator接口实现引入依赖在对应子工程中引入依赖<dependencies> <dependency> <groupId>org.hibernate.validator</groupId> <artifactId>hibernate-validator</artifactId> <version>7.0.5.Final</version> </dependency> <dependency> <groupId>org.glassfish</groupId> <artifactId>jakarta.el</artifactId> <version>4.0.1</version> </dependency> </dependencies>创建实体类，定义属性，创建对应的set和get方法Person类public class Person { private String name; private int age; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } } 创建类，实现接口，实现接口的方法，编写校验逻辑PersonValidator类public class PersonValidator implements Validator { /* supports方法表示校验用在哪个类型上 validate方法就是具体的校验逻辑 */ @Override public boolean supports(Class<?> clazz) { //表示对哪个类型校验 return Person.class.equals(clazz); } //校验规则 @Override public void validate(Object target, Errors errors) { //name不能为空 //ValidationUtils.rejectIfEmpty()方法判断非空 //参数一：errors表示为空出现错误。 //参数二：属性(成员变量) //参数三：错误的编码，可随意 //参数四：提示信息 ValidationUtils.rejectIfEmpty( errors, "name", "name.empty", "name is null"); //age不能小于0，不能大于200 //先做一个强制转换 Person p = (Person) target; if(p.getAge() < 0){ /* errors.rejectValue() 参数一：属性 参数二：错误编码，同上可以随意 参数三：提示信息 */ errors.rejectValue("age","age.value.error","age < 0"); }else if(p.getAge() > 200){ errors.rejectValue("age","age.value.error.old","age > 200"); } } } 完成测试//完成最终的校验测试 public class TestPerson { public static void main(String[] args) { //创建person对象 Person person = new Person(); //创建person对应databinder DataBinder binder = new DataBinder(person); //设置校验器 binder.setValidator(new PersonValidator()); //调用方法执行校验 binder.validate(); //输出校验结果 BindingResult result = binder.getBindingResult(); //输出所有信息 System.out.println(result.getAllErrors()); } } 注解实现Validation创建配置类，配置LocalValidatorFactoryBean@Configuration @ComponentScan("com.atguigu.spring6.validator.two") public class ValidationConfig { @Bean public LocalValidatorFactoryBean validator(){ return new LocalValidatorFactoryBean(); } }创建实体类，定义属性，生成get和set方法，在属性上面使用 [label color="blue"]注解[/label] 设置校验规则User类public class User { @NotNull private String name; @Min(0) @Max(150) private int age; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } } [note type="primary flat"]常用注解说明@NotNull 限制必须不为null@NotEmpty 只作用于字符串类型，字符串不为空，并且长度不为0@NotBlank 只作用于字符串类型，字符串不为空，并且trim()后不为空串@DecimalMax(value) 限制必须为一个不大于指定值的数字@DecimalMin(value) 限制必须为一个不小于指定值的数字@Max(value) 限制必须为一个不大于指定值的数字@Min(value) 限制必须为一个不小于指定值的数字@Pattern(value) 限制必须符合指定的正则表达式@Size(max,min) 限制字符长度必须在min到max之间@Email 验证注解的元素值是Email，也可以通过正则表达式和flag指定自定义的email格式[/note]创建校验器校验器有两种，分别编写类完成。第一种校验器import jakarta.validation.ConstraintViolation; import jakarta.validation.Validator; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.Set; //第一种校验器:注意导包，使用原生校验器 @Service //或者是@Component public class MyValidation1 { @Autowired private Validator validator; public boolean validatorByUserOne(User user){ Set<ConstraintViolation<User>> validate = validator.validate(user); //表示是否有校验信息 return validate.isEmpty(); } }第二种校验器import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.validation.BindException; import org.springframework.validation.ObjectError; import org.springframework.validation.Validator; import java.util.List; //第二种校验器：使用Spring中 @Service public class MyValidation2 { @Autowired private Validator validator; public boolean validatorByUserTwo(User user){ BindException bindException = new BindException(user,user.getName()); validator.validate(user,bindException); List<ObjectError> allErrors = bindException.getAllErrors(); //输出错误信息 System.out.println(allErrors); //表示是否有错误信息，hasErrors()没有错误信息显示false，有错误信息输出true return bindException.hasErrors(); } }完成测试基于两种校验器编写两个测试类import org.junit.jupiter.api.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; public class TestUser { //两个校验器分别测试 @Test public void testValidationOne() { //加载配置类创建容器 ApplicationContext context = new AnnotationConfigApplicationContext(ValidationConfig.class); MyValidation1 validation1 = context.getBean(MyValidation1.class); User user = new User(); user.setName("lucy"); user.setAge(-1); boolean message = validation1.validatorByUserOne(user); System.out.println(message);//为空(不符合编写要求是false)是false //有值(符合编写要求就是true)true } @Test public void testValidationTwo() { //Spring中原生写法 ApplicationContext context = new AnnotationConfigApplicationContext(ValidationConfig.class); MyValidation2 validation2 = context.getBean(MyValidation2.class); User user = new User(); user.setName("lucy"); user.setAge(70); boolean message = validation2.validatorByUserTwo(user); System.out.println(message); } }基于方法实现校验创建配置类，配置 [label color="blue"]MethodValidationPostProcessor[/label]@Configuration @ComponentScan("com.atguigu.spring6.validator.three") public class ValidationConfig { @Bean public MethodValidationPostProcessor validationPostProcessor(){ return new MethodValidationPostProcessor(); } } 创建实体类，使用注解设置校验规则public class User { @NotNull private String name; @Min(0) @Max(150) private int age; @Pattern(regexp = "^1(3|4|5|7|8)\\d{9}$",message = "手机号码格式错误")//以1开头，一共有11位数 @NotBlank(message = "手机号码不能为空") private String phone; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getPhone() { return phone; } public void setPhone(String phone) { this.phone = phone; } }定义Service类，通过注解操作对象@Service @Validated public class MyService { public String testMethod(@NotNull @Valid User user){ return user.toString(); } }完成测试public class TestUser { public static void main(String[] args) { ApplicationContext context = new AnnotationConfigApplicationContext(ValidationConfig.class); MyService service = context.getBean(MyService.class); User user = new User(); user.setName("lucy"); user.setPhone("234356"); service.testMethod(user); } } 自定义校验如果注解不能满足规则，可以实现自定义注解加上注解类，里面添加相关属性，这些可以参考其他注解实现，在里面加上校验器的类//创建一个注解类，实现不能有空格的校验 @Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Constraint(validatedBy = {CannotBlankValidation.class})//指定校验器类名，并创建对应类 public @interface CannotBlank { //默认提示错误信息 String message() default "不能包含空格"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; @Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE}) @Retention(RetentionPolicy.RUNTIME) @Documented //指定多个的时候使用 public @interface List { CannotBlank[] value(); } }校验器中添加校验规则//实现一个接口,泛型一：自定义注解名。泛型二：值的类型 public class CannotBlankValidation implements ConstraintValidator<CannotBlank,String> { @Override public boolean isValid(String value, ConstraintValidatorContext context) { //实现真正校验规则 if(value != null && value.contains(" ")){ //获取默认提示信息 String defaultConstraintMessageTemplate = context.getDefaultConstraintMessageTemplate(); System.out.println("default message :" + defaultConstraintMessageTemplate); //禁用默认提示信息 context.disableDefaultConstraintViolation(); //设置提示语 context.buildConstraintViolationWithTemplate("can not contains blank").addConstraintViolation(); return false;//表示包含空格 } return true; } }测试AOT提前编译[note type="info flat"]1.JIT(JUST IN TIME),动态编译(实时编译)，边运行边编译。在程序运行时候，进行JIT动态编译，可以进行性能优化，动态生成代码，会造成启动比较慢，编译的时候需要占用运行时的资源。JIT在程序运行过程中，把字节码文件转换成硬盘上直接运行机器码，部署到环境过程。2.AOT(AHEAD OF TIME),运行前编译，提前编译可以把源代码转换成机器码，好处就是直接启动，速度快，都编译好后直接运行，内存占用很低，运行的时候无法优化，程序的安装时间变长。在程序运行之前，就把字节码转换成机器码。[/note]现在正处于云原生，降本增效的时代，Java 相比于 Go、Rust 等其他编程语言非常大的弊端就是启动编译和启动进程非常慢，这对于根据实时计算资源，弹性扩缩容的云原生技术相冲突，Spring6 借助 AOT技术在运行时内存占用低，启动速度快，逐渐的来满足 Java 在云原生时代的需求，对于大规模使用 Java应用的商业公司可以考虑尽早调研使用 JDK17，通过云原生技术为公司实现降本增效。Spring6 支持的 AOT 技术，这个 GraalVM 就是底层的支持，Spring 也对 GraalVM 本机映像提供了一流的支持。我书写这篇文章的初衷就是总结学习的进度，遗忘之际可以拿出来翻看，如有不对的地方还望指正，多多海涵。
spring笔记，有关spring中另一个核心技术AOP的理解。涉及到代理设计模式(后面会更新)静态代理中由于将某些代码写死，降低了一定的扩展性，所以说后来产生了动态代理，而AOP的底层就是基于动态代理.关于静态代理不做过多赘述，笔记中以AOP的实现为主延伸动态代理。动态代理public class ProxyFactory //目标对象传递CalculatorImpl private Object target; //把目标对象传递进来 public ProxyFactory(Object target) { this.target = target; } //创建一个方法，返回代理对象（动态） public Object getProxy() { //动态代理创建对象 /* Proxy.newProxyInstance()方法有三个参数 参数一：ClassLoader:加载动态生成代理类的类加载器 参数二：Class[] interfaces:目标对象实现所有接口class类型数组(数组结构) 参数三：InvocationHandler:设置代理对象实现目标对象目标方法的过程 */ //参数一：ClassLoader:加载动态生成代理类的类加载器 ClassLoader classLoader = target.getClass().getClassLoader(); //参数二：Class[] interfaces:目标对象实现所有接口class类型数组(数组结构) Class<?>[] interfaces = target.getClass().getInterfaces(); //参数三：InvocationHandler:设置代理对象实现目标对象目标方法的过程 InvocationHandler invocationHandler = new InvocationHandler() { //InvocationHandler是一个接口，使用匿名内部类来实现invoke方法 //参数一：代理对象 //参数二：需要重写的目标对象的方法 //参数三：method方法里面的参数 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //方法调用之前做输出 System.out.println("[动态代理][日志] " + method.getName() + "，参数：" + Arrays.toString(args)); //调用目标的方法 Object result = method.invoke(target, args); //方法调用之后做个输出 System.out.println("[动态代理][日志] " + method.getName() + "，结果：" + result); return result; } }; return Proxy.newProxyInstance(classLoader, interfaces, invocationHandler); } } AOPAOP是一种设计思想，它以通过预编译的方式和运行期动态代理方式实现，在不修改原代码的情况下，给程序动态统一添加额外功能的技术。使得各个业务逻辑各部分之间耦合度降低。相关知识点横切关注点：我们在方法中抽取一些同样一类的非核心业务，比如：用户验证、事务处理通知(增强)：增强的功能，比如：用户的安全校验，事务，日志...通过方法进行实现的叫做通知方法。动态代理分类：JDK的动态代理(有接口的情况。生成一个接口实现类的代理对象，代理对象和目标对象实现了相同的接口)和cglib的动态代理(没有接口的情况。生成一个子类的代理对象，继承被代理目标类)AspectJ：是AOP框架，Spring只是借用了AspectJ中的注解实现了AOP功能。注解方式实现AOP引入AOP相关依赖父工程中添加依赖 <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>6.0.2</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>6.0.2</version> </dependency> 子工程resources文件中添加xml文件，调整配置信息，开启组件扫描<?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" 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/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <context:component-scan base-package="com.atguigu.spring6.aop.annoaop"></context:component-scan> <aop:aspectj-autoproxy></aop:aspectj-autoproxy> </beans>创建目标资源接口public interface Calculator { public abstract int add(int i, int j); public abstract int sub(int i, int j); public abstract int mul(int i, int j); public abstract int div(int i, int j); } 实现类//最基本的实现类，实现了加减乘除 @Component public class CalculatorImpl implements Calculator { @Override public int add(int i, int j) { int result = i + j; System.out.println("方法内部 result = " + result); return result; } @Override public int sub(int i, int j) { int result = i - j; System.out.println("方法内部 result = " + result); return result; } @Override public int mul(int i, int j) { int result = i * j; System.out.println("方法内部 result = " + result); return result; } @Override public int div(int i, int j) { int result = i / j; System.out.println("方法内部 result = " + result); return result; } }创建一个切面类切入点通知类型//切面类 @Aspect //表示它是一个切面类 @Component//交给spring IoC容器进行管理 public class LogAspect { //设置切入点和通知类型 //切入点表达式execution(访问修饰符 增强方法返回的类型 方法所在类型的全类名(增强方法所在类的全路径).方法名(参数列表)) //通知类型： // @Before(value = "切入点表达式 配置切入点")前置通知 //以前置为例 @Before(value = "execution(public int com.atguigu.spring6.aop.annoaop.CalculatorImpl.add(..))") public void beforeMethod(){ System.out.println("Logger-->前置通知"); } // @AfterReturning返回通知 // @AfterThrowing异常通知 // @After()后置通知 // @Around()环绕通知 // 每种通知类型需要添加方法，添加注解 }这里面有个最重要的切入点表达式测试然后完善剩下四种通知。//切面类 @Aspect //表示它是一个切面类 @Component //交给spring IoC容器进行管理 public class LogAspect { //设置切入点和通知类型 //切入点表达式execution(访问修饰符 增强方法返回的类型 方法所在类型的全类名(增强方法所在类的全路径).方法名(参数列表)) //通知类型： // @Before(value = "切入点表达式 配置切入点")前置通知 //以前置为例 @Before(value = "execution(public int com.atguigu.spring6.aop.annoaop.CalculatorImpl.add(..))") public void beforeMethod(JoinPoint joinPoint) { String methodName = joinPoint.getSignature().getName(); //用joinPoint里面获取前置通知里面的参数 Object[] args = joinPoint.getArgs(); System.out.println("Logger-->前置通知,增强方法名称:" + methodName + ",参数为:" + Arrays.toString(args)); } // @After()后置通知 @After(value = "execution(* com.atguigu.spring6.aop.annoaop.CalculatorImpl.*(..))") public void afterMethod(JoinPoint joinPoint) { //通过省略的参数joinPoint可以获得相关信息 String methodName = joinPoint.getSignature().getName(); System.out.println("Logger-->后置通知,增强方法名称:" + methodName); } // @AfterReturning返回通知 //后置通知在返回之后执行 //返回通知：它能得到增强目标方法的返回值 @AfterReturning(value = "execution(* com.atguigu.spring6.aop.annoaop.CalculatorImpl.*(..))", returning = "result") public void afterReturningMethod(JoinPoint joinPoint, Object result) { String methodName = joinPoint.getSignature().getName(); System.out.println("Logger-->返回通知,增强方法名称:" + methodName + ",返回结果" + result); } // @AfterThrowing异常通知 //目标方法执行过程中，出现了异常，这个通知会执行，能够获取到目标方法的异常信息 @AfterThrowing(value = "execution(* com.atguigu.spring6.aop.annoaop.CalculatorImpl.*(..))", throwing = "ex") public void afterThrowingMethod(JoinPoint joinPoint, Throwable ex) { //通过省略的参数joinPoint可以获得相关信息 String methodName = joinPoint.getSignature().getName(); System.out.println("Logger-->异常通知,增强方法名称:" + methodName + ",异常信息：" + ex); } // @Around()环绕通知,可以呈现出上面四种效果，在之前之后都会执行 @Around("execution(* com.atguigu.spring6.aop.annoaop.CalculatorImpl.*(..))") public Object aroundMethod(ProceedingJoinPoint joinPoint) { //通过省略的参数joinPoint可以获得相关信息 String methodName = joinPoint.getSignature().getName(); Object[] args = joinPoint.getArgs(); String arg = Arrays.toString(args); Object result = null; try { System.out.println("环绕通知 == 目标方法之前执行"); //调用目标方法 result = joinPoint.proceed(); System.out.println("环绕通知 == 目标方法返回值之后执行"); } catch (Throwable throwable) { throwable.printStackTrace(); System.out.println("环绕通知 == 目标方法出现异常执行"); } finally { System.out.println("环绕通知 == 目标方法执行完毕执行"); } return result; } }重用切入点表达式考虑到上面切入点表达式每次使用都是同一种情况，重复书写，抽取。实际上写成一个单独的方法，提供切入点表达式，需要加上 [label color="red"]@Pointcut[/label] ,括号中传递切入点表达式 //重用切入点表达式 @Pointcut(value = "execution(* com.atguigu.spring6.aop.annoaop.CalculatorImpl.*(..))") public void pointCut(){ } 在需要使用切入点表达式的时候，直接调用方法，当然还要考虑到是不是同一个切面。基于XML的AOP把之前@Aspect以及通知注解删除，调整对应切面类//切面类 @Component //交给spring IoC容器进行管理 public class LogAspect { //前置通知 public void beforeMethod(JoinPoint joinPoint) { String methodName = joinPoint.getSignature().getName(); //用joinPoint里面获取前置通知里面的参数 Object[] args = joinPoint.getArgs(); System.out.println("Logger-->前置通知,增强方法名称:" + methodName + ",参数为:" + Arrays.toString(args)); } //后置通知 public void afterMethod(JoinPoint joinPoint) { //通过省略的参数joinPoint可以获得相关信息 String methodName = joinPoint.getSignature().getName(); System.out.println("Logger-->后置通知,增强方法名称:" + methodName); } //返回通知,获取目标方法的返回值 public void afterReturningMethod(JoinPoint joinPoint, Object result) { String methodName = joinPoint.getSignature().getName(); System.out.println("Logger-->返回通知,增强方法名称:" + methodName + ",返回结果" + result); } //异常通知 public void afterThrowingMethod(JoinPoint joinPoint, Throwable ex) { //通过省略的参数joinPoint可以获得相关信息 String methodName = joinPoint.getSignature().getName(); System.out.println("Logger-->异常通知,增强方法名称:" + methodName + ",异常信息：" + ex); } //环绕通知 public Object aroundMethod(ProceedingJoinPoint joinPoint) { //通过省略的参数joinPoint可以获得相关信息 String methodName = joinPoint.getSignature().getName(); Object[] args = joinPoint.getArgs(); String arg = Arrays.toString(args); Object result = null; try { System.out.println("环绕通知 == 目标方法之前执行"); //调用目标方法 result = joinPoint.proceed(); System.out.println("环绕通知 == 目标方法返回值之后执行"); } catch (Throwable throwable) { throwable.printStackTrace(); System.out.println("环绕通知 == 目标方法出现异常执行"); } finally { System.out.println("环绕通知 == 目标方法执行完毕执行"); } return result; } //重用切入点表达式 @Pointcut(value = "execution(* com.atguigu.spring6.aop.xmlaop.CalculatorImpl.*(..))") public void pointCut() { } } 补充spring的XML文件，在XML文件中配置五种通知类型<?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" 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/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <context:component-scan base-package="com.atguigu.spring6.aop.xmlaop"></context:component-scan> <aop:config> <aop:aspect ref="logAspect"> <aop:pointcut id="pointcut" expression="execution(* com.atguigu.spring6.aop.xmlaop.CalculatorImpl.*(..))"/> <aop:before method="beforeMethod" pointcut-ref="pointcut"></aop:before> <aop:after method="afterMethod" pointcut-ref="pointcut"></aop:after> <aop:after-returning method="afterReturningMethod" returning="result" pointcut-ref="pointcut"></aop:after-returning> <aop:after-throwing method="afterThrowingMethod" throwing="ex" pointcut-ref="pointcut"></aop:after-throwing> <aop:around method="aroundMethod" pointcut-ref="pointcut"></aop:around> </aop:aspect> </aop:config> </beans>最后测试类中读取该xml文件即可。我书写这篇文章的初衷就是总结学习的进度，遗忘之际可以拿出来翻看，如有不对的地方还望指正，多多海涵。
通过手写代码，写出spring框架最核心的部分之前在JavaWeb中的学习里一段初步手写IoC可供参考和跳转初步学习实现过程创建子模块()引入依赖创建测试类和接口（service dao）创建(手写)两个注解，实现IoC功能@Bean 创建对象@Di 属性注入创建Bean容器接口 [label color="blue"]ApplicationContext[/label] ,定义方法，返回对象实现Bean容器接口，实现类中功能返回对象根据包的扫描规则，加载bean扫描规则:比如包是com.atguigu扫描com.atguigu这个包，和他的子包里面的所有类，扫描这个上面是否有@Bean注解，如果有，那就把这个类通过反射进行实例化。准备工作简单来说就是通过手写代码的方式实现两个注解(对应第三步)创建两个注解类，配置方式如下创建bean容器接口并实现(第四步)实现容器接口(第五步：实现第一个注解)@Bean创建对象我们创建一个集合，重写接口中的方法，通过getBean方法，在集合中获取，返回我们需要的对应类的对象。在有参数的构造当中，我们需要编写对应的扫描规则。首先我们需要获取对应包的绝对路径，同时添加一个简化路径的变量rootPath方便后面扫描，根据路径添加一个扫描包的方法 [label color="blue"]loadBean[/label] 随后我们具体实现扫描包的方法。public class AnnotationApplicationContext implements ApplicationContext { //创建map集合，放bean实例对象 private Map<Class, Object> beanFactory = new HashMap<>(); //简化路径 private static String rootPath; //此方法用于返回我们创建的对象 @Override public Object getBean(Class clazz) { //根据key返回对象 return beanFactory.get(clazz); } //设置包的扫描规则 //当前包及其子包里面，哪个类有@Bean注解，把这个类通过反射进行实例化 //创建有参数构造，传递包的路径basePackage，设置扫描规则 //public AnnotationApplicationContext(String basePackage) throws IOException { public static void pathdemo1(String basePackage) { try { //设置包的扫描规则 com.atguigu.spring6 //找到这个包所在的路径，找到绝对路径，看一下文件夹中是否有子文件夹和文件 //1.把.替换成\ String packagePath = basePackage.replaceAll("\\.", "\\\\"); //2.获取包的绝对路径 Enumeration<URL> urls = Thread.currentThread().getContextClassLoader().getResources(packagePath); while (urls.hasMoreElements()) { //遍历，得到里面的每一个具体值 URL url = urls.nextElement(); //转码 String filePath = URLDecoder.decode(url.getFile(), "utf-8"); //获取包前面的路径部分，用作字符串截取 rootPath = filePath.substring(0, filePath.length() - packagePath.length()); //根据路径进行包扫描过程 loadBean(new File(filePath)); } } catch (IOException e) { throw new RuntimeException(e); } } public static void main(String[] args) { pathdemo1("com.atguigu"); } }@Bean创建对象实现loadBean方法的大致思路如下 //包扫描过程，实例化 private static void loadBean(File file) { //1.判断当前文件是否文件夹 //2.是文件夹，获取文件夹中里面的所有内容 //3.判断文件夹里面内容为空，直接返回 //4.如果文件夹里面不为空，遍历文件夹里面的所有内容 //4.1遍历得到每部分内容file对象，继续判断，如果还是文件夹，递归 //4.2如果遍历得到的file对象，不是一个文件夹，是文件 //4.3得到包路径 + 类名称部分 --->字符串截取 //4.4判断当前文件的类型是否是.class类型 //是，反射 //4.5如果是.class类型，把路径中的\替换成. 把.class去掉 //com.atguigu.service.UserServiceImpl //4.6判断类上面是否有注解@Bean，如果有，进行实例化过程 //4.7把对象实例化之后，放到Map集合中beanFactory去 //4.7.1判断当前类有接口，让接口作为key //4.7.2判断当前类没有接口，让自己作为key } [label color="red"]实现包扫描过程，用反射进行实例化，按照步骤完成。[/label] //包扫描过程，实例化 private void loadBean(File file) throws Exception { //1.判断当前文件是否文件夹 if (file.isDirectory()) { //2.是文件夹，获取文件夹中里面的所有内容 File[] childrenFiles = file.listFiles(); //3.判断文件夹里面内容为空，直接返回 if (childrenFiles == null || childrenFiles.length == 0) { return; } //4.如果文件夹里面不为空，遍历文件夹里面的所有内容 for (File child : childrenFiles) { //4.1遍历得到每部分内容file对象，继续判断，如果还是文件夹，递归 if (child.isDirectory()) { //递归的方式 loadBean(child); } else { //4.2如果遍历得到的file对象，不是一个文件夹，是文件 //4.3得到包路径 + 类名称部分 --->字符串截取 String pathWithClass = child.getAbsolutePath().substring(rootPath.length() - 1); //4.4判断当前文件的类型是否是.class类型 if (pathWithClass.contains(".class")) { //4.5如果是.class类型，把路径中的\替换成. 把.class去掉 //com.atguigu.service.UserServiceImpl String allName = pathWithClass.replaceAll("\\\\", ".") .replace(".class", ""); //4.6判断类上面是否有注解@Bean，如果有，进行实例化过程 //4.6.1得到了类的全路径，获取类的class对象 Class<?> clazz = Class.forName(allName); //4.6.2判断不是接口，才进行实例化 if (!clazz.isInterface()) { //4.6.3判断类上面是否有注解@Bean Bean annotation = clazz.getAnnotation(Bean.class); if (annotation != null) { //表示类有注解 //4.6.4对象 进行实例化 Object instance = clazz.getDeclaredConstructor().newInstance(); //4.7把对象实例化之后，放到Map集合中beanFactory去 //4.7.1判断当前类有接口，让接口class作为key if (clazz.getInterfaces().length > 0) { beanFactory.put(clazz.getInterfaces()[0], instance); } else { //4.7.2判断当前类没有接口，让自己class作为key beanFactory.put(clazz, instance); } } } } } } } }[label color="red"]最终源码，实现@Bean创建对象 + 扫描[/label]package com.atguigu.bean; import com.atguigu.anno.Bean; import java.io.File; import java.net.URL; import java.net.URLDecoder; import java.util.Enumeration; import java.util.HashMap; import java.util.Map; public class AnnotationApplicationContext implements ApplicationContext { //创建map集合，放bean实例对象 private Map<Class, Object> beanFactory = new HashMap<>(); //简化路径 private static String rootPath; //此方法用于返回我们创建的对象 @Override public Object getBean(Class clazz) { //根据key返回对象 return beanFactory.get(clazz); } //设置包的扫描规则 //当前包及其子包里面，哪个类有@Bean注解，把这个类通过反射进行实例化 //创建有参数构造，传递包的路径basePackage，设置扫描规则 public AnnotationApplicationContext(String basePackage) { try { //设置包的扫描规则 com.atguigu.spring6 //找到这个包所在的路径，找到绝对路径，看一下文件夹中是否有子文件夹和文件 //1.把.替换成\ String packagePath = basePackage.replaceAll("\\.", "\\\\"); //2.获取包的绝对路径 Enumeration<URL> urls = Thread.currentThread().getContextClassLoader().getResources(packagePath); while (urls.hasMoreElements()) { //遍历，得到里面的每一个具体值 URL url = urls.nextElement(); //转码 String filePath = URLDecoder.decode(url.getFile(), "utf-8"); //获取包前面的路径部分，用作字符串截取 rootPath = filePath.substring(0, filePath.length() - packagePath.length()); //根据路径进行包扫描过程 loadBean(new File(filePath)); } } catch (Exception e) { throw new RuntimeException(e); } } //包扫描过程，实例化 private void loadBean(File file) throws Exception { //1.判断当前文件是否文件夹 if (file.isDirectory()) { //2.是文件夹，获取文件夹中里面的所有内容 File[] childrenFiles = file.listFiles(); //3.判断文件夹里面内容为空，直接返回 if (childrenFiles == null || childrenFiles.length == 0) { return; } //4.如果文件夹里面不为空，遍历文件夹里面的所有内容 for (File child : childrenFiles) { //4.1遍历得到每部分内容file对象，继续判断，如果还是文件夹，递归 if (child.isDirectory()) { //递归的方式 loadBean(child); } else { //4.2如果遍历得到的file对象，不是一个文件夹，是文件 //4.3得到包路径 + 类名称部分 --->字符串截取 String pathWithClass = child.getAbsolutePath().substring(rootPath.length() - 1); //4.4判断当前文件的类型是否是.class类型 if (pathWithClass.contains(".class")) { //4.5如果是.class类型，把路径中的\替换成. 把.class去掉 //com.atguigu.service.UserServiceImpl String allName = pathWithClass.replaceAll("\\\\", ".") .replace(".class", ""); //4.6判断类上面是否有注解@Bean，如果有，进行实例化过程 //4.6.1得到了类的全路径，获取类的class对象 Class<?> clazz = Class.forName(allName); //4.6.2判断不是接口，才进行实例化 if (!clazz.isInterface()) { //4.6.3判断类上面是否有注解@Bean Bean annotation = clazz.getAnnotation(Bean.class); if (annotation != null) { //表示类有注解 //4.6.4对象 进行实例化 Object instance = clazz.getDeclaredConstructor().newInstance(); //4.7把对象实例化之后，放到Map集合中beanFactory去 //4.7.1判断当前类有接口，让接口class作为key if (clazz.getInterfaces().length > 0) { beanFactory.put(clazz.getInterfaces()[0], instance); } else { //4.7.2判断当前类没有接口，让自己class作为key beanFactory.put(clazz, instance); } } } } } } } } } 浅浅的测试一下。@Bi属性注入[label color="red"]进行@Di注解的注入属性实现，添加一个方法loadDi()[/label] //进行属性的注入 private void loadDi() { //实例化对象在beanFactory的map集合中里面 //1.遍历beanFactory的map集合 //2.获取map集合的每个对象(value)，每个对象中的属性获取到 //3.遍历得到的每个对象属性的数组，得到里面的每个属性 //4.判断属性上面是否有@Di注解 //如果私有属性，设置可以设置值 //5.如果有@Di注解，把对象进行设置(注入) }根据注解完成对应的核心实现。 //进行属性的注入 private void loadDi() { //实例化对象在beanFactory的map集合中里面 //1.遍历beanFactory的map集合 Set<Map.Entry<Class, Object>> entries = beanFactory.entrySet(); for (Map.Entry<Class, Object> entry : entries) { //2.获取map集合的每个对象(value)，每个对象中的属性获取到 Object obj = entry.getValue(); //获取对象的class Class<?> clazz = obj.getClass(); //获取每个对象中的属性 Field[] declaredFields = clazz.getDeclaredFields(); //3.遍历得到的每个对象属性的数组，得到里面的每个属性 for (Field field : declaredFields) { //4.判断属性上面是否有@Di注解 Di annotation = field.getAnnotation(Di.class); if(annotation != null){ //如果私有属性，设置可以设置值 field.setAccessible(true); //5.如果有@Di注解，把对象进行设置(注入) try { field.set(obj,beanFactory.get(field.getType())); //表示向obj设置值 //field.getType()获得变量的类型，通过map容器找到该类的一个实例对象注入。 } catch (IllegalAccessException e) { throw new RuntimeException(e); } } } } }测试一下最终IoC核心源码package com.atguigu.bean; import com.atguigu.anno.Bean; import com.atguigu.anno.Di; import java.io.File; import java.lang.reflect.Field; import java.net.URL; import java.net.URLDecoder; import java.util.Enumeration; import java.util.HashMap; import java.util.Map; import java.util.Set; public class AnnotationApplicationContext implements ApplicationContext { //创建map集合，放bean实例对象 private Map<Class, Object> beanFactory = new HashMap<>(); //简化路径 private static String rootPath; //此方法用于返回我们创建的对象 @Override public Object getBean(Class clazz) { //根据key返回对象 return beanFactory.get(clazz); } //设置包的扫描规则 //当前包及其子包里面，哪个类有@Bean注解，把这个类通过反射进行实例化 //创建有参数构造，传递包的路径basePackage，设置扫描规则 public AnnotationApplicationContext(String basePackage) { try { //设置包的扫描规则 com.atguigu.spring6 //找到这个包所在的路径，找到绝对路径，看一下文件夹中是否有子文件夹和文件 //1.把.替换成\ String packagePath = basePackage.replaceAll("\\.", "\\\\"); //2.获取包的绝对路径 Enumeration<URL> urls = Thread.currentThread().getContextClassLoader().getResources(packagePath); while (urls.hasMoreElements()) { //遍历，得到里面的每一个具体值 URL url = urls.nextElement(); //转码 String filePath = URLDecoder.decode(url.getFile(), "utf-8"); //获取包前面的路径部分，用作字符串截取 rootPath = filePath.substring(0, filePath.length() - packagePath.length()); //根据路径进行包扫描过程 loadBean(new File(filePath)); } } catch (Exception e) { throw new RuntimeException(e); } //完成属性的注入 loadDi(); } //包扫描过程，实例化 private void loadBean(File file) throws Exception { //1.判断当前文件是否文件夹 if (file.isDirectory()) { //2.是文件夹，获取文件夹中里面的所有内容 File[] childrenFiles = file.listFiles(); //3.判断文件夹里面内容为空，直接返回 if (childrenFiles == null || childrenFiles.length == 0) { return; } //4.如果文件夹里面不为空，遍历文件夹里面的所有内容 for (File child : childrenFiles) { //4.1遍历得到每部分内容file对象，继续判断，如果还是文件夹，递归 if (child.isDirectory()) { //递归的方式 loadBean(child); } else { //4.2如果遍历得到的file对象，不是一个文件夹，是文件 //4.3得到包路径 + 类名称部分 --->字符串截取 String pathWithClass = child.getAbsolutePath().substring(rootPath.length() - 1); //4.4判断当前文件的类型是否是.class类型 if (pathWithClass.contains(".class")) { //4.5如果是.class类型，把路径中的\替换成. 把.class去掉 //com.atguigu.service.UserServiceImpl String allName = pathWithClass.replaceAll("\\\\", ".") .replace(".class", ""); //4.6判断类上面是否有注解@Bean，如果有，进行实例化过程 //4.6.1得到了类的全路径，获取类的class对象 Class<?> clazz = Class.forName(allName); //4.6.2判断不是接口，才进行实例化 if (!clazz.isInterface()) { //4.6.3判断类上面是否有注解@Bean Bean annotation = clazz.getAnnotation(Bean.class); if (annotation != null) { //表示类有注解 //4.6.4对象 进行实例化 Object instance = clazz.getDeclaredConstructor().newInstance(); //4.7把对象实例化之后，放到Map集合中beanFactory去 //4.7.1判断当前类有接口，让接口class作为key if (clazz.getInterfaces().length > 0) { beanFactory.put(clazz.getInterfaces()[0], instance); } else { //4.7.2判断当前类没有接口，让自己class作为key beanFactory.put(clazz, instance); } } } } } } } } //进行属性的注入 private void loadDi() { //实例化对象在beanFactory的map集合中里面 //1.遍历beanFactory的map集合 Set<Map.Entry<Class, Object>> entries = beanFactory.entrySet(); for (Map.Entry<Class, Object> entry : entries) { //2.获取map集合的每个对象(value)，每个对象中的属性获取到 Object obj = entry.getValue(); //获取对象的class Class<?> clazz = obj.getClass(); //获取每个对象中的属性 Field[] declaredFields = clazz.getDeclaredFields(); //3.遍历得到的每个对象属性的数组，得到里面的每个属性 for (Field field : declaredFields) { //4.判断属性上面是否有@Di注解 Di annotation = field.getAnnotation(Di.class); if(annotation != null){ //如果私有属性，设置可以设置值 field.setAccessible(true); //5.如果有@Di注解，把对象进行设置(注入) try { field.set(obj,beanFactory.get(field.getType())); //表示向obj设置值 //field.getType()获得变量的类型，通过map容器找到该类的一个实例对象注入。 } catch (IllegalAccessException e) { throw new RuntimeException(e); } } } } } } 我书写这篇文章的初衷就是总结学习的进度，遗忘之际可以拿出来翻看，如有不对的地方还望指正，多多海涵。
Spring笔记：依赖注入|基于XML和注解方式管理Bean到全注解开发
有关Spring6的学习笔记，从配置XML文件到最后使用全注解开发。[note type="primary flat"]IoC 是 Inversion of Control 的简写，译为“控制反转”.是一种设计思想，并不是一种技术。Spring 通过 IoC 容器来管理所有 Java 对象的实例化和初始化，控制对象与对象之间的依赖关系。我们将由 IoC 容器管理的 Java 对象称为 Spring Bean，它与使用关键字 new 创建的 Java 对象没有任何区别。对象从创建到销毁过程都是通过容器来管理的容器放bean对象，使用map集合，存放方便。在xml配置文件中定义相应的Bean信息BeanDefinition，通过接口BeanDefinitionReader读取加载,放入IoC容器中，存储Bean定义信息。在IoC容器中实例化Bean的定义信息(当然这个信息可以修改)，创建通过BeanFactory工厂(模式) + 反射，目前(实例化)是一个空对象，然后进行初始化才可以使用。通过context.getBean();可以使用。依赖注入：指Spring创建对象的过程中，将对象依赖属性通过配置进行注入依赖注入常见的实现方式包括两种：第一种：set注入第二种：构造注入Bean管理说的是：Bean对象的创建，以及Bean对象中属性的赋值（或者叫做Bean对象之间关系的维护）.[/note]三种方式获取bean对象获取bean对象前的操作省略，无非就是配置spring依赖，读取xml的配置文件方式一：根据id获取bean对象 //1.根据id获取bean对象 User user = (User) context.getBean("user");//括号中加上xml文件中配置的id属性值 System.out.println("1.根据id获取bean:" + user); 方式二：根据类型获取bean对象 //2.根据类型来获取bean对象 User user2 = context.getBean(User.class); System.out.println("2.根据类型获取bean:" + user2); [label color="red"]注意：这种方式获取bean，要求IOC容器中指定类型的bean有且只能有一个[/label] 如果xml配置多个bean，使用这种方式报错。 <bean id="user" class="com.atguigu.sping6.iocxml.User"></bean> <bean id="user1" class="com.atguigu.sping6.iocxml.User"></bean>如果是 [label color="blue"]接口 = 实现类对象[/label] 的形式，根据接口类型可以得到bean对象。如果一个接口有多个实现类，这些实现类都配置了bean，根据接口类型 [label color="red"]不可以[/label] 获取到bean。因为不唯一，一个接口有多个对象。方式三：根据 id + 类型 获取bean对象 //3.根据id和类型获取bean对象 User user3 = context.getBean("user", User.class); System.out.println("3.根据id和类型:" + user3); 依赖注入类有属性，创建对象的过程中，向属性(成员变量)设置值，有两种方法基于set方法完成注入创建类，定义属性，生成属性set方法。准备类：package com.atguigu.sping6.iocxml.di; public class Book { //一会，创建对象中完成属性设置 private String bname; private String author; public Book() { } public Book(String bname, String author) { this.bname = bname; this.author = author; } public static void main(String[] args) { //set方法注入 Book book = new Book(); book.setBname("java"); book.setAuthor("尚硅谷"); //通过构造器方法注入 Book book1 = new Book("C++","尚硅谷"); } /** * 设置 * @param bname */ public void setBname(String bname) { this.bname = bname; } /** * 设置 * @param author */ public void setAuthor(String author) { this.author = author; } /** * 获取 * @return bname */ public String getBname() { return bname; } /** * 获取 * @return author */ public String getAuthor() { return author; } public String toString() { return "Book{bname = " + bname + ", author = " + author + "}"; } } 在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" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="book" class="com.atguigu.sping6.iocxml.di.Book"> <property name="bname" value="前端开发"></property> <property name="author" value="尚硅谷"></property> </bean> </beans>基于构造器完成注入创建类，定义属性，生成 [label color="red"]有参数的构造方法[/label]准备Book类同上spring里面进行配置 <bean id="bookCon" class="com.atguigu.sping6.iocxml.di.Book"> <constructor-arg name="bname" value="java开发"></constructor-arg> <constructor-arg name="author" value="尚硅谷"></constructor-arg> <!--<constructor-arg index="1" value=""></constructor-arg>--> </bean>实际上我们可以看到他调用的是类的有参构造。特殊值处理字面量赋值<property name="bname" value="前端开发"></property>null值 <property name="others"> <null/> </property>XML实体 <!--<>在XML文件中定义标签的，不能随便使用，需要做转义处理 < < > > --> <property name="others" value="<>"></property>CDATA节 <property name="others"> <value> a < b </value> </property>特殊属性注入对象类型属性注入引用外部bean <bean id="dept" class="com.atguigu.sping6.iocxml.ditest.Dept"> <property name="dname" value="安保部"></property> </bean> <bean id="emp" class="com.atguigu.sping6.iocxml.ditest.Emp"> <property name="ename" value="lucy"></property> <property name="age" value="50"></property> <property name="dept" ref="dept"></property> </bean> 内部bean <bean id="emp2" class="com.atguigu.sping6.iocxml.ditest.Emp"> <property name="ename" value="mary"></property> <property name="age" value="20"></property> <property name="dept" > <bean id="dept2" class="com.atguigu.sping6.iocxml.ditest.Dept"> <property name="dname" value="财务部"></property> </bean> </property> </bean>级联属性赋值 <bean id="dept3" class="com.atguigu.sping6.iocxml.ditest.Dept"> <property name="dname" value="技术研发部"></property> </bean> <bean id="emp3" class="com.atguigu.sping6.iocxml.ditest.Emp"> <property name="ename" value="tom"></property> <property name="age" value="30"></property> <property name="dept" ref="dept3"></property> <property name="dept.dname" value="测试部"></property> </bean>相当于emp3 [label color="orange"]bean标签[/label] 中给dept3单独赋值。为数组类型属性赋值 <property name="loves"> <array> <value>吃饭</value> <value>睡觉</value> <value>敲代码</value> </array> </property> 为集合类型属性赋值List集合属性赋值 <bean id="empone" class="com.atguigu.sping6.iocxml.ditest.Emp"> <property name="ename" value="lucy"></property> <property name="age" value="20"></property> </bean> <bean id="emptwo" class="com.atguigu.sping6.iocxml.ditest.Emp"> <property name="ename" value="mary"></property> <property name="age" value="30"></property> </bean> <bean id="dept" class="com.atguigu.sping6.iocxml.ditest.Dept"> <property name="dname" value="技术部"></property> <property name="empList"> <ref bean="empone"></ref> <ref bean="emptwo"></ref> </property> </bean> Map集合属性赋值 <bean id="teacherone" class="com.atguigu.sping6.iocxml.dimap.Teacher"> <property name="teacherid" value="100"></property> <property name="teachername" value="西门讲师"></property> </bean> <bean id="teachertwo" class="com.atguigu.sping6.iocxml.dimap.Teacher"> <property name="teacherid" value="200"></property> <property name="teachername" value="上官讲师"></property> </bean> <bean id="student" class="com.atguigu.sping6.iocxml.dimap.Student"> <property name="sid" value="2000"></property> <property name="sname" value="张三"></property> <property name="teacherMap"> <!--<entry>表示map集合里面的一个键值对--> <entry> <key> <value>10010</value> </key> <ref bean="teacherone"></ref> </entry> <entry> <key> <value>10086</value> </key> <ref bean="teachertwo"></ref> </entry> </property> </bean>Properties属性赋值springMVC中异常处理器提及引用bean为集合类型属性赋值相当于把使用过的集合放在util标签下，需要调整XML配置信息<?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:util="http://www.springframework.org/schema/util" xsi:schemaLocation="http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> 然后使用util标签<?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:util="http://www.springframework.org/schema/util" xsi:schemaLocation="http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="student" class="com.atguigu.sping6.iocxml.dimap.Student"> <property name="sid" value="10000"></property> <property name="sname" value="lucy"></property> <property name="lessonList" ref="lessonList"></property> <property name="teacherMap" ref="teacherMap"></property> </bean> <util:list id="lessonList"> <ref bean="lessonone"></ref> <ref bean="lessontwo"></ref> </util:list> <util:map id="teacherMap"> <entry> <key> <value>10010</value> </key> <ref bean="teacherone"></ref> </entry> <entry> <key> <value>10086</value> </key> <ref bean="teachertwo"></ref> </entry> </util:map> <bean id="lessonone" class="com.atguigu.sping6.iocxml.dimap.Lesson"> <property name="lessonName" value="Java开发"></property> </bean> <bean id="lessontwo" class="com.atguigu.sping6.iocxml.dimap.Lesson"> <property name="lessonName" value="前端开发"></property> </bean> <bean id="teacherone" class="com.atguigu.sping6.iocxml.dimap.Teacher"> <property name="teacherid" value="100"></property> <property name="teachername" value="西门讲师"></property> </bean> <bean id="teachertwo" class="com.atguigu.sping6.iocxml.dimap.Teacher"> <property name="teacherid" value="200"></property> <property name="teachername" value="欧阳讲师"></property> </bean> </beans>Lesson类:public class Lesson { private String lessonName; public Lesson() { } public Lesson(String lessonName) { this.lessonName = lessonName; } /** * 获取 * @return lessonName */ public String getLessonName() { return lessonName; } /** * 设置 * @param lessonName */ public void setLessonName(String lessonName) { this.lessonName = lessonName; } public String toString() { return "Lesson{lessonName = " + lessonName + "}"; } } Student类:public class Student { private String sid; private String sname; private Map<String, Teacher> teacherMap; private List<Lesson> lessonList; public Student() { } public Student(String sid, String sname, Map<String, Teacher> teacherMap, List<Lesson> lessonList) { this.sid = sid; this.sname = sname; this.teacherMap = teacherMap; this.lessonList = lessonList; } public void run() { System.out.println("学生的编号:" + sid + "学生的名称:" + sname); System.out.println(teacherMap); System.out.println(lessonList); } /** * 获取 * @return sid */ public String getSid() { return sid; } /** * 设置 * @param sid */ public void setSid(String sid) { this.sid = sid; } /** * 获取 * @return sname */ public String getSname() { return sname; } /** * 设置 * @param sname */ public void setSname(String sname) { this.sname = sname; } /** * 获取 * @return teacherMap */ public Map<String, Teacher> getTeacherMap() { return teacherMap; } /** * 设置 * @param teacherMap */ public void setTeacherMap(Map<String, Teacher> teacherMap) { this.teacherMap = teacherMap; } /** * 获取 * @return lessonList */ public List<Lesson> getLessonList() { return lessonList; } /** * 设置 * @param lessonList */ public void setLessonList(List<Lesson> lessonList) { this.lessonList = lessonList; } public String toString() { return "Student{sid = " + sid + ", sname = " + sname + ", teacherMap = " + teacherMap + ", lessonList = " + lessonList + "}"; } } Teacher类:public class Student { private String sid; private String sname; private Map<String, Teacher> teacherMap; private List<Lesson> lessonList; public Student() { } public Student(String sid, String sname, Map<String, Teacher> teacherMap, List<Lesson> lessonList) { this.sid = sid; this.sname = sname; this.teacherMap = teacherMap; this.lessonList = lessonList; } public void run() { System.out.println("学生的编号:" + sid + "学生的名称:" + sname); System.out.println(teacherMap); System.out.println(lessonList); } /** * 获取 * @return sid */ public String getSid() { return sid; } /** * 设置 * @param sid */ public void setSid(String sid) { this.sid = sid; } /** * 获取 * @return sname */ public String getSname() { return sname; } /** * 设置 * @param sname */ public void setSname(String sname) { this.sname = sname; } /** * 获取 * @return teacherMap */ public Map<String, Teacher> getTeacherMap() { return teacherMap; } /** * 设置 * @param teacherMap */ public void setTeacherMap(Map<String, Teacher> teacherMap) { this.teacherMap = teacherMap; } /** * 获取 * @return lessonList */ public List<Lesson> getLessonList() { return lessonList; } /** * 设置 * @param lessonList */ public void setLessonList(List<Lesson> lessonList) { this.lessonList = lessonList; } public String toString() { return "Student{sid = " + sid + ", sname = " + sname + ", teacherMap = " + teacherMap + ", lessonList = " + lessonList + "}"; } }p命名空间我们可以添加一个p命名空间 [label color="red"]准备：[/label] 修改XML配置信息配置信息中添加一行： xmlns:p="http://www.springframework.org/schema/p" 位于 [label color="blue"]schemaLocation[/label] 之上。[note type="flat"]xmlns:自定义="http://www.springframework.org/schema/自定义"实际上在配置信息中声明，为了不重复，需要自定义一个名，然后再尾巴路径出修改即可。有点类似于Java的this关键字[/note]<?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:util="http://www.springframework.org/schema/util" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="studentp" class="com.atguigu.sping6.iocxml.dimap.Student" p:sid="100" p:sname="mary" p:lessonList-ref="lessonList" p:teacherMap-ref="teacherMap"> </bean>引入外部属性文件[label color="red"]应用场景：[/label] 在一个文件中我们需要使用很多bean的值注入，要修改维护不方便。实际中把特定的固定值放到外部文件中，然后引入外部文件进行注入。这样好处就是只需要修改外部文件，不需要修改spring里面。引入数据库相关依赖 <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.30</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.2.15</version> </dependency> 创建外部配置文件properties,定义数据信息，用户 密码 地址等。druid.propertiesdriverClassName=com.mysql.cj.jdbc.Driver username=root password=abc123 url=jdbc:mysql:///atguigu initialSize=5创建spring配置文件，引入context命名空间引入属性文件，使用表达式完成注入这里的 location="classpath:druid.properties" classpath读取时你自己的配置文件名<?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/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <context:property-placeholder location="classpath:druid.properties"></context:property-placeholder> </beans>使用表达式完成注入 <bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="url" value="${url}"></property> <property name="username" value="${username}"></property> <property name="password" value="${password}"></property> <property name="driverClassName" value="${driverClassName}"></property> <property name="initialSize" value="${initialSize}"></property> </bean>这里的value值是通过读取properties文件中的key值，拿取对应的value值。好处就是通过spring加载配置文件，而不用工具类或者硬编码的方式写成java程序bean作用域取值含义获取对象的时机singleton（默认）IOC容器中，这个bean的对象始终为单实例IOC容器初始化时prototype这个bean在IOC容器中有多个实例获取bean时Bean的生命周期bean对象的创建（调用无参数构造）给bean设置相关的属性（相关注入）调用bean后置处理器（初始化之前）bean对象的初始化操作（调用指定初始化方法）调用bean后置处理器（初始化之后）bean的对象创建完成了，可以使用了bean对象销毁（配置指定的销毁方法）IoC容器最终关闭自动装配我们在实际业务中，我们需要Controller层调用Service层的业务，而一个业务对应多个单精度Dao方法，通过自动装配的形式调用。原生方法：创建Service和Dao的接口，然后创建实现类对象。从Controller层开始一层一层开始通过多态创建对象，调用里面方法。根据类型(byType)完成自动装配在需要调用其他类对象的类中添加其他类的成员变量，通过变量调用方法在该类中添加这个成员变量的 [label color="orange"]set方法[/label]spring中配置 [label color="red"]autowire[/label] <bean id="userController" class="com.atguigu.sping6.iocxml.auto.controller.UserController" autowire="byType"> </bean> <bean id="userService" class="com.atguigu.sping6.iocxml.auto.service.UserServiceImpl" autowire="byType"> </bean> <bean id="userDao" class="com.atguigu.sping6.iocxml.auto.dao.UserDapImpl"> </bean> [note type="info flat"]byType根据类型自动装配，在Controller注入Service，注入类型（byType）找到类型对应的对象(在spring中找)，完成注入。 [label color="red"]需要注意的是接口对应的是实现类的对象必须是单个(单实现)，否则报错[/label] [/note]根据名称(byName)完成自动装配[note type="info flat"]修改autowire就可。根据名称注入，需要保证添加其他类成员变量名要和对应bean标签id一致。[/note]注解管理Bean(☆)注解可以是代码中的一种特殊标记 [label color="red"]格式 @注解名称( 属性名称 = 属性值...)[/label] 注解可以使用在类上面，属性上面，方法上面。通过注解实现自动装配，简化SpringXML配置，实际开发中 [label color="red"]基于注解做实现[/label]。开启组件扫描引入相关依赖开启组件的扫描Spring默认不适用注解装配Bean，我们需要在SpingXMl配置信息中开启 [label color="red"]自动扫描功能[/label] 。添加context空间 xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd 开启组件扫描， base-package="com.atguigu" 会扫描当前包和他的子包下的所有类。不考虑其他情况完整的XML文件是这样的<?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/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <context:component-scan base-package="com.atguigu"></context:component-scan> </beans>还有其他的特殊情况 <context:component-scan base-package="com.atguigu.spring6"> <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/> <!--<context:exclude-filter type="assignable" expression="com.atguigu.spring6.controller.UserController"/>--> </context:component-scan> <context:component-scan base-package="com.atguigu" use-default-filters="false"> <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/> <!--<context:include-filter type="assignable" expression="com.atguigu.spring6.controller.UserController"/>--> </context:component-scan>用注解创建bean对象注解说明@Component普通Bean定义@Repository一般用在Dao层@Service用在Service层@Controller一般用在Web层@Autowired注入单独使用@Autowired注解，默认根据类型装配(byType)。流程见自动装配，必须开启组件扫描属性注入bean对象创建定义相关属性，在属性上面添加注解set注入为成员变量定义set方法，位于set方法之上添加注解构造方法注入形参上注入单构造方法省略注解当类中只有一个构造方法的时候，可以 [label color="red"]省略@Autowired不写[/label] 。但是一个类中既有带参数构造，又有无参数构造(构造方法＞1) [label color="red"]报错[/label] 。@Autowired和@Qualifier通过名称联合注入@Autowired默认使用类型注入，但是当 [label color="red"]一个接口对应两个或者多个实现类[/label] 的时候，就会报错。Dao接口对应多个实现类的对象的时候，Service层调Dao层时使用@Autowired就会找到多个同类型。此时我们需要 [label color="red"]根据名称注入[/label] ，找到具体的某个实现类对象，采取联合注入。@Resource注入Resource注解默认根据名称装配byName，未指定名称时，使用属性名作为name。通过name找不到的话会自动启动通过类型byType装配(下面会有三种方式的诠释)。其余@Resource与@Autowired区别请自行百度父工程中导依赖<dependency> <groupId>jakarta.annotation</groupId> <artifactId>jakarta.annotation-api</artifactId> <version>2.1.1</version> </dependency>指定名称注入使用属性名称保持一致不指定Resource用属性名称(成员变量名)进行匹配根据类型进行匹配全注解开发之前spring中添加了一个组件扫描，现在我们不需要书写spring配置文件,直接都用注解方式开发，不再编写任何spring配置文件。采取 [label color="blue"]配置类[/label] 代替 [label color="orange"]配置文件[/label] ，最终实现全注解开发。@Configuration //表示一个类是配置类 @ComponentScan("com.atguigu.spring6")//扫描包的路径，和配置文件中组件扫描相同 public class SpringConfig { }测试类中原先使用 ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml"); 读取XML配置文件，现在改为 ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class); 加载配置类。我书写这篇文章的初衷就是总结学习的进度，遗忘之际可以拿出来翻看，如有不对的地方还望指正，多多海涵。