Spring学习笔记
Spring学习笔记
第一章 引言
1.EJB存在的问题(Enterprise Java Bean)
运行环境苛刻,造价高,扩展性,定制型差
tomcat核心东西,servlet引擎
weblogic、websphere ——applicationServer 收费且不开源
代码移植性差
总结:EJB重量级框架
2.什么是Spring
Spring是一个轻量级的JavaEE解决方案,聚合众多优秀的设计模式
轻量级:
- 对于运行环境没有额外要求
- 可以让选择开源(tomcat)的或者收费(weblogic)的环境,直接运行在服务器上
- 代码移植性高(不需要实现额外接口)
javaEE的解决方案
概念:
Spring是一个javaEE开源的轻量级框架,可以解决企业开发中的难题,让编码更加简单,核心组件IOC容器和AOC面向切面编程。
- IOC控制反转:把整个对象的创建过程,统一交给SpringIOC容器处理,底层使用反射+工厂模式实现。
- AOP面向切面编程:对我们的功能(方法)前实现增强,减少代码的冗余,事务,权限管理,底层是基于动态代理模式实现的。
整合设计模式:
- 工厂
- 代理
- 模板
- 策略
3.设计模式
广义概念:
面向对象设计中,解决特定问题的经典代码
狭义概念:
GOF 4人帮定义的23设计模式:工厂、设配器、装饰器、门面、代理、模板等等的
4.工厂设计模式
概念:通过工厂类创建对象
传统方式创建对象:User user=new User();
好处:解耦合(实现类硬编码在程序中就会存在耦合),耦合较高的话不利于代码维护
对象的创建方式:
直接调用构造方法创建对象
UserService userService =new UserServiceImpl();
通过反射的形式创建对象,解耦合(反射+定义配置文件)
- 获取Class类对象:Classs c1= Class.forName(“com.yxnu.UserServiceImpl”);
- 创建对象:UserService userservice=(UserService ) c1.newInstance();
- 但是反射创建时候,”com.yxnu.UserServiceImpl”还是存在耦合,那我们通过properties配置文件解决。
- 解决userDao耦合方式和一样,工厂模式创建(反射+定义配置文件)
Properties配置文件:
- Properties 集合 存储Propertie文件的内容
- 特殊的Map key和value都必须是String类型的,key=String value=String
- Properties [userService=com.yxnu.UserServiceImpl]
- 读取值:Properties.getProperty(“userService”);
1 |
|
方法:
- 一个方法申明(五部分):修饰符 返回值类型 函数名 参数列表 可选项
1public static void test(String str) throw Exception{}
2.方法实现
set注入
提供set和get方法
真正用到的是set方法,即使没有创建变量,只要有set方法spring容器依然可以创建对象
spring配置文件
- bean标签内采用property标签进行赋值,以及简化配置和p命名空间简化配置
构造注入
==提供有参构造方法==
spring的配置文件
- bean标签内采用constructor-arg标签进行赋值,constructor-arg标签个数和顺序和构造方法参数一一对应
构造方法重载
注入的总结
未来的实战中,引用set注入还是构造注入?
答:set注入更多
- 构造注入麻烦(重载)
- spring框架底层大量应用了set注入
第七章 反转控制和依赖注入
1.反转(转移)控制(IOC Inverse Of Control)
控制:对于成员变量赋值的控制权
==反转控制:==把对于成员变量的控制权,从代码中反转(转移)到Spring工厂和配置文件中完成。
- 好处:解耦合
底层实现:工厂设计模式
2.依赖注入(Dependency Injection DI)
注入:通过Spring的工厂以及配置文件,为对象(bean,组件)的成员变量赋值
依赖注入:当一个类需要另外一个类时,就意味着依赖,一旦出现依赖,就可以把另外一个类作为本类的成员变量,最终通过spring配置文件进行注入(赋值),好处:解耦合
1 |
|
第八章 Spring工厂创建复杂对象
1.什么是复杂对象
简单对象:指的是可以直接通过new 构造方法 创建对象
复杂对象:指的是不能直接通过new 构造方法 创建对象
1 |
|
2.Spring工厂创建复杂对象的三种方式
FactoryBean接口
实现FactoryBean接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16public class MyFactoryBean implements FactoryBean{
public Object getObject(){
//用于书写创建复杂对象的代码,并把复杂对象作为方法的返回值返回
}
public Class getObjectType(){
//返回所创建对象的Class对象
}
public boolean isSingleton(){
//需要创建一次
return true
//每一次调用 都需要创建一个新的复杂对象
return false
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26package org.example.FactoryBean;
import org.springframework.beans.factory.FactoryBean;
import java.sql.Connection;
import java.sql.DriverManager;
public class ConnectionFactoryBean implements FactoryBean<Connection> {
//用于书写创建复杂对象的代码
@Override
public Connection getObject() throws Exception {
Class.forName("com.mysql.jdbc.Driver");
Connection conn = DriverManager.getConnection("jdbc:mysql:///wxf", "root", "123456");
return conn;
}
//获得所创建的复杂对象的Class
@Override
public Class<?> getObjectType() {
return Connection.class;
}
//true表示只创建一次,false表示每次都创建
@Override
public boolean isSingleton() {
return false;
}
}Spring配置文件的配置
1
2如果class中指定的类是FactoryBean接口的实现类,那么通过id获取的是这个类所创建的复杂对象,Connection对象
<bean id="conn" class="com." />细节
如果就想获得FactoryBean类型的对象,
ctx.getBean("&conn");
获得的就是ConnectionFactoryBean对象mysql高版本在连接创建时,需要制定SSL证书,时区设置
1
url="jdbc:mysql://localhost:3306/test?useSSL=false&?serverTimezone=UTC"
isSingleton方法
- 返回true只会创建一个复杂对象
- 返回false每一次都会创建新的对象
- 所以根据这个对象的特点来决定是true还是false,Connection为false,因为如果两个人共用一个连接对象,期中一个事务完成提交,但是另外一个并没有完成?所以是每次创建新的对象
依赖注入的体会:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16把ConnectionFactoryBean中依赖的4个字符串信息,进行配置文件的注入,解耦合
1.
private String DriverClassName ;
private String url;
private String username;
private String password;
生成set和get方法,用于set注入
2.
<bean id="conn" class="org.example.FactoryBean.ConnectionFactoryBean">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql:///wxf?serverTimezone=UTC"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</bean>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63package org.example.FactoryBean;
import org.springframework.beans.factory.FactoryBean;
import java.sql.Connection;
import java.sql.DriverManager;
public class ConnectionFactoryBean implements FactoryBean<Connection> {
private String DriverClassName ;
private String url;
private String username;
private String password;
public String getDriverClassName() {
return DriverClassName;
}
public void setDriverClassName(String driverClassName) {
DriverClassName = driverClassName;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
//用于书写创建复杂对象的代码
@Override
public Connection getObject() throws Exception {
Class.forName(DriverClassName);
Connection conn = DriverManager.getConnection(url,username,password);
return conn;
}
//获得所创建的复杂对象的Class
@Override
public Class<?> getObjectType() {
return Connection.class;
}
//true表示只创建一次,false表示每次都创建
@Override
public boolean isSingleton() {
return false;
}
}
FactoryBean的实现原理(简易版)
接口回调
- 为什么Spring规定FactoryBean接口来实现,并且getObject()?
- ctx.getBean(“conn”)获得的是复杂对象,而不是获得ConnectionFactoryBean对象ctx.getBean(“&conn”)?
Spring内部运行流程
1
Connection conn = (Connection) ctx.getBean("conn");
通过conn读取配置文件bean标签相关信息,conn获得ConnectionFactoryBean类的对象,进而通过instanceof判断是否是FactoryBean接口的实现类
1
<bean id="conn" class="org.example.FactoryBean.ConnectionFactoryBean">
Spring按照规定找到你写的复杂对象创建,调用getObject,获得Connection对象
1
2
3
4
5
6
7//用于书写创建复杂对象的代码
@Override
public Connection getObject() throws Exception {
Class.forName(DriverClassName);
Connection conn = DriverManager.getConnection(url,username,password);
return conn;
}返回Connection对象
1
<bean id="conn" class="org.example.FactoryBean.ConnectionFactoryBean">
FactoryBean总结
Spring中用于创建复杂对象的一种方式,也是Spring原生提供的,在Spring整合其他框架时,大量应用FactoryBean
实例工厂
好处:
- 避免Spring框架的侵入
- 整合遗留系统(公司遗留的系统)
开发步骤:
公司已经有遗留的系统,ConnectionFactory
1
2
3
4
5
6
7
8
9
10
11
12public class ConnectionFactory {
public Connection getConnection(){
Connection conn=null;
try {
Class.forName("com.mysql.cj.jdbc.Driver");
conn= DriverManager.getConnection("jdbc:mysql:///wxf?serverTimezone=UTC","root","123456");
} catch (ClassNotFoundException | SQLException e) {
e.printStackTrace();
}
return conn;
}
}配置文件
1
2<bean id="connFactory" class="org.example.FactoryBean.ConnectionFactory"/>
<bean id="conn1" factory-bean="connFactory" factory-method="getConnection"/>
静态工厂
开发步骤
- ```java
/**- 直接StaticConnectionFactory.getConnection();
- /
public class StaticConnectionFactory {
public static Connection getConnection(){
}Connection conn=null; try { Class.forName("com.mysql.cj.jdbc.Driver"); conn= DriverManager.getConnection("jdbc:mysql:///wxf?serverTimezone=UTC","root","123456"); } catch (ClassNotFoundException | SQLException e) { e.printStackTrace(); } return conn;
}1
2
3
4
5
- 配置文件
```xml
<bean id="conn2" class="org.example.FactoryBean.StaticConnectionFactory" factory-method="getConnection"/>
- ```java
3.Spring工厂创建对象的总结
简单对象:
1 |
|
复杂对象:
1 |
|
两种对象的创建方式注入都可以采用上面提到set注入和构造方法注入
第九章 控制Spring工厂创建对象的次数
1.如何控制简单对象的创建次数
1 |
|
2.如何控制复杂对象的创建次数
1 |
|
3.为什么要控制对象的控制次数
好处:节省不必要的内存浪费
什么样的对象只创建一次?
- SqlSessionFactory
- DAO
- 无状态的Service
什么样的对象每一次都要创建?
- Connection,因为要控制事务不可能大家共用
- SqlSession Session
- Controler
如果能被共用,线程是安全的,创建一次即可,如果不能被共用或者线程是不安全的,则需要每次都创建
第十章 对象的生命周期
1.什么是对象的生命周期
指的是一个对象创建、存活、消亡的一个完整过程
2.为什么要学习对象的生命周期
由Spring负责对象的创建、存活、销毁,了解生命周期,有利于使用好Spring为什么创建对象
3.生命周期的三个阶段
创建阶段
spring工厂如何创建对象
1
scope="singleton", spring工厂创建的同时,完成对象的创建
注意:如果想设置scope=”singleton”这种情况下,也需要在获取对象的同时,创建对象,设置lazy-init=”true”
1
2
3scope="prototype",spring工厂会在获取对象的同时,创建对象
什么叫获取对象?
ctx.getBean("user");1
2
3
4
5
6
7
8
9@Test
public void test1(){
//1.获得Spring工厂,对应scope="singleton",这个时候对象已经创建完成了,如果想获取的同时创建对象,加上lazy-init="true"
//对于scope="prototype",在获取对象的时候,getBean()时才创建对象
ApplicationContext ctx=new ClassPathXmlApplicationContext("/applicationContext.xml");
//2.通过工厂类来获取(拿到)对象
Person person= (Person) ctx.getBean("person");
System.out.println(person);
}代码演示
1
2
3
4
5public class Product {
public Product() {
System.out.println("创建对象的时候回调用默认的无参构造");
}
}1
2<!--对象的生命周期-->
<bean id="product" scope="singleton" class="org.example.life.Product" lazy-init="true"/>1
2
3
4
5
6
7
8
9
10
11/**
* 测试对象的生命周期
*/
@Test
public void test13(){
//Spring工厂在创建的时候,就创建了对象
ApplicationContext ctx=new ClassPathXmlApplicationContext("applicationContext.xml");
//Spring工厂会在获取对象的同时,创建对象
ctx.getBean("product");
}
}初始化阶段
Spring工厂在创建完对象后,调用对象的初始化方法,完成对应的初始化操作
1.初始化方法提供:程序员根据需求,提供初始化方法,最终完成初始化操作
2.初始化方法调用:Spring工厂进行调用
- initialzingBean接口(好处就是接口是Spring定义的,不需要额外配置)
1
2
3
4
5
6
7
8
9
10
11
12
13//程序员根据需求,实现的方法,完成初始化操作
afterPropertiesSet()
public class Product implements InitializingBean {
/**
* 这个就是初始化方法:做一些初始化操作
* Spring会进行调用
*/
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("Spring会进行初始化的调用");
}
}对象中提供一个普通的方法(好处:不需要耦合Spring框架,不需要实现Spring规定的接口,但是就是需要额外配置文件)
1
2
3
4
5public void myInit(){
System.out.println("对象中提供初始化的调用");
}
<bean id="product" class="" init-method="myInit"/>细节分析
如果一个对象既实现initialzingBean接口,同时又提供了普通的初始化方法?
答:都会执行,顺序是先执行initalzingBean接口的初始化方法,再执行普通的初始化方法
==注入发生在初始化之前==
什么叫做初始化操作?
答:资源的初始化:数据库 IO 网络
销毁阶段
Spring在销毁对象前,会调用对象的销毁方法,完成销毁操作
1.Spring什么时候销毁所创建的对象?
答:ctx.close()
2.销毁方法:程序员根据自己需求,定义销毁方法,完成销毁操作,Spring工厂会完成调用
DisposableBean
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15public class Product implements InitializingBean, DisposableBean {
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("Spring会进行初始化的调用");
}
@Override
public void destroy() throws Exception {
System.out.println("实现DisposableBean接口的销毁方法");
}
public void myClose(){
System.out.println("自定义的销毁方法");
}
}1
2
3
4
5ApplicationContext ctx=new ClassPathXmlApplicationContext("applicationContext.xml");
//因为ctx.close()是子类的方法,父类没办法调用,所以改成父类ClassPathXmlApplicationContext
ClassPathXmlApplicationContext ctx=new ClassPathXmlApplicationContext("applicationContext.xml");
ctx.getBean("product");
ctx.close();定义一个普通的销毁方法
1
2
3
4
5public void myClose(){
System.out.println("自定义的销毁方法");
}
<bean id="product" scope="singleton" class="" init-method="myInit" destroy-method="myClose"/>细节分析:
销毁方法的操作只适用于
scope="singleton"
什么叫做销毁操作
答:主要值得是,资源的释放操作,io.close(),connection.close()
生命周期的总结:
先创建对象,然后注入(赋值),再初始化(初始化如果使用的是spring提供的方法,会先调用spring的初始化方法再调用自定义的初始化方法),最后销毁(销毁和初始化同样的,如果使用的spring的销毁方法,先调用spring销毁方法再调用自定义的)
第十一章 配置文件参数化
把Spring配置文件中需要经常修改的字符串信息,转移到一个更小的配置文件中
1.spring的配置文件中存在需要经常修改的字符串吗?
答:存在的,比如数据库连接相关的参数,(通常在spring配置文件中是字符串,而且需要经常修改)
2.经常变化的字符串,在spring配置文件中,直接修改的话
答:不利于项目维护(修改)
3.转移到一个小的配置文件,propertises,利于后序修改
4.好处:利于spring配置文件的维护(修改)
1.配置文件参数化的开发步骤
提供一个小的配置文件(.properties),名字和位置随便
1
2
3
4jdbc.driverClassName=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql:///wxf?serverTimezone=UTC
jdbc.username=root
jdbc.password=123456把小配置文件和spring配置文件进行整合
1
2<!--spring配置文件和小配置文件进行整合-->
<context:property-placeholder location="classpath:db.properties"/>在spring配置文件中通过**${key}**获取小配置文件中对应的值
1
2
3
4
5
6<bean id="conn" class="org.example.FactoryBean.ConnectionFactoryBean">
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
后续需要改这块的信息,只需要修改db.properties文件的内容即可
第十二章 自定义类型转化器
1.类型转换器(convert接口)
为什么是接口?
答:因为接口可以多实现,可能你需要多种类型转换。
作用:spring通过类型转化器把配置文件中字符串类型的数据,转换成了对象中成员变量所对应类型的数据,进而完成注入
2.自定义类型转换器
原因:
当spring内部没有提供特定类型转换器时,而程序员在应用的过程中需要使用,那么就需要程序员自定义类型转换器。
比如在set注入时, 实体类中private Date birthday,
此时就会发现spring自带的类型转换器不能转换成,因为每个国家的日期格式都不一样,spring不好做出判断满足需要,所以就需要程序员自定义类型转换器。
步骤:
实现convert接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20public class MyDateConvert implements Converter<String, Date> {
/**
* convert方法作用:String类型转换为Date类型<String,Date>
* SimpleDateFormat sdf=new SimpleDateFormat();
* sdf.praset(String);
* param:source 代表的是配置文件中日期字符串,<value>2021-9-6<value/>
* return:当把转换好的Date作为convert方法的返回值,spring自动为birthday属性进行注入
*/
@Override
public Date convert(String s) {
Date parse = null;
try {
SimpleDateFormat sdf=new SimpleDateFormat("YYYY-MM-DD");
parse = sdf.parse(s);
} catch (ParseException e) {
e.printStackTrace();
}
return parse;
}
}在spring配置文件中进行注册
创建MyDateConvert对象创建出来
1
<bean id="myDateConvert" class="org.example.convert.MyDateConvert"/>
类型转换器的注册
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19目的:告诉spring,我们所创建的MyDateConvert是一个类型转化器
<!--自定义类型转换器-->
<bean id="birthday" class="org.example.convert.Person">
<property name="birthday" value="2021-9-6"/>
</bean>
<!--spring创建MyDateConvert类型对象-->
<bean id="myDateConvert" class="org.example.convert.MyDateConvert"/>
<!--spring提供了注册对象的类ConversionServiceFactoryBean-->
<!--用于注册类型转换器-->
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name="converters">
<!--因为ConversionServiceFactoryBean中的变量converters是set集合-->
<set>
<ref bean="myDateConvert"/>
</set>
</property>
</bean>
细节
把MyDateConvert中的日期格式,通过set注入,解耦合。
1
2
3
4
5
6
7
8
9private String pattern;
public void setPattern(String pattern) {
this.pattern = pattern;
}
public String getPattern() {
return pattern;
}1
2
3<bean id="myDateConvert" class="org.example.convert.MyDateConvert">
<property name="pattern" value="YYYY-MM-DD"/>
</bean>ConversionServiceFactoryBean定义时,id必须是conversionService,大写都不能变,一定得是这个
spring框架内置日期类型的转换器
日期格式:2021/9/6 ,不支持2021-9-6这种格式
第十三章 后置处理Bean
总结:先构造,然后注入,如果实现InitializingBean调用afterPropertiesSet方法,最后调用配置指定的init-method方法,单例,destory方法,多实例,垃圾回收
BeanPostProcessor作用:对spring工厂所创建的对象,进行再加工
1 |
|
BeanPostProcessor运行原理和流程
程序员实现BeanPostProcessor接口中规定的方法
1 |
|
实战中,很少做初始化操作,那么这个时候就没有必要区分Before还是After,只需要取其中一个方法即可(After)
BeanPostProcessor开发步骤
1.类实现BeanPostProcessor接口
1 |
|
2.spring配置文件中进行配置
1 |
|
3.BeanPostProcessor细节
BeanPostProcessor会对spring工厂中所有创建的对象进行加工
1 |
|
AOP编程
第一章 静态代理设计模式
1.为什么需要代理设计模式?
1.1 问题
在javaEE分层开发中,哪个层次对于我们来讲最重要
答:Service层最为重要
Service层包含了哪些代码?
答:Service层=核心功能(几十行 上百行代码)+ 额外功能
核心功能:业务运算,Dao调用
额外功能:不属于业务,可有可无,代码量小,比如事务,日志,性能等等的
额外功能书写在Service层好不好?
答:Service层的调用者角度(controler):比如事务功能就希望额外增加
(软件设计者):不需要额外功能,以便修改出错
现实生活中解决方式:
2.代理设计模式
概念:
通过代理类,为原始类(目标)增加额外的功能
好处:利于原始类(目标)类的维护
名词解释:
原始类(目标类):指的是业务类,只负责核心功能
目标方法(原始方法):指的就是原始类中的方法
额外功能(附加功能):日志、事务、性能等等的
代理开发的核心要素:
代理类=原始类(目标类)+ 额外功能 + 和原始类实现相同的接口
1 |
|
编码:
1 |
|
1 |
|
静态代理存在的问题
静态类文件数量过多,不利于项目管理,
1
2UserServiceImpl UserServiceProxy
OrderServiceImpl OrderServiceProxy额外功能维护性差
如果需要修改代理类中的额外功能,那么每一个代理类都需要修改
第二章 Spring的动态代理开发
1.Spring动态代理的概念
概念:通过代理类为原始类增加额外功能,从概念上和静态代理一样
好处:利于原始类的维护
2.搭建开发环境
1 |
|
3.Spring动态代理的开发步骤
创建原始对象
1
2
3
4
5
6
7
8
9
10
11
12public class UserServiceImpl implements UserService{
@Override
public void reg(User user) {
System.out.println("注册:业务运算+Dao");
}
@Override
public boolean login(String name, String password) {
System.out.println("登录");
return true;
}
}1
<bean id="userServiceImpl" class="yxnu.edu.proxy.UserServiceImpl"/>
提供额外功能
MethodBeforeAdivice接口
额外的功能书写在接口的实现中,运行在原始类方法运行之前
1
2
3
4
5
6
7
8
9public class Before implements MethodBeforeAdvice {
/**
* 作用:需要把运行在原始方法执行之前的额外功能,书写在before中
*/
@Override
public void before(Method method, Object[] objects, Object o) throws Throwable {
System.out.println("---method before advice log---");
}
}1
<bean id="before" class="yxnu.edu.dynamic.Before"/>
定义切入点(配置文件中完成)
切入点:额外功能加入的位置,根据需要比如加入在register,login,这就叫切入点
1
2
3
4<aop:config>
<!--所有的方法,都作为切入点,加入额外功能-->
<aop:pointcut id="pc" expression="execution(* *(..))"/>
</aop:config>组装(2 3整合)
把切入点和额外功能进行整合
1
2
3<!--组装:目的是把切入点和额外功能进行整合-->
<!--所有的方法,login,register都加入before这个额外功能-->
<aop:advisor advice-ref="before" pointcut-ref="pc"/>调用
目的:获得spring工厂创建的动态代理对象,并进行调用
注意:
- ==spring工厂通过原始对象id值获得的是代理对象==
- 获得代理对象后,可以通过声明接口类型,并进行对象的存储
1
2
3
4ApplicationContext ctx=new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService=(UserService)ctx,getBean("userServiceImpl");
userService.reg();
userService.login();
4.spring动态代理的细节
sprin创建的动态代理类在哪里?
spring框架在运行时,通过动态字节码技术,在JVM创建的,运行在JVM内部,等程序结束后,会和JVM一起消失。
什么叫做动态字节码技术:通过第三方动态字节码框架,在JVM中创建对应类的字节码,进而创建对象,当虚拟机结束时,动态字节码跟着消失。
结论:动态代理不需要定义类文件,都是JVM运行过程中动态创建的,所以不会造成静态代理的类文件数量过多,影响项目管理的问题。
动态代理编程简化代理的开发
在额外功能不改变的情况下,创建其他原始类的代理对象时,只需要指定原始类对象即可。
动态代理额外功能维护性增强
打开扩展,关闭修改
如果之前的额外功能方法不满足需求了,需要新的额外功能,这个时候只需要增加新的额外功能方法,然后修改配置文件即可,尽量开闭原则
1
<bean id="before" class="yxnu.edu.dynamic.NewBefore"/>
第三章 spring动态代理详解
1.额外功能详解
MethodBeforeAdvice分析
1.MethodBeforeAdvice接口作用:额外需要运行在原始方法之前,进行额外功能操作
1 |
|
2.before方法的3个参数在实战中如何使用
答:根据需要进行使用,有可能用到有可能用不到
MethodInterceptor(方法拦截器)
可以在原始方法前、后、前后、抛异常时使用,甚至可以改变原始方法返回值运行结果
MethodBeforeAdvice——只能在原始方法执行之前,而MethodInterceptor可以在原始方法执行前或者后或者前后都可以使用
代码演示:
- 额外功能运行在原始方法之前:
1 |
|
- 额外功能运行在原始方法之后:
1 |
|
- 额外功能运行在原始方法之前之后:
1 |
|
- 额外功能原始方法抛出异常的时候
1 |
|
MethodInterceptor影响原始方法的返回值
原始方法的返回值,直接作为invoke方法的返回值,是不会影响原始方法的返回值
但是,MethodInterceptor也是可以影响原始方法的返回值的,invoke方法的返回值不要返回原始方法的返回运行结果即可。
2.切入点详解
切入点决定额外功能加入位置(主要指在哪些方法上加切入点)
1 |
|
1 |
|
切入点表达式
1.方法切入点表达式
1 |
|
1 |
|
定义login方法作为切入点
1 |
|
定义login方法,且有两个字符串类型的参数作为切入点
1 |
|
上面所讲解的方法切入点不精准
精准的方法切入点限定
1 |
|
2.类切入点
指定特定的类作为切入点,自然这个类中的所有方法,都会加上对应的额外功能
1 |
|
这个类中的所有方法都作为切入点,加入额外功能
假设a、b两个包都有一个同样名称的类,这样的话就需要把包名写成*,忽略包
1 |
|
3.包切入点 ——更具实战性
指定包作为额外功能加入的位置,包中所有的类和方法都会加入额外功能
1 |
|
切入点函数
执行切入点表达式
1.execution
- 最为重要的切入点函数,功能最全
- 可以执行方法切入点表达式、类切入点、包切入点
- 弊端:书写麻烦
- 注意:其他切入点函数,是简化execution书写复杂度的,功能上是一样的
2.args
- 作用:主要用于函数(方法)参数的匹配
- 切入点:方法参数必须得是2个字符串类型的参数
- execution(* *(String,String)) args(String,String)
3.within
- 作用:主要用于进行类、包切入点表达式的匹配
- 切入点:UserServiceImpl这个类
类切入点:execution(* *..UserServiceImpl.*(..)) within(*..UserServiceImpl)
包切入点:within(yxnu.edu.proxy..*)
4.@annotation
作用:具有特殊注解的方法加入额外功能
```xml
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Log {
}@Log @Override public void reg(User user) { System.out.println("注册:业务运算+Dao");
// throw new RuntimeException(“测试异常”);
}
<aop:pointcut id=”pc” expression=”@annotation(yxnu.edu.proxy.Log)”/>
1
2
3
4
5
6
7
8
9
10
11
12
13
#### 切入点函数的逻辑运算
指的是 **整合多个切入点函数一起配合**工作,进而完成更为复杂的需求
- and操作
```xml
案列:方法login 参数为2个字符串
execution(* login(String,String))
execution(* login(..)) and args(String,String)1
2<!--切入函数的逻辑运算-->
<aop:pointcut id="pc" expression="execution(* login(..)) and args(String,String)"/>注意:and操作不能用于两边都是同种类型的切入函数
or操作
1
2案例:register方法和login方法作为切入点
executin(* login(..)) or execution(* register(..))
总结:
为什么需要动态代理类?
通过代理类为原始类(目标类)增加额外功能,
好处:利于原始类的维护
开发步骤:
目标对象(原始对象)
额外功能
MethodInterceptor接口中的invoke方法
切入点
切入点表达式
切入点函数
组装
将切入点和额外功能整合在一起
第四章 Aop编程
1.Aop概念
AOP(Aspect Oriented Programing) 面向切面编程
以切面为基本单位的程序开发,。。。
切面=切入点+额外功能
00P 面向对象编程
以对象为基本单位的程序开发,通过对象间的彼此协调,相互协调,完成程序的构建
POP 面向过程编程
以过程(函数、方法)为基本单位的程序开发,通过过程间的彼此协调,相互协调,完成程序的构建
本质就是Spring的动态代理开发,通过动态代理类为原始类增加额外功能,好处:有利于原始类的维护
注意:Aop编程不可能取代OOP编程,它是OOP的补充
2.Aop编程的开发步骤
1.原始对象
2.额外功能
3.切入点
4.组装切面
3.切面的名词解释
切面=切入点+额外功能
第五章 AOP的底层实现原理
1.核心问题
AOP如何创建多态代理类
Spring的工厂是如何加工创建代理对象
通过原始对象的id值获得的是代理对象
2.动态代理类的创建
2.1 JDK的动态代理
- Proxy.newProxyInstance方法参数详解
1 |
|
2.2 CGlib的动态代理
- CGlib创建动态代理类的原理:父子继承关系创建代理对象,原始类作为父类,代理类作为子类,这样既可以保证二者方法一致,同时还可以在代理类中增加新的实现。
- 编码
1 |
|
2.3 总结
- JDK创建动态代理 ——Proxy.newProxyInstance() 通过接口创建代理的实现类
- CGlib创建动态代理——Enhancer 通过继承父类创建的代理类
3.Spring工厂如何加工原始对象
思路分析
第六章 基于注解的AOP编程
1.基于注解的AOP编程开发步骤
原始对象
额外功能
切入点
组装切面
通过切面类定义了 额外功能、切入点
@Around("execution(* login(..))")
切面类 @Aspect
1 |
|
1 |
|
1 |
|
2.细节
1.切入点复用
在切面类中定义一个函数,加上@Pointcut
注解,通过这种方式定义切入点表达式,后续更加有利于切入点复用、项目的维护
1 |
|
2.动态代理的创建方式
AOP底层实现 两种代理创建方式
1 |
|
默认情况下,spring使用的是jdk动态代理创建对象
如果要切换成CGlib
基于注解AOP开发
1
<aop:aspectj-autoproxy proxy-target-class="true"/>
传统的AOP开发
1
2
3<aop:config proxy-target-class="true">
...
</aop>
第七章 AOP开发中的一个坑
坑:在同一个业务类中,进行业务方法间的相互调用,只有最外层的方法加入了额外功能,而内部的方法,通过普通方式调用,是原始类对象调用的方法,无法加入额外功能,如果想让内层的方法也调用代理对象的方法,就需要获得工厂,进而获得代理对象。
ApplicationContextAware接口
1 |
|