重学 Spring(上)

此篇博客是学习了 之后所总结下来的内容

孙哥说Spring5 全部更新完毕 完整笔记、代码看置顶评论链接~学不会Spring? 因为你没找对人_哔哩哔哩_bilibili


先看看目录(红色为重难点)

1.Spring 工厂化的过程

简单工厂

工厂改进

通用工厂

2.Spring 工厂API

applicationContext.xml 配置bean

Spring⼯⼚的相关的⽅法

3.Spring和日志框架log4j整合

4.Spring 注入

为什么需要注入

setter注入

构造注入

5.Spring反转控制IOC 与 依赖注入DI概念

6.Spring⼯⼚创建复杂对象

FactoryBean

实例工厂

静态工厂 

7.Spring对象的创建次数 

8.Spring中对象的生命周期(Bean的声明周期)

对象创建

对象初始化(少用)

方法一

方法二

如果再加上对成员变量的注入,则先后顺序怎样呢

对象销毁

 9.Spring配置文件参数化

10.Spring自定义类型转化器

默认时间转换器

 自定义的日期转换器展示自己想用的日期格式

11.Spring的后置处理器

12.静态代理设计模式

什么是代理模式

代理模式演示

13.Spring动态代理

概念

实战(MethodBeforeAdvice )

实战(MethodInterceptor)

切入点表达式

切入点函数

实例步骤总结

14.AOP 概念及其原理

概念

核心问题

JDK 的动态代理

CGlib动态代理

 代理对象创建时机

15.Spring 基于注解的AOP编程、AOP总结

开发过程

进阶

注解aop开发中切换

AOP总结


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();
            }
        }
    }

 注意点:

  1. 加载applicationContext.properties中的文件,因为是流操作,费资源,所以放在static中。仅加载一次
  2. 一定要加  / ,从当前项目的根根路径开始的

 此时新增新的业务处理类:UserServiceNewImpl.java,抛弃:UserServiceNew.java

 修改配置文件

 其他的什么也不用修改,项目就走了新的业务类UserServiceNewImpl,运行项目一切正常

现在只有一个UserService 对象,那么如果我们有1000个对象呢?这个是个大问题,肯定需要更为通用的做法,这个通用的方法就是通用工厂

通用工厂

通过userService和userDao的比较,我们很容易发现他们的共同点和不同点
总结如下:

  1. bean的名字不同,可以通过参数传递过去,
  2. 返回值统一为Object,在调用的地方进行类型转换即可(当然这里使用泛型会更好)。

 

 然后在调用的地方做个简单更改就可以了

通过以上的说明与实例,我们明白了工厂到底是怎么产生对象的

  1. 定义对象(实体类)
  2. 通过applicationContext.properties告诉工厂name=value的对应关系
  3. 调用工厂的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 原理

  1. 先加载配置文件(ApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext.xml");)
  2. 拿配置文件中的 id 和 类 的全限定名
  3. 底层通过 反射 去创建对象
  4. 反射时调用了对象的 构造方法
  5. 如果构造方法是 私有的也可以调用
  6. 没有无参构造方法会报错
     

3.Spring和日志框架log4j整合

为什要让日志框架和spring整合。整合后可以看到spring的debug,info,error等日志了。

spring1,2,3整合commos-logging
spring4,5整合log4j2、logback
这里整合log4j

  1. 引入log4j的pom依赖
  2. 创建log4j.properties文件
  3. 配置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提供了相关的技术支持。

 步骤

  1. 小配置化的配置文件 db.properties
  2. 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)"/>

实例步骤总结

  1. 目标类:UserServiceImpl
  2. 增强: MyArround implements MethodInterceptor
  3. 切入点配置 <aop:pointcut id=“pointcut” expression=“execution(* login(…))”/>
  4. 切入点和增强合并为切面进行增强。 <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()方法,然后在前后进行加强。
也需要三个必要条件:

  1. 目标类
  2. 父类对象(接口)
  3. 增强代码(这点和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. 目标对象
  2. 增强功能
  3. 切入点
  4. 编制切面(切入点和增强功能整合在一起)

开发过程

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 (上)完结


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值