Spring6全面详解-自溜用

Spring

Spring是一个框架,这个框架是用来帮我们解决一些问题的.其中就有IOC和AOP,分别是控制反转和面向切面编程

IOC: Inversion of Control,IOC容器放对象,使用map集合存放. Spring通过IOC容器进行管理所有JAVA对象的实例化和初始化,控制对象和对象之间的依赖关系,它管理的JAVA对象称为SpringBean,和new出来的JAVA对象没有区别.IOC容器可以将多个耦合起来的对象进行解耦合,再把系统中所有的对象粘在一起,这样就无需考虑对象之间的依赖关系了.IOC是通过BeanFactory工厂加反射技术实现的.

AOP: Aspect Oriented Programming, AOP是OOP的延续,利用AOP可以对业务逻辑的各个部分进行隔离,从而是的业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。编程中,对象与对象之间,方法与方法之间,模块与模块之间都是一个个切面。

Spring快速入门

  1. 创建一个空的maven项目

  2. 在pom中引入Spring的核心依赖 -> spring-context. context表示上下文关系.

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>6.1.8</version>
        </dependency>
  1. 在src>main>resources>目录下创建一个*SpringConfig.xml文件,名字可以自定义,这里我起名Bean.xml

  2. 创建一个简单的User类进行后续测试

public class User {
    public void add(){
        System.out.println("addUser...");
    }
}
  1. 在Bean.xml也就是我们SpringConfig文件中配置Bean,这里的id一般是类名首字母小写,class是我们类的具体路径

<?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 -->
    <bean id="user" class="com.otnios.User"></bean>
</beans>
  1. 通过读取xml中的配置Bean直接创建对象,不用手动去new对象,可以正常调用执行我们的方法BeanFactory不对外使用,所以我们用的ClassPathXmlApplicationContext是实现它的类,我们下面通过这个类读取xml中的配置信息进行创建IOC容器内的对象.

    // 测试User类 通过读取xml文件中的Bean直接创建对象
    @Test
    public void testSpringFirstUser() {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("Bean.xml");
        User user = (User) context.getBean("user");
        System.out.println(user);
        user.add();
    }
  1. 通过反射模拟Spring获取对象的过程

    @Test
    public void testReflectGetObject() throws Exception {
        Class<?> clazz = Class.forName("com.otnios.User");
        // clazz.getDeclaredConstructor() 通过声明的构造.newInstance()得到对象实例,JDK17后的新方法
        User user = (User) clazz.getDeclaredConstructor().newInstance();
        user.add();
    }

依赖注入DI

依赖注入实现了IOC控制反转的思想, Spring在对象创建过程中,将对象依赖属性通过配置进行注入

log4j2日志框架的整合

  1. maven导入相关依赖

            <!--        整合log4j2日志框架 引入依赖+xml配置文件-->
            <!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-core -->
            <dependency>
                <groupId>org.apache.logging.log4j</groupId>
                <artifactId>log4j-core</artifactId>
                <version>2.23.1</version>
            </dependency>
            <!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-slf4j2-impl -->
            <dependency>
                <groupId>org.apache.logging.log4j</groupId>
                <artifactId>log4j-slf4j2-impl</artifactId>
                <version>2.23.1</version>
                <scope>test</scope>
            </dependency>

  2. log4j2.xml配置文件,这里日志等级设置为DEBUG,写了三个日志输出,分别是>Console控制台输入,File本地文件保存,RollingFile实现日志文件滚动更新.具体的配置可以百度查看一下,不看也不影响使用.

    <?xml version="1.0" encoding="UTF-8"?>
    <Configuration status="WARN">
        <Loggers>
            <Root level="DEBUG">
                <AppenderRef ref="Console"/>
                <AppenderRef ref="File"/>
                <AppenderRef ref="RollingFile"/>"
            </Root>
        </Loggers>
    ​
        <Appenders>
            <Console name="Console" target="SYSTEM_OUT">
                <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-3level %logger{1024} - %msg%n"/>
            </Console>
    ​
            <File name="File" fileName="E:/IdeaProjects/Spring6_test/spring6_log/test.log" append="false">
                <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
            </File>
    ​
            <RollingFile name="RollingFile" fileName="E:/IdeaProjects/Spring6_test/spring6_log/app.log"
                             filePattern="E:/IdeaProjects/Spring6_test/spring_log/$${date:yyyy-MM}/app-%d{MM-dd-yyyy}-%i.log.gz">
                <PatternLayout pattern="%d{yyyy-MM-dd 'at' HH:mm:ss z} %-5level %class{36} %L %M - %msg%xEx%n"/>
                <SizeBasedTriggeringPolicy size="50MB"/>
                <DefaultRolloverStrategy max="20"/>
            </RollingFile>
        </Appenders>
    </Configuration>

IOC

基于XML管理Bean

Bean管理: 包括对象的创建与Bean对象中属性的赋值(Bean对象之间的关系维护)

获取Bean:
    <!-- 配置bean -->
    <bean id="user" class="com.otnios.User"></bean>
​
    <!-- 根据接口类型获得实现类对象 -->
    <bean id="userDaoImpl" class="com.otnios.dao.UserDaoImpl"></bean>
public class User {
    private String name;
    private String age;
    public void run(){
        System.out.println("User run");
    }
}
public class UserDaoImpl implements UserDao{
    @Override
    public void run() {
        System.out.println("UserDaoImpl run");
    }
}

  1. 根据id

        public void testGetUser() {
            ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("Bean.xml");
            // 根据id获取Bean
            User user1 = (User) context.getBean("user");
            System.out.println("根据id获取Bean:" + user1);
        }

  2. 根据类型: 要求IOC容器中指定类型的Bean有且只能有一个

        public void testGetUser() {
            ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("Bean.xml");
            // 根据类型获取Bean
            User user2 = context.getBean(User.class);
            System.out.println("根据类型获取Bean:" + user2);
        }

  3. 根据id+类型

        public void testGetUser() {
            ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("Bean.xml");
            // 根据id与类型虎丘
            User user3 = context.getBean("user", User.class);
            System.out.println("根据id与类型获取Bean:" + user3);
        }

  4. 如果组件类实现的接口bean唯一的话,根据接口类型获得实现类对象

        public void testGetUserDaoImpl() {
            ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("Bean.xml");
            UserDao userDaoImpl = context.getBean(UserDao.class);
            System.out.println(userDaoImpl);
            userDaoImpl.run();
        }
    // com.otnios.dao.UserDaoImpl@7d42c224
    // UserDaoImpl run

  5. 如果一个接口有多个实现类,这些实现类都配置了bean,bean不唯一则根据接口类型无法获取bean.很简单的道理,你要是有多个实现问我要,我不知道该给你哪一个,是这种情况.

    创建另一个接口的实现类测试.

public class UserDaoImpl2 implements UserDao{
    @Override
    public void run() {
        System.out.println("UserDaoImpl2 run...");
    }
}
public void testGetMoreImpl() {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("Bean.xml");
        UserDao userDaoImpl2 = context.getBean(UserDao.class);
        System.out.println(userDaoImpl2);
        userDaoImpl2.run();
    }
依赖注入-setter注入 property

Bean.xml中在<Bean>标签里面使用<property>标签进行属性的配置

  1. 创建类,定义属性,生成属性的set方法,这里用book类进行测试,这里使用了lombok,大家可以自己引入依赖

  2. 在spring配置文件配置

  3. 执行

    @Data
    public class Book {
        private String bookName;
        private String author;
    }
        <bean id="book" class="com.otnios.di.Book">
            <property name="bookName" value="Spring6"></property>
            <property name="author" value="otto"></property>
        </bean>
        public void testSetBook() {
            ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("Bean.xml");
            Object book = context.getBean("book", Book.class);
            System.out.println(book);
            // Book(bookName=Spring6, author=otto)
        }
依赖注入-构造器方法 constucter-arg
  1. 创建类,定义属性,生成有参构造方法

  2. 进行配置

        <!-- 构造方法注入 -->
        <bean id="bookCon" class="com.otnios.di.Book">
            <constructor-arg name="bookName" value="数学分析"></constructor-arg>
            <constructor-arg name="author" value="姜萍"></constructor-arg>
        </bean>

依赖注入-特殊值处理

  1. 字面量赋值

    使用value属性给bean属性赋值时,spring会把value的属性值当作字面量

  2. null赋值 在bean中添加null标签

            <constructor-arg name="others">
                <null></null>
            </constructor-arg>
  3. xml实体赋值 小于号在标签中不能随便使用 使用转义字符 /

    <!-- &gt; 代表<
         &lt; 代表>   -->
        <bean id="bookCon" class="com.otnios.di.Book">
            <constructor-arg name="bookName" value="数学分析"></constructor-arg>
            <constructor-arg name="author" value="姜萍"></constructor-arg>
            <constructor-arg name="others" value="&lt; &gt;"></constructor-arg>
        </bean>
  4. CDATA节 CDATA区中可以写任何特殊符号

        <bean id="bookCon" class="com.otnios.di.Book">
            <constructor-arg name="bookName" value="数学分析"></constructor-arg>
            <constructor-arg name="author" value="姜萍"></constructor-arg>
            <constructor-arg name="others">
                <value><![CDATA[><]]></value>
            </constructor-arg>
        </bean>

特殊类型属性

创建员工类与部门类进行测试\

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Department {
    private String dName;
}
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Employee {
    private String eName;
    private String age;
    private Department department;
    public void work(){
        System.out.println(age+"岁的"+eName+"正在"+department.getDName()+"工作");
    }
}
  1. 对象类型

    • 引入外部bean

          <bean id="department" class="com.otnios.ditest.Department">
              <property name="DName" value="阿里七七"></property>
          </bean>
          <bean id="employee" class="com.otnios.ditest.Employee">
              <property name="EName" value="电棍"></property>
              <property name="age" value="39"></property>
              <!-- 引入外部bean ref>部门bean的id值-->
              <property name="department" ref="department"></property>
          </bean>
          public void testDiTest() {
              ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("Bean.xml");
              Employee employee = context.getBean("employee", Employee.class);
              System.out.println(employee);
              employee.work();
          }
    • 内部bean

          <!--    内部bean注入-->
          <bean id="employee" class="com.otnios.ditest.Employee">
              <property name="EName" value="山泥若"></property>
              <property name="age" value="49"></property>
              <property name="department">
                  <bean id="department" class="com.otnios.ditest.Department">
                      <property name="DName" value="监狱学园"></property>
                  </bean>
              </property>
          </bean>
          public void testDiTest() {
              ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("Bean.xml");
              Employee employee = context.getBean("employee", Employee.class);
              System.out.println(employee);
              employee.work();
          }
    • 级联属性赋值 通过department.DName获取已经配置好的Bean的属性,再进行赋值

          <!--  级联赋值  -->
          <bean id="department" class="com.otnios.ditest.Department">
              <property name="DName" value="福州十五中"></property>
          </bean>
          <bean id="employee" class="com.otnios.ditest.Employee">
              <property name="EName" value="男闺蜜"></property>
              <property name="age" value="18"></property>
              <property name="department" ref="department"></property>
              <property name="department.DName" value="福州十六中"></property>
          </bean>
          public void testDiTest() {
              ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("Bean.xml");
              Employee employee = context.getBean("employee", Employee.class);
              System.out.println(employee);
              employee.work();
          }
  2. 数组类型

    添加一个数组属性方便后边注入,随后在xml文件中操作

        private String[] hobby;
        <!-- 数组赋值 -->
        <bean id="department" class="com.otnios.ditest.Department">
            <property name="DName" value="摸鱼部门"></property>
        </bean>
        <bean id="employee" class="com.otnios.ditest.Employee">
            <property name="EName" value="老八"></property>
            <property name="age" value="29"></property>
            <property name="department" ref="department"></property>
            <property name="hobby">
                <array>
                    <value>吃饭</value>
                    <value>睡觉</value>
                    <value>开祷</value>
                </array>
            </property>
         </bean>
  3. 集合类型

  • List集合

    在部门类中添加一个员工列表,后续在bean中使用<List>标签中的<ref>引入多个外部定义好的员工Bean

        <!-- 数组赋值 -->
        <bean id="department" class="com.otnios.ditest.Department">
            <property name="DName" value="摸鱼部门"></property>
        </bean>
        <bean id="employee" class="com.otnios.ditest.Employee">
            <property name="EName" value="老八"></property>
            <property name="age" value="29"></property>
            <property name="department" ref="department"></property>
            <property name="hobby">
                <array>
                    <value>吃饭</value>
                    <value>睡觉</value>
                    <value>开祷</value>
                </array>
            </property>
         </bean>
        <bean id="employee1" class="com.otnios.ditest.Employee">
            <property name="EName" value="张飞"></property>
        </bean>
        <bean id="employee2" class="com.otnios.ditest.Employee">
            <property name="EName" value="关羽"></property>
        </bean>
        <bean id="employee3" class="com.otnios.ditest.Employee">
            <property name="EName" value="刘备"></property>
        </bean>
        <bean id="department" class="com.otnios.ditest.Department">
            <property name="DName" value="刘氏集团"></property>
            <property name="empList">
                <list>
                    <ref bean="employee1"></ref>
                    <ref bean="employee2"></ref>
                    <ref bean="employee3"></ref>
                </list>
            </property>
        </bean>
        public void testListDI() {
            ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("Bean.xml");
            Department department = context.getBean("department", Department.class);
            department.info();
        }
  • Map集合

    创建学生类和教师类,学生类一对多教师类,创建Map. Map类使用<map><entry></entry></map>各标签进行注入

    @AllArgsConstructor
    @NoArgsConstructor
    @Data
    public class Teacher {
        private String teacherId;
        private String teacherName;
    }
    @AllArgsConstructor
    @NoArgsConstructor
    @Data
    public class Student {
        private String sId;
        private String sName;
        private Map<String, Teacher> teacherMap;
        public void info(){
            System.out.println("id:" + sId + "姓名:" + sName);
            System.out.println(teacherMap);
        }
    }
        <!--  Map类型注入  教师类-->
        <bean id="teacher1" class="com.otnios.dimap.Teacher">
            <property name="teacherId" value="007"></property>
            <property name="teacherName" value="徐庶"></property>
        </bean>
        <bean id="teacher2" class="com.otnios.dimap.Teacher">
            <property name="teacherId" value="008"></property>
            <property name="teacherName" value="诸葛亮"></property>
        </bean>
    <!--  Map类型注入 学生类  -->    
    <bean id="student" class="com.otnios.dimap.Student">
            <property name="SId" value="01"></property>
            <property name="SName" value="刘备"></property>
            <property name="teacherMap">
                <map>
                    <entry>
                        <key>
                            <value>初级讲师</value>
                        </key>
                        <ref bean="teacher1"></ref>
                    </entry>
                    <entry>
                        <key>
                            <value>进阶讲师</value>
                        </key>
                        <ref bean="teacher2"></ref>
                    </entry>
                </map>
            </property>
        </bean>
        public void testMapDI() {
            ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("Bean.xml");
            Student student = context.getBean("student", Student.class);
            student.info();
        }

    测试成功

  1. 引用集合类型 util:类型定义

    引入util标签所需要的命名空间

    xmlns:util="http://www.springframework.org/schema/util
    http://www.springframework.org/schema/util 
    http://www.springframework.org/schema/util/spring-util.xsd

    这个标签相当于将List和Map分别定义,后面只需要在使用的地方ref引入即可,也是有点解耦合的意思在这. 创建一个lesson课程类

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class Lesson {
        private String lessonName;
    }

    先将老师与课程的Bean写下

        <bean id="teacher1" class="com.otnios.dimap.Teacher">
            <property name="teacherId" value="007"></property>
            <property name="teacherName" value="徐庶"></property>
        </bean>
        <bean id="teacher2" class="com.otnios.dimap.Teacher">
            <property name="teacherId" value="008"></property>
            <property name="teacherName" value="诸葛亮"></property>
        </bean>
        <bean id="lesson1" class="com.otnios.dimap.Lesson">
            <property name="lessonName" value="隆中对"></property>
        </bean>
        <bean id="lesson2" class="com.otnios.dimap.Lesson">
            <property name="lessonName" value="出师表"></property>
        </bean>

    使用<util>标签定义

        <util:list id="l1">
            <ref bean="lesson1"></ref>
            <ref bean="lesson2"></ref>
        </util:list>
        <util:map id="t1">
            <entry>
                <key>
                    <value>初级讲师</value>
                </key>
                <ref bean="teacher1"></ref>
            </entry>
            <entry>
                <key>
                    <value>中级讲师</value>
                </key>
                <ref bean="teacher2"></ref>
            </entry>
        </util:map>

    最后在学生类中直接引入util标签中的id就可以

        <bean id="student" class="com.otnios.dimap.Student">
            <property name="SId" value="01"></property>
            <property name="SName" value="刘备"></property>
            <property name="teacherMap" ref="t1"></property>
            <property name="lessons" ref="l1"></property>
        </bean>
        public void testMapDI() {
            ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("Bean.xml");
            Student student = context.getBean("student", Student.class);
            student.info();
        }
P命名空间

是一个简化写法,我们先引入p命名空间

xmlns:p="http://www.springframework.org/schema/p"
    <!--  P命名空间  -->
    <bean id="studentP" class="com.otnios.dimap.Student" p:SId="002" p:SName="曹操"
        p:lessons-ref="l1" p:teacherMap-ref="t1"></bean>

这里用p:属性类型直接写就可以

    public void testPName() {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("Bean.xml");
        Student student = context.getBean("studentP", Student.class);
        student.info();
    }
引入外部属性文件

当bean里定义多了,我们可以引入外部properties文件,方便后面修改维护.这里用jdbc.properties测试

        <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.33</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.2.23</version>
        </dependency>
 

bean.xml文件中引入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

创建jdbc.properties文件

jdbc.user=root
jdbc.password=root
jdbc.url=jdbc:mysql://localhost:3306/test1?serverTimezone=UTC&useSSL=false&characterEncoding=utf8
jdbc.driver=com.mysql.cj.jdbc.Driver

bean.xml中引入外部文件

<!--    引入外部属性文件-->
    <context:property-placeholder location="jdbc.properties"></context:property-placeholder>
<!--    完成数据库信息的注入-->
    <bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="username" value="${jdbc.user}"></property>
        <property name="password" value="${jdbc.password}"></property>
        <property name="url" value="${jdbc.url}"></property>
        <property name="driverClassName" value="${jdbc.driver}"></property>
    </bean>

再测试-完成!

    public void testJDBCDI() {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("Bean.xml");
        DruidDataSource druidDataSource = context.getBean("druidDataSource", DruidDataSource.class);
        String url = druidDataSource.getUrl();
        System.out.println(url);
    }
Bean的作用域与生命周期

Spring中可以通过配置bean标签中的scope属性指定bean的作用域范围

取值含义创建对象时机
singleton(默认)IOC容器中,bean对象始终为单例IOC容器初始化时创建所有对象
prototypeIOC容器中有多个实例获取Bean时创建
request(WebApplicationContext环境下)在一个请求范围内有效
session(WebApplicationContext环境下)在一个会话范围内有效

Bean的生命周期

  • Bean对象创建(调用无参构造器)

  • Bean对象设置属性

  • Bean的后置处理器(初始化之前)

  • Bean对象初始化(配置Bean时指定初始化方法)

  • Bean的后置处理器(初始化之后)

  • Bean对象就绪可以使用

  • Bean对象销毁(配置Bean时指定销毁方法)

  • IOC容器关闭

    后置处理器需要自己写一个类,实现BeanPostProcessor接口,并重写里面的两个方法,

    • postProcessBeforeInitialization()

    • postProcessAfterInitialization(),随后在Bean.xml文件中配置生效.整个IOC容器都会生效.

FactoryBean

FactoryBean是Spring提供的一种整合第三方框架的常用机制,和普通的Bean不同,配置一个FactoryBean类型的bean,在获取bean的时候得到的并不是class属性中配置的类的对象,而是FactoryBean接口中的getObject()方法的返回值.通过这种机制,Spring可以帮我们把复杂组件创建的全部过程和繁琐细节都屏蔽起来,只把最简洁的使用界面展示给我们.以后整合mybatis时,Spring就是通过FactoryBean的机制创建SqlSessionFactory对象的.

public class MyFactoryBean implements FactoryBean<User> {
    @Override
    public User getObject() throws Exception {
        return new User();
    }
​
    @Override
    public Class<?> getObjectType() {
        return User.class;
    }
}
<!--  FactoryBean  -->
    <bean id="myFactoryBean" class="com.otnios.factorybean.MyFactoryBean"></bean>
    public void testFactoryBean() {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("Bean.xml");
        Object myFactoryBean = context.getBean("myFactoryBean");
        System.out.println(myFactoryBean);
    }
基于XML自动装配

自动装配: 根据指定的策略,在IOC容器中匹配某一个Bean,自动为指定的bean中所依赖的类类型或接口类型属性赋值

下面写三各类UserController > UserServiceImpl > UserDaoImpl,互相需要下位的对象,这时候只需要在Bean中定义对象,使用标签Autowire就可以实现自动装备配了

@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserController {
    private UserServiceImpl userService;
    public void addUser(){
        System.out.println("Controller方法执行...");
        userService.addUserService();
    }
}
@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserServiceImpl implements UserService{
    private UserDaoImpl userDao;
    @Override
    public void addUserService() {
        System.out.println("addUserService运行...");
        userDao.addUserDao();
    }
}
public class UserDaoImpl implements UserDao{
    @Override
    public void addUserDao() {
        System.out.println("addUserDao运行...");
    }
}
<!--    自动装配-->
    <bean id="userController" class="com.otnios.autodi.controller.UserController" autowire="byName"></bean>
    <bean id="userService" class="com.otnios.autodi.service.UserServiceImpl" autowire="byName"></bean>
    <bean id="userDao" class="com.otnios.autodi.dao.UserDaoImpl"></bean>
    public void testAutoDI() {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("Bean.xml");
        UserController userController = context.getBean("userController", UserController.class);
        userController.addUser();
    }

基于注解管理Bean

Spring默认不使用注解装配Bean,因此需要在Spring的XML配置中,通过context:component-scan元素开启Spring Beans的自动扫描功能.开启此功能后,Spring会自动从扫描指定的包(base-package属性设置)及其子包下的所有类,如果类上使用了@component注解,就将该类装配到容器中.

spring配置文件中引入context命名空间,开启context:component-scan元素的自动扫描功能

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       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.otnios"></context:component-scan>
</beans>

Spring提供了多个注解,可以直接标注在类上,将其定义为Spring Bean.

注解说明
@Component用于描述Spring中的Bean,是泛化的概念,仅仅表示容器中的一个组件(Bean),并且可以作用在应用的任何层次(controller,service,dao)
@Repository该注解用于数据访问层(dao层)将其类标识为Spring中的Bean
@Service该注解用于业务层(service层)将其类标识为Spring中的Bean
@Controller该注解用于控制层(controller层)将其类标识为Spring中的Bean
@Repository
public class User {
    public void info(){
        System.out.println("User注解对象注入");
    }
}
    @Test
    public void testAnnotationDi() {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("SpringBean.xml");
        User user = context.getBean("user", User.class);
        System.out.println(user);
        user.info();
    }
注解注入-@Autowired
  • @Autowired-根据类型注入[默认是byType]

    属性注入

    @Controller
    public class UserController {
        //通过属性注入对应对象
        @Autowired
        private UserService userService;
        public void addUser(){
            System.out.println("controller...");
            userService.addUser();
        }
    }
    @Service
    public class UserServiceImpl implements UserService{
        //通过属性注入对应对象
        @Autowired
        private UserDao userDao;
        @Override
        public void addUser() {
            System.out.println("service...");
            userDao.addUser();
        }
    }
    @Repository
    public class UserDaoImpl implements UserDao{
        @Override
        public void addUser() {
            System.out.println("dao...");
        }
    }
        @Test
        public void testUserAnnotationDi() {
            ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("SpringBean.xml");
            UserController userController = context.getBean("userController", UserController.class);
            userController.addUser();
        }

通过set方法注入

@Controller
public class UserController {
    private UserService userService;
    
    //通过set方法注入
    @Autowired
    public void setUserService(UserService userService){
        this.userService = userService;
    }
​
    public void addUser(){
        System.out.println("controller...");
        userService.addUser();
    }
​
}

通过构造方法注入

@Controller
public class UserController {
    private UserService userService;
​
    //通过构造方法注入
    @Autowired
    public UserController(UserService userService){
        this.userService = userService;
    }
​
    public void addUser(){
        System.out.println("controller...");
        userService.addUser();
    }
​
}

通过形参注入

@Controller
public class UserController {
    private UserService userService;
​
    //通过形参注入
    public UserController(@Autowired UserService userService){
        this.userService = userService;
    }
​
    public void addUser(){
        System.out.println("controller...");
        userService.addUser();
    }
​
}

只有一个有参数构造函数,可以无注解注入

@Controller
public class UserController {
    private UserService userService;
​
    //只有一个有参数的构造函数,注解省略
    public UserController(UserService userService){
        this.userService = userService;
    }
​
    public void addUser(){
        System.out.println("controller...");
        userService.addUser();
    }
}

@Autowired注解与@Qualifier注解联合,可通过byName进行属性注入.如果dao有两个实现类,需要使用该方法

@Repository
public class UserRedisDaoImpl implements UserDao{
    @Override
    public void addUser() {
        System.out.println("Redis dao...");
    }
}
@Service
public class UserServiceImpl implements UserService{
    //通过属性注入对应对象
    @Autowired
    //指定注入对象名称
    @Qualifier(value = "userRedisDaoImpl")
    private UserDao userDao;
    @Override
    public void addUser() {
        System.out.println("service...");
        userDao.addUser();
    }
}

以上多种注入都正常使用.

注解注入-@Resource

@Resource是JDK扩展包中的标准注解,更具有通用性.@Autowired是Spring框架中带的.

@Resource默认根据名称装配byName,未指定name会使用属性名作为name.通过name找不到后再根据type进行装配.

@Autowired如果想要根据名称装配,需要配合@Qualifier注解一起使用

@Resource可以使用在属性上,setter上.

@Autowired可以使用在属性上,setter上,构造方法和构造方法参数上.

@Resource是JDK扩展包中的内容,高于JDK11或低于JDK8的版本需要引入下面的依赖.

<!-- https://mvnrepository.com/artifact/jakarta.annotation/jakarta.annotation-api -->
<dependency>
    <groupId>jakarta.annotation</groupId>
    <artifactId>jakarta.annotation-api</artifactId>
    <version>3.0.0</version>
</dependency>
@Controller
public class OrderController {
    //根据指定名称注入
    @Resource(name = "myOrderService")
    private OrderService orderService;
​
    public void addOrder(){
        System.out.println("Resource addOrder oderController...");
        orderService.addOrder();
    }
}
@Service(value = "myOrderService")
public class OrderServiceImpl implements OrderService{
    //不指定名称,根据属性名称进行注入
    @Resource
    private OrderDao myOrderDao;
    @Override
    public void addOrder() {
        System.out.println("Resource addOrder OrderService...");
    }
}
@Repository("myOrderDao")
public class OrderDaoImpl implements OrderDao{
    @Override
    public void addOrder() {
        System.out.println("Resource add OrderDao...");
    }
}

如果都不写,直接根据类型进行注入.

全注解开发

前面还是写了一个spring的配置文件用来开启组件扫描.这里可以创建也给配置类进行替代.

@Configuration
@ComponentScan("com.otnios")
public class Spring6Config {
}
    @Test
    public void testConfigClass() {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Spring6Config.class);
        OrderController orderController = context.getBean("orderController", OrderController.class);
        orderController.addOrder();
    }

AOP

AOP是一种设计思想,面向切面编程.它通过预编译方式和运行期间动态代理方式实现.在不修改源码的情况下,给程序动态统一添加额外功能的一种技术.利用AOP可以对业务逻辑的各个部分进行隔离,从而使业务逻辑各部分之间耦合度降低,提高程序可重用性,同时提高开发效率.

当代理的对象有接口的情况下,使用JDK动态代理,生成接口实现类的代理对象,代理对象和目标对象实现同样的接口.如果没有使用的是cglib的动态代理.通过继承被代理的目标类,生成子类代理对象.

AspectJ:是AOP思想的一种实现,本质上是静态代理,将代理逻辑织入被代理的目标类编译得到的字节码文件,所以最终效果的实现是动态的,weaver就是织入器.Spring只是借用了AspectJ中的注解.

场景模拟

创建一个计算器接口,创建实现其中功能的计算器类.

public interface Calculator {
​
    public int add(int i, int j);
    public int sub(int i, int j);
    public int mul(int i, int j);
    public int div(int i, int j);
​
}
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;
    }
}

如果我们想要在每个方式执行前后都加上日志方法,使用本来的写法需要在每个方法内部都添加上功能语句,类似于下面这样

public class CalculatorLogImpl implements Calculator{
    @Override
    public int add(int i, int j) {
        System.out.println("[日志]add方法开始了,参数是:"+i+","+j);
        int result = i+j;
        System.out.println("方法内部result=" + result);
        System.out.println("[日志]add方法结束了,结果是:"+result);
        return result;
    }
​
}

这种方法不仅不利于维护,也会对核心业务功能产生影响.我们需要对这种问题进行解耦.把附加功能抽取出来.但是要抽取出来的重复代码在方法内,所以我们需要引入新技术->代理模式.

代理模式

二十三种设计模式中的一种,属于结构型模式.它的作用是通过一个代理类,让我们在调用目标方法时,不再直接对目标方法进行调用,而是通过代理类间接调用.让不属于目标方法的核心逻辑代码从目标方法中剥离出来(解耦).调用目标方法时先调用代理对象的方法,减少对目标方法的调用和打扰,同时让附加功能能够集中在一起也有利于统一维护.

  • 静态代理

    @Component
    public class CalculatorStaticProxy implements Calculator{
        @Resource
        private CalculatorImpl calculatorImpl;
        @Override
        public int add(int i, int j) {
            System.out.println("静态代理对象开始操作");
            int addResult = calculatorImpl.add(i ,j);
            System.out.println("静态代理对象操作完毕");
            return addResult;
        }
    }
  • 动态代理

    解决静态代理的强耦合问题,把其中代理的代码抽取出来.用到了反射.

public class ProxyFactory {
​
    private final Object target;
    public ProxyFactory(Object target){
        this.target = target;
    }
​
    //得到代理对象
    public Object getProxy(){
        //1-ClassLoader:加载动态生成代理类的类加载器
        ClassLoader classLoader = target.getClass().getClassLoader();
        //2-class[] interfaces:目录对象实现的所有接口的class类型数组
        Class<?>[] interfaces = target.getClass().getInterfaces();
        //3-InvocationHandler:设置代理对象实现目标对象方法的过程
        InvocationHandler invocationHandler = new InvocationHandler(){
​
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                //proxy:代理对象
                //method:需要重写的目标对象方法
                //Object[] args:method方法里面的参数
                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);
    }
​
}
    @Test
    public void testProxyFactory() {
        ProxyFactory proxyFactory = new ProxyFactory(new CalculatorImpl());
        Calculator proxy = (Calculator) proxyFactory.getProxy();
        proxy.add(1,1);
    }

测试成功

基于注解实现AOP

切入点表达式:execution(权限修饰符 方法返回值 方法所在的全类名 方法名 (参数列表)

        <!-- https://mvnrepository.com/artifact/org.springframework/spring-aop -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>6.1.8</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.springframework/spring-aspects -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>6.1.8</version>
        </dependency>

先引入AOP依赖 在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"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="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
       http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--    开启组件扫描-->
    <context:component-scan base-package="com.otnios.example"></context:component-scan>
<!--    开启AspectJ自动代理,为目标对象生成代理,让Spring认识切面类注解-->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
@Component
@Aspect
public class LogAspect {
    // value + 切入点表达式:execution(权限修饰符 方法返回值 方法所在的全类名 方法名 (参数列表) 
    // *表示所有方法 ..表示所有参数
    @Before(value = "execution(public int com.otnios.example.*.*(..))")
    public void beforeMethod(){
        System.out.println("Logger -> 前置通知");
    }
}
    @Test
    public void testLogAspect() {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("Bean.xml");
        Calculator calculator = context.getBean(Calculator.class);
        int add = calculator.add(1, 1);
        System.out.println(add);
    }

测试成功

各种通知
    @After(value = "execution(* com.otnios.example.*.*(..))")
    public void afterMethod(JoinPoint joinPoint){
        String methodName = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        System.out.println("Logger -> 后置通知,方法为:"+methodName+"参数为:"+Arrays.toString(args));
    }

写一个后置通知,在切入方法执行完成后进行执行.可以传入的参数JoinPoint,从中能获得执行切入方法的方法名和参数等等属性,进行输出

    //返回通知 可以获取到切入方法要返回的值,通过注解中的returning设置.
    @AfterReturning(value = "execution(* com.otnios.example.CalculatorImpl.*(..))", returning = "result")
    public void returnMethod(JoinPoint joinPoint, Object result){
        String methodName = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        System.out.println("Logger -> 返回通知,方法为:"+methodName+"参数为:"+Arrays.toString(args)+"返回结果为:"+result);
    }

返回通知在后置通知之前执行,可以截取到返回的值.

    //异常通知 可以获取到切入方法的异常信息
    @AfterThrowing(value = "execution(* com.otnios.example.CalculatorImpl.*(..))", throwing = "e")
    public void afterThrowingMethod(JoinPoint joinPoint, Throwable e){
        String methodName = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        System.out.println("Logger -> 异常通知,方法为:"+methodName+",参数为:"+Arrays.toString(args)+",异常值为:"+e);
    }

异常通知得到异常值后可以进行处理.

    //环绕通知 可以通过try catch finally结构中完成所有通知类型
    @Around(value = "execution(* com.otnios.example.CalculatorImpl.*(..))")
    public Object aroundMethod(ProceedingJoinPoint joinPoint){
        String methodName = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        String argsString = Arrays.toString(args);
        Object result = null;
        try {
            System.out.println("环绕通知=>目标方法执行之前");
            //调用目标方法
            result = joinPoint.proceed();
            System.out.println("环绕通知=>目标方法返回值之后");
        } catch (Throwable e) {
            System.out.println("环绕通知=>目标方法发生异常之后");
            throw new RuntimeException(e);
        } finally {
            System.out.println("环绕通知=>目标方法执行之后");
        }
        return result;
    }

环绕通知可以实现各种通知,像一个综合体

重用切入点表达式
    @Pointcut("execution(* com.otnios.example.CalculatorImpl.*(..))")
    public void pointCut(){}

有了这个类,后面所有要用的该切入表达式的类都可以使用 @通知类型(value="pointCut()")代替,达到重用效果.

切面的优先级

相同方法上的多个切面存在优先级,优先级控制着切面的内外嵌套顺序.外层的切面优先级更高.可以使用@Order(较大或较小的数)控制切面的优先级.

使用xml实现AOP

不写想了 感觉注解够用了,打两把大乱斗去

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/753977.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【UE5.3】笔记6-第一个简单小游戏

打砖块小游戏&#xff1a; 1、制造一面砖块组成的墙 在关卡中放置一个cube&#xff0c;放这地面上&#xff0c;将其转换成蓝图类,改名BP_Cube&#xff0c;更换砖块的贴图&#xff0c;按住alt键进行拷贝&#xff0c;堆出一面墙&#xff0c;复制出来的会很多&#xff0c;全选移动…

Java学习笔记(一)Java内容介绍、程序举例、DOS命令、Java跨平台特性的本质、课后练习

Hi i,m JinXiang ⭐ 前言 ⭐ 本篇文章主要介绍Java内容介绍、程序举例、DOS命令、Java跨平台特性的本质还有几道课后练习详细介绍以及部分理论知识 🍉欢迎点赞 👍 收藏 ⭐留言评论 📝私信必回哟😁 🍉博主收将持续更新学习记录获,友友们有任何问题可以在评论区留言 …

JavaScript:实现内容显示隐藏(展开收起)功能

一、场景 点击按钮将部分内容隐藏&#xff08;收起&#xff09;&#xff0c;再点击按钮时将内容显示&#xff08;展开&#xff09;出来。 二、技术摘要 js实现实现内容显示隐藏js动态给ul标签添加li标签js遍历数组 三、效果图 四、代码 js_block_none.js代码 var group1 doc…

MySQL高级-SQL优化-小结

文章目录 1、insert 优化2、主键优化3、order by 优化4、group by 优化5、limit 优化6、count 优化7、update 优化 1、insert 优化 insert&#xff1a;批量插入、手动控制事务、主键顺序插入 大批量插入&#xff1a;load data local infile 2、主键优化 主键长度尽量短、顺序插…

webpack【实用教程】

基础配置 配置的拆分和合并 通常 webpack 的配置文件会有3个 webpack.common.js 公共配置&#xff08;会被另外两个配置文件导入并合并&#xff09;webpack.dev.js 开发环境的配置webpack.prod.js 生产环境的配置 开发环境的本地服务 在 webpack.dev.js 中配置 devServer:…

探索未来的AI革命:GPT-5的即将登场

人不走空 &#x1f308;个人主页&#xff1a;人不走空 &#x1f496;系列专栏&#xff1a;算法专题 ⏰诗词歌赋&#xff1a;斯是陋室&#xff0c;惟吾德馨 目录 &#x1f308;个人主页&#xff1a;人不走空 &#x1f496;系列专栏&#xff1a;算法专题 ⏰诗词歌…

柔性数组(flexible array)

柔性数组从C99开始支持使用 1.柔性数组的概念 概念&#xff1a; 结构体中&#xff0c;结构体最后一个元素允许是未知大小的数组&#xff0c;这就叫[柔性数组]的成员 struct S {int n;char arr[]; //数组大小未知(柔性数组成员) }; 柔性数组的特点&#xff1a; 结构体中柔性…

右键新建没有TXT文本文档的解决办法

电脑右键新建&#xff0c;发现没有txt了&#xff0c;我查网上办法都有点复杂&#xff0c;诸如注册表的&#xff0c;但是其实很简单&#xff0c;重启windows资源管理器就可以了。 点击重新启动&#xff0c;之后新建就有txt文档了。

Windows server 由于没有远程桌面授权服务器可以提供许可证,远程会话连接已断开。

问题现象&#xff1a; 解决办法 临时远程方式1: 打开 mstsc 时带上 /admin 等参数&#xff0c;如下图所示&#xff1a; 使用“mstsc /admin /v:目标ip”来强制登录服务器&#xff0c;但只能是管理员身份。 远程方式2&#xff1a; 通过VM远程登陆系统后&#xff0c;运行输入R…

安卓速度下载v1.0.5/聚合短视频解析下载

功能特色 短视频下载与高级管理 – 支持短视频下载&#xff0c;为您提供一系列高级视频管理功能包括视频内容提取、智能防重复技术、视频体积压缩以及视频转换成GIF图片等&#xff1b; 磁-力链接下载升级 – 现支持磁力链接下载&#xff0c;实现边下载边播放的便捷体验&#x…

LLaMA2模型训练加速秘籍:700亿参数效率提升195%!

点击蓝字 关注我们 关注并星标 从此不迷路 计算机视觉研究院 公众号ID &#xff5c; 计算机视觉研究院 学习群 &#xff5c; 扫码在主页获取加入方式 开源地址&#xff1a;https://github.com/hpcaitech/ColossalAI 计算机视觉研究院专栏 Column of Computer Vision Ins…

【深度学习】服务器炼丹代码配置、Python使用指定gpu显卡运行代码

【显卡】服务器炼丹代码配置 写在最前面一、查看哪几块显卡能用二、使用指定gpu运行代码1、指定使用GPU0运行脚本&#xff08;默认是第一张显卡, 0代表第一张显卡的id,其他的以此类推&#xff09;2、指定使用多张显卡运行脚本 三、如何使用1、单块显卡使用2、多GPU训练使用Data…

亚太杯赛题思路发布(中文版)

导读&#xff1a; 本文将继续修炼回归模型算法&#xff0c;并总结了一些常用的除线性回归模型之外的模型&#xff0c;其中包括一些单模型及集成学习器。 保序回归、多项式回归、多输出回归、多输出K近邻回归、决策树回归、多输出决策树回归、AdaBoost回归、梯度提升决策树回归…

javaSE知识点整理总结(上)

目录 一、面向对象 1. 类、对象、方法 2.面向对象三大特征 &#xff08;1&#xff09;封装 &#xff08;2&#xff09;继承 &#xff08;3&#xff09;多态 二、常用类 1.Object类 2.Array类 3.基本数据类型包装类 4.String类 5.StringBuffer类 6.Math类 7.Random…

ONLYOFFICE 8.1 桌面编辑器测评:引领数字化办公新潮流

目录 前言 下载安装 新功能概述 1.PDF 编辑器的改进 2. 演示文稿中的幻灯片版式 3.语言支持的改进 4. 隐藏“连接到云”板块 5. 页面颜色设置和配色方案 界面设计&#xff1a;简洁大方&#xff0c;操作便捷 性能评测&#xff1a;稳定流畅&#xff0c;高效运行 办公环…

恭喜!Apache SeaTunnel2024开源之夏学生中选名单出炉!

经过严格的筛选&#xff0c;开源之夏组委会及导师已经选出并录取项目对应的学生&#xff0c;社区联合中科院开展的开源之夏活动也进入到了激动人心的中选公示阶段。 在这里&#xff0c;我们恭喜下面的同学&#xff0c;已成功匹配到Apache SeaTunnel社区的项目&#xff0c;即将开…

主从复制、哨兵以及Cluster集群

目录 1.Redis高可用 2.Redis主从复制 2.1 主从复制的作用 2.2 主从复制流程 2.3 搭建Redis主从复制 2.3.1 修改Redis配置文件&#xff08;Master节点操作&#xff09; 2.3.2 修改Redis配置文件&#xff08;Slave节点操作&#xff09; 2.3.2 验证主从复制结果 3.Redis哨…

数据分析三剑客-Matplotlib

数据分析三剑客 数据分析三剑客通常指的是在Python数据分析领域中&#xff0c;三个非常重要的工具和库&#xff1a;Pandas、NumPy和Matplotlib。Pandas主要负责数据处理和分析&#xff0c;NumPy专注于数值计算和数学运算&#xff0c;而Matplotlib则负责数据可视化。这三个库相…

聊聊啥项目适合做自动化测试

作为测试从业者&#xff0c;你是否遇到过这样的场景&#xff0c;某天公司大Boss找你谈话。 老板&#xff1a;小李&#xff0c;最近工作辛苦了 小李&#xff1a;常感谢您的认可&#xff0c;这不仅是对我个人的鼓励&#xff0c;更是对我们整个团队努力的认可。我们的成果离不开每…

【python】一篇文零基础到入门:快来玩吧~

本笔记材料源于&#xff1a; PyCharm | 创建你的第一个项目_哔哩哔哩_bilibili Python 语法及入门 &#xff08;超全超详细&#xff09; 专为Python零基础 一篇博客让你完全掌握Python语法-CSDN博客 0为什么安装python和pycharm&#xff1f; 不同于c&#xff0c;c&#xff0…