Spring 01 初识 Spring

13 篇文章 0 订阅
10 篇文章 0 订阅

初识 Spring

一、学习任务
  1. 编写Spring程序,实现控制台输出的功能
  2. 使用Spring框架实现打印机功能
  3. 使用Spring AOP实现日志功能
二、本章目标
  1. 理解Spring IoC原理
  2. 掌握Spring IoC配置
  3. 理解Spring AOP原理
  4. 掌握Spring AOP配置
三、Spring框架简介
  • Spring 英文单词词意:春天

  • Spring理念 : 使现有技术更加实用 . 本身就是一个大杂烩 , 整合现有的框架技术

  • 官网 : http://spring.io/

  • 官方下载地址 : https://repo.spring.io/libs-release-local/org/springframework/spring/

  • GitHub : https://github.com/spring-projects


  • Spring是Java EE编程领域的一个轻量级开源J2EE应用程序框架,该框架由一个叫Rod Johnson的程序员在 2002 年最早提出并随后创建,是为了解决企业级编程开发中的复杂性,实现敏捷开发的应用型框架,很难想象Rod Johnson的学历 , 他是悉尼大学的博士,然而他的专业不是计算机,而是音乐学 。

  • Spring是一个开源容器框架,它集成各类型的工具,通过核心的Bean factory实现了底层的类的实例化和生命周期的管理。在整个框架中,各类型的功能被抽象成一个个的 Bean,这样就可以实现各种功能的管理,包括动态加载和切面编程。 Spring是独特的,因为若干个原因:

  • 它定位的领域是许多其他流行的framework没有的。Spring致力于提供一种方法管理你的业务对象。
    Spring是全面的和模块化的。Spring有分层的体系结构,这意味着你能选择使用它孤立的任何部分,它的架构仍然是内在稳定的。例如,你可能选择仅仅使用Spring来简单化JDBC的使用,或用来管理所有的业务对象。

  • 它的设计从底部帮助你编写易于测试的代码。Spring是用于测试驱动工程的理想的framework。
    Spring对你的工程来说,它不需要一个以上的framework。Spring是潜在地一站式解决方案,定位于与典型应用相关的大部分基础结构。它也涉及到其他framework没有考虑到的内容。

  • Spring解决了开发者在J2EE开发中遇到的许多常见的问题,提供了功能强大IOC、AOP及Web MVC等功能。

  • Spring框架主要由七部分组成,分别是 Spring Core、 Spring AOP、 Spring ORM、 Spring DAO、Spring Context、 Spring Web和 Spring Web MVC,在接下来的课程中将会给大家详细讲解。

3.1.Spring框架作用与优点

作用:

  • Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器(框架)。

  • 企业应用开发的"一站式"选择,贯穿于表现层、业务层、持久层。

优点

  1. Spring是一个开源免费的框架 , 容器 .
  2. Spring是一个轻量级的框架 , 非侵入式的 .
  3. 控制反转 IoC , 面向切面 Aop
  4. 对事物的支持 , 对框架的支持良好整合
3.2.Spring框架体系结构介绍

Spring体系结构图
在这里插入图片描述

Spring 框架是一个分层架构,由 7 个定义良好的模块组成。Spring 模块构建在核心容器之上,核心容器定义了创建、配置和管理 bean 的方式 .组成 Spring 框架的每个模块(或组件)都可以单独存在,或者与其他一个或多个模块联合实现。每个模块的功能如下:

①核心容器:

​ 核心容器提供 Spring 框架的基本功能。核心容器的主要组件是 BeanFactory,它是工厂模式的实现。BeanFactory 使用控制反转(IOC) 模式将应用程序的配置和依赖性规范与实际的应用程序代码分开。

②Spring 上下文:

​ Spring 上下文是一个配置文件,向 Spring 框架提供上下文信息。Spring 上下文包括企业服务,例如 JNDI、EJB、电子邮件、国际化、校验和调度功能。

③Spring AOP:

​ 通过配置管理特性,Spring AOP 模块直接将面向切面的编程功能 , 集成到了 Spring 框架中。所以,可以很容易地使 Spring 框架管理任何支持 AOP的对象。Spring AOP 模块为基于 Spring 的应用程序中的对象提供了事务管理服务。通过使用 Spring AOP,不用依赖组件,就可以将声明性事务管理集成到应用程序中。

④Spring DAO:

​ JDBC DAO 抽象层提供了有意义的异常层次结构,可用该结构来管理异常处理和不同数据库供应商抛出的错误消息。异常层次结构简化了错误处理,并且极大地降低了需要编写的异常代码数量(例如打开和关闭连接)。Spring DAO 的面向 JDBC 的异常遵从通用的 DAO 异常层次结构。

⑤Spring ORM:

​ Spring 框架插入了若干个 ORM 框架,从而提供了 ORM 的对象关系工具,其中包括 JDO、Hibernate 和 iBatis SQL Map。所有这些都遵从 Spring 的通用事务和 DAO 异常层次结构。

⑥Spring Web 模块:

​ Web 上下文模块建立在应用程序上下文模块之上,为基于 Web 的应用程序提供了上下文。所以,Spring 框架支持与 Jakarta Struts 的集成。Web 模块还简化了处理多部分请求以及将请求参数绑定到域对象的工作。

⑦Spring MVC 框架:

​ MVC 框架是一个全功能的构建 Web 应用程序的 MVC 实现。通过策略接口,MVC 框架变成为高度可配置的,MVC 容纳了大量视图技术,其中包括 JSP、Velocity、Tiles、iText 和 POI。

3.3.Spring框架设计理念与核心技术

Spring设计理念

​ 【是面向Bean的编程】

Spring两大核心技术

  1. 控制反转(IoC:Inversion of Control)/依赖注入(DI:Dependency Injection)
  2. 面向切面编程(AOP:Aspect Oriented Programming)

在这里插入图片描述

四、控制反转/依赖注入
4.1.控制反转/依赖注入

问题:

  1. 在学习JSP分层模式中,用户模块业务层调用数据层,用户模块业务层与数据层高度耦合,怎么解决?

解决方案1:

步骤一:先写一个UserDao接口

/**
 * 用户DAO接口
 * @author Aiden
*/
public interface UserDao {
	/**
     * 新增用户信息
     * @param user 用户对象
     * @return int 受影响的行数
	 */
	int insert(User user);
}

步骤二:再去写UserDao的实现类

public class UserDaoMySqlImpl  implements UserDao {
	//日志
	private Logger logger = Logger.getLogger(UserDaoMySqlImpl.class);
    @Override
    public int insert(User user) {
        //模拟MySql数据库存储操作
        logger.info("连接MySql数据库...");
        logger.info("正在保存信息...");
        logger.info("用户信息" + user.toString() + "保存入库。");
        return 1;//默认标识成功
    }
}

步骤三:然后去写UserService的接口

/**
 * 用户业务接口
 * @author Aiden
*/
public interface UserService {
	/**
 	 * 新增用户信息
  	 * @param user 用户对象
  	 * @return int 受影响的行数
 	 */
	int insert(User user);
}

步骤四:最后写UserService的实现类

/**
 * 用户业务实现类
 * @author Aiden
*/
public class UserServiceImpl implements UserService {
//日志
private Logger logger = Logger.getLogger(UserService.class);
//实例化需要依赖的UserDao实例对象
private UserDao userDao = new UserDaoMySqlImpl();
@Override
public int insert(User user) {
  return userDao.insert(user);
}
}

步骤五:单元测试一下

@Test
public void testAddUser() {
	User user = new User("张三", "男");
	//用户业务实例
	UserService userService = new UserServiceImpl();
	//调用新增方法
	int result = userService.insert(user);
}

设计缺陷:

​ 由以上代码不难看出在用户业务实现类中高度依赖UserDaoMySqlImpl实现类,但是现在如果我们要使用Oracle 或者MSSQL, 那么又需要去service实现类里面修改对应的实现 . 假设我们的这种需求非常大 , 这种方式就根本不适用了, 甚至反人类对吧 , 每次变动 , 都需要修改大量代码 . 这种设计的耦合性太高了, 牵一发而动全身 。

解决方案2:

  1. 使用set方法实现解耦
public class UserServiceImpl implements UserService {
	private UserDao userDao;
	//利用set实现
	public void setUserDao(UserDao userDao) {
 		this.userDao = userDao;
	}

	@Override
	public int insert(User user) {
  		return userDao.insert(user);
	}
}
@Test
public void testAddUser(){
	User user = new User("张三", "男");
	UserService service = new UserServiceImpl();
	//①设置mysql数据库
	service.setUserDao(new UserDaoMySqlImpl() );
	//调用新增方法
	int result1 = userService.insert(user);

	//②设置oracle数据库
	service.setUserDao(new UserDaoOracleImpl());
	//调用新增方法
	int result2 = userService.insert(user);
}
  1. 工厂设计模式实现解耦
/**
 * UserDao工厂类
 * @author Aiden
*/
public class UserDaoFactory {
	/**
     * 简单工厂
     * @param dbType 数据库类型
     * @return UserDao
	 */
	public static UserDao getInstance(String dbType){
  		switch (dbType) {
     		 case "mysql":
          		return new UserDaoMySqlImpl();
      		 case "oracle":
          		return new UserDaoOracleImpl();
      		 case "mssql":
          		return new UserDaoMSSqlImpl();
      		 default:
          		throw new RuntimeException("无效的数据库类型:"+dbType+" ,DAO获取失败");
 		 }
	}
}
/**
 * 用户业务实现类
*
 * @author Aiden
*/
public class UserServiceImpl implements UserService {
	// 通过工厂获取所依赖的 UserDao 对象
	private UserDao dao = UserDaoFactory.getInstance();}

代码改造后总结:

1.通过以上的代码改造,仔细去思考一下 , 以前所有东西都是由程序去进行控制创建 , 而现在是由我们自行控制创建对象 , 把主动权交给了调用者 . 程序不用去管怎么创建,怎么实现了 . 它只负责提供一个接口 。

2.这种思想 , 从本质上解决了问题 , 我们程序员不再去管理对象的创建了 , 更多的去关注业务的实现 . 耦合性大大降低 . 这也就是IOC的原型!

4.2控制反转/依赖注入

控制反转(IoC)

  • 将创建对象的控制权转移,是一种程序设计思想。

依赖注入(DI)

  • 将依赖的对象注入到需要的类中去,是"控制反转"设计思想的具体实现方式。
  1. 控制反转IoC(Inversion of Control),是一种设计思想,DI(依赖注入)是实现IoC的一种方法,也有人认为DI只是IoC的另一种说法。没有IoC的程序中 , 我们使用面向对象编程 , 对象的创建与对象间的依赖关系完全硬编码在程序中,对象的创建由程序自己控制,控制反转后将对象的创建转移给第三方。
  2. IoC是Spring框架的核心内容,使用多种方式完美的实现了IoC,可以使用XML配置,也可以使用注解,新版本的Spring也可以零配置实现IoC。
  3. Spring容器在初始化时先读取配置文件,根据配置文件或元数据创建与组织对象存入容器中,程序使用时再从Ioc容器中取出需要的对象。
  4. 采用XML方式配置Bean的时候,Bean的定义信息是和实现分离的,而采用注解的方式可以把两者合为一体,Bean的定义信息直接以注解的形式定义在实现类中,从而达到了零配置的目的。
  5. 控制反转是一种通过描述(XML或注解)并通过第三方去生产或获取特定对象的方式。在Spring中实现控制反转的是IoC容器,其实现方法是依赖注入(Dependency Injection,DI)。
五、Spring实现"控制反转"

问题:

  • 如何使用Spring实现"控制反转" ?

需求:

  • 在控制台输出 “反转的人生,如此惊艳”

分析:

  1. 创建maven项目添加所需要的依赖jar包。
  2. 编写Spring核心配置文件
  3. 编写测试代码通过Spring进行属性注入

步骤:

1、添加maven配置: spring 需要导入commons-logging进行日志记录 。利用maven , 他会自动下载对应的依赖项 。

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

2、添加log4j配置文件:log4j.properties

# rootLogger是所有日志的根日志,修改该日志属性将对所有日志起作用
# 下面的属性配置中,所有日志的输出级别是info,输出源是con
log4j.rootLogger=info,con
# 定义输出源的输出位置是控制台
log4j.appender.con=org.apache.log4j.ConsoleAppender
# 定义输出日志的布局采用的类
log4j.appender.con.layout=org.apache.log4j.PatternLayout
# 定义日志输出布局
log4j.appender.con.layout.ConversionPattern=%d{MM-dd HH:mm:ss}[%p]%c%n -%m%n

3、编写一个Hello实体类

/**
 * 第一个Spring程序,输出"反转的人生,如此惊艳!"。
*/
public class HelloSpring {
	// 定义hello属性,该属性的值将通过Spring框架进行设置
	private String hello = null;

	/**
	 * 打印方法,从Spring配置文件中获取属性并输出。
	 */
	public void print() {
		System.out.println("Spring say:," + this.getHello() + "!");
	}

	public String getHello() {
		return hello;
	}

	public void setHello(String hello) {
		this.hello = hello;
	}
}

4、Spring核心配置文件:applicationContext.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"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
	http://www.springframework.org/schema/beans/spring-beans.xsd">
	
    <!-- 通过bean元素声明需要Spring创建的实例。该实例的类型通过class属性指定,并通过id属性为该实例指定一个名称,以便在程序中使用 -->
	<bean id="helloSpring" class="com.aiden.pojo.HelloSpring">
		
        <!-- property元素用来为实例的属性赋值,此处实际是调用setHello()方法实现赋值操作 -->
		<property name="hello">
			<!-- 此处将字符串"Spring"赋值给hello属性 -->
			<value>反转的人生,如此惊艳</value>
		</property>
	</bean>
</beans>

5、单元测试类:HelloSpringTest

public class HelloSpringTest {
  @Test
  public void helloSpring() {
     // 1.通过ClassPathXmlApplicationContext实例化Spring的上下文
     ApplicationContext context = new ClassPathXmlApplicationContext(
             "applicationContext.xml");
     // 2.通过ApplicationContext的getBean()方法,根据id来获取bean的实例
     HelloSpring helloSpring = context.getBean("helloSpring",HelloSpring.class);
     // 3.执行print()方法
     helloSpring.print();
  }
}

思考:

  1. Hello 对象是谁创建的 ? hello 对象是由Spring创建的
  2. Hello 对象的属性是怎么设置的 ? hello 对象的属性是由Spring容器设置的

结论:

如上这个过程就叫控制反转 :

  • 控制 : 谁来控制对象的创建 , 传统应用程序的对象是由程序本身控制创建的 , 使用Spring后 , 对象是由Spring来创建的。
  • 反转 : 程序本身不创建对象 , 而变成被动的接收对象 。

六、IOC创建对象的方式

方式1:通过无参构造方法来创建

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd">

	<bean id="user" class="com.aiden.pojo.User">
    	<property name="name" value="朱美亮"/>
	</bean>
</beans>

方式2:通过有参构造方法来创建

<!-- 第一种根据index参数下标设置 -->
<bean id="user" class="User">
	<!-- index指构造方法 , 下标从0开始 -->
	<constructor-arg index="0" value="刘旭"/>
</bean>

<!-- 第二种根据参数名字设置 -->
<bean id="user" class="com.aiden.pojo.User">
	<!-- name指参数名 -->
	<constructor-arg name="fullName" value="刘旭"/>
</bean>
<!-- 第三种根据参数类型设置 -->
<bean id="user" class="com.aiden.pojo.User">
	<constructor-arg type="java.lang.String" value="刘旭"/>
</bean>

对应的实体类

package com.aiden.pojo;

import java.io.Serializable;

/**
 * 用户信息实体类
 * @author Aiden
 */
public class User implements Serializable {

    private Integer userId;//编号
    private String fullName;//姓名

    //构造函数1
    public User() {
        System.out.println("User无参构造方法");
    }
	//构造函数2
    public User(String fullName) {
        this.fullName = fullName;
    }
	//构造函数3
    public User(String fullName, String gender) {
        this.fullName = fullName;
        this.gender = gender;
    }
    //省略getter/setter...
}

七、Spring常用配置介绍

1.别名

  • Spring中我们可以通过 alias 属性设置bean的别名 , 并且支持设置多个别名,语法如下:
<!--设置别名:在获取Bean的时候可以使用别名获取-->
<alias name="user" alias="userNew"/>

2.Bean的配置

  1. id 是bean的标识符,要唯一,如果没有配置id,name就是默认标识符,class是bean的全限定名=包名+类名
  2. 如果配置id,又配置了name,那么name是别名。
  3. name可以设置多个别名,可以用逗号,分号,空格隔开。
  4. 如果不配置id和name,可以根据 applicationContext.getBean(.class) 获取对象。
<!--bean就是java对象,由Spring创建和管理-->
<bean id="hello" name="hi,sayHi;hello1 hello2" class="com.aiden.pojo.Hello">
	<property name="name" value="Spring"/>
</bean>

3.import

  • 实际开发中,项目团队的合作主要通过import 标签来实现bean的注入,如下语法所示,{path}标识bean的xml资源路径。
<import resource="{path}/beans.xml"/>
八、依赖注入(DI)

依赖注入(Dependency Injection,DI)。

依赖 : 指Bean对象的创建依赖于容器 . Bean对象的依赖资源 .

注入 : 指Bean对象所依赖的资源 , 由容器来设置和装配 .

在这里插入图片描述

1、构造器注入
  • 见主题六(IOC创建对象的方式)中介绍。
2、Set 注入 (本课重点)

注意事项: 这里我们需要注意的是被注入的属性或对象 , 必须提供set方法,且set方法的方法名称由set + 属性首字母大写组成, 如果属性是boolean类型 , 则没有set方法 , 是 is 。

步骤1: 定义Address地址类

package com.aiden.pojo;
/**
 * 地址信息类
 *
 * @author Aiden
 */
public class Address {
 	private String address;

 	public String getAddress() {
 	     return address;
 	}

 	public void setAddress(String address) {
    	 this.address = address;
 	}
}

步骤2: 定义Student学生信息类

package com.aiden.pojo;

import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

/**
 * 学生信息类
*
 * @author Aiden
*/
public class Student {
    
 private String name;             //姓名
 private Address address;         //地址
 private String[] musics;         //喜欢的音乐
 private List<String> hobbys;     //爱好
 private Map<String, String> card;//银行卡
 private Set<String> games;       //喜欢的游戏
 private String phone;            //手机号码
 private Properties properties;   //基础信息(学号、性别、出生日期)

 public void setName(String name) {
     this.name = name;
 }

 public void setAddress(Address address) {
     this.address = address;
 }

 public void setMusics(String[] musics) {
     this.musics = musics;
 }

 public void setHobbys(List<String> hobbys) {
     this.hobbys = hobbys;
 }

 public void setCard(Map<String, String> card) {
     this.card = card;
 }

 public void setGames(Set<String> games) {
     this.games = games;
 }
 public void setPhone(String phone) {
     this.phone = phone;
 }
 public void setProperties(Properties properties) {
     this.properties = properties;
 }

 public void show() {
     System.out.println("姓名:" + this.name
                + "家庭住址:" + address.getAddress()
                + "喜欢的音乐:"
     );
     for (String music : musics) System.out.print( music +"\t");
     System.out.println("\n爱好:" + this.hobbys);
     System.out.println("银行卡:" + this.card);
     System.out.println("喜欢的游戏:" + this.games);
     System.out.println("手机号码:" + this.phone);
     System.out.println("基础信息:" + this.properties);
 }
}
3、扩展的注入
3.1、常量注入
<bean id="student" class="com.aiden.pojo.Student">
      <property name="name" value="小明"/>
</bean>
@Test
public void test01(){
  ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
  Student student = context.getBean("student",Student.class);
  System.out.println(student.getName());
}
3.2、Bean注入
<bean id="addressObj" class="com.aiden.pojo.Address">
  <property name="address" value="长沙"/>
</bean>

<bean id="student" class="com.aiden.pojo.Student">
  <property name="name" value="小明"/>
  <!--注意点:这里的值是一个引用,ref-->
  <property name="address" ref="addressObj"/>
</bean>
3.3、数组注入
<bean id="student" class="com.aiden.pojo.Student">
  <property name="name" value="小明"/>
  <property name="address" ref="addressObj"/>
  <property name="musics">
      <array>
          <value>《中国心》</value>
          <value>《青花瓷》</value>
          <value>《我和你》</value>
      </array>
  </property>
</bean>
3.4、List注入
<property name="hobbys">
  <list>
      <value>阅读</value>
      <value>看电影</value>
      <value>听歌</value>
  </list>
</property>
3.5、Map注入
<property name="card">
  <map>
      <entry key="中国农业" value="6228280128069313663"/>
      <entry key="中国建设" value="621700166000758262"/>
  </map>
</property>

3.6、set注入

<property name="games">
  <set>
      <value>原神</value>
      <value>王者荣耀</value>
      <value>第五人格</value>
  </set>
</property>
3.6、Null注入
<property name="phone"><null/></property>
3.7、Properties 注入
<property name="properties">
  <props>
      <prop key="学号">20221008</prop>
      <prop key="性别"></prop>
      <prop key="出生日期">2002年5月20日</prop>
  </props>
</property>
3.8、P命名和C命名注入
/**
 * 用户信息
 * @author Aiden
 */
public class User {
  private String name;
  private int age;

  public void setName(String name) {
      this.name = name;
 }

  public void setAge(int age) {
      this.age = age;
 }

  @Override
  public String toString() {
      return "User{" +
              "name='" + name + '\'' +
              ", age=" + age +
              '}';
 }
}

​ 3.9.1.P命名空间注入 : 需要在头文件中加入约束文件

<!--导入约束 : xmlns:p="http://www.springframework.org/schema/p"-->

<!--P(属性: properties)命名空间 , 直接注入属性-->
<bean id="user" class="com.aiden.pojo.User"  p:name="张三"  p:age="20"/>

​ 3.9.2.C 命名空间注入 : 需要在头文件中加入约束文件

<!--导入约束 : xmlns:c="http://www.springframework.org/schema/c"-->
<!--C(构造: Constructor)命名空间 , 使用构造器注入-->
<bean id="user" class="com.aiden.pojo.User"  c:name="张三"  c:age="20"/>
九、Bean的作用域(重点)
9.1.Bean的作用域

在Spring中,那些组成应用程序的主体及由Spring IoC容器所管理的对象,被称之为bean。简单地讲,bean就是由IoC容器初始化、装配及管理的对象 。

类别说明
singleton在Spring Ioc容器中仅存在一个Bean实例,Bean以单例方式存在
prototype每次从父容器中调用Bean时,都会返回一个新的实例,即每次调用getBean( )时,相当于执行 new XxxBean( )
request每次HTTP请求都会创建一个新的Bean,该作用域仅适用于WebApplicationContext环境
session同一个HTTP Session共享一个Bean,不同的session使用不同的Bean,仅适用于WebApplicationContext环境

注意: 几种作用域中,request、session作用域仅在基于web的应用中使用(不必关心你所采用的是什么web应用框架),只能用在基于web的Spring ApplicationContext环境。

9.2.1 Singleton(单例模式)

当一个bean的作用域为Singleton,那么Spring IoC容器中只会存在一个共享的bean实例,并且所有对bean的请求,只要id与该bean定义相匹配,则只会返回bean的同一实例。Singleton是单例类型,就是在创建起容器时就同时自动创建了一个bean的对象,不管你是否使用,他都存在了,每次获取到的对象都是同一个对象。注意,Singleton作用域是Spring中的缺省作用域

要在XML中将bean定义成singleton,可以这样配置:

<bean id="userServiceImpl" class="com.aiden.service.UserServiceImpl" scope="singleton">

测试:

@Test
public void test03(){
  	ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
  	User user =  context.getBean("user",User.class);
  	User user2 =  context.getBean("user",User.class);
  	System.out.println(user==user2);//true
}
9.2.2 Prototype(原型模式)

当一个bean的作用域为Prototype,表示一个bean定义对应多个对象实例。Prototype作用域的bean会导致在每次对该bean请求(将其注入到另一个bean中,或者以程序的方式调用容器的getBean()方法)时都会创建一个新的bean实例。Prototype是原型类型,它在我们创建容器的时候并没有实例化,而是当我们获取bean的时候才会去创建一个对象,而且我们每次获取到的对象都不是同一个对象。

根据经验,对有状态的bean应该使用prototype作用域,而对无状态的bean则应该使用singleton作用域。在XML中将bean定义成prototype,可以这样配置:

<bean id="account" class="com.foo.DefaultAccount" scope="prototype"/>  
<!--或者-->
<bean id="account" class="com.foo.DefaultAccount" singleton="false"/>
9.2.3 Request

当一个bean的作用域为Request,表示在一次HTTP请求中,一个bean定义对应一个实例;即每个HTTP请求都会有各自的bean实例,它们依据某个bean定义创建而成。该作用域仅在基于web的SpringApplicationContext情形下有效。考虑下面bean定义:

<bean id="loginAction" class="com.aiden.action.LoginAction" scope="request"/>

针对每次HTTP请求,Spring容器会根据loginAction bean的定义创建一个全新的LoginAction bean实例,且该loginAction bean实例仅在当前HTTP request内有效,因此可以根据需要放心的更改所建实例的内部状态,而其他请求中根据loginAction bean定义创建的实例,将不会看到这些特定于某个请求的状态变化。当处理请求结束,request作用域的bean实例将被销毁

9.2.4 Session

当一个bean的作用域为Session,表示在一个HTTP Session中,一个bean定义对应一个实例。该作用域仅在基于web的Spring ApplicationContext情形下有效。考虑下面bean定义:

<bean id="userPreferences" class="com.foo.UserPreferences" scope="session"/>

针对某个HTTP Session,Spring容器会根据userPreferences bean定义创建一个全新的userPreferences bean实例,且该userPreferences bean仅在当前HTTP Session内有效。与request作用域一样,可以根据需要放心的更改所创建实例的内部状态,而别的HTTP Session中根据userPreferences创建的实例,将不会看到这些特定于某个HTTP Session的状态变化。当HTTP Session最终被废弃的时候,在该HTTP Session作用域内的bean也会被废弃掉。

十、Bean的自动装配

自动装配说明

  • 自动装配是使用spring满足bean依赖的一种方法
  • spring会在应用上下文中为某个bean寻找其依赖的bean。

Spring中bean三种装配机制

  1. 在xml中显式配置;
  2. 在java中显式配置
  3. 隐式的bean发现机制和自动装配。

本章节我们主要讲第三种: 自动化的装配bean

Spring的自动装配需要从两个角度来实现

  1. 组件扫描(component scanning):spring会自动发现应用上下文中所创建的bean;
  2. 自动装配(autowiring):spring自动满足bean之间的依赖,也就是我们说的IoC/DI;

组件扫描和自动装配组合发挥巨大威力,使得显示的配置降低到最少。

**推荐不使用自动装配xml配置 , 而使用注解 **


测试环境搭建

步骤1:新建一个项目

步骤2:新建两个实体类Dog、Cat 都有一个吃食的方法

//猫类
public class Cat {
	public void eat() {
    	 System.out.println("小猫正在吃小鱼干...");
	}
}
//狗狗类
public class Dog {
	public void eat() {
   		 System.out.println("小狗正在吃狗粮...");
	}
}

步骤3:新建一个用户类 User

public class User {
	private Cat cat;
	private Dog dog;
	private String fullName;
}

步骤4:编写Spring配置文件注入User、Dog、Cat三个对象

<?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="dog" class="com.aiden.pojo.Dog"/>
<bean id="cat" class="com.aiden.pojo.Cat"/>

<bean id="user" class="com.aiden.pojo.User">
    <property name="cat" ref="cat"/>
    <property name="dog" ref="dog"/>
    <property name="fullName" value="张三"/>
</bean>
</beans>

步骤5:测试

public class MyTest {
	@Test
	public void testMethodAutowire() {
        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
    	User user =  context.getBean("user",User.class);
    	user.getCat().eat();
    	user.getDog().eat();
	}
}

步骤6:查看运行结果

小猫正在吃小鱼干...
小狗正在吃狗粮...

byName

autowire byName (按名称自动装配)

我们在手动配置xml过程中,经常发生字母缺漏和大小写等错误,从而导致无法对其进行检查,使得开发效率降低,此时我们需要采用自动装配将避免这些错误,并且使配置简单化。

测试:

  1. 修改bean配置,增加一个属性 autowire=“byName”

    <bean id="user" class="com.aiden.pojo.User" autowire="byName">
    	<property name="fullName" value="张三"/>
    </bean>
    
  2. 再次测试,结果依旧成功输出!

    小猫正在吃小鱼干...
    小狗正在吃狗粮...
    
  3. 我们将 cat 的 bean id 修改为 catXXX

  4. 再次测试, 执行时报空指针java.lang.NullPointerException。因为按byName规则找不对应set方法,真正的setCat就没执行,对象就没有初始化,所以调用时就会报空指针错误。

小结:

当一个bean节点带有 autowire byName的属性时。

  1. 将查找其类中所有的set方法名,例如setCat,获得将set去掉并且首字母小写的字符串,即cat。
  2. 去spring容器中寻找是否有此字符串名称id的对象。
  3. 如果有,就取出注入;如果没有,就报空指针异常。

byType

autowire byType (按类型自动装配)

使用autowire byType首先需要保证:同一类型的对象,在spring容器中唯一。如果不唯一,会报不唯一的异常。

NoUniqueBeanDefinitionException

测试:

​ 1、将user的bean配置修改一下 : autowire=“byType”

​ 2、测试,正常输出

​ 3、在注册一个cat 的bean对象!

<bean id="dog" class="com.aiden.pojo.Dog"/>
<bean id="cat" class="com.aiden.pojo.Cat"/>
<bean id="cat2" class="com.aiden.pojo.Cat"/>

<bean id="user" class="com.aiden.pojo.User" autowire="byType">
<property name="fullName" value="张三"/>
</bean>

​ 4、测试,报错:NoUniqueBeanDefinitionException

​ 5、删掉cat2,将cat的bean名称改掉!测试!因为是按类型装配,所以并不会报异常,也不影响最后的结果。甚至将id属性去掉,也不影响结果。

这就是按照类型自动装配!

使用注解

jdk1.5开始支持注解,spring2.5开始全面支持注解。下章节讲解~

十一、Spring IoC实现打印机

使用Spring IoC实现JavaBean注入

问题:

  • 如何"组装"一个打印机?

需求:

  1. 可使用彩色墨盒或灰色墨盒进行打印
  2. 可灵活配置打印页面的大小
  3. 打印机功能的实现依赖于墨盒和纸张

步骤分析:

  1. 定义接口规范,创建纸张和墨盒接口
  2. 开发打印功能
  3. 创建纸张、墨盒接口实现类
  4. 组装打印机
  5. 运行测试

1.printer包中定义墨盒、纸张接口、打印机程序类

package cn.printer;
/**
 * 墨盒接口。
 */
public interface Ink {
 /**
     * 定义打印采用的颜色的方法。
     * @param red 红色值
     * @param green 绿色值
     * @param blue 蓝色值
     * @return 返回打印采用的颜色
  */
 public String getColor(int red, int green, int blue);
}
/**
 * 纸张接口
*/
public interface Paper {
 public static final String newline = "\r\n";
 /**
     * 输出一个字符到纸张。
  */
 public void putInChar(char c);
 /**
     * 获取输出到纸张上的内容。
  */
 public String getContent();
}
package cn.printer;
/**
 * 打印机程序。
 * 面向接口编程,而不是具体的实现类
*/
public class Printer {
	private Ink ink = null;
	private Paper paper = null;
	/**
	 * 设值注入所需的setter方法。
	 * @param ink 墨盒
	 */
	public void setInk(Ink ink) {
		this.ink = ink;
	}
	/**
	 * 设值注入所需的setter方法。
	 * @param paper 纸张
	 */
	public void setPaper(Paper paper) {
		this.paper = paper;
	}
	/**
	 * 打印方法
	 * @param message 要打印内容
	 */
	public void print(String message) {
		// 输出颜色标记
		System.out.println("使用" + ink.getColor(255, 200, 0) + "颜色打印:\n");
		// 逐字符输出到纸张
		for (int i = 0; i < message.length(); ++i) {
			paper.putInChar(message.charAt(i));
		}
		// 将纸张的内容输出
		System.out.print(paper.getContent());
	}
}

2.paper包中定义纸张实现类:

package cn.paper;
import cn.printer.Paper;
/**
 * 纸张实现类
*/
public class TextPaper implements Paper {
 // 每行字符数
 private int charPerLine = 16;
 // 每页行数
 private int linePerPage = 5;
 // 纸张中内容
 private String content = "";
 // 当前横向位置,从0到charPerLine-1
 private int posX = 0;
 // 当前行数,从0到linePerPage-1
 private int posY = 0;
 // 当前页数
 private int posP = 1;

 public String getContent() {
     String ret = this.content;
     // 补齐本页空行,并显示页码
     if (!(posX == 0 && posY == 0)) {
         int count = linePerPage - posY;
         for (int i = 0; i < count; ++i) {
             ret += Paper.newline;
         }
         ret += "== 第" + posP + "页 ==";
     }
     return ret;
 }
 public void putInChar(char c) {
     content += c;
     ++posX;
     // 判断是否换行
     if (posX == charPerLine) {
         content += Paper.newline;
         posX = 0;
         ++posY;
     }
     // 判断是否翻页
     if (posY == linePerPage) {
         content += "== 第" + posP + "页 ==";
         content += Paper.newline + Paper.newline;
         posY = 0;
         ++posP;
     }
 }
 // setter方法,用于属性注入
 public void setCharPerLine(int charPerLine) {
     this.charPerLine = charPerLine;
 }
 // setter方法,用于属性注入
 public void setLinePerPage(int linePerPage) {
     this.linePerPage = linePerPage;
 }
}

3.ink包中定义彩色、灰色墨盒实现类,该类实现Ink接口:

package cn.ink;
import java.awt.Color;
import cn.printer.Ink;
/**
 * 彩色墨盒
*/
public class ColorInk implements Ink {
	/**
	 * 打印采彩色文字
	 */
	public String getColor(int red, int green, int blue) {
		Color color = new Color(red, green, blue);
		return "#" + Integer.toHexString(color.getRGB()).substring(2);
	}
}

package cn.ink;
import java.awt.Color;
import cn.printer.Ink;
/**
 * 灰色墨盒
*/
public class GreyInk implements Ink {
	/**
	 * 打印采灰色文字
	 */
	public String getColor(int red, int green, int blue) {
		int c = (red + green + blue) / 3;
		Color color = new Color(c, c, c);
		return "#" + Integer.toHexString(color.getRGB()).substring(2);
	}
}

4.在spring核心配置中注入相应的属性和对象

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
	http://www.springframework.org/schema/beans/spring-beans.xsd">
	<!-- 定义A4纸张bean,该bean的id是a4Paper,class指定该bean实例的实现类 -->
	<bean id="a4Paper" class="cn.paper.TextPaper">
		<!-- property元素用来指定需要容器注入的属性,charPerLine需要容器注入, TextPaper类必须拥有setCharPerLine()方法。 注入每行字符数 -->
		<property name="charPerLine" value="10" />
		<!-- property元素用来指定需要容器注入的属性,linePerPage需要容器注入,TextPaper类必须拥有setLinePerPage()方法。 注入每页行数 -->
		<property name="linePerPage" value="8" />
	</bean>
    
	<!-- 定义B5纸张bean,该bean的id是b5Paper,class指定该bean实例的实现类 -->
	<bean id="b5Paper" class="cn.paper.TextPaper">
		<!-- property元素用来指定需要容器注入的属性,charPerLine需要容器注入, TextPaper类必须拥有setCharPerLine()方法。注入每行字符数 -->
		<property name="charPerLine" value="6" />
		<!-- property元素用来指定需要容器注入的属性,linePerPage需要容器注入, TextPaper类必须拥有setLinePerPage()方法。注入每页行数 -->
		<property name="linePerPage" value="5" />
	</bean>
    
	<!-- 定义彩色墨盒bean,该bean的id是colorInk,class指定该bean实例的实现类 -->
	<bean id="colorInk" class="cn.ink.ColorInk" />
    
	<!-- 定义灰色墨盒bean,该bean的id是greyInk,class指定该bean实例的实现类 -->
	<bean id="greyInk" class="cn.ink.GreyInk" />

	<!-- 组装打印机。定义打印机bean该bean的id是printer,class指定该bean实例的实现类 -->
	<bean id="printer" class="cn.printer.Printer">
		<!-- 通过ref属性注入已经定义好的bean -->
		<!-- 注入彩色墨盒 -->
		<property name="ink" ref="colorInk"></property>
		<!-- 注入B5打印纸张 -->
		<property name="paper" ref="b5Paper"></property>
	</bean>
    
</beans>

5.定义单元测试类测试打印机

package cn.test;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import cn.printer.Printer;
/**
 * 测试打印机。
*/
public class PrinterTest {
 @Test
	public void printerTest() {
		ApplicationContext context = new ClassPathXmlApplicationContext(
				"applicationContext.xml");
		// 通过Printer bean的id来获取Printer实例
		Printer printer = (Printer) context.getBean("printer");
		String content = "轻灵..轻灵的鼠标一点,神奇的世界出现。双手..把双手放在胸前" +
				",敲打出美丽的画卷。轻灵..轻灵的鼠标一点,神奇的世界出现。双手..把双" +
				"手放在胸前,敲打出美丽的画卷。是谁让IT生活多彩绚烂,青鸟在我们身边," +
				"纵然是IT时代风云变幻,青鸟青鸟与我们相伴。飞翔吧青鸟,将知识在神州大" +
				"地撒遍;飞翔吧青鸟,让教育改变我们的生活。是你帮助了我们实现了心愿," +
				"又是你让我们的世界变成无限。因为有你,地更广阔,天更蓝";
		printer.print(content);
	}
}

十二、面向切面编程(AOP)
12.0.AOP在Spring中的作用
  • 提供声明式事务;
  • 允许用户自定义切面
12.1.面向切面编程

下图中的代码, 除了保存用户的业务功能外,还包含了事务处理、日志记录、异常处理等功能,怎样才能专注于业务功能处理呢?

在这里插入图片描述

12.2.面向切面编程(AOP)

AOP目标

  • ​ 让我们专注于业务功能处理

AOP原理

  • ​1)将复杂的需求分解出不同方面,将不同对象、不同模块之间的共同业务集中解决

  • 2)通过动态代理的方式,把抽离出来的共性代码"织入"到业务代码中,实现对原有代码的增强处理

在这里插入图片描述

12.3.面向切面编程(AOP)

AOP相关术语

==横切关注点:==跨越应用程序多个模块的方法或功能。即是,与我们业务逻辑无关的,但是我们需要关注的部分,就是横切关注点。如日志 , 安全 , 缓存 , 事务等等 …
切面(ASPECT):横切关注点 被模块化 的特殊对象。即,它是一个类。
通知(Advice):切面必须要完成的工作。即,它是类中的一个方法。
目标(Target):被通知对象。
代理(Proxy):向目标对象应用通知之后创建的对象。
切入点(PointCut):切面通知 执行的 “地点”的定义。
连接点(JointPoint):与切入点匹配的执行点。

十三、Spring AOP在项目中的运用
13.1.Spring AOP在项目中的运用

需求:

  • 使用Spring AOP实现日志输出

步骤

  1. maven中引入依赖的jar包
  2. 编写保存用户的业务代码以及用于增强处理的代码
  3. 编写Spring核心配置文件
  4. 运行测试
13.2.Spring AOP在项目中的运用4-2

目标方法

public class UserServiceImpl implements UserService {
	// …省略代码
	public void save(User user){
		userDao.saveUser(user);
	}
}

增强处理

public class UserServiceLogger {
	private static final Logger log = Logger.getLogger(UserServiceLogger.class);
	//前置增前 
public void before(JoinPoint jp) {
		log.info("调用" + jp.getTarget() + "的" + jp.getSignature().getName() + "方法。方法入参:" + Arrays.toString(jp.getArgs()));
	}
//后置增强
	public void afterReturning(JoinPoint jp, Object result) {
		log.info("调用" + jp.getTarget() + "的" + jp.getSignature().getName() + "方法。方法返回值:" + result); 
	}
}
13.3.Spring AOP在项目中的运用

定义切入点

  • 切入点:简单的说,就是连接点的查询条件
<aop:config>
<!-- execution:切入点表示式,符合该表达式的方法可以被织入增强处理-->
<aop:pointcut id = "pointcut" expression = "execution(public void save(entity.User))"/>
</aop:config>

表达式匹配规则举例

public * addNewUser(entity.User)"*" //表示匹配所有类型的返回值
public void *(entity.User)"*"       //表示匹配所有方法名
public void addNewUser(..)".."      //表示匹配任意参数个数和类型
* com.service.*.*(..)                 //匹配com.service包下所有类的所有方法
* com.service..*.*(..)                //匹配com.service包及其子包下所有类的所有方法
13.4.Spring AOP在项目中的运用
<aop:config>
<!---->
	<aop:pointcut id = "pointcut" 
		expression = "execution(public void save(entity.User))"/>
<!--ref="theLogger": 增强处理对象-->
	<aop:aspect ref="theLogger" >
  <!--method="before":前置增强处理方法,pointcut-ref="pointcut":切入点-->
		<aop:before method="before" 
			pointcut-ref="pointcut" />
  <!--method="afterReturning":后置增强处理方法,pointcut-ref="pointcut":切入点, returning="result": 指定需要注入返回值的参数名为result-->
		<aop:after-returning method="afterReturning" 
			pointcut-ref = "point" returning="result" />
	</aop:aspect>
</aop:config>

maven中添加aop依赖包

<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.6</version>
<scope>runtime</scope>
</dependency>

log4j.properties

# rootLogger是所有日志的根日志,修改该日志属性将对所有日志起作用
# 下面的属性配置中,所有日志的输出级别是info,输出源是con
log4j.rootLogger=info,con
# 定义输出源的输出位置是控制台
log4j.appender.con=org.apache.log4j.ConsoleAppender
# 定义输出日志的布局采用的类
log4j.appender.con.layout=org.apache.log4j.PatternLayout
# 定义日志输出布局
log4j.appender.con.layout.ConversionPattern=%d{MM-dd HH:mm:ss}[%p]%c%n -%m%n

entity包

package entity;
/**
 * 用户实体类
*/
public class User implements java.io.Serializable {
	private Integer id; // 用户ID
	private String username; // 用户名
	private String password; // 密码
	private String email; // 电子邮件
	// getter & setter
	public Integer getId() {
		return id;
	}
	public void setId(Integer id) {
		this.id = id;
	}
	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 getEmail() {
		return email;
	}
	public void setEmail(String email) {
		this.email = email;
	}
}

dao包

package dao;
import entity.User;
/**
 * 增加DAO接口,定义了所需的持久化方法
*/
public interface UserDao {
	public void saveUser(User user);
}

dao.impl包

package dao.impl;
import dao.UserDao;
import entity.User;
/**
 * 用户DAO类,实现IDao接口,负责User类的持久化操作
*/
public class UserDaoImpl implements UserDao {

	public void saveUser(User user) {
		// 这里并未实现完整的数据库操作,仅为说明问题
		System.out.println("保存用户信息到数据库");
	}
}

service包

package service;

import entity.User;

/**
 * 用户业务接口,定义了所需的业务方法
*/
public interface UserService {
	public void save(User user);
}

service.impl包

package service.impl;

import service.UserService;
import dao.UserDao;
import entity.User;

/**
 * 用户业务类,实现对User功能的业务管理
*/
public class UserServiceImpl implements UserService {

	/**
	 * 声明接口类型的引用,和具体实现类解耦合
	 */
	private UserDao userDao;

	/**
	 * 保存用户
	 */
	public void save(User user) {
		// 调用用户DAO的方法保存用户信息
		userDao.saveUser(user);
	}

	/**
	 * dao 属性的setter访问器,会被Spring调用,实现设值注入
	 */
	public void setUserDao(UserDao userDao) {
		this.userDao = userDao;
	}
}

aop包

package aop;

import java.util.Arrays;
import org.apache.log4j.Logger;
import org.aspectj.lang.JoinPoint;

/**
 * 增强处理
 * 提取的公共日志代码
*/
public class UserServiceLogger {
	private static final Logger log = Logger.getLogger(UserServiceLogger.class);

	public void before(JoinPoint jp) {
	    log.info("调用 " + jp.getTarget() + " 的 " + jp.getSignature().getName()
	            + " 方法。方法入参:" + Arrays.toString(jp.getArgs()));
	}
	
	public void afterReturning(JoinPoint jp, Object result) {
	    log.info("调用 " + jp.getTarget() + " 的 " + jp.getSignature().getName()
	            + " 方法。方法返回值:" + result);
	}
}

spring核心配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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/aop
	http://www.springframework.org/schema/aop/spring-aop.xsd">

<bean id="userDao" class="dao.impl.UserDaoImpl"></bean>
<bean id="userService" class="service.impl.UserServiceImpl">
  <property name="userDao" ref="userDao"></property>
</bean>
<!-- 声明增强方法所在的Bean -->
<bean id="theLogger" class="aop.UserServiceLogger"></bean>
<!-- 配置切面 -->
<aop:config>
  <!-- 定义切入点 -->
  <aop:pointcut id="pointcut"
      expression="execution(public void save(entity.User))" />
  <!-- 引用包含增强方法的Bean -->
  <aop:aspect ref="theLogger">
      <!-- 将before()方法定义为前置增强并引用pointcut切入点 -->
      <aop:before method="before" pointcut-ref="pointcut"/>
      <!-- 将afterReturning()方法定义为后置增强并引用pointcut切入点 -->
      <!-- 通过returning属性指定为名为result的参数注入返回值 -->
      <aop:after-returning method="afterReturning"
          pointcut-ref="pointcut" returning="result" />
  </aop:aspect>
</aop:config>
</beans>

测试类AopTest

package test;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import service.UserService;
import service.impl.UserServiceImpl;
import entity.User;

public class AopTest {

@Test
	public void aopTest() {
		ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
		UserService service = (UserService) ctx.getBean("userService");
		
		User user = new User();
		user.setId(1);
		user.setUsername("test");
		user.setPassword("123456");
		user.setEmail("test@163.com");

		service.save(user);
	}
}
十四、本章总结

在这里插入图片描述

常见错误

在这里插入图片描述

解决方案:

  • proxy-target-class属性是用来配置AOP产生代理对象时的代理模式的。

  • springAOP的代理方法无所谓是注解还是XML配置,默认情况下都是查看目标类是否有实现任何接口,如果实现接口则使用JDK代理方式,如果没有实现接口,就使用CGLib方式。

  • 当然有些情况下我们可能需要强制要求使用CGLib,这时在XML配置方式我们可以使用aop:aspectj-autoproxy属性proxy-target-class修改为true,强行要求spring使用CGLib进行动态代理。

在这里插入图片描述

  • 8
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

众生云海,一念初见

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值