上海品牌设计有限公司,百度关键词相关性优化软件,安徽淮北发现一例,cms网站开发一、Spring系统架构介绍
1.1、定义
Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器#xff08;框架#xff09;。Spring官网 Spring是一款主流的Java EE 轻量级开源框架#xff0c;目的是用于简化Java企业级引用的开发难度和开发周期。从简单性、可测试性和松耦…一、Spring系统架构介绍
1.1、定义
Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器框架。Spring官网 Spring是一款主流的Java EE 轻量级开源框架目的是用于简化Java企业级引用的开发难度和开发周期。从简单性、可测试性和松耦合度的角度而言任何Java应用都可以从Spring中受益。Spring框架提供自己提供功能外还提供整合其他技术和框架的能力。 Spring中默认Bean的名称是对应类的首字母小写
1.2、Spring核心
Spring指的是Spring Framework通常我们称之为Spring框架。Spring框架是一个分层的面向切面的Java应用程序的一站式解决框架它是Spring技术栈的核心和基础是为了解决企业级引用开发的复杂性而创建的。
Spring有两个核心模块IoC和AOP。
IocInverse of Control的简写为 控制反转指把创建对象交给Spring进行管理。 AOPAspect Oriented Programming 的简写为 面向对象编程。AOP用来封装多个类的公共行为将那些与业务无关却为业务模块共同调用的逻辑封装起来减少系统的重复代码降低模块间的耦合度。另外AOP还解决一些系统层面上的问题比如日志、事务、权限等。
1.3、Spring Framework的特点
控制反转IoC反转资源获取方向把自己创建的资源、向环境索取资源变为环境将资源准备好我们享受资源注入。面向切面编程AOP在不修改源代码的基础上增强代码功能。容器SpringIoC是一个容器因为它包含并管理组件对象的生命周期组件享受到了容器化的管理替程序员屏蔽了组件创建过程中的大量细节极大降低了使用门槛大幅度提高了开发效率。一站式在IOC和AOP的基础上可以整合各种企业应用的开源框架和优秀的第三方库而且在Spring旗下的项目已经覆盖了广泛领域很多方面的功能性需求可以在SpringFramework 的基础上全部使用Spring来实现。
1.4、Spring优点
1、Spring是一个开源免费的框架 , 容器 . 2、Spring是一个轻量级的框架导入包就可以用 , 非侵入式的 引入后不会对原来的代码造成影响. 3、控制反转 IoC , 面向切面 Aop 4、对事物的支持 , 对框架的支持
1.5、使用准备
Spring6要求最低使用JDK17版本 Maven依赖包Spring WebMVC和Spring JDBC
!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc --
dependencygroupIdorg.springframework/groupIdartifactIdspring-webmvc/artifactIdversion6.1.11/version
/dependency!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc --
dependencygroupIdorg.springframework/groupIdartifactIdspring-jdbc/artifactIdversion6.1.11/version
/dependency
1.6、Spring Boot 与Spring Cloud
Spring Boot 是 Spring 的一套快速配置脚手架可以基于Spring Boot 快速开发单个微服务;SpringCloud是基于Spring Boot实现的Spring Boot专注于快速、方便集成的单个微服务个体SpringCloud关注全局的服务治理框架Spring Boot使用了约束优于配置的理念很多集成方案已经帮你选择好了能不配置就不配置 , Spring Cloud很大的一部分是基于Spring Boot来实现Spring Boot可以离开SpringCloud独立使用开发项目但是Spring Cloud离不开Spring Boot属于依赖的关系。SpringBoot在SpringClound中起到了承上启下的作用如果你要学习SpringCloud必须要学习SpringBoot。
二、入门案例
2.1、基本开发步骤 2.2、创建工程与案例实现 创建子模块
1、第一步子工程的Pom中添加依赖 !-- https://mvnrepository.com/artifact/org.springframework/spring-context --dependencygroupIdorg.springframework/groupIdartifactIdspring-context/artifactIdversion6.1.11/version/dependency!-- https://mvnrepository.com/artifact/junit/junit --dependencygroupIdjunit/groupIdartifactIdjunit/artifactIdversion4.13.2/versionscopetest/scope/dependency2、第二步创建类定义属性方法
public class User {public void add(){System.out.println(Spring 测试);}
}3、第三步按照Spring要求创建配置文件XML格式
创建spring配置文件resources目录下创建bean.xml 配置相关信息完成类对象创建
!--完成user对象创建id属性唯一标识class属性要创建的对象所在类的绝对路径--bean iduser classcn.tedu.spring.User/bean4、测试
public class Test {org.junit.Testpublic void testUserObject01(){//加载Spring配置文件创建对象ApplicationContext contextnew ClassPathXmlApplicationContext(bean.xml);//获取创建的对象,默认创建的对象时Object类型转为对应类的类型即可Object user context.getBean(user);User user (User) context.getBean(user);//使用对象进行方法调用user.add();}
}2.3 、案例分析
1、对象创建无参构造是否执行
public class User {public User() {System.out.println(这是User类的无参构造);}public void add(){System.out.println(Spring 测试);}
}2、不用new的形式还可以怎样创建对象 1、反射-创建对象的过程 org.junit.Testpublic void Test() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {//01\通过Class.forName获取需要创建对象的类路径Class? aClass Class.forName(com.bipt.User);//02\调用方法创建对象User user (User) aClass.getDeclaredConstructor().newInstance();//03\测试对象user.add();}3、创建的对象放哪里
双击Shift输入DefaultListableBeanFactory名字中的BeanFactory可以理解为Bean工厂用来生产Bean 创建的对象存放再Map集合中
private final MapString, BeanDefinition beanDefinitionMap;
this.beanDefinitionMap new ConcurrentHashMap集合中的KeyValue key唯一标识 value类的定义描述信息
可以查看 BeanDefinition 的源码有类的描述信息是否初始化的状态等等
2.4 Log4J2的日志框架2.XX 1、引入依赖 dependencygroupIdorg.apache.logging.log4j/groupIdartifactIdlog4j-api/artifactIdversion2.15.0/version/dependencydependencygroupIdorg.apache.logging.log4j/groupIdartifactIdlog4j-core/artifactIdversion2.15.0/version/dependencydependencygroupIdorg.apache.logging.log4j/groupIdartifactIdlog4j-slf4j-impl/artifactIdversion2.15.0/version/dependency
2、配置文件
?xml version1.0 encodingUTF-8?
Configuration statusWARNAppendersConsole nameConsole targetSYSTEM_OUTPatternLayout pattern%d{yyyy-MM-dd HH:mm:ss} %-5level %logger{36} - %msg%n//ConsoleFile nameFile fileNamelog-output/app.log appendfalsePatternLayout pattern%d{yyyy-MM-dd HH:mm:ss} %-5level %logger{36} - %msg%n//File/AppendersLoggersRoot leveldebugAppenderRef refConsole/AppenderRef refFile//Root/Loggers
/Configuration3、通过Logger对象手动向日志插入 org.junit.Testpublic void testUserObject01(){/*创建Log对象*/Logger logger LoggerFactory.getLogger(Test.class);//加载Spring配置文件能够读取到User类的路径信息用于后面通过反射创建对象ApplicationContext contextnew ClassPathXmlApplicationContext(bean.xml);//获取创建的对象,默认创建的对象时Object类型转为对应类的类型即可Object user context.getBean(user);User user (User) context.getBean(user);//使用对象进行方法调用user.add();logger.info(调用Logger成功);}三、容器IOC
3.1、IOC容器
1、简介
Spring通过IoC容器来管理所有的Java对象的实例化和初始化控制着对象与对象之间的依赖关系。我们将由IoC容器管理的Java对象称为 Spring Bean它与使用关键字 new 创建的Java对象没有任何区别
2、IOC容器创建bean的过程 3、依赖注入
DI Dependency Injection依赖注入依赖注入实现了控制反转的思想是指Spring创建对象的过程中将对象依赖属性通过配置进行注入。
依赖注入常见的实现方式有两种
set注入构造注入
所以 IoC 是一种控制反转的思想而 DI 是对IoC的一种具体实现。 Bean管理指Bean对象的创建以及Bean对象中属性的赋值或Bean对象之间关系的维护
4、IOC容器实现
Spring中的IoC容器就是IoC思想的一个落地产品实现。IoC容器中管理的组件也叫做bean。在创建bean之前首先需要创建IoC容器Spring提供了IoC容器的两种实现方式
① BeanFactory
这是IoC容器的基本实现是Spring内部使用的接口。面向Spring本身不提供给开发人员使用。我们使用下面它的子接口ApplicationContext
//加载Spring配置文件能够读取到User类的路径信息用于后面通过反射创建对象
//ClassPathXmlApplicationContext是ApplicationContext 接口的一个实现类
ApplicationContext contextnew ClassPathXmlApplicationContext(bean.xml);② ApplicationContext
BeanFactory的子接口提供了更多高级特性面向Spring的使用者几乎所有场合都使用 ApplicationContext而不是底层的BeanFactory
3.2、基于XMl管理bean
3.2.1、 获取bean的三种方式
1、环境配置 创建Spring配置文件bean.xml
?xml version1.0 encodingUTF-8?
beans xmlnshttp://www.springframework.org/schema/beansxmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexsi:schemaLocationhttp://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
!--通过标签完成类对象创建bean标签--!--完成user对象创建id属性唯一标识class属性要创建的对象所在类的绝对路径--/beans创建实体类User
public class User {private String name;private int age;public User() {System.out.println(这是User类的无参构造);}public void add(){System.out.println(Spring 测试);}
}2、根据Id、类型、Id类型获取bean org.junit.Testpublic void testUserObject01(){//加载Spring配置文件能够读取到User类的路径信息用于后面通过反射创建对象ApplicationContext contextnew ClassPathXmlApplicationContext(bean.xml);//获取创建的对象,默认创建的对象时Object类型转为对应类的类型即可Object user context.getBean(user);User user (User) context.getBean(user);System.out.println(1-根据Id获取beanuser);User user2 context.getBean(User.class);System.out.println(2-根据类型获取bean: user2);User user3 context.getBean(user, User.class);System.out.println(3-根据id和类型获取bean user3); 3、问题
当根据类型获取bean时要求IoC容器中指定类型的bean只能有一个当配置两个时会抛出异常通过类型获取的时候期望创建单实例bean
4、解决
当一个bean配置文件中对于同一个类有多个bean时获取bean对象要么通过id获取要么通过类型id方式获取。
5、根据接口类型获取bean 问题1如果组件类实现了接口可以通过接口类型获取bean吗 问题2接口有多个实现类通过接口类型获取bean可以吗
6、总结 3.2.2、依赖注入
类有属性创建对象过程中向属性注入具体的值所谓的注入其实就是给依赖的成员变量赋值
创建实体类
package com.bipt.DI;/*** author Pengwei Qu* Date 2024/9/3 13:43*/
public class Book {private String bookName;private String bookAuthor;public Book() {}public Book(String bookName, String bookAuthor) {this.bookName bookName;this.bookAuthor bookAuthor;}public String getBookName() {return bookName;}public void setBookName(String bookName) {this.bookName bookName;}public String getBookAuthor() {return bookAuthor;}public void setBookAuthor(String bookAuthor) {this.bookAuthor bookAuthor;}
} 下面通过配置文件实现
3.2.3、依赖注入之Setter注入 !--通过Set方法完成注入--bean idbook classcom.bipt.DI.Bookproperty namebookName value前端开发/propertyproperty namebookAuthor valueJack/property/beanTestpublic void DiTest01(){ApplicationContext applicationContext new ClassPathXmlApplicationContext(beanDI.xml);Book book (Book) applicationContext.getBean(book, com.bipt.DI.Book);System.out.println(book);/*由于实体类Book中重写了toString直接输出对象输出的是属性值*/}3.2.4、依赖注入之构造注入
有参构造进行测试 bean idbook_construct classcom.bipt.DI.Bookconstructor-arg namebookName valueJava基础/constructor-arg namebookAuthor valueTom//beanTestpublic void DiTest02(){ApplicationContext applicationContext new ClassPathXmlApplicationContext(beanDI.xml);Book book (Book) applicationContext.getBean(book_construct, com.bipt.DI.Book);System.out.println(book);/*由于实体类Book中重写了toString直接输出对象输出的是属性值*/}3.2.5、特殊值处理
1、字面量
string number 10; 声明一个变量number初始化为 10此时number就不代表字符number了而是作为一个变量的名字。当引用number时实际拿到的结果是 10。 而如果number是带引号的 “number” 则它不是一个变量而代表 number 本身这个字符串。 这就是字面量所以字面量没有引申含义就是我们看到的这个数据本身。
!-- 使用value属性给bean的属性赋值时spring会把value的属性值看作是字面量 --
property namenumber value1016/property2、null 使用或者null标签实现注入。 !--通过Set方法完成注入--bean idbook classcom.bipt.DI.Bookproperty namebookName value前端开发/propertyproperty namebookAuthor valueJack/propertyproperty nameothernull/null/property/bean!--通过构造方法注入--bean idbook_construct classcom.bipt.DI.Bookconstructor-arg namebookName valueJava基础/constructor-arg namebookAuthor valueTom/constructor-arg nameother null/null/constructor-arg/bean3、Xml实体 4、CDATA区
CDATA区是xml中一种特有的写法在CDATA区中可以包含特殊符号 表示方式 ![CDATA[内容]] 在内容区域可以存放普通字符和特殊符号
3.2.6、对象类型注入 1、引用外部beanref 2、引用内部bean 3、级联赋值 3.2.7、数组类型属性注入
!-- 创建Person对象并注入属性 --
bean idperson classcn.tedu.spring.diarray.Person!-- 普通属性注入 --property namename value孙悟空/property nameage value36/!-- 数组属性注入使用array标签 --property namehobbyarrayvalue抽烟/valuevalue喝酒/valuevalue烫头/value/array/property
/bean
3.2.8、集合类型的注入
1、List集合
Teacher类
public class Teacher {// 老师姓名private String tName;// 老师所教学生的对象放到List集合中private ListStudent studentList;public String gettName() {return tName;}public void settName(String tName) {this.tName tName;}public ListStudent getStudentList() {return studentList;}public void setStudentList(ListStudent studentList) {this.studentList studentList;}Overridepublic String toString() {return Teacher{ tName tName \ , studentList studentList };}
}
Student类
public class Student {// 学生姓名、年龄private String sName;private String age;public String getsName() {return sName;}public void setsName(String sName) {this.sName sName;}public Integer getAge() {return age;}public void setAge(Integer age) {this.age age;}Overridepublic String toString() {return Student{ sName sName \ , course course \ };}
}
!-- 创建2个Student对象用于Teacher对象的注入 --
bean idstu1 classcn.tedu.spring.dimap.Studentproperty namesName value梁山伯/property nameage value43/
/bean
bean idstu2 classcn.tedu.spring.dimap.Studentproperty namesName value祝英台/property nameage value33/
/bean!-- 创建Teacher类的bean对象并注入属性 --
bean idteacher classcn.tedu.spring.dimap.Teacher!-- 普通属性注入 --property nametName value沙师弟/!-- List集合属性注入 --property namestudentListlistref beanstu1/ref beanstu2//list/property
/bean
2、Map集合
// 老师姓名private String tName;// 老师所教学生的对象放到List集合中private ListStudent studentList;//Map集合private MapString,Student map;bean idstu3 classcom.bipt.DI_Collection.Studentproperty namesName value刘备/property nameage value35//bean!--老师类Map注入--bean idtea2 classcom.bipt.DI_Collection.Teacherproperty nametName value李老师/property namemapmapentrykeyvalue1001/value/keyref beanstu3//entryentrykeyvalue1002/value/keyref beanstu2//entry/map/property/bean
3、引用集合类型bean注入
因为要使用utils标签在xml配置文件中引入util约束
xmlnshttp://www.springframework.org/schema/beansxmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexmlns:utilhttp://www.springframework.org/schema/utilxsi:schemaLocationhttp://www.springframework.org/schema/util http://www.springframework.org/schema/beans/spring-util.xsdxsi:schemaLocationhttp://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd!-- Map集合util标签 --
util:map idxxx/util:map!-- List集合util标签 --
util:list idxxx/util:list
!--引用集合类型bean注入--
bean idstuUtil classcn.tedu.spring.dilistmap.Studentproperty namesName value孔慈/property nameage value36/property nameteacherMap refteacherMap/propertyproperty namecourseList refcourseList/property
/beanutil:map idteacherMapentrykeyvalue10000/value/keyvalue小泽老师/value/entryentrykeyvalue10001/value/keyvalue王老师/value/entry
/util:maputil:list idcourseListvalueSpring/valuevalueSpringMVC/valuevalueMyBatis/value
/util:list
3.2.9、p命名空间注入
这也是一种注入方式可以在xml中定义命名空间或者叫名称空间可以简化xml代码。 ① 在xml配置文件中定义命名空间
xmlns:phttp://www.springframework.org/schema/p
② 在xml文件进行命名空间属性注入
!-- p命名空间注入: 注入学生属性 --
bean idstudentp classcn.tedu.spring.iocxml.dimap.Student p:sid100 p:sname铁锤妹妹 p:courseList-refcourseList p:teacherMap-refteacherMap
3.2.10、引入外部属性文件
当前所有的配置和数据都在xml文件中一个文件中有很多bean修改和维护起来很不方便生产环境中会把特定的固定值放到外部文件中然后引入外部文件进行注入比如数据库连接信息。将外部文件中的数据引入xml配置文件进行注入,xml文件中的内容就不需要更改如果更改数据直接更改外部文件的数据就行
1、以数据库文件为例引入连接数据库的依赖包
!--01mysql包--dependencygroupIdmysql/groupIdartifactIdmysql-connector-java/artifactIdversion8.0.33/version/dependency
!--连接池依赖--
dependencygroupIdcom.alibaba/groupIdartifactIddruid/artifactIdversion1.2.8/version
/dependency Spring配置文件修改 引入外部文件jdbc.properties
!-- 引入外部属性文件 jdbc.properties--context:property-placeholder locationclasspath:jdbc.properties/context:property-placeholder!-- 完成数据库信息注入 --bean iddruidDataSource classcom.alibaba.druid.pool.DruidDataSourceproperty nameurl value${jdbc.url}/propertyproperty nameusername value${jdbc.user}/propertyproperty namepassword value${jdbc.password}/propertyproperty namedriverClassName value${jdbc.driver}/property/beanjdbc.properties文件
jdbc.userroot
jdbc.password123456
jdbc.urljdbc:mysql://localhost:3306/mybatis01?useSSLfalseuseUnicodetruecharacterEncodingUTF-8useServerPrepStmtstrue
jdbc.drivercom.mysql.jdbc.Driverspring中properties文件中的属性为什么要加jdbc作为前缀 如果属性文件中配置的不是jdbc.username而是usernameroot666那么使用${username}获取到的不是root666而是计算机的名称。
原因系统属性的优先级比我们属性文件中的高替换了我们的usernameroot666。
3.2.11、Bean的作用域范围Scope
bean的作用域是指在交给spring创建bean对象时可以指定是单实例还是多实例通过bean标签中的scope属性来指定默认是单实例。
单实例 单实例Singleton是指某个类只能创建唯一的一个实例对象并且该类提供一个全局的访问点静态方法来让外界获取这个实例常常用在那些只需要一个实例来处理所有任务的场景下例如配置类或数据库连接池等。多实例 多实例Multiple Instance则是指可以在同一个类的定义下创建多个实例对象。每个对象都是相互独立的有自己的状态和行为常常用于需要同时处理多个任务的场景。 1、单实例scope“singleton” !--默认就是singleton单实例--bean idScope1 classcom.bipt.Scope.Order scopesingletonproperty nameorderId value001//bean2、多实例scope“prototype” bean idScope1 classcom.bipt.Scope.Order scopeprototypeproperty nameorderId value001//bean3.2.12、Bean的生命周期 ① 创建包life创建类User
package cn.tedu.spring.life;import org.springframework.beans.BeansException;public class User {private String username;// 1.无参数构造public User(){System.out.println(1-bean对象创建调用无参数构造。);}// 3.初始化阶段public void initMethod(){System.out.println(3-bean对象初始化调用指定的初始化方法);}// 5.销毁阶段public void destoryMethod(){System.out.println(5-bean对象销毁调用指定的销毁方法);}public String getUsername() {return username;}public void setUsername(String username) {this.username username;// 2.给bean对象属性赋值System.out.println(2-通过set方法给bean对象赋值。);}Overridepublic String toString() {return User{ username username \ };}
}
② 创建spring配置文件 bean-life.xml
bean iduser classcn.tedu.spring.iocxml.life.User scopesingleton init-methodinitMethod destroy-methoddestroyMethodproperty nameusername value聂风/property
/bean
③ 创建测试类TestUser测试 ClassPathXmlApplicationContext类中才有close方法所以拆改那就context对象的时候要使用类不再用ApplicationContext接口。
Test
public void testUser(){ClassPathXmlApplicationContext context new ClassPathXmlApplicationContext(bean-life.xml);User user context.getBean(user, User.class);// 4.bean对象初始化完成可以使用System.out.println(4-bean对象初始化完成开发者可以使用了。);// 销毁beancontext.close();
}
1、初始化前后两步测试 1、后置处理器处理演示新建类MyBeanPost实现BeanPostProcessor
public class MyBeanPost implements BeanPostProcessor {// BeanPostProcessor接口Overridepublic Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {System.out.println(3之前:bean后置处理器在初始化之前执行。 beanName : bean);return bean;}Overridepublic Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {System.out.println(3之后:bean后置处理器在初始化之后执行。 beanName : bean);return bean;}
}
2、配置文件中配置
!-- bean的后置处理器需要放到IoC容器中才能生效 --
bean idmyBeanPost classcn.tedu.spring.life.MyBeanPost/bean3.2.13FactoryBean public class MyFactoryBean implements FactoryBeanUser {Overridepublic User getObject() throws Exception {return new User();}Overridepublic Class? getObjectType() {return User.class;}
}bean iduser classcom.bipt.FactoryBean.MyFactoryBeanpublic class FactoryBeanTest {Testpublic void Test(){ApplicationContext app1new ClassPathXmlApplicationContext(beanFactoryBean.xml);User user (User) app1.getBean(user);System.out.println(user);}
}3.2.14自动装配
自动装配说明 根据指定的策略在IoC容器中匹配某一个bean自动为指定的bean中的所依赖的类类型或者接口类型属性赋值。 ① 创建包auto创建部门和员工的两个java类
② 部门类 Dept
public class Dept {private String dName;Overridepublic String toString() {return Dept{ dName dName \ };}public String getdName() {return dName;}public void setdName(String dName) {this.dName dName;}
}
③ 员工类 Emp,员工类中定义部门的对象并在配置文件中的Emp对应的bean标签引入autoWire
public class Emp {private String eName;private Dept dept;Overridepublic String toString() {return Emp{ eName eName \ , dept dept };}public String geteName() {return eName;}public void seteName(String eName) {this.eName eName;}public Dept getDept() {return dept;}public void setDept(Dept dept) {this.dept dept;}
} ④ 创建spring配置文件bean-auto.xml
!--通过byType和byName自动装配--
bean iddept classcn.tedu.spring.iocxml.auto.Deptproperty namedName value技术部/property
/bean!--autowirebyType 或者 autowirebyName--
bean idemp classcn.tedu.spring.iocxml.auto.Emp autowirebyTypeproperty nameeName value步惊云/property
/bean
3.3、基于注解管理bean
从Java5开始Java增加了对注解Annotation的支持它是代码中的一种特殊标记可以在编译、类加载和运行时被读取执行相应的处理。开发人员可以通过注解在不改变原有代码和逻辑的情况下在源代码中嵌入补充信息。 Spring从2.5版本开始提供了对注解技术的全面支持我们可以使用注解来实现自动装配简化Spring的xml配置。
3.3.1、使用步骤
Spring通过注解实现自动装配
1、引入SpringFramework依赖
dependenciesdependencygroupIdorg.springframework/groupIdartifactIdspring-context/artifactIdversion5.3.24/version/dependency
/dependencies
2、开启组件扫描 Spring默认不使用注解装配Bean因此需要在Spring的xml配置中通过context:component-scan元素开启Spring Beans的自动扫描功能。开启此功能后Spring会自动从扫描指定的包basepackage属性设置及其子包下的所有类如果类上使用了Component注解就将该类装配到容器中。 ?xml version1.0 encodingUTF-8?
beans xmlnshttp://www.springframework.org/schema/beansxmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexmlns:contexthttp://www.springframework.org/schema/contextxsi:schemaLocationhttp://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!-- 2.开启组件扫描让spring可以通过注解方式实现bean管理包括创建对象、属性注入 --!-- base-package:扫描哪个包中的注解在com.bipt的包或者子包中建了类在类上、属性上、方法上加了spring的Component注解这里就能扫描到--context:component-scan base-packagecom.bipt/context:component-scan/beans
3、使用注解定义 Spring提供了以下多个注解这些注解可以直接标注在java类上将它们定义成Spring Bean。4、Bean 依赖注入:Autowired注入Resource注入 单独使用Autowired注解默认根据类型装配byType Autowired注解有一个required属性默认值是true表示在注入的时候要求被注入的Bean必须存在如果不存在则报错。如果required属性设置为false表示注入的Bean存在或者不存在都没关系存在就注入不存在也不报错。
3.3.2、Autowired,默认bytype装配
1、基本环境配置 2、AutoWired在属性上注入 1、bean对象创建通过加注解实现bean对象创建,注意注解是加在实现类上不是接口上
创建了三个bean对象 2、在Controller实现类中注入Service在Service实现类中注入Dao
Controller
public class UserControllerImp implements UserController{// 注入service// 第一种方式属性注入Autowired // 根据类型找到对象完成注入private UserService userService;public void addController(){System.out.println(Controller实现类中的方法...............););userService.addService();}
}Service
public class UserServiceImp implements UserService{// 第一种方式属性注入Autowiredprivate UserDao userDao;Overridepublic void addService() {System.out.println(Service实现类中的方法...............);}
}UserImp
Repository
public class UserDaoImp implements UserDao{Overridepublic void addDao() {System.out.println(Dao实现类中的方法...............);}
}测试 org.junit.Testpublic void testUser2(){ApplicationContext context new ClassPathXmlApplicationContext(bean1.xml);UserController controller context.getBean(UserController.class);controller.addController();}3、AutoWired在set方法上注入
// 方式二通过set方法注入
private UserService userService;Autowired
public void setUserService(UserService userService) {this.userService userService;
}
4、AutoWired在构造方法上注入
// 第三种方式构造方法注入
private UserService userService;Autowired
public UserController(UserService userService) {this.userService userService;
}
5、AutoWired在形参上注入
// 第四种方式形参注入
private UserService userService;public UserController(Autowired UserService userService) {this.userService userService;
}
6、Autowire注解和Qualifier注解联合
而Autowired注解根据byType定位如果某接口存在两个实现类会报错需要通过Qualifier指定使用哪个实现类。
创建第二个实现类
Repository
public class UserDaoImp2 implements UserDao{Overridepublic void addDao() {System.out.println(UserDao第二个实现类);}
}通过注解Qualifier解决问题value userDaoImp2默认是类名首字母小写在Spring框架中Qualifier注解通常与Autowired注解一起使用以便在存在多个相同类型Bean的情况下指定要注入的具体Bean。其中Qualifier注解的value属性用于指定注入的Bean的名称。传入的类名首字母小写这是因为Spring在默认情况下Bean的名称是类名的首字母小写的形式
Service
public class UserServiceImp implements UserService{// 第一种方式属性注入AutowiredQualifier(value userDaoImp2)private UserDao userDao;Overridepublic void addService() {System.out.println(Service实现类中的方法...............);userDao.addDao();}}3.3.3、 Resource注入默认ByName装配 !-- https://mvnrepository.com/artifact/jakarta.annotation/jakarta.annotation-api --
dependencygroupIdjakarta.annotation/groupIdartifactIdjakarta.annotation-api/artifactIdversion2.1.1/version
/dependency
1、根据名称进行注入
1、默认根据名称注入 2、如果名称找不到或没配置使用属性名注入 3、Servicevalue“类名”类名没有配置属性名也和类Repository(value “UserDaoImp2”)中的UserDaoImp2不一样会自动变成根据类型查找ByType
3.3.4面对多个实现类两种注解方式的解决方法
报错由于有两个实现类不指定使用哪个
1、Autowire注解和Qualifier注解联合
Qualifier(value “userDaoImp2”)value指定使用哪个类传入的是类名首字母小写
Service
public class UserServiceImp implements UserService{// 第一种方式属性注入AutowiredQualifier(value userDaoImp2)private UserDao userDao;Overridepublic void addService() {System.out.println(Service实现类中的方法...............);userDao.addDao();}}2、Resource注解
通过name UserDaoImp2指定类UserDaoImp2是对应类Repository(value “UserDaoImp2”)注解中 定义的别名Resource(name “UserDaoImp2”)中的value不可以像Qualifier注解中的value直接可以使用类首字母小写名。
Resource(name UserDaoImp2)private UserDao userDao;3.3.5、全注解开发
全注解开发就是不再使用spring配置文件了写一个配置类来代替配置文件。 ① 工程下创建包config创建类SpringConfig
// 配置类
Configuration
// 开启组件扫描
ComponentScan(com.bipt)
public class SpringConfig {
}
测试类原本加载配置文件.xml的语句改成**加载配置类**AnnotationConfigApplicationContext(SpringConfig.class);
public class TestUserControllerAnno {public static void main(String[] args) {// 加载配置类ApplicationContext context new AnnotationConfigApplicationContext(SpringConfig.class);UserController controller context.getBean(UserController.class);controller.add();}
}
四、手写IOC
五、AOP
5.1、概述
5.1.1、概念
AOPAspect Oriented Programming是一种设计思想是软件设计领域中的面向切面编程它是面向对象编程的一种补充和完善它以通过预编译方式和运行期动态代理方式实现在不修改源代码的情况下给程序动态统一添加额外功能的一种技术。利用AOP可以对业务逻辑的各个部分进行隔离从而使得业务逻辑各部分之间的耦合度降低提高程序的可重用性同时提高了开发的效率。
5.1.2、作用
代码重用和模块化AOP可以将一些通用的行为例如日志记录、安全控制、事务管理等抽象出来形成可重用的模块避免在每个业务逻辑中都重复编写这些代码。分离关注点AOP将不同的关注点分离开来使得各个模块间职责更加清晰明确代码的可读性和可维护性也更强。简化开发AOP可以帮助开发人员将关注点从业务逻辑中分离出来使得开发更加简单明了。提高系统的可扩展性在系统需求变化时只需要修改AOP模块而不是修改业务逻辑这可以使得系统更加易于扩展和维护。降低代码耦合度AOP的作用是将不同的关注点分离开来这可以避免代码之间的紧耦合提高代码的可复用性和可维护性。
5.2、代理
代理模式是一种结构型设计模式它使得代理对象可以代表另一个对象进行访问。它是二十三种设计模式中的一种属于结构型模式。它的作用就是通过提供一个代理类让我们在调用目标方法的时候不再是直接对目标方法进行调用而是通过代理类间接调用。让不属于目标方法核心逻辑的代码从目标方法中剥离出来——解耦。调用目标方法时先调用代理对象的方法减少对目标方法的调用和打扰同时让附加功能能够集中在一起也有利于统一维护。 代理将非核心逻辑剥离出来以后封装这些非核心逻辑的类、对象、方法
5.2.0、场景
1、场景一最简单计算器功能实现
1、对于一个计算器拥有加减乘除的功能相当于一个计算器类对应着加减乘除四个方法。 1定义一个计算器接口
public interface Calculator {//加public int add1(int i, int j);//减public int sub2(int i, int j);//乘public int mul3(int i, int j);//除public int div4(int i, int j);
}
2计算器接口实现类
package com.bipt;/*** author Pengwei Qu* Date 2024/9/4 21:24*/
public class CalculatorImpl implements Calculator{Overridepublic int add1(int i, int j) {// 核心业务逻辑int addResultij;System.out.println(计算结果是addResult);return addResult;}Overridepublic int sub2(int i, int j) {// 核心业务逻辑int subResulti-j;System.out.println(计算结果是subResult);return subResult;}Overridepublic int mul3(int i, int j) {// 核心业务逻辑int mulResulti*j;System.out.println(计算结果是mulResult);return mulResult;}Overridepublic int div4(int i, int j) {// 核心业务逻辑int divResulti/j;System.out.println(计算结果是divResult);return divResult;}
}
需要哪一个功能直接new出来实现类通过对象调用即可
2、带有日志功能的计算器
在每一个方法中增加日志功能在调用加减乘除方法进行计算时在开始计算的前后有一个输出日志提示的功能。
package com.bipt;/*** author Pengwei Qu* Date 2024/9/4 21:24*/
public class CalculatorImpl implements Calculator{Overridepublic int add1(int i, int j) {// 附加功能由代理类中的代理方法来实现System.out.println([日志] add1 方法开始了参数是 i , j);// 核心业务逻辑int Resultij;System.out.println([日志] add1 方法结束了结果是 Result);return Result;}Overridepublic int sub2(int i, int j) {System.out.println([日志] sub2 方法开始了参数是 i , j);// 核心业务逻辑int Resulti-j;System.out.println([日志] sub2 方法结束了结果是 Result);return Result;}Overridepublic int mul3(int i, int j) {System.out.println([日志] mul3 方法开始了参数是 i , j);// 核心业务逻辑int Resulti*j;System.out.println([日志] mul3 方法结束了结果是 Result);return Result;}Overridepublic int div4(int i, int j) {System.out.println([日志] div4 方法开始了参数是 i , j);// 核心业务逻辑int Resulti/j;System.out.println([日志] div4 方法结束了结果是 Result);return Result;}
}
问题分析
对于加减乘除每一个方法对然增加了日志功能但是主要功能还是加减乘除计算计算功能前后添加的提示语句只是辅助功能即使这些辅助功能没有照样可以实现加减乘除功能。现在核心功能和辅助功能全部定义在同一个类中而且每一个核心功能前后添加的辅助功能都一样因此可以把辅助功能提出来专门用一个类写也就是代理类。核心功能还用原来的类只实现加减乘除的功能即可。
5.2.1、静态代理
在静态代理中代理类是在编译时期创建的代理类和委托类实现相同的接口或继承相同的类并在代理类中实现委托类中的方法在调用委托类的方法之前或之后执行一些附加操作。
抽象角色:《interface》Subject通过接口或抽象类声明真实角色实现的业务方法。代理角色:Proxy实现抽象角色是真实角色的代理通过真实角色的业务逻辑方法来实现抽象方法并可以附加自己的操作。真实角色RealSubject实现抽象角色定义真实角色所要实现的业务逻辑供代理角色调用。
1核心代码类代理目标
/*核心方法类*/
public class Core_Calculator implements Calculator{Overridepublic int add1(int i, int j) {int result i j;return result;}Overridepublic int sub2(int i, int j) {int result i - j;return result;}Overridepublic int mul3(int i, int j) {int result i * j;return result;}Overridepublic int div4(int i, int j) {int result i / j;return result;}
}2以下代码是代理角色的代码
//代理类
public class CalculatorStaticProxy implements Calculator{// 将被代理的目标对象声明为成员变量private Calculator target;//通过构造函数赋值public CalculatorStaticProxy(Calculator target) {this.target target;}Overridepublic int add1(int i, int j) {// 附加功能由代理类中的代理方法来实现System.out.println([日志] add 方法开始了参数是 i , j);// 通过目标对象来实现核心业务逻辑int addResult target.add1(i, j);// 附加功能由代理类中的代理方法来实现System.out.println([日志] add 方法结束了结果是 addResult);return addResult;}Overridepublic int sub2(int i, int j) {// 附加功能由代理类中的代理方法来实现System.out.println([日志] sub2 方法开始了参数是 i , j);// 通过目标对象来实现核心业务逻辑int addResult target.sub2(i, j);// 附加功能由代理类中的代理方法来实现System.out.println([日志] sub2 方法结束了结果是 addResult);return addResult;}Overridepublic int mul3(int i, int j) {// 附加功能由代理类中的代理方法来实现System.out.println([日志] mul3 方法开始了参数是 i , j);// 通过目标对象来实现核心业务逻辑int addResult target.mul3(i, j);// 附加功能由代理类中的代理方法来实现System.out.println([日志] mul3 方法结束了结果是 addResult);return addResult;}Overridepublic int div4(int i, int j) {// 附加功能由代理类中的代理方法来实现System.out.println([日志] div4 方法开始了参数是 i , j);// 通过目标对象来实现核心业务逻辑int addResult target.div4(i, j);// 附加功能由代理类中的代理方法来实现System.out.println([日志] div4 方法结束了结果是 addResult);return addResult;}
} 1、静态类存在的问题
核心类代码确实是抽出来了其余的非核心代码通过代理的方式代理类进行编写
问题一代理类中的非核心代码写死了如果换个需求这个需求也是非核心代码需要另外再创建一个代理类。问题二非核心类只是从核心代码中抽出来了又写在了代理类中实际上非核心代码并没有真正意义的抽离出来。
5.2.2、动态代理动态创建代理对象
动态代理就可以实现以上问题的解决将非核心代码日志代码集中到一个代理类中将来任何日志需求都可以直接通过这一个代理类实现。在动态代理中代理类是在运行时期动态创建的。它不需要事先知道委托类的接口或实现类而是在运行时期通过 Java 反射机制动态生成代理类 创建动态代理的源码 第一个参数CLassLoader:加载动态生成代理类的类加载器 第二个参数CLass[]interfaces:目标对象实现的所有接口cLass类型数组 目标类实现了哪些接口这些接口的class类型存入数组 第三个参数InvocationHandler:设置代理对象实现目标对象方法的过程
InvocationHandler handler new MyInvocationHandler(...);
Foo f (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(), new class?[]{Foo.class}, InvocationHandler handler);
创建一个代理工厂类
package org.example; import lombok.val; import javax.print.attribute.standard.JobKOctets;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy; public class ProxyFactory { Object target;//目标对象需要找代理的类拥有核心功能的类 public ProxyFactory(Object target) { this.target target; } public Object getProxy() { //第一个参数CLassLoader:加载动态生成代理类的类加载器 ClassLoader cLassLoader target.getClass().getClassLoader(); //第二个参数CLass[]interfaces:目标对象实现的所有接口cLass类型数组 Class[] classes target.getClass().getInterfaces(); //第三个参数InvocationHandler:设置代理对象实现目标对象方法的过程 //匿名内部类的方式InvocationHandler()是一个接口InvocationHandler invocationHandler new InvocationHandler() { Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //调用方法前日志 System.out.println([动态代理][调用前日志] method.getName() 参数 args); //调用目标方法 此处的invoke与方法名invoke不是一个东西Object result method.invoke(target, args); //调用方法后日志 System.out.println([动态代理][调用后日志] method.getName() 参数 args); return result; } }; return Proxy.newProxyInstance(cLassLoader, classes, invocationHandler); }
}
编写测试类
Test
public void calculatorTest(){ ProxyFactory proxyFactorynew ProxyFactory(new CalculatorImpl()); Calculator proxy(Calculator) proxyFactory.getProxy(); proxy.add(1,1);
}
输出结果
[动态代理][调用前日志]add参数[Ljava.lang.Object;7d0587f1
result2
[动态代理][调用后日志]add参数[Ljava.lang.Object;7d0587f1
5.2.3动态代理的两种类型 5.3、AOP
5.3.1概念
AOPAspect Oriented Programming是一种设计思想是软件设计领域中的面向切面编程它是面向对象编程的一种补充和完善它以通过预编译方式和运行期动态代理方式实现在不修改源代码的情况下给程序动态统一添加额外功能的一种技术。利用AOP可以对业务逻辑的各个部分进行隔离从而使得业务逻辑各部分之间的耦合度降低提高程序的可重用性同时提高了开发的效率。
5.3.2相关术语
1、横切关注点
2、通知增强 3、切面
在AOP中切面指的是横切关注点和通知的组合它是一个模块化的横向分割可以理解为一个横向的切片。切面是对横切关注点和通知的封装它包含了一组切点和通知用于描述在何处、何时、以及如何执行横切逻辑。切面可以在不修改原代码的情况下对原有的代码进行功能的增强或改变。通常切面是以一个类的形式存在的它包含了一个或多个通知和一个或多个切点。
简单来说切面就是封装通知方法的类
4、目标对象
在AOPAspect-Oriented Programming中**目标Target**是指被通知的对象或者被切面所影响的对象。它是应用程序中的一个具体元素可以是类、接口、方法或者字段等。 简单点说目标就是被代理的目标对象
5、代理对象
在AOPAspect-Oriented Programming中**代理Proxy**是一种设计模式用于控制对目标对象的访问并在访问过程中插入额外的逻辑。 简单点说代理就是向目标对象应用通知增强之后创建的代理对象
6、连接点
在 AOP 中连接点Join Point表示在程序执行过程中能够插入一个切面的点例如方法调用、异常处理、字段访问等。连接点定义了在程序中的哪个位置可以应用切面。切面可以在连接点前后增加额外的处理逻辑从而影响程序的行为。通俗地讲连接点就是在程序执行中可以被拦截的地方
7、切入点
在 AOP 中切入点Join Point是指程序执行过程中明确的点通常是方法调用的时候也可以是异常处理程序的处理过程。切入点定义了哪些方法是需要被拦截或增强的例如我只希望乘法和加法对应的实现方法进行增强是 AOP 中最重要的概念之一。切入点通常以方法的形式被定义比如某个类的所有方法、某个包下的所有方法等等。在 AOP 中通常会使用表达式语言定义切入点如使用 Spring AOP 中的 Pointcut 注解定义
表达式
5.3.3解用AspectJ注解实现步骤 1、引入依赖
!-- https://mvnrepository.com/artifact/org.springframework/spring-aop --
dependencygroupIdorg.springframework/groupIdartifactIdspring-aop/artifactIdversion6.1.11/version
/dependency
!-- https://mvnrepository.com/artifact/org.springframework/spring-aspects --
dependencygroupIdorg.springframework/groupIdartifactIdspring-aspects/artifactIdversion6.0.10/version
/dependency
2、创建目标接资源实现类没有接口默认使用cglib代理 Service(userService)
public class UserService {//目标类public void login(){//目标方法System.out.println(正在登录............);}
}创建切面类 Aspect表示这个类是一个切面类 Component注解保证这个切面类能够放入IOC容器
Aspect
Component
public class LogAspect {// 前置通知// 异常通知// 返回通知// 后置通知// 环绕通知……
} 3、配置Spring文件
?xml version1.0 encodingUTF-8?
beans xmlnshttp://www.springframework.org/schema/beansxmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexmlns:contexthttp://www.springframework.org/schema/contextxmlns:aophttp://www.springframework.org/schema/aopxsi:schemaLocationhttp://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://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.xsd!--基于注解的AOP的实现1、将目标对象和切面交给IOC容器管理注解扫描2、开启AspectJ的自动代理为目标对象自动生成代理3、将切面类通过注解Aspect标识--context:component-scan base-packagecom.bipt.AOP_Annotation/context:component-scanaop:aspectj-autoproxy /
/beans
测试IOC容器能否正常创建bean
public class Test {org.junit.Testpublic void test(){ApplicationContext applicationContextnew ClassPathXmlApplicationContext(bean.xml);UserService userService applicationContext.getBean(userService, UserService.class);userService.login();}
}4、设置切面类
/设置切入点与通知类型/
Aspect
Component
public class LogAspect {// 前置通知Before(execution(public int com.atguigu.aop.annotation.CalculatorImpl.*(..)))
public void beforeMethod(JoinPoint joinPoint){// getSignature获取连接点签名getName获取连接点名称String methodName joinPoint.getSignature().getName();String args Arrays.toString(joinPoint.getArgs());System.out.println(Logger--前置通知方法名methodName参数args);
}// 异常通知AfterThrowing(value execution(* com.atguigu.aop.annotation.CalculatorImpl.*(..)), throwing ex)
public void afterThrowingMethod(JoinPoint joinPoint, Throwable ex){String methodName joinPoint.getSignature().getName();System.out.println(Logger--异常通知方法名methodName异常ex);
}// 返回通知AfterReturning(value execution(* com.atguigu.aop.annotation.CalculatorImpl.*(..)), returning result)
public void afterReturningMethod(JoinPoint joinPoint, Object result){String methodName joinPoint.getSignature().getName();System.out.println(Logger--返回通知方法名methodName结果result);
}// 后置通知After(execution(* com.atguigu.aop.annotation.CalculatorImpl.*(..)))public void afterMethod(JoinPoint joinPoint){String methodName joinPoint.getSignature().getName();System.out.println(Logger--后置通知方法名methodName);}// 环绕通知Around(execution(* com.atguigu.aop.annotation.CalculatorImpl.*(..)))
public Object aroundMethod(ProceedingJoinPoint joinPoint){String methodName joinPoint.getSignature().getName();String args Arrays.toString(joinPoint.getArgs());Object result null;try {System.out.println(环绕通知--目标对象方法执行之前);//目标对象连接点方法的执行result joinPoint.proceed();System.out.println(环绕通知--目标对象方法返回值之后);} catch (Throwable throwable) {throwable.printStackTrace();System.out.println(环绕通知--目标对象方法出现异常时);} finally {System.out.println(环绕通知--目标对象方法执行完毕);}return result;
}
}
5.3.4重用切入点
1、声明
Pointcut(execution(* com.atguigu.aop.annotation.*.*(..)))
public void pointCut(){}
2、同切面使用
在这里插入代码片Before(pointCut())
public void beforeMethod(JoinPoint joinPoint){String methodName joinPoint.getSignature().getName();String args Arrays.toString(joinPoint.getArgs());System.out.println(Logger--前置通知方法名methodName参数args);
}
3、不同切面使用
Before(com.atguigu.aop.CommonPointCut.pointCut())
public void beforeMethod(JoinPoint joinPoint){String methodName joinPoint.getSignature().getName();String args Arrays.toString(joinPoint.getArgs());System.out.println(Logger--前置通知方法名methodName参数args);
}