1. IDEA环境配置
- ctrl+F12查看方法结构
- ctrl+H 查看类图
- ctrl+D向下复制一行
- Alt+Insert 提供构造/get/set/to String
- Ctel+Alt+T 选中代码快速try/catch
eclipse工作空间里面
项目没有必然联系
IDEA没有工作空间的概念
工作的维度是项目,项目间有父子级关系
新建文件夹,IDEA openfile
git上新建仓库
在IDEA工作空间根目录打开cmd窗口 ,输入提示代码
上传和下拉代码
插件
file–setting–plugins–chinese 汉化包
file–setting–plugins–Lombok 热部署
键位配置
file–setting–Keymap
字母大小写提示
file–setting–Editor–General–Mouse Control
file–setting–Editor–General–Code Completion–Match case( 取消区分大小写提示)
设置自动编译
file–setting–Build,Execution,Deployment–Compiler–Build and compiler
设置自动保存
file–setting–Appearance&Behaver–System Setting–Synchorn
参数提示
file–setting–Editor–Code Completion–Parameter Info
修改字符集
file–setting–Editor–file Encodings–都改为U8
配置Maven(项目构建工具)
file–setting–Build,Execution,Deployment–Build Tools–Maven
选中pom右键,添加Maven项目模板
关于Maven命令
- maven坐标
<groupId>com.jt</groupId> 不同公司不同组ID<artifactId>Spring_Demo_1_IOC</artifactId> 不同项目不同项目名<version>1.0-SNAPSHOT</version> 版本自己说了算
主要作用:用来检索jar包文件,maven通过这样的方式管理了几乎所有的开源jar包(中央/私服镜像),本地使用哪个依赖哪个
大型项目中将公共的文件/工具/方法/代码进行抽取,打包成jar包
- 本地仓库的jar包主要作用就是被自己的子项目依赖体现松耦合思想
- install 生成两个包,一个在target目录下,部署项目时使用,另外一个在本地仓库中,主要作用是被自己的项目依赖 java—class—jar
- clean 删除target目录
- validate 校验需要特殊校验的项目
- compile 编译,将原有的.java文件编译为.class文件
- test 执行maven中的测试方法
- package 生成.jar包文件 class—jar
- site 生成maven报告
xml文件中的转义字符
转义字符 | xml中符号 |
---|---|
< ; | < |
> ; | > |
& ; | & 和 |
&aops ; | ’ 单引号 |
" ; | " 双引号 |
万能转义字符
<![CDATA[XXX任意字符]]> 按照程序员写的任意字符展现,不做解析
2.框架思想(是什么)
说明:将一些公共的模块(功能),进行高级的抽取(接口/父级)形成通用的代码体,使用时只需要引入特定的jar包/类(class)/方法即可以使用框架中的功能
实际意义:简化代码的开发,提高软件的扩展性
2.1MVC模型说明(代码分层思想)
说明:在大型项目中由于模块众多,如果将所有的项目都写到一起则代码特别的混乱.不便于后期的维护. 所以通过MVC设计模型,将代码进行分级.
MVC是一种程序设计的模型,是适用于任何框架,任何软件的开发思想,这种思想主要目的是实现代码的解耦
三层结构(CSD)是我们基于MVC框架思想之上的代码的具体实现,也就是解耦的具体体现,无论分成几层,都是MVC思想的产物
模型-视图-控制器(Model View Controller)
1️⃣ Model:持久层:代码与数据库进行交互的代码(Mybatis–dao层)
mybaits面向接口开发,接口就是持久层
接口名的全类名==映射文件(xml)的namespace属性
sql语句的id=接口方法名
2️⃣ View:视图层 一般指用户看到的内容(页面)
3️⃣ Controller控制层:完成某项业务的具体操作过程分为两级(Controller层和Service层)
官网https://spring.io/projects/spring-framework
GA为稳定版
目标:
-
IOC/DI思想
- IOC 核心: 对象的创建
- 通过bean标签进行创建 属性
id/class
- 工厂模式:创建抽象类/接口 对象
- 单例多例/懒加载: scope属性
- 生命周期:1.创建对象 2初始化数据 3.被用户调用 4.对象销毁
- 通过bean标签进行创建 属性
- DI核心: 为属性赋值
- set注入 – property
- 构造注入(必须有无参构造)– constructor-arg
- 属性赋值的高级操作
- 集合 list/set/map
- 对象的引用 ref
- IOC 核心: 对象的创建
-
Spring MVC三层代码结构
- 属性注入,调用方法传参
-
Spring注解开发
- 自动装配
- 配置类 管理数据包扫描,动态获取外部数据
- 接口多实现类
- 注解模式执行过车
- 官网
https://spring.io/projects/spring-framework
GA为稳定版
目标:
-
IOC/DI思想
- IOC 核心: 对象的创建
- 通过bean标签进行创建 属性
id/class
- 工厂模式:创建抽象类/接口 对象
- 单例多例/懒加载: scope属性
- 生命周期:1.创建对象 2初始化数据 3.被用户调用 4.对象销毁
- 通过bean标签进行创建 属性
- DI核心: 为属性赋值
- set注入 – property
- 构造注入(必须有无参构造)– constructor-arg
- 属性赋值的高级操作
- 集合 list/set/map
- 对象的引用 ref
- Spring MVC三层代码结构
- 属性注入,调用方法传参
- Spring注解开发 4个
- 自动装配 3个属性注解 @Autowired
- 全注解方式MVC结构
- 配置类@包扫描@, 管理数据@动态获取外部数据@
- 接口多实现类
- 注解模式执行过程(Spring内存加载机制)
- IOC 核心: 对象的创建
-
SpringAOP
3.Spring IOC | DI
3.1Spring介绍
Spring专注于企业应用程序的“管道”,以便团队可以专注于应用程序级别的业务逻辑,而不必与特定的部署环境建立不必要的联系。
Spring主要作用是将其他框架进行整合,以一种统一的通用的方式进行管理框架对象(框架的大管家)
Spring框架J2EE应用程序框架,是针对bean的生命周期进行管理的轻量级容器(lightweight container)。
Bean:被Spring容器(container)管理的对象称之为Bean
Spring框架中实例化框架对象,框架间以通用方式进行调用
提供功能强大IOC{控制反转(Inversion of Control)}、AOP{面向切面编程 Aspect Oriented Programming)}
Web MVC{模型-视图-控制器(Model View Controller) }等功能。
Spring可以单独应用于构筑应用程序,也可以和Struts、Webwork、Tapestry等众多Web框架组合使用,
并且可以与 Swing等桌面应用程序AP组合。因此, Spring不仅仅能应用于JEE应用程序之中,也可以应用于桌面应用程序以及小应用程序之中。
Spring框架主要由七部分组成,分别是 Spring Core、 Spring AOP、 Spring ORM、 Spring DAO、Spring Context、 Spring Web和 Spring Web MVC。
- 引入(为什么使用SpringIOC)
代码分层是为了解耦
分层之后应该以一种统一的方式进行调用
如何做到统一???
要想代码做到统一的规范,就要进行抽取,抽取共性形成接口,接口中的方法用于定义行为规范,实现类实现不同逻辑(需求)代码
实现类new持久层类的对象,对象.方法即可调用持久层代码,实现与数据库的交互
为了避免多次创建对象,调用对象应为成员变量
new持久层类的对象,代码耦合性太高 - 优化
不能解决耦合性高的问题—不在实现类new持久层对象–IOC思想
传统代码直接通过new的方式创建对象,这样的方式将对象与对象紧紧的绑定到一起,不便于代码的扩展,所以需要进行松耦合处理
3.2 IOC
核心: 对象的创建
1.通过bean标签进行创建 属性 id/class
2.工厂模式:创建抽象类/接口 对象
3.单例多例/懒加载:
4.生命周期
3.2.1 IOC思想(租房问题)
http://jinnianshilongnian.iteye.com/blog/1413846
是什么:
将创建对象的权利交给Spring管理,由Spring管理对象的生命周期
对象的生命周期: 创建—初始化—使用—销毁
控制反轉:
-
谁控制谁?控制了什么?
我们直接在对象内部通过new进行创建对象,是程序主动去创建依赖对象;
而IOC有专门一个容器来创建这些对象
即由Ioc容器来控制对象的创建
主要控制了外部资源获取(不只是对象包括比如文件等)。 -
为什么是反转?哪里反转了?
正转:传统应用程序是由我们自己在对象中主动控制去直接获取依赖对象,也就是正转
反转:由容器来帮忙创建及注入依赖对象
由容器帮我们查找及注入依赖对象,对象只是被动的接受依赖对象,所以是反转
依赖对象的获取被反转了。
应用程序原本是老大,要获取什么资源都是主动出击,但是在IoC/DI思想中,应用程序就变成被动的了,被动的等待IoC容器来创建并注入它所需要的资源了。
IoC很好的体现了面向对象设计法则之一—— 好莱坞法则:“别找我们,我们找你”;即由IoC容器帮对象找相应的依赖对象并注入,而不是由对象主动去找。
通过控制反转,对象在被创建的时候,由一个调控系统内所有对象的外界实体将其所依赖的对象的引用传递给它。也可以说,依赖被注入到对象中。
需要什么对象,容器帮忙创建,依赖的对象可以随时随地的变化,容器给谁你用谁,而Service注入的永远是对象的引用引入IOC从根本上解决业务层与依赖对象的耦合性问题
3.2.2 Spring IOC 具体实现(怎么做)
检查JDK环境配置
jdk版本:Java -version
检查PATH路径:检查环境变量中有几个JDK,如果不确定,将自己的JDK设置为第一项
创建项目
蓝色方框表示Maven方式构建的java工程
pom.xml是maven项目的依赖配置,maven文件的标识
pom文件添加jar包
<dependencies>core--Spring核心包<dependency><groupId>org.springframework</groupId><artifactId>spring-core</artifactId><version>5.3.6</version></dependency>引入SpringBean:被spring容器管理的对象叫bean<dependency><groupId>org.springframework</groupId><artifactId>spring-beans</artifactId><version>5.3.6</version></dependency>引入context包--上下文--对象和对象之间沟通的纽带<dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.3.6</version></dependency>ecpression--引入表达式包<dependency><groupId>org.springframework</groupId><artifactId>spring-expression</artifactId><version>5.3.6</version></dependency>引入日志依赖<dependency><groupId>commons-logging</groupId><artifactId>commons-logging</artifactId><version>1.2</version></dependency>单元测试框架junit---引入测试包<dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version></dependency></dependencies>
编辑实体类(POJO)
entity:实体对象对应于plain object java object
一般与数据库中的表进行对应
数据库表名 | 实体对象的类名 |
---|---|
user | User |
package com.jt.pojo;public class User {public void say(){System.out.println("你好Spring框架");}
}
编辑application.xml(习惯)
resource目录下的application.xml是Spring用于管理对象的配置文件
- shema–xml的文件约束beans约束
- bean标签–标识被Spring容器管理的类
- id:Spring容器中对象的唯一标识,不能重复,一般取为类名首字母小写标识容器创建的依赖对象,用于调用
- 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 id="user" class="com.jt.pojo.User"></bean>
</beans>
测试容器获取对象的不同方式
-
方法的签名唯一确定方法,与返回值无关,所以单元测试框架规定测试方法无返回值
-
Spring程序执行时,首先会根据配置文件的内容进行解析,获取到容器对象时,容器已经创建好所配置的对象
-
ClassPathXmlApplicationContext类是ApplicationContext接口的实现类,通过getBean()方法的重载,根据不同参数(不同方式)获取依赖对象
public class TestUser {public void testuser01(){ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");User user = context.getBean(User.class);User user1 = (User) context.getBean("com.jt.pojo.user");User user2 = context.getBean(User.class, "com.jt.pojo.user");user.say();user1.say();user2.say();}
}
3.2.3 Spring IOC原理
面试:Spring容器如何创建对象的?
- 当Spring程序执行时,首先会根据配置文件的内容进行解析
- 当程序解析到Bean标签时,则会根据反射机制实例化对象,反射机制 必然调用对象的无参构造方法
关于反射机制
看到标签中写到了class,源码中的实现必然用到了反射机制
反射机制 必然调用对象的无参构造方法
@Testpublic void demo2() throws ClassNotFoundException, IllegalAccessException, InstantiationException {//1.通过类路径实例化类型...Class userClass = Class.forName("com.jt.pojo.User");//2.实例化对象User user = (User) userClass.newInstance();user.say();}
- Bean标签的id属性作为Key,将实例化好的对象当做Value保存到一个超大的Map<K,V>集合中
- Map<id,对象>
- 从容器中获取对象,则从Map集合中通过id获取对象即可.
User user = context.getBean(User.class);
3.2.4 工厂模式
3.2.4.1需求
通过String容器创建的对象一般是通过反射机制调用,但是有时候由于业务需
要需要实例化抽象类/复杂的接口
说明:String提供了工厂模式用于实例化复杂对象
案例:工厂模式实例化抽象类
3.2.4.2静态工厂模式
- Tips
静态方法特点:
-
静态方法可以通过类名直接调用
-
静态属性,内存当中独一份
编辑静态工厂
public class StaticFactory {/*通过静态工厂获取数据*/public static Calendar getCalendar(){//API: Calendar.getInstance()静态方法,使用默认时区和语言环境获得一个日历return Calendar.getInstance();}
}
编辑配置文件
- 与实例化工厂不同,静态工厂不需要实例化 ,可通过class属性指定类型.直接实例化所需对象
- 给定了类,factory-method属性调用的方法必须为静态
<!--静态工厂实例化对象的写法 方法必须是static-->
<!--加载类,加载静态方法,必然返回一个calender对象--><bean id="calendar1" class="com.jt.factory.StaticFactory" factory-method="getCalendar"/>
编辑测试方法
@Testpublic void testStatic(){ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");Calendar calendar1 = (Calendar)context.getBean("calendar1");System.out.println("获取当前时间:"+calendar1.getTime());}
3.2.4.3 实例化工厂模式
调用: 工厂对象.方法( )
编辑实例化工厂
- 非静态方法必须通过工厂对象调用
public class InstanceFactory {//获取Calendar对象public Calendar getCalendar(){return Calendar.getInstance();}
}
编辑xml配置文件
- 实例化工厂需要先把工厂交给容器管理,然后才可通过factory-bean属性,通过工厂对象调用工厂提供的非静态方法来实例化抽象类
- factory-bean属性,表示所使用的工厂对象的引用,所以一般用id
<!--2.实例化工厂 --> <!--步骤1:将工厂交给spring容器管理 -->
<bean id="instanceFactory" class="com.jt.factory.InstanceFactory"></bean><!-- 步骤2: 通过工厂对象调用所需方法 -->
<bean id="calendar2"factory-bean="instanceFactory"factory-method="getCalendar"></bean>
编辑测试类
@Test
public void testInstance(){
ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
Calendar calendar1 =(Calendar)context.getBean("calendar2");System.out.println("获取当前时间:"+calendar1.getTime());
}
3.2.4.4 Spring工厂模式
- spring提供的工厂API,更为通用
- 创建那些不能直接new或者更为复杂的对象(抽象类/接口)
- FactoryBean< T >-Spring所提供的的接口
- 接口想要方法返回默认值,用关键字default修饰,return默认值,所以实现FactoryBean接口只默认重写了前两个方法
public Calendar getObject()
–工厂模式实例化对象的方法public Class<?> getObjectType()
–工厂模式获取对象类型的方法public boolean isSingleton()
–默认单例模式,表示容器(内存中)独一份的对象
- 自己重写后默认值变为false
Spring容器管理对象,用户需求是调用对象中的方法,无论是一个对象还是多个对象,所调用的方法都一样,所以默认条件下spring容器中都是单例对象 ,节省空间
编辑工厂代码
implements FactoryBean<Calendar>
标识要实例化的类型<bean id="calendar3" class="com.jt.factory.SpringFactory">
context.getBean("calendar3")
直接调用
public class SpringFactory implements FactoryBean<Calendar> {@Overridepublic Calendar getObject() throws Exception {//工厂模式实例化对象的方法return Calendar.getInstance();}//获取类型@Overridepublic Class<?> getObjectType() {return Calendar.class;}//默认单例@Overridepublic boolean isSingleton() {return true;}
编辑xml配置文件
<bean id="calendar3" class="com.jt.factory.SpringFactory"></bean>
编辑测试类代码
@Test
public void testSpringFactory(){ApplicationContext context =new ClassPathXmlApplicationContext("application.xml");Calendar calendar = (Calendar)context.getBean("calendar3"); System.out.println("获取当前间:"+calendar.getTime());}
Spring容器回调执行原理
测试时,代码从头到尾执行
- 特殊定制功能,也就是对外暴露的接口,你想要用什么功能,就去实现 Spring提供的该接口.
- 如果不用接口,程序也会按照结构化规则,按照结构顺序依次往下执行(加载),当加载FactoryBean接口时,发现用户实现了该接口,所以要执行接口所实现的方法—这类接口方法就是回调方法
只要实现了某一特定接口,Spring框架会控制接口方法的调用.
3.2.4.5 Spring容器管理对象的方式
单例对象与多例对象
- Spring容器中默认的对象都是单例对象(通过构造方法实例化对象)
- 有时需要通过多例对象为用户提供服务,如数据源链接,即连接池(DBCP,C3P0)
public class User {public User(){System.out.println("无参创建对象");}
<bean id="user" class="com.jt.pojo.User"/>
@Testpublic void singliten(){ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");context.getBean("user");context.getBean("user");context.getBean("user");context.getBean("user");}
懒加载
懒加载: 当用户需要获取对象时,容器才创建对象,称之为懒加载
说明: Spring容器中默认的规则是:容器创建则对象创建.
如果需要配置懒加载 则需要添加额外的属性lazy-init
– true 开启懒加载
– false/default 懒加载不生效
原则: 只要是多例对象 都是懒加载,lazy-init
属性无效 懒加载只对单例对象有效
关于懒加载说明: 一般服务器对象应该先行创建(不懒),用户直接使用即可.
有些业务需求比较特殊,刚开始创建的资源特别消耗内存,但使用的人不多,这类资源可以设置为懒加载.
多例对象: 用户使用时创建,同时将对象的生命周期交给使用者管理, Spring不负责维护对象的生命周期 (随用随销)
3.2.4.6 Spring管理对象的生命周期
只要在xml中配置,即由容器管理对象的整个生命周期
- 实例化对象(创建对象)
- 初始化操作 (一般对对象的属性赋值)
- 用户使用对象(调用其中的方法)
- 对象销毁 (一般都是释放资源)
Spring生命周期 | 对应属性 |
---|---|
实例化对象(创建对象) | pojo类的无参构造 |
初始化操作 (一般对对象的属性赋值) | init-method="init" bean标签属性 |
用户使用对象(调用其中的方法) | pojosay(){ } |
对象销毁 (一般都是释放资源) | destroy-method="destory" bean标签属性 |
- 注意属性值与方法名相同
package com.jt.pojo;public class User {//数据源链接private String conn;//实例化对象public User(){System.out.println("无参创建对象");}//初始化数据public void init(){this.conn="赋值数据源链接";System.out.println(this.conn);}//用户调用方法public void say(){System.out.println("我是用户");}//销毁方法public void destory(){this.conn=null;System.out.println("释放资源"+this.conn+"~~~~~~");}
}
<bean id="user" class="com.jt.pojo.User" init-method="init" destroy-method="destory"/>
- 测试
public class TestLifeCycle {//测试生命周期@Testpublic void test(){//公共接口不做自杀式操作,不会主动关闭容器ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("application.xml");//获取单例对象--容器启动即创建对象User user = (User) context.getBean("user");//用户调用方法user.say();//关闭容器---容器关闭之前需要销毁对象context.close();}
3.3 DI思想
是什么
DI—Dependency Injection,即依赖注入:由容器在运行期决定组件之间的依赖关系,形象的说,即由容器动态的将某个依赖关系注入到组件之中。依赖注入的目的并非为软件系统带来更多功能,而是**为了提升组件重用的频率,并为系统搭建一个灵活、可扩展的平台。**通过依赖注入机制,我们只需要通过简单的配置,而无需任何代码就可指定目标需要的资源,完成自身的业务逻辑,而不需要关心具体的资源来自何处,由谁实现。
对象中的属性应该由Spring容器动态赋值
●谁依赖于谁:应用程序依赖于IoC容器;
●为什么需要依赖:应用程序需要IoC容器来提供对象需要的外部资源;
●谁注入谁:很明显是IoC容器注入应用程序某个对象所需要的对象属性;
●注入了什么:就是注入某个对象所需要的外部资源(包括对象、资源、常量数据)。
3.3.1入门案例
构建pojo实体类
设置私有属性GET/SET/构造/To String为属性赋值的两种方法: 1.get/set 2.构造
xml配置
property
属性底层调用对象的set方法实现赋值,所以 set方法必须添加name
为属性名称value
为要赋的值
<!--set注入--><bean id="user" class="com.jt.pojo.User"><property name="id" value="10086"/><property name="name" value="曹操"/></bean>
<!--构造注入--><bean id="user" class="com.jt.pojo.User"><constructor-arg name="id" value="1111"/><constructor-arg name="name" value="2222"/></bean>
测试
public class TestSpringDI {@Testpublic void testDI(){ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");System.out.println(context.getBean(User.class));}
}
3.3.2 属性注入的高级用法
为集合赋值
构造注入是可能造成参数过多问题,一般不用
编码规范:为pojo属性赋值时,首选set注入
- 数据库中的资源文件一般会通过Properties类型的文件进行依赖和加载
- Properties 属性内部只能保存String类型数据
- list/set底层是数组,有多个Value,在< list >内可通过< value >直接赋值
- Map底层是Entry对象,Entry对象是<K,V>结构 需要在< map >标签内通过< entry >对map对象赋值
<!--为集合赋值--><bean id="user" class="com.jt.pojo.User"><property name="id" value="10086"/><property name="name" value="曹操"/><property name="set"><set><value>张三</value><value>王五</value><value>赵六</value></set></property><property name="list"><list><value>11</value><value>22</value><value>33</value></list></property><property name="map"><map><entry key="id" value="8888"></entry><entry key="name" value="张飞"></entry></map></property><property name="pro"><props><prop key="proId">100</prop><prop key="proName">唐老鸭</prop></props></property></bean>
使用对象的引用赋值
通过定义公共的Map标签,可以对不同实体类取值相同的map属性进行赋值
- 定义新的Spring提供的根标签 util
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:util="http://www.springframework.org/schema/util"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.xsdhttp://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">
- property标签的 ref 属性表示引用的公共标签,一般取公共标签的id值
自定义util公共标签可以同时为两个实体类的map属性进行赋值
3.4 Spring 容器管理三层代码结构
三层结构对应关系 | 名称 |
---|---|
实体类(pojo)对应库表名 | User |
持久层(Dao) | UserDao接口 |
持久层实现类 | UsrDaoImpl |
业务层接口 | Service |
业务层实现类 | ServiceImpl |
控制层 | Controller |
- 面向接口开发便于功能扩展,接口定义公共的方法,不同功能调用不同实现类
- controller层依赖于Service对象,注入Service属性,提供set方法为Service属性赋值,依赖于User属性(前端参数),提供set方法为用于为User属性赋值,同时将参数传递至Service层进行业务逻辑操作
- Service(ServiceImpl)层依赖于Dao层,注入UserDao属性,提供Set方法为UserDao属性赋值,为Dao层传递入库参数.
Controller层代码
public class UserController {//spring容器负责注入Service对象private UserService userService;private User user; //代替用户传入的数据public void setUser(User user) {this.user = user;}public void setUserService(UserService userService) {this.userService = userService;}public void addUser(){userService.addUser(user);}
}
xml配置Spring容器管理对象
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"><!--1.构建user对象--><bean id="user" class="com.jt.pojo.User"><property name="id" value="100"></property><property name="name" value="springMVC设计模式"></property></bean><!--2.构建Dao对象根据面向接口编程Id:接口的名称class:实现类的包路径--><bean id="userDao" class="com.jt.dao.UserDaoImpl"></bean><!--3.构建Service--><bean id="userService" class="com.jt.service.UserServiceImpl"><property name="userDao" ref="userDao"></property></bean><!--4.构建Controller--><bean id="userController" class="com.jt.controller.UserController"><property name="userService" ref="userService"></property><property name="user" ref="user"></property></bean>
</beans>
测试
@Testpublic void testSpringMVC(){ApplicationContext context =new ClassPathXmlApplicationContext("application.xml");UserController userController = (UserController) context.getBean("userController");userController.addUser();System.out.println("SpringMVC三层代码结构");}
3.5 Spring注解模式
关于注解的说明
在开发一些复杂功能如缓存,权限等这些代码重复率较高,(作用)为了以一种低耦合度的方式去使用这些功能
tips:如果注解中只有value属性则可以省略value 直接写值.
- 钩子函数
实现接口,程序运行时就会去执行这些接口的方法 - 元注解
@Target({ElementType.TYPE}) 标识注解对谁有效
@Retention(RetentionPolicy.RUNTIME) 标识注解的生命周期
@Documented 表示该注解会编译到API文档中 - 由谁加载:由Spring内部的源码负责调用.
Spring基于配置文件 为了让属性(对象的引用[ref])注入更加的简单.推出了自动装配模式. 1.根据名称自动装配 2.根据类型自动装配
<property name="id" value="100"></property>
- set注入–property: 根据name属性查找对象的setId()方法,将value属性值当做参数,并调用set方法为id属性赋值
User对象中的属性,是普通的包装类,不是对象的引用,不会被Spring自动装配
将实现类对象交给容器管理,被Service进行了引用
<bean id="userDao" class="com.jt.dao.UserDaoImpl" />
service依赖注入userDao
<bean id="userService" class="com.jt.service.UserServiceImpl">
自动装配:程序无需手动编辑property属性<property name="userDao" ref="userDao" />
</bean>
<bean id="userDao" class="com.jt.dao.UserDaoImpl"></bean><bean id="userService" class="com.jt.service.UserServiceImpl" autowire="byName"></bean><bean id="userController" class="com.jt.controller.UserController" autowire="byName"/>
</beans>
3.5.1 自动装配原理:
自动装配: 程序无需手动的编辑property属性
1.autowire=“byName” 根据属性的名称进行注入
-
setUserDao—>UserDao—>userDao 不去直接找属性名,是因为依赖注入的前提是要有set()或构造方法.
-
Spring会根据对象的属性查询自己维护的Map集合,根据userDao名称(K),查找Map中的Key与之对应,如果匹配成功.则能自动调用set方法实现注入(必需有set方法)
2.autowire="byType"根据属性的类型进行注入
- 找到对象的所有的set方法 setUserDao()
- 根据set方法找到方法中参数的类型UserDao.class
- Spring根据自己维护对象的Class{对象.getclass}进行匹配.如果匹配成功则实现注入(set方法)
3.5.2 注解模式
Spring为了简化xml配置方式,研发了注解模式.代替xml配置文件< bean >标签,
Spring为了程序更加的严谨,通过不同的注解标识不同的层级 但是注解的功能一样
- @Controller 用来标识Controller层的代码 相当于该类的对象交给Spring管理
- @Service 用来标识Service层代码,相当于该类的对象交给Spring管理
- @Repository 用来标识持久层,相当于该类的对象交给Spring管理
- @Component 万用注解
3.5.3 注解使用原理
通过注解标识类之后,相当于动态拼接< bean >
<bean id="类名首字母小写~~userDaoImpl" class="UserDaoImpl.class" />
如果需要修改beanId则手动添加value属性即可
@Repository(value = "userDao")
public class UserDaoImpl implements UserDao{@Overridepublic void addUser(User user) {System.out.println("链接数据库执行insert into :"+user);}
}
3.5.4 配置包扫描–规定某个对象交给容器
- 添加context头信息
xmlns:context
- 让注解生效,要开启包扫描配置文件添加
<context:component-scan>
标签- 属性1: base-package: 根据指定的包路径 查找注解
- 包路径特点: 给定包路径,则自动扫描同包及子孙包中的类,若想扫描个别包,可以用逗号隔开分开扫
- 属性2:use-default-filters 默认规则
- true 表示扫描包路径
- false 按照用户指定的注解进行加载,默认规则不生效
- 属性1: base-package: 根据指定的包路径 查找注解
<context:include-filter>
:用户自定义规则- type属性:表示扫描的类型
- expression:表达式,写的是类的类型表达式,copy
<context:exclude-filter>
:自定义规则,排除在外的类,SpringBoot使用居多
<!--1.构建user对象--><bean id="user" class="com.jt.pojo.User"><property name="id" value="100"></property><property name="name"><value><![CDATA[<范冰冰>]]></value></property></bean><!--<context:component-scan base-package="com.jt.controller,com.jt.service,com.jt.dao"></context:component-scan>--><context:component-scan base-package="com.jt"/><!--业务需求1: 只想扫描@controller注解 --><context:component-scan base-package="com.jt" use-default-filters="false"><context:include-filter type="annotation" expression="org.springframework.stereotype.Service"/></context:component-scan><!--com.jt.controller.UserController@318ba8c8--><!--业务需求2: 不想扫描@controller注解--><context:component-scan base-package="com.jt"><!--通过包扫描 可以加载其他的注解 排除Controller注解--><context:exclude-filter type="annotation"expression="org.springframework.stereotype.Controller"/></context:component-scan>
</beans>
No bean named 'userController' available
3.5.5属性的注解
对象的引用ref—-xml中自动装配byNamebyType—-@Autowired
* 1.@Autowired: 可以根据类型/属性名称进行注入 首先按照类型进行注入如果类型注入失败,则根据属性名称注入
* 2.@Qualifier: 如果需要按照名称进行注入,则需要额外添加@Qualifier
* 3.@Resource(type = "xxx.class",name="属性名称") 功能更强大
* 关于注解补充: 由于@Resource注解 是由java原生提供的,不是Spring官方的.所以不建议使用
上述的属性的注入在调用时 自动的封装了Set方法,所以Set方法可以省略不写
3.5.6 MVC模式的纯注解开发
xml配置包扫描
<?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/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"><context:component-scan base-package="com.jt"/>
</beans>
bean对象注入问题
一般需要检查注解是否正确配置
警告: Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'userController': Unsatisfied dependency expressed through field 'userService'; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.jt.service.UserService' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
面试题 BeanFactory与FactoryBean的区别
1.Spring源码中创建对象都是采用工厂模式 接口:BeanFactory(顶级接口)程序执行应该执行那些过程由该接口进行定义
2.Spring开发中需要手动的创建对象时,一般采用 FactoryBean(业务接口)
3.BeanFactory是Spring中用于创建 对象的顶级接口,Spring中的所有对象都是通过该接口进行的调用,使用Spring加载第三方对象时一般使用FactoryBean接口
3.5.7 优化xml配置文件
说明: 随着软件技术发展,xml配置文件显得臃肿 不便于操作,所以Spring后期提出了配置类的思想.即将所有的配置文件中的内容,写到java类中
-
@Configuration 标识我是一个配置类 相当于application.xml
-
@ComponentScan(“com.jt”)设定包扫描的路径,如果注解中只有value属性 则可以省略 —
<context:component-scan base-package="com.jt"/>
@Configuration //标识我是一个配置类 相当于application.xml
//设定包扫描的路径
@ComponentScan("com.jt")//如果注解中只有value属性 则可以省略value
public class SpringConfig {
}
通过配置类测试代码测试
- API :
ClassPathXml--->AnnotationConfig
参数写类型
@Testpublic void testAnno(){ApplicationContext context =new AnnotationConfigApplicationContext(SpringConfig.class);UserController userController = context.getBean(UserController.class);userController.addUser();}
3.5.8 Spring注解模式执行过程
- 当程序通过注解环境启动Spring容器时,AnnotationConfigApplicationContext(SpringConfig.class),利用BeanFactory实例化对象
- 根据配置类中的包扫描(com.jt)开始加载指定的注解(Controller-Service–Repository–Component),根据配置文件的顺序依次加载
- 当程序实例化Controller时,由于缺少Service对象(注入属性还未初始化),所以线程挂起,继续执行后续逻辑,当创建Service对象时,由于缺少Dao对象,所以挂起线程,继续执行后续逻辑,当实例化Dao成功后,保存到Spring所维护的Map集合中,开始返回执行之前挂起的线程,依次类推所有对象实现封装,容器启动成功(递归思想)
- 根据指定的注解注入指定的对象之后,统一交给Spring容器进行管理,最终程序启动成功
3.5.9 Spring中常见问题
接口多实现类情况
@Autowired: 可以根据类型/属性名称进行注入 首先按照类型进行注入如果类型注入失败,则根据属性名称注入
@Autowiredprivate UserService userService;
业务需求: 要求给UserService接口提供2个实现类.
No qualifying bean of type 'com.jt.service.UserService' available: expected single matching bean but found 2: userServiceImpl,userServiceImplB
方案一:给其中一个实现类起名与controller注入属性名一致
@Service("userService")
public class UserServiceImpl implements UserService @Service("userServiceB")
public class UserServiceImplB implements UserService
方案二: 两个实现类名称与controller注入的属性名都不匹配时,使用@Qualifier
//表示注入的Service对象ID必须为UserServiceA
@Autowired
@Qualifier("userServiceA")
private UserService userService;@Service("userServiceA")
public class UserServiceImpl implements UserService @Service("userServiceB")
public class UserServiceImplB implements UserService
3.5.10 Spring容器管理业务数据(User类)
首先想到的是给类添加万用注解@Component交给容器管理,但是其中的属性值该怎么注入呢?容器管理第三方对象—@Bean
-
Spring配置文件写法 < bean id=“方法名称” class=“返回值的类型” />
-
执行@Bean标记的方法 将方法名称当做ID,返回值的对象当做value 直接保存到Map集合中
package com.jt.config;
相当于xml文件中有个Bean标签@Configuration
@ComponentScan("com.jt")
public class SpringConfig {@Beanpublic User user(){User user = new User();user.setId(101);user.setUsername("Spring容器");return user;}
}
@Controller
public class UserController {@Autowired@Qualifier("userServiceA")private UserService userService;@Autowiredprivate User user; //从容器中动态获取public void addUser(){userService.addUser(user);}
}
Spring动态获取外部数据
现在虽然能够注入User类的数据,但是写死在了程序中,应该使用业务数据的配置文件 properties
编辑user.properties文件
- resource目录下编辑properties文件
- 规则:
数据结构类型: k-v结构
存储数据类型: 只能保存String类型
加载时编码格式: 默认采用ISO-8859-1格式解析 中文必然乱码
Spring容器获取的当前计算机的名称 :user.name(慎用)
- 规则:
user.id=1001
user.username=鲁班七号
编辑配置类
Tips: " " 表示 属性 value = “”
@PropertySource("classpath:/user.properties")
-
@PropertySource 表示配置文件源,用于加载指定的配置文件(user.peoperties),将数据保存到Spring容器中,但凡看到source,就代表源,起手就写 classpath, / 是根目录,即resource目录. 现在数据已经放到了容器中,得从容器中拿出来给属性赋值 . encoding属性 : 指定字符集编码格式
@Value("${user.id}")
-
@Value( ) 表示给属性赋值如
@Value(123) private Integer id;
表示给id赋值123,想要从容器中取值,应遵循Spring语法表达式: ${ } ,Spring EL简称spel表达式@Value( " ${user.id} " )
表示在Spring容器中查找key=user.id的数据.
package com.jt.config;@PropertySource("classpath:/user.properties")
public class SpringConfig {//定义对象属性,准备接受数据@Value("${user.id}")private Integer id;@Value("${user.username}")private String username;@Beanpublic User user(){User user = new User();user.setId(id);user.setUsername(username);return user;}
}
测试
@Testpublic void Anoo(){ApplicationContext context=new AnnotationConfigApplicationContext(SpringConfig.class);UserController userController = context.getBean(UserController.class);userController.addUser();}
指定字符集加载
@PropertySource(value = "classpath:/user.properties",encoding = "UTF-8")
4. SpringAOP – 代理思想
Spring AOP 就是对动态代理的优化 可以再CSD任意层级对方法进行扩展,常用于切面的功能有:事务控制,权限控制,日志操作等.
切面只有在程序运行时才会生成,可以理解成程序执行过程中必须要经过的一道关卡,满足切入点表达式的方法进入切面后即可进行扩展
通过Spring提供的一系列注解(5大通知),属性(returning,throwing),对象(joinpoint,proceedingjoinpoint)等对目标方法思想全方位监控和扩展,
案例引入-数据库的事务管理
- userMapper.insert(User对象)
- deptMapper.insert(dept对象)
业务需求:入库员工表的同时,必须入库部门表那么就要求方法要么同时入库,要么同时回滚.所以必须通过事务进行控制.
Spring中规定,如果传入的是接口的类型,则自动查找/注入 改接口的实现类,但是唯一限定条件:该接口只有一个实现类
向上造型,用接口接实现类
@Testpublic void testTransactionManager(){ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);// UserService userService = (UserService) context.getBean(UserService.class);UserService userservice = (UserService) context.getBean("userServiceImpl");User user = new User();user.setId(888);user.setName("猪八戒");userservice.addUser(user);
事务控制是业务的一种常见形式,数据入库操作,不应该与事务控制代码混合起来
如:初期利用JDBC进行事务控制,优化后利用Spring方式进行事务控制,代码功能耦合性太高,还需要重新打包.增删改查都离不开事务控制,造成代码冗余
由此引入AOP思想—采用代理模式进行编辑
4.1 代理模式
4.1.1 组成部分
- 要求代理者实现与被代理者相同的接口
- 在代理方法中实现功能的扩展
- 用户调用代理对象完成功能(用户认为代理就是目标对象)
房屋中介代理模式:
1.房东: 自己手里有房子 需要出租换钱
2.中介机构 1.本职工作 带客户看房/出租房屋 2.收取中介费(服务费)
3.租客: 满足自身需求 租房子
代码思维建模:
1.暴露一个公共的接口(租房子)
2.客户与中介(代理)机构进行沟通,中介(代理)看起来和房东功能一致.(代理看起来就是真实的对象
)
3.中介需要完成自己额外的需求—即完成用户额外的操作(收取中介费))
4.1.2 调用流程
用户调用的是代理出租房屋的方法
代理调用真实的出租房屋的方法即目标对象的方法
事务管理案例中,目标对象即 业务层:只做业务逻辑处理,所以代理还需要额外的功能,即事务控制,目的就是在不影响原有业务层功能之上,为原有方法进行功能上的扩展
代理与目标对象实现了同一接口(出租方法),所以在用户看来,调用代理与调用目标对象一样,用户在调用代理过程中就添加了额外功能(事务控制),并且还能实现目标方法的调用
4.2静态代理
- 继承中重写父类方法—装饰模式
4.2.1通过代理模式实现事务控制
角色划分:
1.目标对象target UserServiceImpl类
2.目标方法 method addUser()方法
3.代理: 实现事务控制.
4.代理对象与目标对象实现相同的接口.
编辑静态代理类
@Service("userService")
@Service("userService")
public class StaticProxy implements UserService{@Autowiredprivate UserService target;@Overridepublic void addUser(User user) {try {System.out.println("事务开始");target.addUser(user);System.out.println("事务结束");}catch (Exception e){e.printStackTrace();System.out.println("事务回滚");}}
}
编辑测试类
public class TestStaticProxy {@Testpublic void aa(){ApplicationContext context =new AnnotationConfigApplicationContext(SpringConfig.class);UserService userService = (UserService) context.getBean("userService");User user = new User();user.setId(6666);user.setName("葫芦娃");userService.addUser(user);}
}
4.2.2 静态代理弊端
- 静态代理只针对于某个接口 不能实现所有接口的代理 实用性较差
4.3 动态代理
4.3.1 面试题:动态代理的分类
- JDK代理-jdk原生提供
要求:
要求目标对象 (房东,真实的方法) 必须实现接口
代理要求: 代理对象也必须实现目标对象实现的接口
目标对象和代理的关系: 目标对象与代理对象兄弟关系
- CGlib代理-第三方,Spring内部整合
要求:
不管目标对象是否有接口,都可以为其创建代理对象
代理要求: 要求代理对象必须继承目标对象
目标对象/代理关系: 目标对象与代理对象是父子关系
动态代理实现的是功能的扩展,无关乎是什么类什么表,User,Dept等等,只要获取了代理对象当做参数传给代理工厂即可获得代理对象,代理对象直接调用方法进行扩展.
面试题:JDK代理与CGliB代理的区别
JDK代理创建速度快,运行时稍慢,因为目标对象实现接口迅速创建,代理运行时中间调用诸多方法所以速度较慢
CGLIB代理创建速度稍慢,运行时快,内部有增强器,增强器通过大量反射去应用目标对象,所以创建时较慢,一旦代理对象创建完成后,其实就是父子级间的调用.所以运行时很快
4.3.2 编辑JDK动态代理
补充:
背会知识点:
- jdk低版本中, 匿名内部类引用外部 参数 (target)要求参数必须final修饰
- 该方法标识 当代理对象执行时,"回调"该方法.
java public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {}
- 目标方法执行
java result = method.invoke(target,args);
<bean id="user" class="com.jt.pojo.User"></bean>
id用于外部调用,class属性获取类的类型,通过反射机制,class.forName()获取User对象存储在map中,得Class者,得天下
使用的方法:
参数分析:
-
ClassLoader loader :类加载器的方式帮助获取目标对象的类型
-
Class<?>[ ] interfaces:JDK要求代理对象与目标对象实现同一接口,java中的接口可以多实现,所以要求传递的是目标对象的 类型接口 数组~
-
InvocationHandler h:框架当中一般的钩子函数都是以InvocationHandle(接口) 的方式执行,代理的主要目的是扩展方法,InvocationHandler作用是对目标方法进行扩展,每一个动态代理都应该完成自己代理的特殊功能,代理有独特性,不通用,如果给该接口准备一个实现类,实现某功能扩展,功能多了过于繁琐,故采用匿名内部类的形式扩展
- invoke方法: 代理对象啥时候调用,invoke方法啥时候执行,扩展方法的编辑位置,即代理对象实例化完成后,对目标方法进行扩展时如
Proxy.add()
时invoke()
方法才会执行,要注意要代理的目标对象的方法不能写死(使用api:method.invoke()
)目标方法执行的返回值要return,注意同一作用域- 参数分析:
- 1.Object proxy 代理对象本身
- 2.Method method 代表用户调用的方法
add()
的对象,method.invoke()
表示执行目标方法,target对象里面的方法- 参数1:obj target
- 参数2:arg target方法的参数
- 3.Object[] args 方法的参数 ( addDept(dept) )
- 参数分析:
- invoke方法: 代理对象啥时候调用,invoke方法啥时候执行,扩展方法的编辑位置,即代理对象实例化完成后,对目标方法进行扩展时如
创建JDK代理工厂(API)
package com.jt.proxy;
//创建JDK代理工厂
//InvocationHandler用于执行方法的扩展,有特殊性,所以该参数使用匿名内部类的形式
public class JDKProxyFactory {
//静态方法通过类名直接调用,底层得到代理对象的过程,需要将目标对象作为参数穿过去进行扩展public static Object getProxy(final Object target){ClassLoader targetclassLoader = target.getClass().getClassLoader();Class<?>[] targetinterfaces = target.getClass().getInterfaces();Object proxy = Proxy.newProxyInstance(targetclassLoader, targetinterfaces, new InvocationHandler() {//用户调用扩展时,incoke执行@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {//目标方法执行后的返回值,注意作用域Object result =null;try {System.out.println("事务开始");//执行目标方法result = method.invoke(target, args);System.out.println("事务提交");} catch (Exception e) {e.printStackTrace();System.out.println("事务回滚");}return result;}});return proxy;}
}
测试jdk动态代理
public class TestJDKProxy {@Testpublic void test (){ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);//获取用户目标对象UserService target = (UserService) context.getBean("target");//获取代理对象,代理对象长得和目标对象一样,神来之笔UserService userService = (UserService) JDKProxyFactory.getProxy(target);//验明真身,查看代理对象类型,看看是否能强制转换System.out.println(userService.getClass());//用户完成调用User user = new User();user.setId(123456);user.setName("JDK动态代理完成");userService.addUser(user);}
通过代理规则,写了一个事务控制,以后项目中所有方法中如果需要用到事务控制,就通过工厂模式获取代理对象,拿到对象进行伪装调用目标对象方法,就可以对其进行扩展
JDK动态代理执行过程
- 代理对象不会凭空产生,JDK进行了封装,会调用即可.
- Proxy.newProxyInstance实例化代理对象,需要3个参数
UserService userService = (UserService) JDKProxyFactory.getProxy(target);
证明 代理对象与目标对象实现同样接口,由接口数组传递而来- 只要代理对象执行add(),就会触发回调invoke()
4.3.3 案例:动态代理记录程序执行时间
需求
要求对Service层的方法记录其执行的时间!!! 通过执行时间的长短 进行针对性的优化!!!
要求: service中 有 addUser方法/deleteUser方法.
要求代码结构扩展性好,耦合性低.
编辑代理工厂
public class JDKProxyFactory {//编辑静态方法获取代理对象public static Object getProxy(final Object target){//3个参数 1.类加载器 2.对象的接口ClassLoader classLoader = target.getClass().getClassLoader();Class[] interfaces = target.getClass().getInterfaces();Object proxy = Proxy.newProxyInstance(classLoader, interfaces,new InvocationHandler() {//代理对象执行目标方法时执行@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {//记录目标方法执行前系统当前时间Long startTime = System.currentTimeMillis(); //执行目标方法 获取返回值 可能为nullObject result = method.invoke(target);System.out.println(method.getClass());System.out.println(method.getName());//根据项目经理要求 给程序预留bug 后期维护时删除 不友好Thread.sleep(2000);//结束时间 Long endTime = System.currentTimeMillis(); System.out.println("程序执行:"+(endTime-startTime)+"毫秒");//将返回值传递给调用者return result;}}) ;return proxy;}
}
编辑测试类
public class TestJDKpROXY2 {@Testpublic void jdkproxy(){ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);Service target = (Service) context.getBean("target");Service proxy = (Service) JDKProxyFactory.getproxy(target);proxy.addUser();proxy.delete();}
}
4.4 SpringAOP 动态代理
实际是封装了动态代理的过程
Aspect Oriented Programming AOP(面向切面编程) 主要利用动态代理的模式 在不修改原有代码的条件下 对方法(业务功能)进行扩展,降低程序的耦合度
切面=切入点表达式+通知方法
4.4.1 关于AOP名词介绍
-
连接点(JoinPoint): 可能被扩展还未执行的方法[ addUser/deleteUser]—方法是连接点
-
切入点(Pointcut): 实际扩展的方法,被代理对象调用之后
proxy.addUser()
变成切入点,切入见名知意即进入代理层执行的方法即为切入点 -
通知: 扩展方法的具体实现,也就是动态代理里面写的内容(@before)
-
切面(@Aspect): 将通知应用到切入点的过程
-
织入:
点面结合,要扩展的方法调用前是连接点,被代理调用变成切入点,配合AOP的通知执行,实现扩展,整个过程即为切面
4.4.2 通知的类型
针对于目标方法Object result = method.invoke(target);
执行前后
-
before通知: 目标方法执行前执行
-
afterReturning 通知: 目标方法执行之后(返回结果时)执行
-
afterThrowing 通知: 目标方法执行之后,抛出异常时执行
-
after通知 无论程序是否执行成功,都要最后执行的通知 (类似与异常控制中的finally(关闭资源))
-
around通知:: 在目标方法执行前后 都要执行的通知(完美体现了动态代理模式)
功能最为强大只有环绕通知可以控制目标方法的执行
总结:
1.环绕通知是处理业务的首选. 可以修改程序的执行轨迹
2.另外的四大通知一般用来做程序的监控.(监控系统) 只做记录
4.4.3 切入点的表达式(把关)
概念:当程序满足切入点表达式,才能进入切面,执行通知方法
- bean(“bean的ID”) 根据beanId进行拦截 只能匹配一个效率低,主要看需求
-
within (包名.类名) 可以使用通配符*? 能匹配多个.
粒度: 上述的切入点表达式 粒度是类级别的. 类中还有相应的增删改查的方法 以事务为例,查询并不需要事务控制,所以为粗粒度表达式. 方法里还有参数,所以最细的粒度是参数级别.
-
execution(返回值类型 包名.类名.方法名(参数列表…)) —万能表达式
粒度: 控制的是方法参数级别. 所以粒度较细. 最常用的.
-
@annotation(包名.注解名) 只拦截注解.
粒度: 注解是一种标记 根据规则标识某个方法/属性/类 细粒度
4.4.4 入门案例
导入jar包
当下的所有jar包都需要手动导入依赖,并确定依赖关系
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.jt</groupId><artifactId>Spring_Demo_9_AOP</artifactId><version>1.0-SNAPSHOT</version><dependencies><!--Spring核心包--><dependency><groupId>org.springframework</groupId><artifactId>spring-core</artifactId><version>5.3.6</version></dependency><!--引入SpringBean--><dependency><groupId>org.springframework</groupId><artifactId>spring-beans</artifactId><version>5.3.6</version></dependency><!--引入context包--><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.3.6</version></dependency><!--引入表达式jar包--><dependency><groupId>org.springframework</groupId><artifactId>spring-expression</artifactId><version>5.3.6</version></dependency><!--引入日志依赖--><dependency><groupId>commons-logging</groupId><artifactId>commons-logging</artifactId><version>1.2</version></dependency><!--引入测试包--><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version></dependency><!--引入AOP包--><dependency><groupId>org.springframework</groupId><artifactId>spring-aop</artifactId><version>5.3.6</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-aspects</artifactId><version>5.3.6</version></dependency><dependency><groupId>org.aspectj</groupId><artifactId>aspectjrt</artifactId><version>1.9.6</version></dependency></dependencies>
</project>
配置切面类
形成aop切面的公式:
切面=切入点表达式+通知方法
@Before("bean(deptServiceImpl)")
-
切面要归于容器管理,所以有要用
@Component
注解 -
切面要区别与其他类(CSR),所以要用
@Aspect
注解标识这是一个切面 -
Spring容器默认不能识别切面注解
@Aspect
,需要手动在配置类进行配置,添加@EnableAspectJAutoProxy
表示启动aop注解创建代理对象,默认启用JDK动态代理(目标对象没有实现接口时启用CGLIB)- 如果想要Spring所有的代理对象都用CGLIB代理方式,则设置
@EnableAspectJAutoProxy(proxyTargetClass=true)
属性为true
- 如果想要Spring所有的代理对象都用CGLIB代理方式,则设置
-
前四种通知类型基本用于监控,所以通知方法无返回值
@Component
@Aspect
public class SpringAOP {@Before("bean(deptServiceImpl)")public void Before(){System.out.println("我是before前置通知");}
}
编辑配置类
@Configuration
@ComponentScan("com.jt")
@EnableAspectJAutoProxy(proxyTargetClass=false)
public class SpringConfig {
}
测试代码
@Testpublic void testSpringAOP(){ApplicationContext context=new AnnotationConfigApplicationContext(SpringConfig.class);// "deptServiceImpl" or DeptService.class(只有一个实现类)DeptService deptservice = context.getBean(DeptService.class);System.out.println(deptservice.getClass());deptservice.addDept();deptservice.updateDept();}
表明容器在获取对象时context.getBean
,获取的就是代理对象,因为配置类开启了代理,切面类立即生效,切入点表达式明确规定要为deptServiceImpl
类所有方法创建代理对象,所以@Before("bean(deptServiceImpl)")
解析时就已经为deptServiceImpl
创建了代理对象
@EnableAspectJAutoProxy(proxyTargetClass=true)时
4.4.5 切入点表达式练习
表达式—String 类型—所以双引号
- " bean(“bean的ID”) " 根据beanId进行拦截
切入点表达式配合通知形成切面,当前拦截的表达式是bean标签,是一个类对象.类对象下所有方法都包含
@Component
@Aspect
public class SpringAOP {@Before("bean(deptServiceImpl)")public void Before(){System.out.println("我是before前置通知");}
}
- " within (包名.类名) " 可以使用通配符 * ? 永远记得最后是类名
@Before("bean(deptServiceImpl)")
@Before("within(com.jt.service.DeptServiceImpl)")
-------效果相同-------
一个点(.)代表一级目录com.jt.*
com.jt.service
com.jt.aa.bb ×
----优势在于通配符*------
@Before("within(com.jt.*.DeptServiceImpl)")
表示匹配com包下的jt包下的所有包的DeptServiceImpl类
@Before("within(com.jt..*.DeptServiceImpl)")
表示匹配com包.jt多级包下的DeptServiceImpl类
@Before("within(com.jt..*)")
表示匹配com包.jt包下的所有类
- execution(返回值类型 包名.类名.方法名(参数列表…)) —万能表达式
@Before("execution(* com.jt..*.deptServiceImpl.add*())")
任意类型返回值 表示匹配com.jt包下所有包中的deptServiceImpl类中以add开头的无参方法
..代表任意参数
@Before("execution(* com.jt..*.*.*(..))")
@Before("execution(* com.jt..*.*(..))")
@Before("execution(* com.jt..*(..))")
三者相同,任意返回值类型 com.jt下的所有包下的所有类中的所有任意参数的方法
-------------------------------------------------
@Before("execution(int com.jt..*.*(int))")
@Before("execution(Integer com.jt..*.*(Integer))")
两者不同
注意在Spring表达式中没有拆装箱功能,注意参数类型
@Override@Cache //被注解标识public void updateDept() {System.out.println("mapper:更新啦啦啦啦");}
}
4.4.6 通知练习
对同一个类进行扩展(多个通知作用于同一目标方法,切入点表达式相同)可以对切入点表达式进行抽取,通过定义pointcut方法定义切入点表达式,使用@Pointcut
注解标识方法,注意,通知中的引用时,要加()标识pointcut是个方法
- pointcut
判断方法能否进入切面
@Pointcut 标识我是一个切入点表达式,内部编辑切入点表达式(4种)
package com.jt.aop;
@Component
@Aspect
public class SpringAOP {@Pointcut("@annotation(com.jt.anno.Cache)")public void pointcut(){}@Before("pointcut()")public void Before(){System.out.println("我是before前置通知");}@After("pointcut()")public void After(){System.out.println("我是After后置通知");}@AfterReturning("pointcut()")public void AfterReturning(){System.out.println("我是AfterReturning通知");}@AfterThrowing("pointcut()")public void AfterThrowing(){System.out.println("我是AfterThrowing异常通知");}
}
@Before
说明: 前置通知,在目标方法执行之前执行
用途: 如果需要记录程序在方法执行前的状态,则使用前置通知.
需求:
1.获取目标对象的类型
2.获取目标方法的名称
3.获取目标方法的参数
getSignature : 方法签名 : void com.jt.service.DeptService.updateDept()getSignature().getDeclaringTypeName():获取目标对象的类名:com.jt.service.DeptServicegetSignature().getName():获取目标对象的方法名:updateDeptgetArgs() : 获取方法的参数 Arrays.toString(joinPoint.getArgs()): 打印数组getTarget(): 获取目标对象
Spring通过joinPoint对象进行数据的传递,动态获取目标对象及方法中的数据
@Before("pointcut()")public void Before(JoinPoint joinPoint){System.out.println("我是before前置通知");System.out.println("获取目标对象的类型:"+joinPoint.getTarget().getClass());System.out.println("获取目标对象的类名:"+joinPoint.getSignature().getDeclaringTypeName());System.out.println("获取目标对象的方法名:"+joinPoint.getSignature().getName());System.out.println("获取方法的参数"+ Arrays.toString(joinPoint.getArgs()));System.out.println("???"+joinPoint.getSignature().getDeclaringType());}
@AfterReturning
说明: afterReturning,在目标方法执行之之后执行
用途: 用来监控方法的返回值,进行日志的记录
@AfterReturning注解的属性:
- pointcut和value属性作用相同,表示关联的切入点表达式
- returning属性:接收目标方法的返回值进行记录(形参),传递给通知方法
@AfterReturning(pointcut = "pointcut()",returning = "result")public void AfterReturning(JoinPoint joinPoint,Object result){System.out.println("我是AfterReturning通知");System.out.println("用户的返回值结果为"+result);}
- 目标方法
@Override@Cachepublic String test1(Integer id) {System.out.println("目标方法执行");return "Spring的通知测试";}
- 测试
@Testpublic void testSpringAOP(){ApplicationContext context=new AnnotationConfigApplicationContext(SpringConfig.class);DeptService deptservice = context.getBean(DeptService.class);System.out.println(deptservice.getClass());String result = deptservice.test1(888);System.out.println("test方法的返回值为"+result);}
@AfterThrowing
作用: 当目标方法执行抛出异常时 可以使用AfterThrowing 进行日志记录.
throwing属性:接收目标方法抛出异常的类型,可传递给通知方法
- 目标方法
@Override@Cachepublic void afterThrow() {System.out.println("目标方法执行");//手动添加异常两种方式int a = 1/0;//throw new RuntimeException();}
- 切面通知
@Pointcut("@annotation(com.jt.anno.Cache)")public void pointcut(){ }@AfterThrowing(pointcut = "pointcut()",throwing ="e")public void AfterThrowing(Exception e){System.out.println("我是AfterThrowing异常通知,目标方法的异常为:"+e);System.out.println("获取异常信息"+e.getMessage());System.out.println("获取异常类型"+e.getClass());}
- 测试方法
//测试AfterThrow通知
public class TestAfterThrowing {@Testpublic void afterthrow(){ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);DeptService deptService = (DeptService) context.getBean("deptServiceImpl");deptService.afterThrow();}
@Around环绕通知
规则: 在目标方法执行前后都要执行
实际作用: 可以控制目标方法是否执行.
参数: ProceedingJoinPoint 通过proceed方法控制目标方法执行.
ProceedingJoinPoint is only supported for around adviceProceedingJoinPoint extends JoinPoint 只支持Around通知
proceed方法作用:
- 执行目标方法
- 接收返回值 (固定形参result)
- catch异常信息
- 执行下一通知
- 目标方法
@Override@Cachepublic String around() {System.out.println("目标方法执行");return "端午安康";}
- 通知方法
@Around("pointcut()")public Object around(ProceedingJoinPoint proceedingJoinPoint) {Object result = null;try {System.out.println("环绕通知开始");long start = System.currentTimeMillis();result = proceedingJoinPoint.proceed();System.out.println(result);long end = System.currentTimeMillis();System.out.println("总耗时:" + (end - start));} catch (Throwable throwable) {throwable.printStackTrace();}System.out.println("环绕通知结束");return result;}
- 测试
@Testpublic void testreturnaround(){ApplicationContext context= new AnnotationConfigApplicationContext(SpringConfig.class);DeptService deptService = (DeptService) context.getBean("deptServiceImpl");System.out.println(deptService.getClass());String result=deptService.around();//System.out.println(result);}
通知方法的执行顺序
- 执行around开始
- 执行before
- 执行目标方法
- 执行afterReturning
- 执行afterThrowing
- 执行after
- 执行around通知结束
Spring是一个大容器,只要五种通知,四种切入点表达式合法.通知方法的参数如JoinPoint
, returning = "result"
, throwing ="e"
, ProceedingJoinPoint
等 由Spring动态为我们赋值, 我们可以直接调用Spring传递而来的对象,查询目标放方法的返回值,方法签名,和参数数组,还可以通过环绕通知中preceed()来控制目标方法的执行
4.4.7 @Order 切面类注解
通过Order注解实现控制切面的执行的顺序
@Order(1) //通过order 控制切面执行顺序 数值越小越先执行
- 编辑切面类
@Component
@Aspect
@Order(2)
public class Before1 {@Before("@annotation(com.jt.anno.Cache)")public void testbefore1(){System.out.println("俺是前置通知one");}
}
@Component
@Aspect
@Order(1)
public class Before2 {@Before("@annotation(com.jt.anno.Cache)")public void testbefore2(){System.out.println("俺是前置通知two");}
}
- 目标方法
@Override@Cachepublic void doorder() {System.out.println("目标方法执行,测试order注解");}
- 编辑测试类
@Testpublic void test(){ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);DeptService deptService = context.getBean(DeptService.class);deptService.doorder();}
4.4.8 案例二:缓存控制
- 业务需求:用户有一个缓存的集合 static Map<k:v> key=id号 value=User对象 根据id将User对象进行了缓存
要求:当用户第二次根据Id查询用户时,如果缓存中有数据,则直接返回
/*** 需求: 用户第一次查询走目标方法* 用户第二次查询走缓存 不执行目标方法* 判断依据: 如何判断用户是否为第一次查询?* 通过map集合进行判断 有数据 证明不是第一次查询* 注意map集合是成员变量,不要写到around方法中,这样才能保证数据有效性* 执行步骤:* 1.获取用户查询的参数* 2.判断map集合中是否有该数据.* true: 从map集合中get之后返回* false: 执行目标方法,之后将user对象保存到Map中* @author CMJ* 2021/6/14 0:54*/
@Component
@Aspect
public class SpringAop {private static Map<Integer,User> map=new HashMap<>();@Around("@annotation(com.jt.anno.Cache)")public Object around(ProceedingJoinPoint joinPoint) {Object result =null;Object[] args = joinPoint.getArgs();//得到参数 User对象 强制类型转化User user = (User) args[0];//获取map集合存取数据的唯一标识Key==="id"Integer id = user.getId();if (map.containsKey(id)){System.out.println("AOP执行缓存查询数据"+user);//System.out.println(map);return map.get(id);}else {try {System.out.println("aop执行目标方法");result = joinPoint.proceed();map.put(id,user);} catch (Throwable throwable) {throwable.printStackTrace();}}return result;}
}