欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 财经 > 产业 > Spring6全面详解-自溜用

Spring6全面详解-自溜用

2024/10/25 14:34:38 来源:https://blog.csdn.net/weixin_44610169/article/details/140053776  浏览:    关键词: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直接创建对象@Testpublic void testSpringFirstUser() {ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("Bean.xml");User user = (User) context.getBean("user");System.out.println(user);user.add();}
  1. 通过反射模拟Spring获取对象的过程

    @Testpublic 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{@Overridepublic void run() {System.out.println("UserDaoImpl run");}
}

  1. 根据id

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

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

        public void testGetUser() {ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("Bean.xml");// 根据类型获取BeanUser 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{@Overridepublic 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/contexthttp://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> {@Overridepublic User getObject() throws Exception {return new User();}
​@Overridepublic 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;@Overridepublic void addUserService() {System.out.println("addUserService运行...");userDao.addUserDao();}
}
public class UserDaoImpl implements UserDao{@Overridepublic 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/contexthttp://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/beanshttp://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注解对象注入");}
}
    @Testpublic 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 {//通过属性注入对应对象@Autowiredprivate UserService userService;public void addUser(){System.out.println("controller...");userService.addUser();}
    }
    @Service
    public class UserServiceImpl implements UserService{//通过属性注入对应对象@Autowiredprivate UserDao userDao;@Overridepublic void addUser() {System.out.println("service...");userDao.addUser();}
    }
    @Repository
    public class UserDaoImpl implements UserDao{@Overridepublic void addUser() {System.out.println("dao...");}
    }
        @Testpublic 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方法注入@Autowiredpublic void setUserService(UserService userService){this.userService = userService;}
​public void addUser(){System.out.println("controller...");userService.addUser();}
​
}

通过构造方法注入

@Controller
public class UserController {private UserService userService;
​//通过构造方法注入@Autowiredpublic 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{@Overridepublic void addUser() {System.out.println("Redis dao...");}
}
@Service
public class UserServiceImpl implements UserService{//通过属性注入对应对象@Autowired//指定注入对象名称@Qualifier(value = "userRedisDaoImpl")private UserDao userDao;@Overridepublic 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{//不指定名称,根据属性名称进行注入@Resourceprivate OrderDao myOrderDao;@Overridepublic void addOrder() {System.out.println("Resource addOrder OrderService...");}
}
@Repository("myOrderDao")
public class OrderDaoImpl implements OrderDao{@Overridepublic void addOrder() {System.out.println("Resource add OrderDao...");}
}

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

全注解开发

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

@Configuration
@ComponentScan("com.otnios")
public class Spring6Config {
}
    @Testpublic 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{@Overridepublic int add(int i, int j) {int result = i+j;System.out.println("方法内部result=" + result);return result;}
​@Overridepublic int sub(int i, int j) {int result = i-j;System.out.println("方法内部result=" + result);return result;}
​@Overridepublic int mul(int i, int j) {int result = i*j;System.out.println("方法内部result=" + result);return result;}
​@Overridepublic int div(int i, int j) {int result = i/j;System.out.println("方法内部result=" + result);return result;}
}

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

public class CalculatorLogImpl implements Calculator{@Overridepublic 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{@Resourceprivate CalculatorImpl calculatorImpl;@Overridepublic 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(){
​@Overridepublic 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);}
​
}
    @Testpublic 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/contexthttp://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop.xsdhttp://www.springframework.org/schema/beanshttp://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 -> 前置通知");}
}
    @Testpublic 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

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

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com