此篇博客是学习了 之后所总结下来的内容
孙哥说Spring5 全部更新完毕 完整笔记、代码看置顶评论链接~学不会Spring? 因为你没找对人_哔哩哔哩_bilibili
先看看目录(红色为重难点)
1.Spring 工厂化的过程
在没有spring 之前,我们生成一个对象是这样子的
问题:如果我们的userServiceImpl修改为UserserviceImplNew怎么办,这个地方就需要修改了,耦合度太高了
简单工厂
耦合度太高的问题,我们引入一个BeanFactory进行修改,让BeanFactory帮我我们生成userservice,这样不就可以解耦了吗,至少在controller中解耦了(即我们的测试类中)
工厂改进
(利用反射来代替new)
上面的字符串:base.service.impl.UserServiceImpl,也有耦合性,为什么呢?
如果的新的业务类名字为base.service.impl.UserServiceNewImpl,这个java类仍然需要改,我们要的效果是java类不需要改的效果。最多修改配置文件,这样就可以解耦。
继续改进,使用properties文件字符串剔除,(只要是字符串就有招,就是用properties)
applicationContext.properties
userService=base.service.impl.UserServiceImpl
加载,获取配置文件信息
static Properties env = new Properties(); //把applicationContext.properties中数据以流的形式加载进来
//加载applicationContext.properties中的文件,因为是流操作,费资源,所以放在static中。仅加载一次。
static {
InputStream resourceAsStream = BeanFactory.class.getResourceAsStream("/applicationContext.properties");
try {
env.load(resourceAsStream);
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
if(null != resourceAsStream)
resourceAsStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
注意点:
- 加载applicationContext.properties中的文件,因为是流操作,费资源,所以放在static中。仅加载一次
- 一定要加 / ,从当前项目的根根路径开始的
此时新增新的业务处理类:UserServiceNewImpl.java,抛弃:UserServiceNew.java
修改配置文件
其他的什么也不用修改,项目就走了新的业务类UserServiceNewImpl,运行项目一切正常
现在只有一个UserService 对象,那么如果我们有1000个对象呢?这个是个大问题,肯定需要更为通用的做法,这个通用的方法就是通用工厂
通用工厂
通过userService和userDao的比较,我们很容易发现他们的共同点和不同点
总结如下:
- bean的名字不同,可以通过参数传递过去,
- 返回值统一为Object,在调用的地方进行类型转换即可(当然这里使用泛型会更好)。
然后在调用的地方做个简单更改就可以了
通过以上的说明与实例,我们明白了工厂到底是怎么产生对象的
- 定义对象(实体类)
- 通过applicationContext.properties告诉工厂name=value的对应关系
- 调用工厂的getBean(String name) 获取对象,然后强制类型装换即可,而spring的工厂也是这样,一模一样,无非就是定义的接口更丰富了。
2.Spring 工厂API
applicationContext.xml 配置bean
<?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="person" class="base.domain.Person"></bean>
</beans>
Spring⼯⼚的相关的⽅法
先创建工厂
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
一系列API:
Spring 原理
- 先加载配置文件(ApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext.xml");)
- 拿配置文件中的 id 和 类 的全限定名
- 底层通过 反射 去创建对象
- 反射时调用了对象的 构造方法
- 如果构造方法是 私有的也可以调用
- 没有无参构造方法会报错
3.Spring和日志框架log4j整合
为什要让日志框架和spring整合。整合后可以看到spring的debug,info,error等日志了。
spring1,2,3整合commos-logging
spring4,5整合log4j2、logback
这里整合log4j
- 引入log4j的pom依赖
- 创建log4j.properties文件
- 配置log4j.properties即可使用
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.25</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
# resources文件夹根目录下
### 配置根
log4j.rootLogger = debug,console
### 日志输出到控制台显示
log4j.appender.console=org.apache.log4j.ConsoleAppender
log4j.appender.console.Target=System.out
log4j.appender.console.layout=org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n
4.Spring 注入
为什么需要注入
先对比一下 一般和注入的写法
一般的写法
使用setter 注入的写法
好处:解耦合,在java代码中么有耦合,全部在配置文件中了
setter注入
基本类型:
private List<String> address;
private Map<String,String> qqs;
private Properties pp;
自定义类型:
public class UserServiceImpl implements UserService {
private UserDao userDao;
public UserDao getUserDao() {
return userDao;
}
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
}
简写:
构造注入
<bean id="customer" class="base.domain.Customer">
<constructor-arg>
<value>HHHH</value>
</constructor-arg>
<constructor-arg>
<value>18</value>
</constructor-arg>
</bean>
测试
@Test
public void test10() {
System.out.println("test9-------------------");
ApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext.xml");
Customer customer = (Customer) ctx.getBean("customer");
System.out.println(customer);
}
顺利输出~
【TIP】构造方法也是方法,会重载怎么区分呢,可以使用type属性进行区分
<bean id="customer" class="base.domain.Customer">
<constructor-arg>
<value>HHHHH</value>
</constructor-arg>
<constructor-arg type = "int">
<value>18</value>
</constructor-arg>
</bean>
通过上面的例子的代码我们看到了setter注入和构造注入的全貌,通过setter注入和构造注入,可以让我们的代码在java中彻底解耦!
在实际的开发过程种使用setter注入更多
5.Spring反转控制IOC 与 依赖注入DI概念
控制反转(IOC Inverse of Control)-是思想
反转:就是原本由代码创建的成员变量,转移到有spring工厂和配置文件中来完成。
好处:解耦合、工程模式实现
依赖注入(Dependency Injection DI)-是具体实现方式
注入:就是使用spirng工厂和配置文件,一旦发现依赖,就可以把一个类作为成员变量。
好处:解耦合
6.Spring⼯⼚创建复杂对象
什么是复杂对象?
不能通过new的方式创建出来的对象就是复杂对象。
下面是对简单对象和复杂对象的说明
下面介绍三种创建复杂对象的方式:
- FactoryBean
- 实例工厂
- 静态工厂
FactoryBean
1、创建MyConnectionFacoryBean实现FactoryBean接口
2、配置文件,返回的就是Connection对象了
<bean id="conn" class="base.comobjectcreate.factorybean.MyConnectionFacoryBean"/>
测试
对于Connection对象,没有应该都是不同的。所以不能使用单例模式
MyConnectionFacoryBean类中的用户名和密码及连接怎么解耦,此类对这三个有依赖,那么有依赖就可以注入,所以我们的思想是注入进去。
MyConnectionFacoryBean.java 定义成员变量
public class MyConnectionFacoryBean implements FactoryBean<Connection> {
String driver;
String url;
String username;
String password;
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;
}
public String getDriver() {
return driver;
}
public void setDriver(String driver) {
this.driver = driver;
}
//创建对象
@Override
public Connection getObject() throws Exception {
Class.forName(driver);
Connection connection = DriverManager.getConnection(url, username, password);
return connection;
}
//返回创建对象的类型
@Override
public Class<?> getObjectType() {
return Connection.class;
}
/**
*
* @return
* 是否是单利模式 true:单利模式
*/
@Override
public boolean isSingleton() {
return false;
}
}
配置文件的修改
<bean id="conn" class="base.comobjectcreate.factorybean.MyConnectionFacoryBean">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://127.0.0.1:3306/spring?useSSl=false"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</bean>
简易原理解析:创建对象的时候,spring层会调用object.instanceOf()方法,看当前对象是不是FacoryBean对象(实现类),如果是,则调用其getObject()方法返回返回值。当然会校验isSinglton是否为true,返回单例或者非单例对象
实例工厂
1、已经存在的类:
package base.comobjectcreate.factorybean;
import java.sql.Connection;
import java.sql.DriverManager;
/**
* @Classname ConnectionFactory
* @Description 已经拥有的类
*/
public class ConnectionFactory {
public Connection getConnection(){
Connection connection = null;
try {
Class.forName("com.mysql.jdbc.Driver");
connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/spring", "root", "root");
return connection;
} catch (Exception e) {
e.printStackTrace();
}
return connection;
}
}
2、配置文件:
<bean id="connectionFactory" class="base.comobjectcreate.factorybean.ConnectionFactory"/>
<bean id="connection" factory-bean="connectionFactory" factory-method="getConnection"/>
测试
/**
* 实例工厂
* 为什叫:实例工厂呢?因为需要先new ConnectionFactory(),获得工厂的实例,然后调用new ConnectionFactory().getConnection()方法,所以叫实例工厂
*/
@Test
public void test13() {
System.out.println("test9-------------------");
ApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext.xml");
Connection connection = (Connection) ctx.getBean("connection");
System.out.println(connection);
}
为什么叫实例呢?
- 因为需要先new ConnectionFactory(),获得工厂的实例,然后调用new ConnectionFactory().getConnection()方法,所以叫实例工厂
静态工厂
静态工厂和实例工厂唯一的区别就是getConnect()方法是静态的。
随之配置有所不同
1、StaticConnectionFactory.java
2、 配置文件
<!--静态工厂-->
<bean id="staticConnectionFactory" class="base.comobjectcreate.factorybean.StaticConnectionFactory" factory-method="getConnection"/>
测试
对接第三方框架的时候,你没的选择,人家是什么你就使用什么样的方式去使用。但是如果是新项目建议使用FactoryBean的方式
7.Spring对象的创建次数
为什么要考虑对象的创建次数呢?
不是每个对象每次均需要创建新的,这里对象分两种,一种是单例,整个程序就一个对象如SqlSessionFacrory。又如下面的对象每次均需要创建新的如Connection,有效的控制对象的控制次数可以控制内存的开销
什么对象需要是单例的呢?
- SqlSessionFactory
- DAO
- Service
此类对象为无状态对象,整个程序一个就可以了。
什么对象需要每次创建新的呢?
- Connection
- Action(struts2)
- SqlSession
每次数据必须在各自的对象中存储。
配置文件对于单例和非单例的控制(singleton|prototype)
前面也讲过复杂对象的单例非单例控制
最后,请记住!请记住!默认是singleton 单利模式
8.Spring中对象的生命周期(Bean的声明周期)
对象均有声明生命,而spring对对象的创建生命周期由spring控制,我们有必要其探究其对对象声明周期的创建,更有利于我们更为微观的掌握spring
生命周期:对象的创建、存活、消亡全过程
声明周期分为三个阶段
- 创建
- 初始化
- 销毁
对象创建
观察以下这个过程
测试结果:*****************Product.Product
说明单例模式下,在创建工厂时创建了对象,调用了构造函数
将配置文件的单例改为非单例,我们再次进行测试
发现在创建工厂的时候没有创建对象,但是可以在获得对象的时候创建对象
结果:*****************Product.Product
说明在获得对象的时候创建了对象,而不是在创建工厂的时候创建的
那么单例模式可否不让其在创建工厂的时候创建对象呢?让他在获得对象的时候创建?可以的使用 lazy-init=“true”。
配置文件:
<!--生命周期-->
<bean id="product" class="base.lifecycle.Product" lazy-init="true"/>
测试:
结果 : 未输出构造函数
最终结论:在默认单例模式下,可以使用lazy-init="true"来延时加创建对象,待到真正使用的时候再去创建
对象初始化(少用)
初始化就是spring给程序员以及机会,让我们有机会再对象创建完成后写一些自己的逻辑代码,比如多资源初始化、数据库、io网络等(但是实际开发中很少使用)。也就是说这个初始化方法我们程序员来定义,但是有spring框架去执行。
这里有两种写初始化的方法
方法一
实现implements InitializingBean接口,重写:afterPropertiesSet()方法
配置文件
<bean id="product" class="base.lifecycle.Product"/>
测试:
@Test
public void test17() {
ApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext.xml");
}
结果:
*****************Product.Product
Product.afterPropertiesSet
说明在初始化后调用了Product.afterPropertiesSet()方法
方法二
自定义初始化方法,然后通过通配置文件告诉spring是那个方法
配置文件:
<bean id="product" class="base.lifecycle.Product" init-method="myInit"/>
注意此处的init-method=“myInit”
结果:
*****************Product.Product
Product.myInit
如果再加上对成员变量的注入,则先后顺序怎样呢
先给出结论,afterPropertiesSet() 从方法明上也能看出,实在propertie set之后的,即,先setter注入,然后afterPropertiesSet() 再调用myInit()方法
证明:
public class Product implements InitializingBean {
//public class Product {
String name;
Double price;
public Product() {
System.out.println("*****************Product.Product");
}
public Product(String name, Double price) {
this.name = name;
this.price = price;
}
public String getName() {
return name;
}
public void setName(String name) {
System.out.println("Product.setName");
this.name = name;
}
public Double getPrice() {
return price;
}
public void setPrice(Double price) {
System.out.println("Product.setPrice");
this.price = price;
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("Product.afterPropertiesSet");
}
/**
* 自定义的init方法,不在实现接口
* @throws Exception
*/
public void myInit() throws Exception {
System.out.println("Product.myInit");
}
}
测试结果集
*****************Product.Product
Product.setName
Product.setPrice
Product.afterPropertiesSet
Product.myInit
对象销毁
销毁和初始化一样可以实现接口DisposableBean重写destroy()方法,或者自定义销毁方法Mydestroy() ,通过配置文件告诉spring
那么在什么时候会调用这个销毁方法呢?在ClassPathXmlApplicationContext.close()的时候会调用销毁方法
注意因为ApplicationContext类中没有close()方法,所以需要声明为ClassPathXmlApplicationContext类,在嗲用close方法的时候先调用重写的销毁方法,还是先调用自己手写的销毁方法呢?
public class Product implements InitializingBean , DisposableBean {
//public class Product {
String name;
Double price;
public Product() {
System.out.println("*****************Product.Product");
}
public Product(String name, Double price) {
this.name = name;
this.price = price;
}
public String getName() {
return name;
}
public void setName(String name) {
System.out.println("Product.setName");
this.name = name;
}
public Double getPrice() {
return price;
}
public void setPrice(Double price) {
System.out.println("Product.setPrice");
this.price = price;
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("Product.afterPropertiesSet");
}
/**
* 自定义的init方法,不在实现接口
* @throws Exception
*/
public void myInit() throws Exception {
System.out.println("Product.myInit");
}
@Override
public void destroy() throws Exception {
System.out.println("Product.destroy");
}
public void Mydestroy() throws Exception {
System.out.println("Product.Mydestroy");
}
}
配置文件
测试方法
@Test
public void test18() {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext.xml");
ctx.close();
}
结果:
从这个结果可看出先调用springg的销毁方法,再调用自定义的方法。
【注意】 :对于销毁的方法,仅对于singleton对象有效,对于prototype无效,因为prototype对象有jvm控制其销毁过程spring生命周期不管他
图示总结:
9.Spring配置文件参数化
在spring的xml配置文件中。有些String类型的数据,对于运维人员十分不友好。比如数据库连接、用户名、密码。有没有好的办法把它们抽离呢?有的,spring提供了相关的技术支持。
步骤
- 小配置化的配置文件 db.properties
- spring的配置文件,为了方便,另外创建一个 applicationContext2.xml
这个${} 语法,是spring的解析语法,会通过 <context:property-placeholder location=“classpath:db.properties”/> 获得值,通过内置的priperties解析了工具PropertySourcesPropertyResolver.java对其进行解析,然后重新放入xml配置文件中
db.properties
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/spring?useSSL=false
jdbc.username=root
jdbc.password=root
xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!--context的命名空间中的properties的占位符,告诉spring properties在什么地方-->
<context:property-placeholder location="classpath:db.properties"/>
<bean id="conn" class="base.comobjectcreate.factorybean.MyConnectionFacoryBean">
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
</beans>
测试方法:
结果没问题!
这样就达到了我们的目标:配置文件的参数化,针对的是string类型的配置文件
10.Spring自定义类型转化器
在spring中是否已经有了日期类型的转换器呢?答案是肯定的有。但是他支持的格式是2020/11/01 并不支持2020-11-01的格式,这样我们在注入的时候就会出现问题。这类我们就来解决这个问题,一点扩面,来掌握自定义类型转换器
默认时间转换器
实体类
package base.domain;
import java.util.Date;
/**
* @Description 主要测试自定义类型转换器
* 使用这个配置文件:applicationContext3.xml
*/
public class Student {
private String name;
private Date bir;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Date getBir() {
return bir;
}
public void setBir(Date bir) {
this.bir = bir;
}
public Student() {
}
public Student(String name, Date bir) {
this.name = name;
this.bir = bir;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", bir=" + bir +
'}';
}
}
配置文件
测试:
/**
* 自定义类型转换器
*/
@Test
public void test20() {
ApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext3.xml");
Student student = (Student)ctx.getBean("student");
System.out.println(student);
}
结果
自定义的日期转换器展示自己想用的日期格式
实体类不变
配置文件:
记得注册转换器到Spring
自己写类型转换器
package base.convert;
import org.springframework.core.convert.converter.Converter;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* @Description 自定义日期类型转换器
*/
public class MyDateConvert implements Converter<String, Date> {
@Override
public Date convert(String source) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
Date date = null;
try {
date = sdf.parse(source); //解析string->Date
} catch (ParseException e) {
e.printStackTrace();
}
return date;
}
}
测试结果:Student{name='张三', bir=Sun Nov 01 00:00:00 CST 2020}
【TIPS】使用转换器的FactoryBean(ConversionServiceFactoryBean)是id必须是conversionService,没有商量,为什么?因为spring底层会按这个id去扫描这个FactoryBean,拿到这个bean之后会把把里面的Filed converters加载为我们的ref bean,完成自定义类型转换器的注册。
11.Spring的后置处理器
如果我们想对bean的创建过程进行精细化的控制可以做到吗?
比如在我们注入bean的属性后,我们想对注入的属性进行修改可以吗?
答案:可以!spring提供相关的接口可以对bean的初始化前后初始化后对bean进行精细化的操作,这就是bean的后置处理器BeanPostProcessor
实现BeanPostProcessor重写postProcessBeforeInitialization(Object bean, String beanName)和postProcessAfterInitialization(Object bean, String beanName)方法
- postProcessBeforeInitialization(Object bean, String beanName)在afterPropertiesSet() 之前执行
- postProcessAfterInitialization(Object bean, String beanName)在myInit() 方法之后执行
实例演示:
1、创建实体类 Category
2、后置处理器
/**
* @Description 此bean的后置处理器会对所有的bean进行加工。所以针对某一个类型的bean进行加工,或者对某一个beanName进行加工,可以进行控制哦!
*/
public class MyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof Category) {
Category category = (Category) bean;
category.setName("yuhl");
}
return bean;
}
}
3、配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<bean id="category" class="base.domain.Category">
<property name="id" value="1"/>
<property name="name" value="于红亮"/>
</bean>
<bean id="product" class="base.lifecycle.Product"/>
<bean id="myBeanPostProcessor" class="base.beanpostProcessor.MyBeanPostProcessor"/>
</beans>
4、测试
【TIPS】会对所有的bean进行后置处理,避免这个情况的发生要善于使用Object bean, String beanName方法参数,进行instanceof或者使用beanName进行某些判断
BeanPostProcessor
-
是Spring工厂中非常有价值的高级特性,底层进行高级封装时都有这个技术的影子
-
AOP底层实现中BeanPostProcessor是很重要的技术环节
12.静态代理设计模式
背景:在我们开发的web过程中我们分为controller、service、dao三层,那么那层最为重要呢?
答案是 service
基于此,我们有什么办法可以做到service的高内聚和低耦合吗?
答案是肯定的我们可以使用代理模式来实现
什么是代理模式
通过代理为原始类增加额外功能
好处:有利于目标类的维护,增强的更能是可插拔的。
代理类和原始类的关系
均实现同一个接口,对目标类进行增强。
代理模式演示
原始类和接口
代理类
测试:
结果:
UserServiceImplProxy.login 【service外围】
UserServiceImpl.login 我是【service核心】
优势: 对原有方法的增强
那有没有更为高明的办法呢?有的,可以使用spring的动态代理
13.Spring动态代理
spring的动态代理很高端吗?
其实本质上就是对静态代理的抽离,封装
概念
概念:通过代理类为原始类(目标类)增加额外功能
好处:利于原始类(目标类)的维护
从这点看和静态代理一样一样的
实战(MethodBeforeAdvice )
先引入依赖
<!--Spring aop支持-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.1.14.RELEASE</version>
</dependency>
<!--aspectj框架包-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.8.8</version>
</dependency>
<!--编制切面包-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.3</version>
</dependency>
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--目标类 此时和代理类无关-->
<bean id = "userService" class="proxy.service.impl.UserServiceImpl"/>
<bean id = "orderService" class="proxy.service.impl.OrderServiceImpl"/>
<!--通知类-->
<bean id= "myBefore" class="proxy.service.aop.MyBefore"/>
<!--aop配置标签,会自动添加工作空间-->
<aop:config>
<!--定义接入点,即那些方法需要被加强-->
<aop:pointcut id="pointcut" expression="execution(* *(..))"/>
<!--切入点和通知的结合-->
<aop:advisor advice-ref="myBefore" pointcut-ref="pointcut"/>
</aop:config>
</beans>
debug 查看 ---- > 确实是代理类
动态代理和之前的静态代理不同,不需要java文件然后通过类加载子系统,加载进运行时数据区,这里是直接使用字节码相关技术,在JVM内存中直接生成当前类的代理类。也就没有那么多的java类让我们去管理,也就解决了这个痛点
另外他实用配置的方式对所有需要增强的类进行切入点的统一配置,这样就没有了代码冗余
那么肯定会有人提出问题,这个只能对方法的前置进行增强太鸡肋了,有没有更好的办法,可以在之前和之后均能增强呢?
您能想到的问题spring都想到了,接着往下看。使用MethodInterceptor(methodinterceptor接⼝额外功能可以根据需要运⾏在原始⽅法执⾏ 前、后、前后)这个就可以实现。 特别注意是这个包下的:import org.aopalliance.intercept.MethodInterceptor;
spring使用了aop联盟的相关接口来处理这个问题,并不是原生的spring的解决方案
实战(MethodInterceptor)
<aop:pointcut id=“pointcut” expression=“execution(* *(…))”/>表示对所以方法进行增强
测试及结果:
MyArround.invoke 【service外围】前面的
UserServiceImpl.login 我是【service核心】
MyArround.invoke 【service外围】后面的
MyArround.invoke 【service外围】前面的
UserServiceImpl.regester 我是【service核心】
MyArround.invoke 【service外围】后面的
切入点表达式
1、方法切入点表达式
* *(..)
对所有方法进行增强
* login(..)
对login方法进行增强
* login(String,String)
对login 方法且两个参数为String的方法增强
* register(proxy.service.User)
对 register方法且参数为User增强
2、类切入点
指定特定类作为切⼊点(额外功能加⼊的位置),⾃然这个类中的所有⽅法,都会加上对 应的额外功能
3、包切入点表达式
指定包作为额外功能加⼊的位置,⾃然包中的所有类及其⽅法都会加⼊额外的功能
切入点函数
1、 execution
可以满足你的所有想象,可以做所有的事情:方法切入、类切入、包切入
2、args 主要⽤于函数(⽅法) 参数的匹配
execution(* *(String,String)) 等价于 args(String,String)
3、within 主要⽤于进⾏类、包切⼊点表达式的匹配
execution(* ..UserServiceImpl.(..)) 等价于 within(..UserServiceImpl)
execution( com.baizhiedu.proxy .. (..)) 等价于 within(com.baizhiedu.proxy..*)
4、@annotation 为具有特殊注解的⽅法加⼊额外功能
使用时:<aop:pointcut id="" expression="@annotation(proxy.Log)"/>
实例步骤总结
- 目标类:UserServiceImpl
- 增强: MyArround implements MethodInterceptor
- 切入点配置 <aop:pointcut id=“pointcut” expression=“execution(* login(…))”/>
- 切入点和增强合并为切面进行增强。 <aop:advisor advice-ref=“myBefore” pointcut-ref=“pointcut”/>
14.AOP 概念及其原理
概念
本质就是Spring得动态代理开发,通过代理类为原始类增加额外功能。 好处:利于原始类的维护
核心问题
1. AOP如何创建动态代理类 (动态字节码技术)
2. Spring⼯⼚如何加⼯创建代理对象 : 通过原始对象的id值,获得的是代理对象
JDK 的动态代理
通过 Proxy.newProxyInstance⽅法
其参数解析:
参数 classloader
借用一个类加载器,创建代理类的Class对象,进而创建代理对象
类加载器作用
1. 把对应类的字节码文件加载到JVM中
2. 创建类的Class对象,进而创建这个类的对象
例如创建一个User,先要写User.java,编译后生成User.class,再经过以上两步,才能创建出一个对象
如何获得类加载器:JVM会为每个.class文件自动分配与之对应的ClassLoader
而动态代理是通过动态字节码技术,将动态代理类字节码直接写到JVM中,没有具体的.java和.class文件,所以JVM不会为之分配CL,但是创建动态代理对象又需要CL来创建其Class对象,此时,就需要借用一个ClassLoader。这就是newProxyInstance方法第一个参数的含义。
类加载器的知识:
参数interfaces:原始对象实现的接口,userService.getClass().getInterfaces();
参数invocationhandler
代码:
/**
* @Description JDK动态代理
*/
public class JDKProxy {
public static void main(String[] args) {
//目标对象
UserService userService = new UserServiceImpl();
InvocationHandler invocationHandler = new InvocationHandler() {
/**
*
* @param proxy 代理对象,暂时不用
* @param method 需要被增强的方法
* @param args 方法的返回值后
* @return 原方法的返回值
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("JDKProxy.invoke 前增强");
Object ret = method.invoke(userService, args);
System.out.println("JDKProxy.invoke 后增强");
return ret;
}
};
UserService userServiceProxy = (UserService)Proxy.newProxyInstance(userService.getClass().getClassLoader(), userService.getClass().getInterfaces(), invocationHandler);
userService.login("yuhl", "1111");
userServiceProxy.login("yuhl", "1111");
}
}
CGlib动态代理
和JDK动态代理一样,cglib可以对一般的类惊醒动态代理,但是他的条件适应的,他的底层是集成了当前类,然后用super.login()方法,然后在前后进行加强。
也需要三个必要条件:
- 目标类
- 父类对象(接口)
- 增强代码(这点和jdkproxy一模一样,唯一不一样的是一个是接口、一个是一般的类)
代码:
目标类:
测试类:
/**
* @Description TODO
*/
public class CjlibTest {
public static void main(String[] args) {
//目标类
StudentService studentService = new StudentService();
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(studentService.getClass());
enhancer.setClassLoader(CjlibTest.class.getClassLoader());
MethodInterceptor methodInterceptor= new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("CjlibTest.intercept 前");
Object rev = method.invoke(studentService,objects);
System.out.println("CjlibTest.intercept 后");
return rev;
}
};
enhancer.setCallback(methodInterceptor);
StudentService studentServiceProxy = (StudentService) enhancer.create();
studentServiceProxy.login("zhangsan", "211111");
//studentServiceProxy.login("yuhl", "3333");
}
}
测试结果:
代理对象创建时机
还记得我们前面讲的BeanPostProcessor吗?
没错!
15.Spring 基于注解的AOP编程、AOP总结
前面我们介绍的aop编程完全可以满足项目的需要,但是有没有更为简便的方式呢?
当然有了,就是aspect为我们提供的,使用注解的aop编程
值得注意的是不管是aop的注解编程,还是在xml中就行配置,他们的主要矛盾是一样的,矛盾的主要方面也是一样的,即
- 目标对象
- 增强功能
- 切入点
- 编制切面(切入点和增强功能整合在一起)
开发过程
1、MyAspctJ .java 切面类(包含了增强功能、切入点、编制切面)
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
/**
* @Description 通过注解的方式aop开发
* 但是这四样一样不能少
* 1. 目标对象 UserServiceImpl
* 2. 增强功能
* 3. 切入点
* 4. 编制切面(切入点和增强功能整合在一起)
*/
@Aspect //@Aspect告诉spring我是一个切面类
public class MyAspctJ {
//1. 目标对象 UserServiceImpl
UserService userService = new UserServiceImpl();
/**
* 切面, 内含2. 增强功能 和 3. 切入点
* 2. 增强功能
* 3. 切入点
* 4. 编制切面(切入点和增强功能整合在一起)
*/
@Around("execution(* login(..))") //3. 切入点
public Object around(ProceedingJoinPoint proceedingJoinPoint){
Object res = null;
try {
System.out.println("MyAspctJ.around 前**********" ); //2. 增强功能
res = proceedingJoinPoint.proceed();
System.out.println("MyAspctJ.around 后**********" ); //2. 增强功能
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return res;
}
}
2、配置文件(不要以为不用配置了)
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="userService" class="aspectJ.UserServiceImpl"/>
<!--注解的aop编程 这个MyAspctJ还是要告诉spirng的-->
<bean id="aspectj" class="aspectJ.MyAspctJ"/>
<!--告诉spring开始注解的aop编程-->
<aop:aspectj-autoproxy/>
</beans>
3、测试
/**
* 基于注解的aop编程
*/
@Test
public void test6() {
ApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext8.xml");
aspectJ.UserService userService = (aspectJ.UserService)ctx.getBean("userService");
userService.login("zhangsan","111111");
userService.register(new User(2, "222222"));
}
4、结果:
MyAspctJ.around 前**********
UserServiceImpl.login
MyAspctJ.around 后**********
UserServiceImpl.register
改善
想想一下,如果我的切面中有n多的方法均要使用这个切面怎办,如下:
切入点复用!
封装为方法
这样就完美解决这个问题
注解aop开发中切换
默认是jdkproxy
切换为 cgLib
AOP总结
Spring (上)完结