动力节点Spring (9-12)

九、Bean的循环依赖问题

9.1 什么是Bean的循环依赖

A对象中有B属性。B对象中有A属性。这就是循环依赖。我依赖你,你也依赖我。
⽐如:丈夫类Husband,妻⼦类Wife。Husband中有Wife的引⽤。Wife中有Husband的引⽤。

9.2 singleton下的set注⼊产⽣的循环依赖 

 Husband类:

//丈夫类
public class Husband {
    private String name;
    private Wife wife;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setWife(Wife wife) {
        this.wife = wife;
    }

    @Override
    public String toString() {
        return "Husband{" +
                "name='" + name + '\'' +
                ", wife=" + wife.getName() +
                '}';
    }
}

Wife类:

//妻子类
public class Wife {
    private String name;
    private Husband husband;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setHusband(Husband husband) {
        this.husband = husband;
    }

    @Override
    public String toString() {
        return "Wife{" +
                "name='" + name + '\'' +
                ", husband=" + husband.getName() +
                '}';
    }
}
spring.xml文件:
    <!--singleton + setter模式下的循环依赖是没有任何问题的。-->
    <!--singleton表示在整个Spring容器当中是单例的,独一无二的对象。-->
    <!--
        在singleton + setter模式下,为什么循环依赖不会出现问题,Spring是如何应对的?
            主要的原因是,在这种模式下Spring对Bean的管理主要分为清晰的两个阶段:
                第一个阶段:在Spring容器加载的时候,实例化Bean,只要其中任意一个Bean实例化之后,马上进行 “曝光”【不等属性赋值就曝光】
                第二个阶段:Bean“曝光”之后,再进行属性的赋值(调用set方法。)。

            核心解决方案是:实例化对象和对象的属性赋值分为两个阶段来完成的。

        注意:只有在scope是singleton的情况下,Bean才会采取提前“曝光”的措施。
    -->
    <bean id="husbandBean" class="com.powernode.spring6.bean.Husband" scope="singleton">
        <property name="name" value="猪猪侠"/>
        <property name="wife" ref="wifeBean"/>
    </bean>

    <bean id="wifeBean" class="com.powernode.spring6.bean.Wife" scope="singleton">
        <property name="name" value="小菲菲"/>
        <property name="husband" ref="husbandBean"/>
    </bean>
    @Test
    public void testCD(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
        Husband husbandBean = applicationContext.getBean("husbandBean", Husband.class);
        Wife wifeBean = applicationContext.getBean("wifeBean", Wife.class);
        System.out.println(husbandBean);
        System.out.println(wifeBean);
//        Husband{name='猪猪侠', wife=小菲菲}
//        Wife{name='小菲菲', husband=猪猪侠}
    }
在singleton + set注⼊的情况下,循环依赖是没有问题的。Spring可以解决这个问题。

9.3 prototype下的set注⼊产⽣的循环依赖

spring.xml文件:

    <!--在prototype + setter模式下的循环依赖,存在问题,会出现异常!-->
    <!-- Bean不会提前曝光,在调用getBean时创建,但是每次调用时又会创建新的对象-->
    <!--BeanCurrentlyInCreationException 当前的Bean正在处于创建中异常。。。-->
    <!-- 注意:当两个bean的scope都是prototype的时候,才会出现异常。如果其中任意一个是singleton的,就不会出现异常。-->
    <bean id="husbandBean" class="com.powernode.spring6.bean.Husband" scope="prototype">
        <property name="name" value="猪猪侠"/>
        <property name="wife" ref="wifeBean"/>
    </bean>

    <bean id="wifeBean" class="com.powernode.spring6.bean.Wife" scope="prototype">
        <property name="name" value="小菲菲"/>
        <property name="husband" ref="husbandBean"/>
    </bean>

执⾏测试程序:发⽣了异常,异常信息如下:

Caused by: org.springframework.beans.factory. BeanCurrentlyInCreationException : ......
当循环依赖的 所有Bean 的scope="prototype"的时候,产⽣的循环依赖,Spring是⽆法解决的,会出现 BeanCurrentlyInCreationException 异常。原因是:

 以上两个Bean,如果其中⼀个是singleton,另⼀个是prototype,是没有问题的。

 <bean id="husbandBean" class="com.powernode.spring6.bean.Husband" scope="prototype">
        <property name="name" value="猪猪侠"/>
        <property name="wife" ref="wifeBean"/>
    </bean>

    <bean id="wifeBean" class="com.powernode.spring6.bean.Wife" scope="singleton">
        <property name="name" value="小菲菲"/>
        <property name="husband" ref="husbandBean"/>
    </bean>
   @Test
    public void testCD(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
        Husband husbandBean = applicationContext.getBean("husbandBean", Husband.class);
        Wife wifeBean = applicationContext.getBean("wifeBean", Wife.class);
        System.out.println(husbandBean);
        System.out.println(wifeBean);
//        Husband{name='猪猪侠', wife=小菲菲}
//        Wife{name='小菲菲', husband=猪猪侠}
    }

9.4 singleton下的构造注⼊产⽣的循环依赖

测试⼀下singleton + 构造注⼊的⽅式下,spring是否能够解决这种循环依赖。
Husband类:
//丈夫类
public class Husband {
    private String name;
    private Wife wife;

    public String getName() {
        return name;
    }

    public Husband(String name, Wife wife) {
        this.name = name;
        this.wife = wife;
    }

    @Override
    public String toString() {
        return "Husband{" +
                "name='" + name + '\'' +
                ", wife=" + wife.getName() +
                '}';
    }
}
Wife类:
//妻子类
public class Wife {
    private String name;
    private Husband husband;

    public String getName() {
        return name;
    }

    public Wife(String name, Husband husband) {
        this.name = name;
        this.husband = husband;
    }

    @Override
    public String toString() {
        return "Wife{" +
                "name='" + name + '\'' +
                ", husband=" + husband.getName() +
                '}';
    }
}
spring2.xml文件:
 <!--构造注入,这种循环依赖有没有问题?-->
    <!--注意:基于构造注入的方式下产生的循环依赖也是无法解决的,所以编写代码时一定要注意。-->
    <bean id="h" class="com.powernode.spring6.bean2.Husband">
        <constructor-arg index="0" value="喜羊羊"></constructor-arg>
        <constructor-arg index="1" ref="w"></constructor-arg>
    </bean>

    <bean id="w" class="com.powernode.spring6.bean2.Wife">
        <constructor-arg index="0" value="美羊羊"></constructor-arg>
        <constructor-arg index="1" ref="h"></constructor-arg>
    </bean>
    @Test
    public void testCD2(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring2.xml");
        Husband h = applicationContext.getBean("h", Husband.class);
        Wife w = applicationContext.getBean("w", Wife.class);
        System.out.println(h);
        System.out.println(w);
        //报错
        //org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'h' defined in class path resource
    }
主要原因是因为通过构造⽅法注⼊导致的:因为构造⽅法注⼊会导致 实例化对象的过程 对象属性赋值的过程 没有分离开,必须在⼀起完成导致的。

9.5 Spring解决循环依赖的机理

Spring为什么可以解决set + singleton模式下循环依赖?
根本的原因在于:这种⽅式可以做到将“实例化Bean”和“给Bean属性赋值”这两个动作分开去完成。
实例化Bean的时候:调⽤⽆参数构造⽅法来完成。 此时可以先不给属性赋值,可以提前将该Bean对象“曝光”给外界。
给Bean属性赋值的时候:调⽤setter⽅法来完成。
两个步骤是完全可以分离开去完成的,并且这两步不要求在同⼀个时间点上完成。
也就是说,Bean都是单例的,我们可以先把所有的单例Bean实例化出来,放到⼀个集合当中(我们可以 称之为缓存),所有的单例Bean全部实例化完成之后,以后我们再慢慢的调⽤setter⽅法给属性赋值。
这样就解决了循环依赖的问题。
那么在Spring框架底层源码级别上是如何实现的呢?请看:

在以上类中包含三个重要的属性:
Cache of singleton objects: bean name to bean instance. 单例对象的缓存:key存储bean名称,
value存储Bean对象【⼀级缓存】
Cache of early singleton objects: bean name to bean instance. 早期单例对象的缓存:key存储bean名称,value存储早期的Bean对象【⼆级缓存】
Cache of singleton factories: bean name to ObjectFactory. 单例⼯⼚缓存:key存储bean名称,
value存储该Bean对应的ObjectFactory对象【三级缓存】
这三个缓存其实本质上是三个Map集合。
我们再来看,在该类中有这样⼀个⽅法addSingletonFactory(),这个⽅法的作⽤是:将创建Bean对象的 ObjectFactory对象提前曝光。

再分析下⾯的源码:

从源码中可以看到,spring会先从⼀级缓存中获取Bean,如果获取不到,则从⼆级缓存中获取Bean,如
果⼆级缓存还是获取不到,则从三级缓存中获取之前曝光的ObjectFactory对象,通过ObjectFactory对
象获取Bean实例,这样就解决了循环依赖的问题。
总结:
Spring只能解决setter⽅法注⼊的单例bean之间的循环依赖。ClassA依赖ClassB,ClassB⼜依赖
ClassA,形成依赖闭环。Spring在创建ClassA对象后,不需要等给属性赋值,直接将其曝光到bean缓存当中。在解析ClassA的属性时,⼜发现依赖于ClassB,再次去获取ClassB,当解析ClassB的属性时,⼜发现需要ClassA的属性,但此时的ClassA已经被提前曝光加⼊了正在创建的bean的缓存中,则⽆需创建新的的ClassA的实例,直接从缓存中获取即可。从⽽解决循环依赖问题。

⼗、回顾反射机制

10.1 分析⽅法四要素

我们先来看⼀下,不使⽤反射机制调⽤⼀个⽅法需要⼏个要素的参与。
SomeService类:
public class SomeService {

    public void doSome(){
        System.out.println("public void doSom()执行!");
    }

    public String doSome(String s){
        System.out.println("public String doSome(String s)执行!");
        return s;
    }

    public String doSome(String s, int i){
        System.out.println("public String doSome(String s,int i)执行!");
        return s+i;
    }
}

不使用反射机制调用方法,使用平常的方法调用:

Test类:

public class Test {
    public static void main(String[] args) {
        //不使用反射机制调用方法

        /*
         * 分析:调用一个方法,当中含有几个要素?四要素。
         * 第一要素:调用哪个对象
         * 第二要素:调用哪个方法
         * 第三要素:调用方法的时候传什么参数
         * 第四要素:方法执行结束之后的返回结果
         *
         * 调用哪个对象的哪个方法,传什么参数,返回什么值。
         *
         * 即使是使用反射机制调用方法,也同样需要具备这四个要素。
         */

        SomeService someService = new SomeService();
        someService.doSome();
        String s1 = someService.doSome("你好!");
        System.out.println(s1);
        String s2 = someService.doSome("认识第几天了:", 1);
        System.out.println(s2);
    }
}

使用反射机制调用方法:

Test2类:

public class Test2 {
    public static void main(String[] args) throws Exception{
        //使用反射机制怎么调用方法
        //获取类
        Class<?> clazz = Class.forName("com.powernode.reflect.SomeService");
        //获取方法
        Method doSomeMethod = clazz.getDeclaredMethod("doSome", String.class, int.class);
        //调用方法
        //四要素:调用哪个对象,哪个方法,传什么参数,返回什么值。
        //obj 要素:哪个参数
        //doSomeMethod 要素:哪个方法
        //"沸羊羊",36 要素;传什么参数
        //retValue 要素:返回什么值
        Object obj = clazz.newInstance();
        Object retValue = doSomeMethod.invoke(obj, "沸羊羊", 36);
        System.out.println(retValue);
//        public String doSome(String s,int i)执行!
//        沸羊羊36


    }
}

10.2 需求

需求:
    假设你现在已知以下信息:
        1. 有这样一个类,类名叫做:com.powernode.reflect.User
        2. 这个类符合javabean规范。属性私有化,对外提供公开的setter和getter方法。
        3. 你还知道这个类当中有一个属性,属性的名字叫做 age
        4. 并且你还知道age属性的类型是int类型。
    请使用反射机制调用set方法,给User对象的age属性赋值。

User类:

public class User {
    private String name;
    private int age;

    public User(){

    }

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }
    //后面跟get,set,toString方法
}

Test4类:

public class Test4 {
    public static void main(String[] args) throws Exception {
         /*
        需求:
            假设你现在已知以下信息:
                1. 有这样一个类,类名叫做:com.powernode.reflect.User
                2. 这个类符合javabean规范。属性私有化,对外提供公开的setter和getter方法。
                3. 你还知道这个类当中有一个属性,属性的名字叫做 age
                4. 并且你还知道age属性的类型是int类型。
            请使用反射机制调用set方法,给User对象的age属性赋值。
         */
        String className="com.powernode.reflect.User";
        String propertyName="age";

        //通过反射机制嗲用setAge(int)方法
        //获取类
        Class<?> clazz = Class.forName(className);
        //获取方法名
        String setMethodName = "set"+propertyName.toUpperCase().charAt(0)+propertyName.substring(1);//setAge

        //根据属性名,获取属性类型
        //Field field = clazz.getDeclaredField(propertyName);

        //获取方法
//        Method setMethod = clazz.getDeclaredMethod(setMethodName, field.getType());
        Method setMethod = clazz.getDeclaredMethod(setMethodName, int.class);

        Object obj = clazz.newInstance();
        //调用方法
        setMethod.invoke(obj,666);
        System.out.println(obj);//User{name='null', age=666}
    }
}

⼗⼀、⼿写Spring框架

Spring IoC容器的实现原理:⼯⼚模式 + 解析XML + 反射机制。
我们给⾃⼰的框架起名为:myspring
第⼀步:创建模块myspring
打包⽅式采⽤jar,并且引⼊dom 4 j和jaxen的依赖,因为要使⽤它解析XML⽂件,还有junit依赖。
pom.xml:
 <packaging>jar</packaging>

    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <!--dom4j是一个能够解析xml文件的java组件-->
        <dependency>
            <groupId>org.dom4j</groupId>
            <artifactId>dom4j</artifactId>
            <version>2.1.3</version>
        </dependency>
        <dependency>
            <groupId>jaxen</groupId>
            <artifactId>jaxen</artifactId>
            <version>1.2.0</version>
        </dependency>

        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-slf4j2-impl</artifactId>
            <version>2.20.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-core</artifactId>
            <version>2.20.0</version>
        </dependency>
    </dependencies>
第⼆步:准备好我们要管理的Bean
准备好我们要管理的Bean( 这些Bean在将来开发完框架之后是要删除的
注意包名,不要⽤org.myspringframework包,因为这些Bean不是框架内置的。是将来使⽤我们框架的程序员提供的。
User类:
public class User {
    private String name;
    private int age;

    public void setName(String name) {
        this.name = name;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
UserDao类:
public class UserDao {
    public void insert(){
        System.out.println("mysql数据库正在保存用户信息.");
    }
}
UserService类:
public class UserService {
    private UserDao userDao;

    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }

    public void save(){
        userDao.insert();
    }
}

第三步:准备myspring.xml配置⽂件
将来在框架开发完毕之后,这个⽂件也是要删除的。因为这个配置⽂件的提供者应该是使⽤这个框架的程序员。
⽂件名随意,我们这⾥叫做:myspring.xml
⽂件放在类路径当中即可,我们这⾥把⽂件放到类的根路径下。

myspring.xml文件:

<?xml version="1.0" encoding="UTF-8"?>

<!--这个配置文件也是使用myspring框架开发人员提供的-->
<beans>
    <bean id="user" class="com.powernode.myspring.bean.User">
        <property name="name" value="猪猪侠"/>
        <property name="age" value="36"/>
    </bean>

    <bean id="userDaoBean" class="com.powernode.myspring.bean.UserDao"></bean>

    <bean id="userService" class="com.powernode.myspring.bean.UserService">
        <property name="userDao" ref="userDaoBean"/>
    </bean>
</beans>

第四步:编写ApplicationContext接⼝
ApplicationContext接⼝中提供⼀个getBean()⽅法,通过该⽅法可以获取Bean对象。
注意包名:这个接⼝就是myspring框架中的⼀员了
//MySpring框架应用上下文接口
public interface ApplicationContext{
    //根据bean的名称获取对应的bean对象
    Object getBean(String beanName);
}

第五步:编写ClassPathXmlApplicationContext
ClassPathXmlApplicationContext是ApplicationContext接⼝的实现类。该类从类路径当中加载
myspring.xml配置⽂件。
刚开始的样子:
package org.myspringframework.core;

public class ClassPathXmlApplicationContext implements ApplicationContext{
 @Override
 public Object getBean(String beanName) {
 return null;
 }
}
最后的样子:
package org.springframework.core;

import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.Node;
import org.dom4j.io.SAXReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class ClassPathXmlApplicationContext implements ApplicationContext{
    private static final Logger logger = LoggerFactory.getLogger(ClassPathXmlApplicationContext.class);

    private Map<String,Object> singletonObjects = new HashMap<>();

    //构造方法,解析myspring的配置文件,然后初始化所有的bean对象
    //configLocation:spring配置文件的路径。注意:使用ClassPathXmlApplicationContext,配置文件应当放到类路径下
    public ClassPathXmlApplicationContext(String configLocation){
        try{
            //解析myspring.xml文件,然后实例化Bean,将Bean存放到singletonObjects集合当中
            //这是dom4j解析xml文件的核心对象
            SAXReader saxReader = new SAXReader();
            //获取一个输入流,指向配置文件
            InputStream in = ClassLoader.getSystemClassLoader().getResourceAsStream(configLocation);
            //读文件
            Document document = saxReader.read(in);
            //获取所有的bean标签
            List<Node> nodes = document.selectNodes("//bean");
            //遍历bean标签
            nodes.forEach(node -> {
                try{
                    //System.out.println(node);
                    //向下转型,原因是Element里面的方法更加丰富,我们使用更加方便
                    Element beanElt =(Element) node;
                    //获取id属性
                    String id = beanElt.attributeValue("id");
                    //获取class属性
                    String className = beanElt.attributeValue("class");
                    logger.info("beanName="+id);
                    logger.info("beanClassName="+className);
                    //通过反射机制创建对象,将其放到Map集合中,提前曝光
                    //获取Class
                    Class<?> aClass = Class.forName(className);
                    //获取无参数构造方法
                    Constructor<?> defaultCon = aClass.getDeclaredConstructor();
                    //调用无参数构造方法实例化bean
                    Object bean= defaultCon.newInstance();
                    //将Bean曝光,加入Map集合
                    singletonObjects.put(id,bean);
                    //记录日志
                    logger.info(singletonObjects.toString());

                }catch (Exception e){
                    e.printStackTrace();
                }
            });

            //再次重新把所有的bean标签遍历一次,这一次主要是给对象的属性赋值
            nodes.forEach(node -> {
                try{
                    Element beanELT = (Element) node;
                    //获取id
                    String id = beanELT.attributeValue("id");
                    //获取className
                    String className = beanELT.attributeValue("class");
                    //获取class
                    Class<?> aClass = Class.forName(className);
                    //获取该bean标签下的所有的属性property标签
                    List<Element> propertys = beanELT.elements("property");
                    //遍历所有属性标签
                    propertys.forEach(property->{
                        try{
                            //获取属性名
                            String propertyName = property.attributeValue("name");
                            logger.info("属性名="+propertyName);
                            //获取属性类型
                            Field field = aClass.getDeclaredField(propertyName);
                            //获取set方法名
                            String setMethodName ="set"+propertyName.toUpperCase().charAt(0)+propertyName.substring(1);
                            //获取set方法
                            Method setMethod = aClass.getDeclaredMethod(setMethodName, field.getType());
                            //获取具体的值
                            String value = property.attributeValue("value");//"30"
                            Object actualValue = null; // 真值
                            String ref = property.attributeValue("ref");
                            if(value!=null){
                                //说明这个值是简单类型
                                //调用set方法(set方法没有返回值)
                                //我们myspring框架声明以下:我们只支持这些类型为简单类型
                                //byte short int long float double boolean char
                                //Byte Short Integer Long Float Double Boolean Character
                                //String
                                //获取属性类型名
                                String propertyTypeSimpleName = field.getType().getSimpleName();
                                switch (propertyTypeSimpleName){
                                    case "byte":
                                        actualValue = Byte.parseByte(value);
                                        break;
                                    case "short":
                                        actualValue = Short.parseShort(value);
                                        break;
                                    case "int":
                                        actualValue = Integer.parseInt(value);
                                        break;
                                    case "long":
                                        actualValue = Long.parseLong(value);
                                        break;
                                    case "float":
                                        actualValue = Float.parseFloat(value);
                                        break;
                                    case "double":
                                        actualValue = Double.parseDouble(value);
                                        break;
                                    case "boolean":
                                        actualValue = Boolean.parseBoolean(value);
                                        break;
                                    case "char":
                                        actualValue = value.charAt(0);
                                        break;
                                    case "Byte":
                                        actualValue = Byte.valueOf(value);
                                        break;
                                    case "Short":
                                        actualValue = Short.valueOf(value);
                                        break;
                                    case "Integer":
                                        actualValue = Integer.valueOf(value);
                                        break;
                                    case "Long":
                                        actualValue = Long.valueOf(value);
                                        break;
                                    case "Float":
                                        actualValue = Float.valueOf(value);
                                        break;
                                    case "Double":
                                        actualValue = Double.valueOf(value);
                                        break;
                                    case "Boolean":
                                        actualValue = Boolean.valueOf(value);
                                        break;
                                    case "Character":
                                        actualValue = Character.valueOf(value.charAt(0));
                                        break;
                                    case "String":
                                        actualValue = value;
                                }
                                setMethod.invoke(singletonObjects.get(id), actualValue);
                            }
                            if(ref!=null){
                                //说明这个值不是简单类型
                                //调用set方法(set方法没有返回值)
                                setMethod.invoke(singletonObjects.get(id), singletonObjects.get(ref));
                            }

                        }catch (Exception e){
                            e.printStackTrace();
                        }
                    });

                }catch (Exception e){
                    e.printStackTrace();
                }
            });

        }catch (Exception e){
            e.printStackTrace();
        }
    }

    @Override
    public Object getBean(String beanName) {
        return singletonObjects.get(beanName);
    }
}
第六步:打包发布
第七步:站在程序员⻆度使⽤myspring框架

⼗⼆、Spring IoC注解式开发

12.1 回顾注解

注解的存在主要是为了简化XML的配置。 Spring 6 倡导全注解开发
我们来回顾⼀下:
      ●第⼀:注解怎么定义,注解中的属性怎么定义?
      ●第⼆:注解怎么使⽤?
      ●第三:通过反射机制怎么读取注解?
注解怎么定义,注解中的属性怎么定义?
Component.java:
//自定义注解
//标注注解的注解,叫元注解 @Target注解用来修饰@Component可以出现的位置
// 以下表示@Component注解可以出现在类上,属性上
//@Target(value = {ElementType.TYPE})
// 使用某个注解的时候,如果注解的属性名是value的话,value可以省略。
//@Target({ElementType.TYPE})
// 使用某个注解的时候,如果注解的属性值是数组,并且数组中只有一个元素,大括号可以省略。
// @Retention 也是一个元注解。用来标注@Component注解最终保留在class文件当中,并且可以被反射机制读取。
@Target(value = {ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Component {

    //定义数组的属性
    //String 是属性类型
    //value是属性名
    String value();

    // 其他的属性
    // 属性类型String
    // 属性名是name
    //String name();

    // 数组属性
    // 属性类型是:String[]
    // 属性名:names
    //String[] names();

    //int[] ages();

    //int age();
}
以上是⾃定义了⼀个注解:Component
该注解上⾯修饰的注解包括:Target注解和Retention注解,这两个注解被称为元注解。
Target注解⽤来设置Component注解可以出现的位置,以上代表表示Component注解只能⽤在类和接⼝上。
Retention注解⽤来设置Component注解的保持性策略,以上代表Component注解可以被反射机制读取。
String value(); 是Component注解中的⼀个属性。该属性类型String,属性名是value。
注解怎么使⽤?
User类:
//@Component(属性名 = 属性值, 属性名 = 属性值, 属性名 = 属性值....)
//@Component(value = "userBean")
// 如果属性名是value,value可以省略。
@Component("UserBean")
public class User {
    // 编译器报错,不能出现在这里。因为我们只让它在类上使用了
    //@Component(value = "test")
    //private String name;
}
⽤法简单,语法格式:@注解类型名(属性名=属性值, 属性名=属性值, 属性名=属性值......)
userBean为什么使⽤双引号括起来,因为value属性是String类型,字符串。
另外如果属性名是value,则在使⽤的时候可以省略属性名。
通过反射机制怎么读取注解?
ReflectAnnotationTest1类:
public class ReflectAnnotationTest1 {
    public static void main(String[] args) throws Exception {
        //通过反射机制怎么读取注解
        //获取类
        Class<?> aClass = Class.forName("com.powernode.bean.User");
        //判断类上面有没有注解
        if(aClass.isAnnotationPresent(Component.class)){
            //获取类上的注解
            Component annotation = aClass.getAnnotation(Component.class);
            //访问注解属性
            System.out.println(annotation.value());//UserBean
        }
    }
}

注解扫描原理

假设我们现在只知道包名:com.powernode.bean。⾄于这个包下有多少个Bean我们不知道。哪些Bean 上有注解,哪些Bean上没有注解,这些我们都不知道,如何通过程序全⾃动化判断。
接下来,我们来写⼀段程序,当Bean类上有Component注解时,则实例化Bean对象,如果没有,则不实例化对象。 我们准备两个Bean,⼀个上⾯有注解,⼀个上⾯没有注解。
User类:
//@Component(属性名 = 属性值, 属性名 = 属性值, 属性名 = 属性值....)
//@Component(value = "userBean")
// 如果属性名是value,value可以省略。
@Component("UserBean")
public class User {
    // 编译器报错,不能出现在这里。因为我们只让它在类上使用了
    //@Component(value = "test")
    //private String name;
}
Vip类:
public class Vip {
}
Order类:
@Component("OrderBean")
public class Order {
}
ComponentScan类:
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/powernode/bean
        //com是在类的根路径下的一个目录
        URL url = ClassLoader.getSystemClassLoader().getResource(packagePath);
        String path = url.getPath();
        //由于在获取文件路径时可能会有中文或其他字符,所以我们需要异常处理,并且将其转换成UTF-8
        try {
            path = URLDecoder.decode(path, "UTF-8");
            System.out.println(path);///D:/codes/IntelliJ IDEA/spring6/review-annotation/target/classes/com/powernode/bean
        } catch (Exception e) {
            e.printStackTrace();
        }
        //获取一个绝对路径下的所有文件
        File file = new File(path);
        File[] files = file.listFiles();
        Arrays.stream(files).forEach(f -> {
            try {
                //System.out.println(f.getName());//Order.class User.class Vip.class
                //通过.进行拆分
                //System.out.println(f.getName().split("\\.")[0]);//Order  User  Vip
                //获取包名
                String className = packageName + "." + f.getName().split("\\.")[0];
                //System.out.println(className);//com.powernode.bean.Order 三个如此

                //通过反射机制解析注解
                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);//{UserBean=com.powernode.bean.User@2e5d6d97, OrderBean=com.powernode.bean.Order@238e0d81}

    }
}

12.2 声明Bean的注解

负责声明Bean的注解,常⻅的包括四个:
      ●@Component
      ●@Controller
      ●@Service
      ●@Repository
@Component注解:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Indexed
public @interface Component {
    String value() default "";
}
@Controller注解:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Controller {
    @AliasFor(
        annotation = Component.class
    )
    String value() default "";
}

@Service注解:

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

@Repository注解:

@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的名字。

12.3 Spring注解的使⽤

如何使⽤以上的注解呢?
      ● 第⼀步:加⼊aop的依赖
      ● 第⼆步:在配置⽂件中添加context命名空间
      ● 第三步:在配置⽂件中指定扫描的包
      ● 第四步:在Bean类上使⽤注解
第⼀步:加⼊aop的依赖
我们可以看到当加⼊spring-context依赖之后,会关联加⼊aop的依赖。所以这⼀步不⽤做。
第⼆步:在配置⽂件中添加context命名空间
<?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">

</beans>
第三步:在配置⽂件中指定要扫描的包
<?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">

    <!--给spring框架指定要扫描哪些包中的类-->
    <context:component-scan base-package="com.powernode.spring6.bean"/>
</beans>

第四步:在Bean类上使⽤注解
@Component(value = "userBean")
public class User {
}
编写测试程序:
    @Test
    public void testBeanComponent(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
        User userBean = applicationContext.getBean("userBean", User.class);
        System.out.println(userBean);
 }
如果注解的属性名是value,那么value是可以省略的。
如果把value属性彻底去掉,spring会被Bean⾃动取名吗?会的。并且默认名字的规律是:Bean类名⾸字⺟⼩写即可。
Order类:
@Service
public class Order {
}
Student类:
@Repository
public class Student {
}
    @Test
    public void testBeanComponent(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
        User userBean = applicationContext.getBean("userBean", User.class);
        System.out.println(userBean);
        Vip vipBean = applicationContext.getBean("vipBean", Vip.class);
        System.out.println(vipBean);
        Student studentBean = applicationContext.getBean("student", Student.class);
        System.out.println(studentBean);
        Object orderBean = applicationContext.getBean("order", Order.class);
        System.out.println(orderBean);
//        com.powernode.spring6.bean.User@759d26fb
//        com.powernode.spring6.bean.Vip@3c73951
//        com.powernode.spring6.bean.Student@3d5c822d
//        com.powernode.spring6.bean.Order@6f46426d
    }
如果是多个包怎么办?有两种解决⽅案:
      ●第⼀种:在配置⽂件中指定多个包,⽤逗号隔开。
      ●第⼆种:指定多个包的共同⽗包。
    <!--给spring框架指定要扫描哪些包中的类-->
    <!--    <context:component-scan base-package="com.powernode.spring6.bean"/>-->

    <!--多个包使用逗号隔开-->
    <context:component-scan base-package="com.powernode.spring6.bean,com.powernode.spring6.dao"/>

    <!--多个包,也可以指定这多个包的共同父包,但是这肯定要牺牲一部分效率-->
    <context:component-scan base-package="com.powernode.spring6"/>

12.4 选择性实例化Bean

假设在某个包下有很多Bean,有的Bean上标注了Component,有的标注了Controller,有的标注了 Service,有的标注了Repository,现在由于某种特殊业务的需要,只允许其中所有的Controller参与 Bean管理,其他的都不实例化。这应该怎么办呢?
A类:
@Component
public class A {
    public A(){
        System.out.println("A 的无参数构造方法!");
    }
}

@Controller
class B{
    public B(){
        System.out.println("B 的无参数构造方法!");
    }
}

@Repository
class C{
    public C(){
        System.out.println("C 的无参数构造方法!");
    }
}

@Service
class D{
    public D(){
        System.out.println("D 的无参数构造方法!");
    }
}

@Controller
class E{
    public E(){
        System.out.println("E 的无参数构造方法!");
    }
}

spring-choose.xml文件:

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


    <!--
       第二种解决方案:
           use-default-filters="true"
           如果这个属性的值是true,表示com.powernode.spring6.bean2下的所有的带有声明Bean的注解全部生效。

           use-default-filters="true" 默认值就是true,不用写。
   -->
    <context:component-scan base-package="com.powernode.spring6.bean2">
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>
use-default-filters="true" 表示:使⽤spring默认的规则,只要有Component、Controller、Service、 Repository中的任意⼀个注解标注,则进⾏实例化。
use-default-filters="false" 表示:不再spring默认实例化规则,即使有Component、Controller、
Service、Repository这些注解标注,也不再实例化。
<context:include-filter type="annotation"
expression="org.springframework.stereotype.Controller"/> 表示只有Controller进⾏实例化。
也可以将use-default-filters设置为true(不写就是true),并且采⽤exclude-filter⽅式排出哪些注解标注的Bean不参与实例化。

12.5 负责注⼊的注解

@Component @Controller @Service @Repository 这四个注解是⽤来声明Bean的,声明后这些Bean将被实例化。接下来我们看⼀下,如何给Bean的属性赋值。给Bean属性赋值需要⽤到这些注解:
      ●@Value
      ●@Autowired
      ●@Qualifier
      ●@Resource

12.5.1 @Value

当属性的类型是简单类型时,可以使⽤@Value注解进⾏注⼊。
MyDataSource类:
@Component
public class MyDataSource implements DataSource {
    //使用Value注解,可以用在属性上,可以不提供set方法
    @Value(value = "com.mysql.cj.jdbc.Driver")
    private String driver;
    @Value("jdbc:mysql://localhost:3306/spring6")
    private String url;
    @Value("root")
    private String username;
    @Value("123456")
    private String password;


    @Override
    public String toString() {
        return "MyDataSource{" +
                "driver='" + driver + '\'' +
                ", url='" + url + '\'' +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                '}';
    }
}
Product类:
@Component
public class Product {
//    @Value("猪猪侠")
//    private String name;
//    @Value("36")
//    private int age;


    private String name;
    private int age;

    public Product(@Value("懒羊羊") String name, @Value("16") int age) {
        this.name = name;
        this.age = age;
    }


    //@Value注解也可以使用在方法上
/*    @Value("隔壁老王")
    public void setName(String name) {
        this.name = name;
    }

    @Value("45")
    public void setAge(int age) {
        this.age = age;
    }*/

    @Override
    public String toString() {
        return "Product{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

spring-di-annotation.xml文件:

    <context:component-scan base-package="com.powernode.spring6.bean3"></context:component-scan>
@Test
    public void testDIByAnnotation(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-di-annotation.xml");
        MyDataSource myDataSource = applicationContext.getBean("myDataSource", MyDataSource.class);
        System.out.println(myDataSource);
        //MyDataSource{driver='com.mysql.cj.jdbc.Driver', url='jdbc:mysql://localhost:3306/spring6', username='root', password='123456'}
        Product product = applicationContext.getBean("product", Product.class);
        System.out.println(product);
        //Product{name='猪猪侠', age=36}
        //Product{name='隔壁老王', age=45}
        //Product{name='懒羊羊', age=16}
    }
通过以上代码可以发现,我们并没有给属性提供setter⽅法,但仍然可以完成属性赋值。
通过测试得知:@Value注解可以出现在属性上、setter⽅法上、以及构造⽅法的形参上。可⻅Spring给我们提供了多样化的注⼊。

12.5.2 @Autowired与@Qualifier

@Autowired注解可以⽤来注⼊ ⾮简单类型 。被翻译为:⾃动连线的,或者⾃动装配。
单独使⽤@Autowired注解, 默认根据类型装配 。【默认是byType】
看源码:
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETE
R, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
boolean required() default true;
}
源码中有两处需要注意:
●第⼀处:该注解可以标注在哪⾥?
        ○构造⽅法上
        ○⽅法上
        ○形参上
        ○属性上
        ○注解上
●第⼆处:该注解有⼀个required属性,默认值是true,表示在注⼊的时候要求被注⼊的Bean必须是存在的,如果不存在则报错。如果required属性设置为false,表示注⼊的Bean存在或者不存在都没关系,存在的话就注⼊,不存在的话,也不报错。
OrderDao类:
public interface OrderDao {
    void insert();
}
OrderDaoImplForMySql类:
@Repository("orderDaoImplForMySQL")
public class OrderDaoImplForMySQL implements OrderDao {
    @Override
    public void insert() {
        System.out.println("MySql数据库正在保存订单信息!");
    }
}
OrderService类:
@Service("orderService")
public class OrderService {
    //@Autowired 注解使用的时候,不需要指定任何属性,直接使用这个注解即可
    //这个注解的作用是根据类型byType进行自动装配
    @Autowired
    private OrderDao orderDao;
    public void generate(){
        orderDao.insert();
    }
}

spring-autowired.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/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:component-scan base-package="org.powernode"></context:component-scan>
</beans>
    @Test
    public void testAutowired(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-autowired.xml");
        OrderService orderService = applicationContext.getBean("orderService", OrderService.class);
        orderService.generate();
        //MySql数据库正在保存订单信息!
        //Oracle数据库正在保存订单信息!
    }

@Autowired和@Qualifier联合使用

当有多个实现类时,不知道要实例化哪一个类,此时就要使用@Qualifier来指定使用byName来确定实例化哪个类

OrderDaoImplForOracle类:

@Repository("orderDaoImplForOracle")
public class OrderDaoImplForOracle implements OrderDao {

    @Override
    public void insert() {
        System.out.println("Oracle数据库正在保存订单信息!");
    }
}

OrderService类:

@Service("orderService")
public class OrderService {
    //@Autowired 注解使用的时候,不需要指定任何属性,直接使用这个注解即可
    //这个注解的作用是根据类型byType进行自动装配
//    @Autowired
//    private OrderDao orderDao;
//    public void generate(){
//        orderDao.insert();
//    }

    @Autowired
//    @Qualifier("orderDaoImplForMySQL")
    @Qualifier("orderDaoImplForOracle")
    private OrderDao orderDao;
    public void generate(){
        orderDao.insert();
    }
}

以上构造⽅法和setter⽅法都没有提供,经过测试,仍然可以注⼊成功。 接下来,
再来测试⼀下@Autowired注解出现在setter⽅法上:
我们再来看看能不能出现在构造⽅法上:
再来看看,这个注解能不能只标注在构造⽅法的形参上: 
@Service("orderService")
public class OrderService {
    //@Autowired 注解使用的时候,不需要指定任何属性,直接使用这个注解即可
    //这个注解的作用是根据类型byType进行自动装配
//    @Autowired
//    private OrderDao orderDao;
//    public void generate(){
//        orderDao.insert();
//    }

//    @Autowired
    @Qualifier("orderDaoImplForMySQL")
//    @Qualifier("orderDaoImplForOracle")

    private OrderDao orderDao;

    //可以出现在set方法上
//    @Autowired
//    public void setOrderDao(OrderDao orderDao) {
//        this.orderDao = orderDao;
//    }

    //可以出现在构造方法上
//@Autowired
//    public OrderService(OrderDao orderDao) {
//        this.orderDao = orderDao;
//    }

    //可以出现在形参上
//    public OrderService(@Autowired OrderDao orderDao) {
//        this.orderDao = orderDao;
//    }


    public void generate() {
        orderDao.insert();
    }
}

总结:
   ●@Autowired注解可以出现在:属性上、构造⽅法上、构造⽅法的参数上、setter⽅法上。
   ●当带参数的构造⽅法只有⼀个,@Autowired注解可以省略。
   ●@Autowired注解默认根据类型注⼊。如果要根据名称注⼊的话,需要配合@Qualifier注解⼀起使⽤。

12.5.3 @Resource

@Resource注解也可以完成⾮简单类型注⼊。那它和@Autowired注解有什么区别?
      ●@Resource注解是JDK扩展包中的,也就是说属于JDK的⼀部分。所以该注解是标准注解,更加具有通⽤性。(JSR-250 标准中制定的注解类型。JSR是Java规范提案。)
      ●@Autowired注解是Spring框架⾃⼰的。
      ●@Resource注解默认根据名称装配byName,未指定name时,使⽤属性名作为name。通过 name找不到的话会⾃动启动通过类型byType装配。
      ●@Autowired注解默认根据类型装配byType,如果想根据名称装配,需要配合@Qualifier注解⼀起⽤。
      ●@Resource注解⽤在属性上、setter⽅法上。
      ●@Autowired注解⽤在属性上、setter⽅法上、构造⽅法上、构造⽅法参数上。
@Resource注解属于JDK扩展包,所以不在JDK当中,需要额外引⼊以下依赖:【 如果是JDK 8 的话不需要额外引⼊依赖。⾼于JDK11 或低于JDK 8 需要引⼊以下依赖。
如果你是Spring 6 +版本请使⽤这个依赖:
<dependency>
 <groupId>jakarta.annotation</groupId>
 <artifactId>jakarta.annotation-api</artifactId>
 <version>2.1.1</version>
</dependency>
⼀定要注意: 如果你⽤Spring 6 ,要知道Spring 6 不再⽀持JavaEE,它⽀持的是JakartaEE 9 。(Oracle 把JavaEE贡献给Apache了,Apache把JavaEE的名字改成JakartaEE了,⼤家之前所接触的所有的javax.* 包名统⼀修改为 jakarta.*包名了。)
如果你是spring 5 -版本请使⽤这个依赖:
<dependency>
 <groupId>javax.annotation</groupId>
 <artifactId>javax.annotation-api</artifactId>
 <version>1.3.2</version>
</dependency>

StudentDao类:

public interface StudentDao {
    void deleteById();
}

StudentDaoImplForMySql类:

给StudentDaoImplForMySql起名字studentDaoImplForMySql

@Repository("studentDaoImplForMySql")
public class StudentDaoImplForMySql implements StudentDao {
    @Override
    public void deleteById() {
        System.out.println("mysql数据库正在删除学生信息!");
    }
}

StudnetServie类:

@Service("studentService")
public class StudentService {
    @Resource(name = "studentDaoImplForMySql")
    private StudentDao studentDao;


    public void deleteStudent(){
        studentDao.deleteById();
    }
}

spring-resource.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/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:component-scan base-package="cn.powernode"></context:component-scan>
</beans>
    @Test
    public void testResource(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-resource.xml");
        StudentService studentService = applicationContext.getBean("studentService", StudentService.class);
        studentService.deleteStudent();//mysql数据库正在删除学生信息!
    }
我们把StudentDaoImplForMySql 的名字studentDaoImplForMySql 修改为studentDao,让这个Bean的名字和StudentService类中的StudentDao属性名⼀致:
@Repository("studentDao")
public class StudentDaoImplForMySql implements StudentDao {
    @Override
    public void deleteById() {
        System.out.println("mysql数据库正在删除学生信息!");
    }
}
@Service("studentService")
public class StudentService {
    @Resource
    private StudentDao studentDao;


    public void deleteStudent(){
        studentDao.deleteById();
    }
}
通过测试得知,当@Resource注解使⽤时没有指定name的时候,还是根据name进⾏查找,这个name是 属性名。
接下来把StudentDaoImplForMySql 类的名字修改⼀下,此时匹配不到属性名,按照byType注入,某种类型的Bean只能有⼀个。

12.6 全注解式开发

所谓的全注解开发就是不再使⽤spring配置⽂件了。写⼀个配置类来代替配置⽂件。
Spring6Config类:
//编写一个类代替Spring框架的配置文件
@Configuration
@ComponentScan({"cn.powernode.dao","cn.powernode.Service"})
public class Spring6Config {
}
    @Test
    public void testNoXML(){
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Spring6Config.class);
        StudentService studentService = context.getBean("studentService", StudentService.class);
        studentService.deleteStudent();//mysql数据库正在删除学生信息!
    }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值