Spring5笔记

视频

概述

Tomcat Javaweb服务器 帮助我们去集中去处理请求和响应 包括完成Servlet以及相关代码的运行和解析

而其中Servlet引擎 最核心的功能

Controller控制器
Service业务处理
DAO数据库的访问和操作
DB数据库


设计模式

  1. ⼴义概念

    ⾯向对象设计中,解决特定问题的经典代码

  2. 狭义概念

    GOF4⼈帮定义的23种设计模式:⼯⼚、适配器、装饰器、⻔⾯、代理、模板… GOF4 (面向对象领域的4个大师)

工厂模式

好处:解耦合

对象的创建方式:

1.直接调用构造方法创建对象 UserService userService=new UserServiceImpl(); 把接口的实现类硬编码在程序中

这行代码有强关联关系,如果想改变UserServiceImpl这行代码,那么整个类都要改变

//UserServiceImpl userService = new UserServiceImpl();

//这里只需要更改BeanFactory就可以更改UserServiceImpl
UserService userService = BeanFactory.getUserservice(); // return new UserServiceImpl();

public class BeanFactory {
    public static UserService getUserservice(){
        return new UserServiceImpl();
    }
}

2.通过反射的形式创建对象解耦合

public class BeanFactory {
    public static UserService getUserservice() throws Exception {
            Class<?> clazz = Class.forName("com.zhang.basic.UserServiceImpl");
            return (UserService) clazz.newInstance();
    }
}


//配置文件的方式
private static final Properties env = new Properties() ;
static {
    InputStream resourceAsStream = BeanFactory.class.getResourceAsStream("/applicationContext.properties");
    try {
        env.load(resourceAsStream);
    } catch (IOException e) {
        e.printStackTrace();
    }
}

public static UserService getUserservice() throws Exception {
    Class<?> clazz = Class.forName(env.getProperty("UserService"));
    return (UserService) clazz.newInstance();
}
#只需要更改配置文件的值就可以更改对应的UserServiceImpl
UserService = com.zhang.basic.UserServiceImpl		

方法声明5个要素 修饰符、返回值类型、方法名、参数表、异常(可选)

通用工厂文件

public class BeanFactory {
    private static Properties env = new Properties();
	//IO 系统级资源 尽量避免重复打开IO 而且最好是在程序启动的时候一次性读取想要的内容  用静态代码块的方式来完成
    static{
        try {
            //为避免耦合 类名最好写在配置文件中
            //第一步 获得IO输入流
            InputStream inputStream=BeanFactory.class.getResourceAsStream("/applicationContext.properties");
            //第二步 文件内容到封装 Properties集合中 key = userService value = com.baizhixx.UserServiceImpl
            env.load(inputStream);       
            inputStream.close();   //关闭IO输入流
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
/* 这里结构所使用的代码基本一致 只有小配置文件的key是有所区别的
 * 所以为每一个对象提供一个独立的工厂是没有价值和意义的
 */
     public static Object getBean(String key){
         Object ret = null;
         try {
             Class clazz = Class.forName(env.getProperty(key));
             ret = clazz.newInstance();
         } catch (Exception e) {
            e.printStackTrace();
         }
         return ret;
     }
    
/*  test文件中  */   
@Test
public void test1() {
    UserService userService = (UserService)BeanFactory.getBean("userService");
	//返回的是Object但实际需要的是UserService  所以需要强制类型转换
 
    userService.login("name", "suns");	//具体user	Service对象的使用
    User user = new User("suns", "123456");
    userService.register(user);
}
将文件内容封装到集合中

Java Properties类:用于读取java的配置文件

https://www.cnblogs.com/bakari/p/3562244.html

load ( InputStream inStream),从输入流中读取属性列表(键和元素对)。通过对指定的文件(比如说xxx.properties 文件)进行装载来获取该文件中的所有键值对。以供 getProperty ( String key) 来搜索。

第一个Spring程序

环境配置

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.1.4.RELEASE</version>
</dependency>

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>5.1.14.RELEASE</version>
</dependency>

<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.11</version>
    <scope>test</scope>
</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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="person" class="com.zhang.basic.Person"/>
</beans>
@org.junit.Test
    public void test3(){
    ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext.xml");
    Person person = (Person) ctx.getBean("person");
}

Spring⼯⼚的相关的⽅法

//第一种方式
Person person = (Person) ctx.getBean("person");
//第二种方式		通过这种⽅式获得对象,就不需要强制类型转换
Person person =ctx.getBean("person",Person.class);
//第三种方式
//当前Spring的配置⽂件中 只能有⼀个bean class是Person类型
Person person =ctx.getBean(Person.class);
//获取的是 Spring工厂配置文件中所有bean标签的id值  person person1
String[] beanDefinitionNames = ctx.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
    System.out.println("beanDefinitionName = " + beanDefinitionName);
}

//根据类型获得Spring配置文件中对应的id值
String[] beanNamesForType = ctx.getBeanNamesForType(Person.class);
for (String id : beanNamesForType) {
    System.out.println("id = " + id);
}

//用于判断是否存在指定id值得bean,不能判断name值
if (ctx.containsBeanDefinition("person")) {
    System.out.println("true = " + true);
}else{
    System.out.println("false = " + false);
}


//用于判断是否存在指定id值得bean,也可以判断name值
if (ctx.containsBean("p")) {
    System.out.println("true = " + true);
}else{
    System.out.println("false = " + false);
}

注入

通过Spring⼯⼚及配置⽂件,为所创建对象的成员变量赋值

为什么需要注⼊?

通过编码的⽅式,为成员变量进⾏赋值,存在耦合

好处:解耦合

如何进⾏注⼊?

  • 类的成员变量提供set get⽅法
  • 配置spring的配置⽂件
String+8种基本类型
private Integer id;
private String name;
//Getter and Setter
public Integer getId() {return id;}
public void setId(Integer id) {this.id = id;}
public String getName() {return name;}
public void setName(String name) {this.name = name;}
<bean id="person" name="p" class="com.baizhiedu.basic.Person">
	<property name="id">
		<value>20</value>
	</property>
	<property name="name">
		<value>zy</value>
	</property>
</bean>

基于属性简化

<!--	value 属性只能简化8种基本类型 + String 注入标签-->

<bean id="person" name="p" class="com.baizhiedu.basic.Person">
    <property name="id" value="20"/>
    <property name="name" value="zy"/>
</bean>

基于p命名空间的简化

P:中间按alt+enter 选择Create namespace declaration

<bean id="person" name="p" class="com.baizhiedu.basic.Person" p:id="20" p:name="zy"/>
数组
<property name="addresses">
  <list>
      <value>zpark</value>
      <value>shangdi</value>
      <value>xierq</value>
      <value>xierq</value>
      <value>xierq</value>
  </list>
</property>
Set集合
<property name="tels">
    <set>
        <value>138111111</value>
        <value>139111111</value>
        <value>166111111</value>
        <value>166111111</value>
        <value>166111111</value>
    </set>
</property>
List集合
<property name="addresses">
  <list>
      <value>zpark</value>
      <value>shangdi</value>
      <value>xierq</value>
      <value>xierq</value>
      <value>xierq</value>
  </list>
</property>

Map集合

<!--注意: map -- entry -- key有特定的   标签 <key></key>
 						  值根据对应类型选择对应类型的标签-->
<property name="qqs">
  <map>
     <entry>
         <key><value>suns</value></key>
         <value>3434334343</value>
     </entry>
     <entry>
         <key><value>chenyn</value></key>
         <value>73737373</value>
     </entry>
  </map>
</property>
Map<String, String> qqs = person.getQqs();
Set<String> keys = qqs.keySet();    //通过keySet获得对象里的key    然后通过对象.get(key)获得value
for (String key : keys) {
    System.out.println(key + " value is " + qqs.get(key));
}

Properites

<!--Properties类型 特殊的Map key=String
						   value=String-->
<props>
     <prop key="key1">
         value1
    </prop>
    
     <prop key="key2">
         value2
    </prop>
</props>
为用户自定义类型赋值
public class UserServiceImpl implements UserService {
//    private UserDAO userDAO = new UserDAOImpl();
//    private UserDAO userDAO = (UserDAO)BeanFactory.getBean("userDAO");
    private UserDAO userDAO;		//具体的赋值使用配置文件来完成->解耦合
    
    public UserDAO getUserDAO() {
        return userDAO;
    }
    public void setUserDAO(UserDAO userDAO) {  //通过set方法为UserDAO对象赋值
        this.userDAO = userDAO;
    }
    
    @Override
    public void register(User user) {
        userDAO.save(user);
    }
    @Override
    public void login(String name, String password) {
        userDAO.queryUserByNameAndPassword(name, password);
    }
}

userService接口里的UserServiceImpl想要使用UserDAO接口里的UserDAOImpl类的方法

 <bean id="userService" class="com.baizhiedu.basic.UserServiceImpl">
       <property name="userDAO">
           <bean class="com.baizhiedu.basic.UserDAOImpl"/>
       </property>
 </bean>
@Test
public void test10() {
   ApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext.xml");
   UserService userService = (UserService) ctx.getBean("userService");

   userService.register(new User("suns", "123456"));   //UserDAO里面的方法可以使用
   userService.login("xiaohei", "999999");
}
自定义类型赋值第二种方式

第⼀种赋值⽅式存在的问题:

  1. 配置⽂件代码冗余;
  2. 被注入的对象 (UserDAO)多次创建,浪费(JVM)内存资源。
<bean id="userDAO" class="com.baizhiedu.dao.UserDAOImpl"></bean>

<bean id="userService" class="com.baizhiedu.service.UserServiceImpl">
    <!--这里的name是Setter里的形参  public void setUserDAO(UserDAO userDAO) {this.userDAO = userDAO; }-->
    <!--														   ↑						       -->
    <property name="userDAO">		 
        <ref bean="userDAO"/>
    </property>
</bean>

基于属性简化

<bean id="userDAO" class="com.baizhiedu.dao.UserDAOImpl"></bean>

<bean id="userService" class="com.baizhiedu.service.UserServiceImpl">
    <property name="userDAO" ref="userDAO"/>
</bean>

基于p命名空间的简化

<bean id="userDAO" class="com.baizhiedu.dao.UserDAOImpl"></bean>

<bean id="userService" class="com.baizhiedu.service.UserServiceImpl" p:userDAO-ref="userDAO"/>

构造注入

public class Customer {
    private String name;
    private int age;
    
    public Customer(String name) {this.name = name;}
    
    public Customer(int age) {this.age = age;}

    public Customer(String name, int age) {  	//被重载的方法必须改变参数列表(参数个数或类型或顺序不一样);
        this.name = name;
        this.age = age;
    }
}
<bean id="customer" class="com.baizhiedu.basic.constructer.Customer">
    <constructor-arg type="int">
        <value>20</value>
    </constructor-arg>
</bean>

反转控制

反转控制(IOC Inverse of Control)

控制:对于成员变量赋值的控制权;

反转控制:把对于成员变量赋值的控制权,从代码中转移(反转)到 Spring ⼯⼚和配置⽂件中完成。

好处:解耦合;

底层实现:工厂设计模式;

依赖注入

依赖注入 (Dependency Injection - DI)

注⼊:通过 Spring 的⼯⼚及配置⽂件,为对象(bean,组件)的成员变量赋值;
依赖注⼊:当⼀个类需要另⼀个类时,就意味着依赖,⼀旦出现依赖,就可以把另⼀个类作为本类的成员变量,最终通过 Spring 配置⽂件进⾏注⼊(赋值)
好处:解耦合;
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-w6khT5az-1592187674958)(photo/image-20200611221048923.png)]

FactoryBean 接口

public class ConnectionFactoryBean implements FactoryBean<Connection> {
    // 用于书写创建复杂对象时的代码
    @Override
    public Connection getObject() throws Exception {
        Class.forName("com.mysql.jdbc.Driver");
        Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/spring", "root", "1234");
        return conn;
    }

    // 返回创建的复杂对象的类型
    @Override
    public Class<Connection> getObjectType() {
        return Connection.class;
    }

    // 是否单例
    @Override
    public boolean isSingleton() {
        return false; // 每一次都创建新的复杂对象
        // return true; // 只创建一次这种类型的复杂对象
    }
}

<!--class 指定了 ConnectionFactoryBean, 获得的是该类创建的复杂对象 Connection -->
<bean id="conn" class="com.baizhiedu.factorybean.ConnectionFactoryBean"/>

如果就想获得 FactoryBean 类型的对象,加个 &ctx.getBean("&conn")

ConnectionFactoryBean cfb = (ConnectionFactoryBean) ctx.getBean("&conn");

Spring 内部运行流程:

  1. 配置文件中通过 id conn 获得 ConnectionFactoryBean 类的对象 ,进而通过 instanceof 判断出是 FactoryBean 接⼝的实现类;
  2. Spring 按照规定 getObject() —> Connection;
  3. 返回 Connection;

实例工厂

  1. 避免 Spring 框架的侵⼊;
  2. 整合遗留系统;
public class ConnectionFactory {
    public Connection getConnection() {
        Connection conn = null;
        try {
            Class.forName("com.mysql.jdbc.Driver");
            conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/spring?useSSL=false", "root", "1234");
        } catch (ClassNotFoundException | SQLException e) {
            e.printStackTrace();
        }
        return conn;
    }
}
<!--实例工厂-->
<!-- 先创建出工厂实例 		实例方法必须先有对象  再有对应的方法调用	-->
<bean id="connFactory" class="com.baizhiedu.factorybean.ConnectionFactory"/>
 <!-- 通过工厂实例里的方法创建复杂对象 -->
<bean id="conn" factory-bean="connFactory" factory-method="getConnection"/>

静态工厂

<!--静态工厂-->
<bean id="conn" class="com.baizhiedu.factorybean.StaticFactoryBean" factory-method="getConnection"/>

控制 Spring 工厂创建对象的次数

控制简单对象的创建次数

配置文件中进行配置:
sigleton:只会创建⼀次简单对象,默认值;
prototype:每⼀次都会创建新的对象;

<!--控制简单对象创建次数-->
<bean id="scope" scope="singleton" class="com.baizhiedu.scope.Scope"/>
控制复杂对象的创建次数

如果是 FactoryBean 方式创建的复杂对象:

public class xxxFactoryBean implements FactoryBean {
	public boolean isSingleton() {
		return true; // 只会创建⼀次
		// return false; // 每⼀次都会创建新的
	}
}

对象的生命周期

创建阶段

Spring 工厂何时创建对象?

  1. scope="prototype":Spring 工厂在获取对象 ctx.getBean("xxx") 的同时,创建对象。
  2. scope="singleton":Spring 工厂创建的同时,创建对象。
    通过配置 <bean lazy-init="true"/> 也可以实现工厂获取对象的同时,创建对象。

初始化阶段

什么时候?Spring 工厂在创建完对象后,调用对象的初始化方法,完成对应的初始化操作。
初始化方法提供:程序员根据需求,提供初始化方法,最终完成初始化操作。
初始化方法调用:Spring 工厂进行调用。

提供初始化方法的两种方式:

InitializingBean 接口:

public cass Product implements InitializingBean {
	//程序员根据需求实现的方法, 完成初始化操作
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("Product.afterPropertiesSet");
    }
}

对象中提供一个普通的初始化方法,配置文件种配置 init-method

public class Product {
    public void myInit() {
        System.out.println("Product.myInit");
    }
}
<bean id="product" class="com.baizhiedu.life.Product" init-method="myInit"/>
  1. 如果⼀个对象既实现 InitializingBean 同时⼜提供的 普通的初始化方法,执行顺序?
    先执行 InitializingBean,再执行 普通初始化方法。
  2. 注入⼀定发⽣在初始化操作的前面。

销毁阶段

Spring 销毁对象前,会调用对象的销毁方法,完成销毁操作。
Spring 什么时候销毁所创建的对象?ctx.close();
销毁方法提供:程序员根据业务需求,定义销毁方法,完成销毁操作
销毁方法调用:Spring 工厂进行调用。

开发流程与初始化操作一样,提供销毁方法的两种方式:

DisposableBean 接口:

public class Product implements DisposableBean {
    // 程序员根据⾃⼰的需求, 定义销毁方法, 完成销毁操作
    @Override
    public void destroy() throws Exception {
        System.out.println("Product.destroy");
    }
}

对象中提供一个普通的销毁方法,配置文件种配置 destroy-method

public class Product {
	// 程序员根据⾃⼰的需求, 定义销毁方法, 完成销毁操作
    public void myDestory() {
        System.out.println("Product.myDestory");
    }
}
<bean id="product" class="com.baizhiedu.life.Product" destroy-method="myDestory"/>
  1. 销毁方法的操作只适用于 scope="singleton",初始化操作都适用。
  2. 销毁操作到底是什么?
    资源的释放:io.close()connection.close()、…

配置文件参数化

${key}

jdbc.driverClassName = com.mysql.jdbc.Driver
jdbc.url = jdbc:mysql://localhost:3306/spring?useSSL=false
jdbc.username = root
jdbc.password = 1234
<!--Spring的配置文件与⼩配置文件进行整合	classpath代表类路径:class文件夹这个路径-->
<!--resources 下的文件在整个程序编译完后会被放到 classpath 目录下,src.main.java中的文件也是-->
<context:property-placeholder location="classpath:/db.properties"/>

<!--	在 Spring 配置文件中通过 ${key} 获取小配置文件中对应的值	-->
<bean id="conn" class="com.baizhiedu.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>

自定义类型转换器

public class MyDateConverter implements Converter<String, Date> {   //<原始类型,转换好的类型>
    /*
     convert方法作用: String ---> Date
     SimpleDateFormat sdf = new SimpleDateFormat();
     sdf.parset(String) ---> Date

     参数:
     source : 代表的是配置文件中, 日期字符串 <value>2020-10-11</value>
     return : 当把转换好的 Date 作为 convert 方法的返回值后,
             Spring ⾃动的为birthday属性进行注入(赋值)
    */
    @Override
    public Date convert(String source) {     //声明的是Data类型的转换器
        Date date = null;
        try {
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
            date = sdf.parse(source);
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return date;
    }
}
<!--创建 MyDateConverter 对象-->
<bean id="myDateConverter" class="com.baizhiedu.converter.MyDateConverter"/>
<!--用于注册类型转换器-->
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
    <property name="converters">
        <set>
            <ref bean="myDateConverter"/>
        </set>
    </property>
</bean>

<bean id="good" class="com.baizhiedu.converter.Good">
    <property name="name" value="zy"/>
    <property name="birthday" value="2020-6-12"/>
</bean>

细节处理

MyDateConverter 中的日期的格式,通过 依赖注入 的方式,由配置文件完成赋值。

public class MyDateConverter implements Converter<String, Date> {
    private String pattern;

    @Override
    public Date convert(String source) {
        Date date = null;
        try {
            SimpleDateFormat sdf = new SimpleDateFormat(pattern);
            date = sdf.parse(source);
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return date;
    }

    public String getPattern() {
        return pattern;
    }

    public void setPattern(String pattern) {
        this.pattern = pattern;
    }
}
<!-- 配置文件完成对日期格式的赋值 -->
<bean id="myDateConverter" class="com.baizhiedu.converter.MyDateConverter">
    <property name="pattern" value="yyyy-MM-dd"/>
</bean>

ConversionSeviceFactoryBean 定义 id属性,值必须是 conversionService

<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
    <property name="converters">
        <set>
            <ref bean="myDateConverter"/>
        </set>
    </property>
</bean>

Spring 框架其实内置了日期类型的转换器:日期格式必须是 2020/06/12

<bean id="good" class="com.baizhiedu.converter.Good">
	<property name="name" value="zhenyu"/>
	<property name="birthday" value="2012/12/12"/>
</bean>

后置处理 Bean

public class MyBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return null;			//Object bean, String beanName  类名  对象名
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (bean instanceof  Category) {    //BeanPostProcessor 会对 Spring 工厂创建的所有对象进行加工  
            Category category = (Category) bean;
            category.setName("baizhiedu");
            return category;
        }
        return bean;
    }
}
<bean id="myBeanPostProcessor" class="com.baizhiedu.beanpost.MyBeanPostProcessor"/>

静态代理

静态代理:为每⼀个原始类,手工编写⼀个代理类(.java .class)

接口
public interface UserService {
	void register(User user);
	boolean login(String name, String password);
}
原始类
public class UserServiceImpl implements UserService {
    @Override
    public void register(User user) {
        System.out.println("UserServiceImpl.register 业务运算 + DAO");
    }

    @Override
    public boolean login(String name, String password) {
        System.out.println("UserServiceImpl.login 业务运算 + DAO");
        return true;
    }
}
代理类

代理类是为原始类添加额外功能的

/**
 * 静态代理类编码实现
 */
public class UserServiceProxy implements UserService { // 实现原始类相同的接口
    private UserService userService = new UserServiceImpl(); // 代理类中必须有原始类
    @Override
    public void register(User user) {
        System.out.println("---log---"); // 额外功能
        userService.register(user);
    }
    @Override
    public boolean login(String name, String password) {
        System.out.println("---log---"); // 额外功能
        return userService.login(name, password);
    }
}

动态代理开发

搭建开发环境
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-aop</artifactId>
  <version>5.1.14.RELEASE</version>
</dependency>
<dependency>
  <groupId>org.aspectj</groupId>
  <artifactId>aspectjrt</artifactId>
  <version>1.8.9</version>
</dependency>
<dependency>
  <groupId>org.aspectj</groupId>
  <artifactId>aspectjweaver</artifactId>
  <version>1.8.13</version>
</dependency>
创建原始对象(目标对象)
public interface UserService {
    void register(User user);
    boolean login(String name, String password);
}
public class UserServiceImpl implements UserService {
    @Override
    public void register(User user) {
        System.out.println("UserServiceImpl.register 业务运算 + DAO");
    }

    @Override
    public boolean login(String name, String password) {
        System.out.println("UserServiceImpl.login 业务运算 + DAO");
        return true;
    }
}
额外功能

MethodBeforeAdvice 接口

public class Before implements MethodBeforeAdvice {
    /**
     * 作用: 把需要运行在原始方法执行之前运行的额外功能, 书写在 before 方法中
     */
    @Override
    public void before(Method method, Object[] objects, Object target) throws Throwable {
        System.out.println("---method before advice log---");
    }
}
<!-- 额外功能 -->
<bean id="before" class="com.baizhiedu.aop.Before"/>
定义 切入点:额外功能的加入

⽬的: 由程序员根据⾃⼰的需要,决定额外功能加入给哪个原始方法(register、login)

<!--所有方法都做为切入点,加入额外的功能(register、login)-->
<aop:config>
    <aop:pointcut id="pc" expression="execution(* * (..))"/>
</aop:config>
组装
<aop:config>
    <aop:pointcut id="pc" expression="execution(* * (..))"/>
    <!--组装:目的把切入点与额外功能进行整合-->
    <aop:advisor advice-ref="before" pointcut-ref="pc"/>
</aop:config>
调用
//Spring 的工厂通过原始对象的 id 值获得的是代理对象
//获得代理对象后,可以通过声明接口类型,进行对象的存储
@Test
public void test() {
    ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext.xml");
    UserService userService = (UserService) ctx.getBean("userService");
    userService.login("admin", "1234");
    userService.register(new User());
}

动态字节码技术:通过第三方动态字节码框架,在 JVM 中创建对应类的字节码,进而创建对象,当虚拟机结束,动态字节码跟着消失。

结论:动态代理不需要定义类文件,都是 JVM 运行过程中动态创建的,所以不会造成类⽂件数量过多,影响项目管理的问题。
动态代理使得 额外功能的维护性大大增强

动态代理开发详解

MethodBeforeAdvice

作用:需要把运行在原始方法执行之前运行的额外功能,书写在before方法中

Method: 额外功能所增加给的那个原始方法
login方法

register方法

showOrder方法

Object[]: 额外功能所增加给的那个原始方法的参数。String name,String password
User

Object: 额外功能所增加给的那个原始对象 UserServiceImpl
OrderServiceImpl

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZgcXD4kA-1592187674960)(photo/image-20200612191140807.png)]

@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
	System.out.println("-----new method before advice log------");
}

MethodInterceptor(方法拦截器)

public class Around implements MethodInterceptor {    //比如事务、抛出异常
    @Override
    public Object invoke(MethodInvocation Invocation) throws Throwable {
    	System.out.println("---额外功能运行在原始方法执行之前---");
        Object ret = Invocation.proceed(); // 原始方法运行, 获取原始方法的返回值
        System.out.println("---额外功能运行在原始方法执行之后---");
        return ret;
    }
}
<!-- 额外功能 -->
<bean id="around" class="com.baizhiedu.dynamic.Around"/>

<aop:config>
    <aop:pointcut id="pc" expression="execution(* * (..))"/>
    <!--组装:目的把切入点与额外功能进行整合-->
    <aop:advisor advice-ref="around" pointcut-ref="pc"/>
</aop:config>

切入点表达式

方法切入点

定义一个方法
public void add(int i, int j)
   *               *      (..)
* * (..)    --> 所有方法

*  --->  修饰符 返回值
*  --->  方法名
() --->  参数表
.. --->  对于参数没有要求 (参数有没有,参数有⼏个都行,参数是什么类型的都行)
<!-- 定义login作为切入点 -->
<aop:pointcut id="pc" expression="execution(* login (..))"/>

<!-- 定义register作为切入点 -->
<aop:pointcut id="pc" expression="execution(* register (..))"/>

精准方法切入点限定

<aop:pointcut id="pc" expression="execution(* com.baizhiedu.proxy.UserServiceImpl.login(..))"/>

<aop:pointcut id="pc" expression="execution(* com.baizhiedu.proxy.UserServiceImpl.login(String, String))"/>

类切入点

# 类中所有的方法加入了额外功能
<aop:pointcut id="pc" expression="execution(* com.baizhiedu.proxy.UserServiceImpl.*(..))"/>
# 忽略包
1. 类只存在一级包
<aop:pointcut id="pc" expression="execution(* *.UserServiceImpl.*(..))"/>
2. 类存在多级包
<aop:pointcut id="pc" expression="execution(* *..UserServiceImpl.*(..))"/>

包切入点(实战中用的多)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zrWzk3qF-1592187674962)(photo/image-20200613094556878.png)]

# 切入点包中的所有类,必须在proxy中,不能在proxy包的⼦包中
<aop:pointcut id="pc" expression="execution(* com.baizhiedu.proxy.*.*(..))"/>
# 切入点当前包及其⼦包都生效
<aop:pointcut id="pc" expression="execution(* com.baizhiedu.proxy..*.*(..))"/>

切入点函数(execution、args、within)

args

args 作用:主要用于 函数(方法) 参数的匹配

切入点:方法参数必须得是 2 个字符串类型的参数

# 使用 execution
<aop:pointcut id="pc" expression="execution(* *(String, String))"/>

# 使用 args
<aop:pointcut id="pc" expression="args(String, String)"/>

within

within 作用:主要用于进行 类、包切入点表达式 的匹配。

切入点: UserServiceImpl 这个类

# 使用 execution
<aop:pointcut id="pc" expression="expression(* *..UserServiceImpl.*(..))"/>

# 使用 within
<aop:pointcut id="pc" expression="within(*..UserServiceImpl)"/>
切入点: com.baizhiedu.proxy 这个包

# 使用 execution
<aop:pointcut id="pc" expression="execution(* com.baizhiedu.proxy..*.*(..)"/>

# 使用 within
<aop:pointcut id="pc" expression="within(com.baizhiedu.proxy..*)"/>

@annotation

作用:为具有特殊注解的 方法 加入额外功能。

@Target(ElementType.METHOD)     //注解需要用在方法上面
@Retention(RetentionPolicy.RUNTIME)   //注解什么时候起作用
public @interface Log {
}
<aop:pointcut id="pc" expression="@annotation(com.baizhiedu.Log)"/>

切入点函数的逻辑运算(and、or)

and 与操作
案例: 方法名叫 login 同时 参数是 2个字符串
# execution
<aop:pointcut id="pc" expression="execution(* login(String, String))"/>
# execution and args
<aop:pointcut id="pc" expression="execution(* login(..)) and args(String, String))"/>
注意:与操作不能⽤于同种类型的切⼊点函数
以下这个是错误的, 因为不存在同时叫 login 和 register 的方法
<aop:pointcut id="pc" expression="execution(* login(..)) and execution(* register(..))"/>

or 或操作:
案例: 方法名叫 register 或 login 的⽅法作为切⼊点
<aop:pointcut id="pc" expression="execution(* login(..)) or execution(* register(..))"/>

AOP

AOP (Aspect Oriented Programing)
⾯向切⾯编程 = Spring动态代理开发
以切⾯为基本单位的程序开发,通过切⾯间的彼此协同,相互调⽤,完成程序的构建
切⾯ = 切⼊点 + 额外功能
OOP (Object Oritened Programing)
⾯向对象编程 Java
以对象为基本单位的程序开发,通过对象间的彼此协同,相互调⽤,完成程序的构建
POP (Producer Oriented Programing)
⾯向过程(⽅法、函数)编程 C
以过程为基本单位的程序开发,通过过程间的彼此协同,相互调⽤,完成程序的构建
AOP的概念:
本质就是Spring得动态代理开发,通过代理类为原始类增加额外功能。
好处:利于原始类的维护
注意:AOP编程不可能取代OOP,OOP编程有意补充。

JDK 的动态代理

在这里插入图片描述
在这里插入图片描述

public class TestJDKProxy {
    /*
        1. 借用类加载器  TestJDKProxy
                       UserServiceImpl
        2. JDK8.x前
            final UserService userService = new UserServiceImpl();
     */
    public static void main(String[] args) {
        //1 创建原始对象
        UserService userService = new UserServiceImpl();
        //2 JDK创建动态代理
        InvocationHandler handler = new InvocationHandler(){
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("------proxy  log --------");       
                Object ret = method.invoke(userService, args);	 //原始方法运行
                return ret;
            }
        };
        UserService userServiceProxy = (UserService)Proxy.newProxyInstance(UserServiceImpl.class.getClassLoader(),userService.getClass().getInterfaces(),handler);
        userServiceProxy.login("suns", "123456");
        userServiceProxy.register(new User());
    }
}

CGlib

CGlib创建动态代理的原理:⽗⼦继承关系创建代理对象,原始类作为⽗类,代理类作为⼦类,这样既可以保证2者⽅法⼀致,同时在代理类中提供新的实现(额外功能+原始⽅法)

public class UserService {
    public void login(String name, String password) {
        System.out.println("UserService.login");
    }

    public void register(User user) {
        System.out.println("UserService.register");
    }
}
package com.baizhiedu.cglib;

import com.baizhiedu.proxy.User;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class TestCglib {
    public static void main(String[] args) {
        //1 创建原始对象
        UserService userService = new UserService();

        /*
          2 通过cglib方式创建动态代理对象
            Proxy.newProxyInstance(classloader,interface,invocationhandler)

            Enhancer.setClassLoader()
            Enhancer.setSuperClass()
            Enhancer.setCallback();  ---> MethodInterceptor(cglib)
            Enhancer.create() ---> 代理
         */

        Enhancer enhancer = new Enhancer();

        enhancer.setClassLoader(TestCglib.class.getClassLoader());
        enhancer.setSuperclass(userService.getClass());

        MethodInterceptor interceptor = new MethodInterceptor() {
            //等同于 InvocationHandler --- invoke
            @Override
            public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
                System.out.println("---cglib log----");
                Object ret = method.invoke(userService, args);
                return ret;
            }
        };

        enhancer.setCallback(interceptor);
        UserService userServiceProxy = (UserService) enhancer.create();
        userServiceProxy.login("suns", "123345");
        userServiceProxy.register(new User());
    }
}

Spring 工厂如何加工原始对象

思路分析:主要通过 BeanPostProcessor 将原始对象加工为代理对象

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LOFIyzv6-1592187674965)(photo/image-20200613151751854.png)]

public class ProxyBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    @Override
    /*
         Proxy.newProxyInstance();
     */
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        InvocationHandler handler = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("----- new Log-----");
                Object ret = method.invoke(bean, args);
                return ret;
            }
        };
      return Proxy.newProxyInstance(ProxyBeanPostProcessor.class.getClassLoader(),bean.getClass().getInterfaces(),handler);
    }
}

基于注解的 AOP 编程的开发

# 通过切⾯类 定义了 额外功能 @Around

​ 定义了 切⼊点 @Around(“execution(* login(…))”)

​ @Aspect 切⾯类

/*
       1. 额外功能
                 public class MyArround implements MethodInterceptor{
                      public Object invoke(MethodInvocation invocation){
                              Object ret = invocation.proceed();
                              return ret;
                      }
                 }
       2. 切入点
             <aop:config
                 <aop:pointcut id=""  expression="execution(* login(..))"/>
 */

@Aspect
public class MyAspect {
    @Around("execution(* login(..))")
    public Object arround(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("----aspect log ------");
        Object ret =joinPoint.proceed();
        return ret;
    }
}
<bean id="userService" class="com.baizhiedu.aspect.UserServiceImpl"/>
<!--
   切面
	 1. 额外功能
	 2. 切入点
	 3. 组装切面
-->
<bean id="arround" class="com.baizhiedu.aspect.MyAspect"/>
<!--告知Spring基于注解进行AOP编程-->
<aop:aspectj-autoproxy proxy-target-class="true" />

切入点复用

切入点复用:在切面类中定义⼀个函数,上面用 @Pointcut 注解。
通过这种方式定义切入点表达式,后续更加有利于切入点复用。

@Aspect
public class MyAspect {
    @Pointcut("execution(* *..UserServiceImpl.*(..))")
    public void myPointcut(){}

    @Around(value="myPointcut()")
    public Object arround(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("----aspect log ------");
        Object ret = joinPoint.proceed();
        return ret;
    }

    @Around(value="myPointcut()")
    public Object arround1(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("----aspect tx ------");
        Object ret = joinPoint.proceed();
        return ret;
    }
}

默认情况 AOP 编程 底层应用 JDK动态代理创建方式

基于注解的 AOP 开发 中切换为 Cglib:

<aop:aspectj-autoproxy proxy-target-class="true"/>

传统的 AOP 开发 中切换为 Cglib:

<aop:config proxy-target-class="true">
	...
</aop:config>

AOP 开发中的一个坑

public class UserServiceImpl implements UserService, ApplicationContextAware {
    private ApplicationContext ctx;
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
              this.ctx = applicationContext;
    }
    @Log
    @Override
    public void register(User user) {
        System.out.println("UserServiceImpl.register 业务运算 + DAO ");
        //throw new RuntimeException("测试异常");
        //调用的是原始对象的login方法 ---> 核心功能
        /*
            设计目的:代理对象的login方法 --->  额外功能+核心功能
            ApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext2.xml");
            UserService userService = (UserService) ctx.getBean("userService");
            userService.login();

            Spring工厂重量级资源 一个应用中 应该只创建一个工厂
         */
        UserService userService = (UserService) ctx.getBean("userService");
        userService.login("suns", "123456");
    }
    @Override
    public boolean login(String name, String password) {
        System.out.println("UserServiceImpl.login");
        return true;
    }
}
 Object ret = joinPoint.proceed();
    return ret;
}

}


**默认情况 AOP 编程 底层应用 JDK动态代理创建方式**。

基于注解的 AOP 开发 中切换为 Cglib:

```xml
<aop:aspectj-autoproxy proxy-target-class="true"/>

传统的 AOP 开发 中切换为 Cglib:

<aop:config proxy-target-class="true">
	...
</aop:config>
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值