Spring
1. 简介
Spring是一个开源框架,它由Rod Johnson创建。它是为了解决企业应用开发的复杂性而创建的。
在表现层它提供了Spring MVC 以及Struts框架的整合功能;
在业务层可以管理事务、记录日志等;
在持久层可以整合Mybatis、Hibernate、JdbcTemplate等技术;
因此,可以说Spring是企业应用开发很好的“一站式”选择,虽然Spring贯穿于表现层、业务逻辑层和持久层,但他并不想取代那些已近有的框架,而是以高度的开放性与他们进行无缝整合。
- 目的:解决企业应用开发的复杂性
- 功能:使用基本的JavaBean代替EJB,并提供了更多的企业应用功能
- 范围:任何Java应用
需要导入的Maven包:
<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.8</version>
</dependency>
2. 组成
Spring 框架是一个分层架构,由 7 个模块组成
-
核心容器(Spring Core):核心容器提供 Spring 框架的基本功能。核心容器的主要组件是 BeanFactory,它是工厂模式的实现。BeanFactory 使用控制反转 (IOC) 模式将应用程序的配置和依赖性规范与实际的应用程序代码分开。
-
Spring 上下文(Spring Context):Spring 上下文是一个配置文件,向 Spring 框架提供上下文信息。Spring 上下文包括企业服务,例如 JNDI(Java命名和目录接口)、EJB(Enterprise Java Beans称为Java 企业Bean)、电子邮件、国际化、校验和调度功能。
-
Spring AOP:通过配置管理特性,Spring AOP 模块直接将面向方面的编程功能集成到了 Spring 框架中。所以,可以很容易地使 Spring 框架管理的任何对象支持 AOP。Spring AOP 模块为基于 Spring 的应用程序中的对象提供了事务管理服务。通过使用 Spring AOP,不用依赖 EJB 组件,就可以将声明性事务管理集成到应用程序中。
-
Spring DAO:JDBC DAO 抽象层提供了有意义的异常层次结构,可用该结构来管理异常处理和不同数据库供应商抛出的错误消息。异常层次结构简化了错误处理,并且极大地降低了需要编写 的异常代码数量(例如打开和关闭连接)。Spring DAO 的面向 JDBC 的异常遵从通用的 DAO 异常层次结构。
-
Spring ORM:Spring 框架插入了若干个 ORM 框架,从而提供了 ORM 的对象关系工具,其中包括 JDO、Hibernate 和 iBatis SQL Map。所有这些都遵从 Spring 的通用事务和 DAO 异常层次结构。
-
Spring Web 模块:Web 上下文模块建立在应用程序上下文模块之上,为基于 Web 的应用程序提供了上下文。Web 模块还简化了处理多部分请求以及将请求参数绑定到域对象的工作。
-
Spring MVC 框架:MVC 框架是一个全功能的构建 Web 应用程序的 MVC 实现。通过策略接口,MVC 框架变成为高度可配置的,MVC 容纳了大量视图技术,其中包括 JSP。
3. IOC理论推倒
1. 先复习下MVC开发
一般模式为:
- UserDao接口
- UserDaoImpl实现类
- UserService业务接口
- UserServiceImpl业务实现类
按照传统编程,将这个模式一一实现,过程如下:
- 各个模块代码如下:
- UserDao接口
public interface UserDao {
void getUser();
}
- UserDaoImpl实现类
public class UserDaoImpl implements UserDao{
public void getUser() {
System.out.println("获取用户默认数据");
}
}
- UserService业务接口
public interface UserService {
void getUser();
}
- UserServiceImpl业务实现类
public class UserServiceImpl implements UserService{
private UserDao userDao= new UserDaoImpl();
public void getUser() {
//业务层调DAO层,使用了组合方式
userDao.getUser();
}
}
接下来需要创建一个测试类MyTset
public class MyTest {
public static void main(String[] args) {
//三层架构,用户调用的是业务层,DAO层他们不需要接触
UserService userService = new UserServiceImpl();
userService.getUser();
}
}
2. IOC思想引入
假设用户需求从“默认获取用户数据”变成“使用Mysql获取用户数据”,则需要在dao包下增加一个UserDaoMysqlImpl类,并将UserServiceImpl业务实现类变为:
public class UserServiceImpl implements UserService{
private UserDao userDao= new UserMySqlImpl();
public void getUser() {
//业务层调DAO层,使用了组合方式
userDao.getUser();
}
}
这样就带来一个问题:用户需求一旦发生变化,就需要改动原有代码,要是代码量庞大,则维护与开发成本太大,费时费力。
为了解决这个问题,可以这样改变:
- 在service层增加一个set接口
public interface UserService {
void getUser();
void setUserDao(UserDao userDao);
}
- UserServiceImpl业务实现类变为:
public class UserServiceImpl implements UserService{
private UserDao userDao;
//利用set进行动态实现值的注入
@Override
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
@Override
public void getUser() {
//业务层调DAO层,使用了组合方式
userDao.getUser();
}
}
- MyTest.java变为:
public class MyTest {
public static void main(String[] args) {
//三层架构,用户调用的是业务层,DAO层他们不需要接触
UserService userService = new UserServiceImpl();
userService.setUserDao(new UserOracleImpl());
userService.getUser();
}
}
这样一来,用户需要什么,就不取决于程序员了,可由用户自己将写好的接口实现类作为参数传入。
4. 第一个HelloSpring
说一下我理解的思想:java创建好对象后,将对象交给Spring管理,当我们需要使用的时候,就向Spring获取就行。
每个实体类需要设置set方法(原理:IOC理论),不然Spring配置会报错。
- 创建一个实体类(pojo):
public class Hello {
private String str;
public String getStr() {
return str;
}
public void setStr(String str) {
this.str = str;
}
@Override
public String toString() {
return "Hello{" +
"str='" + str + '\'' +
'}';
}
}
- 创建一个Bean.xml文件(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">
</beans>
ips:ieda可自动生成Spring的配置文件,右键选择新建–>xml配置文件–>Spring配置文件
- 在上面的实体类的基础上,配置一个bean.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">
<!-- 使用spring来创建对象,在spring这些称为Bean -->
<bean id="hello" class="cn.sdablog.pojo.Hello">
<property name="str" value="Spring"/>
</bean>
</beans>
小提示:
如果是实体类别Spring托管,可在实体类上看见一个小叶子,点击即可跳转到对应的配置xml。
- 来一个测试类MyTest:
public class MyTest {
public static void main(String[] args) {
//获取spring上下文对象
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
//我们的对象现在都在spring中管理了,我们只需要直接去里面获取就行
Hello hello = (Hello) context.getBean("hello");
System.out.println(hello.toString());
}
}
注:“ApplicationContext context = new ClassPathXmlApplicationContext(“beans.xml”)”,这个是固定代码,就是获取你bean文件里的对象并赋值给context。
getBean 方法:获取Spring里面的对象(传入对象名)。
输出结果:
注:Spring容器通过id获取的实现类,默认对无参构造进行实例化。
5. IOC创建方式
- 使用无参构造创建对象(默认)
- 使用有参构造创建对象
- 下标赋值
<bean id="hello" class="cn.sdablog.pojo.Hello">
<constructor-arg index="0" name="name" value="张三"/>
</bean>
hello类的有参构造中,第几个参数。
- 参数名
<bean id="hello" class="cn.sdablog.pojo.Hello">
<constructor-arg name="name" value="张三"/>
</bean>
6. Spring配置说明
6.1 别名
<!-- 别名,如果添加了别名,可以使用别名获取这个对象-->
<alias name="userDao" alias="hello2"/>
6.2 Spring Bean定义
<!-- id:bean的唯一标识
class:bean对象对应的全限定名,包名+类型
name:就是别名,而且name可以同时取多个别名
-->
<bean id="hello" class="cn.sdablog.pojo.Hello" name="hello3">
<property name="name" value="张三"/>
</bean>
由 Spring IoC 容器管理的对象称为 Bean,Bean 根据 Spring 配置文件中的信息创建。
我们可以把 Spring IoC 容器看作是一个大工厂,Bean 相当于工厂的产品。如果希望这个大工厂生产和管理 Bean,就需要告诉容器需要哪些 Bean,以哪种方式装配。
Spring 配置文件支持两种格式,即 XML 文件格式和 Properties 文件格式。
- Properties 配置文件主要以 key-value 键值对的形式存在,只能赋值,不能进行其他操作,适用于简单的属性配置。
- XML 配置文件采用树形结构,结构清晰,相较于 Properties 文件更加灵活。但是 XML 配置比较繁琐,适用于大型的复杂的项目。
通常情况下,Spring 的配置文件都是使用 XML 格式的。XML 配置文件的根元素是 ,该元素包含了多个子元素 。每一个 元素都定义了一个 Bean,并描述了该 Bean 是如何被装配到 Spring 容器中的。
在 XML 配置的 元素中可以包含多个属性或子元素,常用的属性或子元素如下表所示。
属性名称 | 描述 |
---|---|
id | Bean 的唯一标识符,Spring IoC 容器对 Bean 的配置和管理都通过该属性完成。id 的值必须以字母开始,可以使用字母、数字、下划线等符号。 |
name | 该属性表示 Bean 的名称,我们可以通过 name 属性为同一个 Bean 同时指定多个名称,每个名称之间用逗号或分号隔开。Spring 容器可以通过 name 属性配置和管理容器中的 Bean。 |
class | 该属性指定了 Bean 的具体实现类,它必须是一个完整的类名,即类的全限定名。 |
scope | 表示 Bean 的作用域,属性值可以为 singleton(单例)、prototype(原型)、request、session 和 global Session。默认值是 singleton。 |
constructor-arg | 元素的子元素,我们可以通过该元素,将构造参数传入,以实现 Bean 的实例化。该元素的 index 属性指定构造参数的序号(从 0 开始),type 属性指定构造参数的类型。 |
property | 元素的子元素,用于调用 Bean 实例中的 setter 方法对属性进行赋值,从而完成属性的注入。该元素的 name 属性用于指定 Bean 实例中相应的属性名。 |
ref | 和 等元素的子元索,用于指定对某个 Bean 实例的引用,即 元素中的 id 或 name 属性。 |
value | 和 等元素的子元素,用于直接指定一个常量值。 |
list | 用于封装 List 或数组类型的属性注入。 |
set | 用于封装 Set 类型的属性注入。 |
map | 用于封装 Map 类型的属性注入。 |
entry | |
init-method | 容器加载 Bean 时调用该方法,类似于 Servlet 中的 init() 方法 |
destroy-method | 容器删除 Bean 时调用该方法,类似于 Servlet 中的 destroy() 方法。该方法只在 scope=singleton 时有效 |
lazy-init | 懒加载,值为 true,容器在首次请求时才会创建 Bean 实例;值为 false,容器在启动时创建 Bean 实例。该方法只在 scope=singleton 时有效 |
6.3import
这个import一般用于团队开发,可以将多个配置文件,导入合并为一个
- applicationContext.xml
<import resource="beans.xml"/>
<import resource="beans1.xml"/>
<import resource="beans2.xml"/>
7. 依赖注入
7.1 构造器注入
- 实体类有参构造
public Hello(String name) {
this.name=name;
}
- bean.xml
<!-- 构造器注入,下标赋值-->
<bean id="hello" class="cn.sdablog.pojo.Hello">
<constructor-arg index="0" name="name" value="张三"/>
</bean>
7.2 Set注入(重点)
- 依赖注入:set注入
- 依赖:bean对象的创建依赖于框架
- 注入: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="address" class="cn.sdablog.pojo.Address"/>
<bean id="student" class="cn.sdablog.pojo.Student">
<!-- 普遍注入,value-->
<property name="name" value="张三"/>
<!-- Bean注入,ref -->
<property name="address" ref="address"/>
<!-- 数组注入-->
<property name="books">
<array>
<value>红楼梦</value>
<value>西游记</value>
<value>三国演义</value>
</array>
</property>
<!-- List注入-->
<property name="hobys">
<list>
<value>爱好</value>
<value>听歌</value>
<value>安电影</value>
</list>
</property>
<!-- Map注入-->
<property name="card">
<map>
<entry key="身份证" value="1324654"/>
<entry key="银行卡" value="1s4444"/>
</map>
</property>
<!-- set注入-->
<property name="game">
<set>
<value>LOL</value>
<value>刺激战场</value>
</set>
</property>
<!-- null注入-->
<property name="wife">
<null/>
</property>
<!-- Properties注入-->
<property name="info">
<props>
<prop key="学号">20202145</prop>
<prop key="性别">男</prop>
</props>
</property>
</bean>
</beans>
7.3 bean的作用域
- 单例模式(Srping默认机制)
<bean id="userDao" class="cn.sdablog.dao.UserDaoImpl" scope="singleton"/>
- 原型模式:每次从容器中创建对象时,都会产生一个新的对象
<bean id="userDao" class="cn.sdablog.dao.UserDaoImpl" scope="prototype"/>
- 其余作用域:用在web开发中使用
8. Bean的自动装配
- 自动装配是Spring满足bean的一种方式
- Spring会在上下文自动寻找,并自动给bean装配属性
在Spring中有三种装配的方式
- 在xml中显示的配置
- 在java中显示的配置
- 隐式的自动装配bean【重要】
bean的自动装配属性 autowire ,有5个属性:
属性值 | 说明 |
---|---|
default (默认值) | 由< bean>的上级标签 < beans>的default-autowire属性值确定。例如<beans default-autowire=“ByName”,则该< bean>元素中的autowire属性对应的属性值就为ByName |
ByName | 根据属性的名称自动装配,容器将根据名称查找与属性完全一致的Bean,并将 属性自动装配 |
buType | 根据属性的数据类型字段装配,如果一个bean的数据类型,兼容另一个bean中属性的数据类型,则自动装配 |
constructor | 根据构造函数参数的数据类型,进行byType模式自动装配 |
no | 在默认情况下,不使用自动装配,bean依赖必须通过ref元素定义 |
8.1 ByName自动装配
显示的依赖注入关系(bean.xml配置文件):
<bean id="cat" class="cn.sdablog.pojo.Cat"/>
<bean id="dog" class="cn.sdablog.pojo.Dog"/>
<bean id="person" class="cn.sdablog.pojo.Person">
<property name="name" value="张三"/>
<property name="cat" ref="cat"/>
<property name="dog" ref="dog"/>
</bean>
byname自动装配:
<bean id="cat" class="cn.sdablog.pojo.Cat"/>
<bean id="dog" class="cn.sdablog.pojo.Dog"/>
<!--
byName:自动在容器上下文查找,和自己对象set方法后对应的beanid
-->
<bean id="person" class="cn.sdablog.pojo.Person" autowire="byName">
<property name="name" value="张三"/>
</bean>
设置byname属性,会根据你的bean的id名去匹配实体类中的set属性名是否相等,相等就会自动装配,否则装配失败,运行报错。
8.2 ByType自动装配
ByType自动装配:
<bean id="cat1" class="cn.sdablog.pojo.Cat"/>
<bean id="dog" class="cn.sdablog.pojo.Dog"/>
<!--
byType:自动在容器上下文查找,和自己对象set方法中对应的类型
-->
<bean id="person" class="cn.sdablog.pojo.Person" autowire="byType">
<property name="name" value="张三"/>
</bean>
bean根据类型进行装配,所以bean的id名可有可无
8.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:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config/>
</beans>
- 配置注解支持
<context:annotation-config/>
@Autowired(require = false or true)
-
直接在属性上使用即可,也可以在set方式上使用,require参数表示如果容器中不存在时会不会报错,默认require=true
-
使用@Autowired我们可以不用编写Set方法了,前提是你这个自动装配的属性在IOC (Spring) 容器中存在(需要有get方法),默认会根据byType进行自动装配
-
如果遇到多个类型相同的bean,可以配合@Qualifier(value = “xxx”)可以根据byName进行自动装配。
- 有多个相同的bean,但id不同,直接用@Autowired会报错,因为bean不知道装配哪个bean
- 用@Qualifier指定bean的id,bean就知道装配哪个bean了
- 有多个相同的bean,但id不同,直接用@Autowired会报错,因为bean不知道装配哪个bean
@Resource(name = “xxx”)
JDK自带,可以配置name=“xxx”来指定bean的id进行装配,默认按byName装配,如果找不到对应的bean,就按照byType装配。
注意:使用Resource是jdk自带的包,需要导入以下依赖包
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.3.2</version>
</dependency>
小结:
@Resource和@ Autowired的区别:
- 都是用来自动装配的,都可以放在属性字段上
- @ Autowired通过byType的方式实现,而且必须要求这个对象存在,如果发现类型大于1时,会按照byName进行装配[常用]
- @ Resource默认通过byname的方式实现,如果找不到名字,则通过byType实现!如果两个都找不到的情况下,就报错! [常用]
- 执行顺序不同: @ Autowired通过byType的方式实现,@Resource通过byName的方式实现。
8.4 普通注入/自动装配/注解装配 的区别
这是我理解的一个大致流程,不对的地方望大家指出~
这三个装配方式最终的效果是一样的,但是往往有时候会弄混着三种方式的装配,所以最后来总结区别下。
-
普通的注入,也叫显式注入
-
自动装配
-
注解装配
beans.xml:
people类:
9. 使用注解开发@Component
- 在使用注解开发前,需要保证AOP包正常导入
- 使用注解需要导入context约束,增加注解支持。
<?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:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<!--开启注解的支持-->
<context:annotation-config/>
</beans>
9.1 Component的使用
- 可使用spring的扫描机制对指定包下的所有Bean类,进行注解解析(推荐)
<!-- 指定要扫描的包,这个包下的注解就会生效。-->
<context:component-scan base-package="cn.sdablog.pojo"/>
该包下的所有bean类不需要一个一个进行配置,在需要使用的类上加上@Component进行声明即可。
9.2 属性注入:
用@Value可对属性,对象进行注入,等价于<property name="name" value="张三"/>
package cn.sdablog.pojo;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class User {
public String name;
//相当于:
// <bean id="user" class="cn.sdablog.pojo.User">
// <property name="name" value="张三"/>
// </bean>
@Value("张三")
public void setName(String name) {
this.name = name;
}
}
9.3 衍生的注解
@Component有几个衍生注解,在web开发中,安装MVC三层架构分层
这四个注解的功能都一样,都是代表将某个类注册到Spring中,装配bean
注解 | 使用层次 | 作用 |
---|---|---|
@Controller | 控制器层(注入服务) | 用于标注控制层组件(如struts中的action) |
@Service | 服务层(注入dao) | 用于标注业务层组件 |
@Repository | 持久层(实现dao访问) | 用于标注数据访问组件,即DAO组件 |
@Component | 标注一个类为Spring容器的Bean(把普通pojo实例化到spring容器中,相当于配置文件中的) | @Component泛指组件,当组件不好归类的时候,我们可以使用这个注解进行标注,标识为一个Bean。 |
9.4 小结
xml与注解:
- xml更加万能功能,适用于任何场合,维护简单方便
- 注解不是自己的类无法使用,维护相对复杂
xml与注解最佳实践:
- xml用来管理bean
- 注解只负责完成属性的注入
- 在使用中需要注意一个问题,必须让注解生效,就需要开启注解的支持
<!--开启注解的支持-->
<context:annotation-config/>
<!-- 指定要扫描的包,这个包下的注解就会生效-->
<context:component-scan base-package="cn.sdablog"/>
10 使用java方式配置Spring
10.1 java方式配置Spring
使用java方式配置Spring目的:减少Spring的xml配置,全部由java来控制
- 实体类:
public class User {
private String name;
public String getName() {
return name;
}
@Value("张三")
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
'}';
}
}
-
创建一个配置类(这个类就相当于bean.xml配置文件):
1、@Configuration 作用于类上,相当于一个xml配置文件;
2、@Bean 作用于方法上,相当于xml配置中的< bean>;尽然java配置文件相当于bean.xml,那java配置文件就应该用其他的注解的使用权利(@Component、@Repository等)
// @Configuration:代表这个类被 Spring托管了,已经注册到容器中了
@Configuration
public class Appconfig {
// 注册一个bean,等价于bean.xml文件里的bean标签
// 方法名 == xml文件里的id名 返回值 == xml文件里的class属性
@Bean
public User getUser(){
return new User();
}
}
如果有多个配置类,需要将配置文件2导入配置文件1,可使用 @Import 引入配置类的class对象
- 测试类
public class MyTest {
public static void main(String[] args) {
// 通过 AnnotationConfig 上下文获取容器, 通过配置类的class对象加载。
ApplicationContext context = new AnnotationConfigApplicationContext(Appconfig.class);
User user = (User) context.getBean("getUser");
System.out.println(user.getName());
}
}
10.2 java配置、XML配置、注解使用的区别
特点\配置方式 | XML | 注解 | Java Config |
---|---|---|---|
类型是否安全 | N | Y | Y |
查找实现类是否方便 | N,需要查找所有xml | Y,只需看哪个实现类上有加注解 | N,需要查找所有Java Config |
可读性 | 差,有很多xml标签,不易阅读 | 很好,注解的同时起到注释的作用 | 较好,对于Java程序员来说,阅读Java代码比阅读xml方便 |
配置简洁性 | 很啰嗦 | 十分简洁 | 有点啰嗦 |
修改配置是否需要重新编译 | N,直接替换xml文件即可 | Y,需重新编译出class文件,然后进行替换 | Y,同注解配置 |
是否会侵入代码 | N | Y | N |
自由度 | 低,可以使用SPEL语法,但是SPEL语法能实现的功能有限 | 低,只能基于注解的属性进行配置 | 高,可以自由使用Java语法,调用各种函数来注入对象 |
是否可以注入不是自己维护的类 | Y | N | Y |
- xml配置相对于其他两种方式来说,几乎没什么优势,唯一的优势就是修改后不需要重新编译,因此对于一些经常切换实现类的对象,可以采用xml的方式进行配置。还有就是由于xml是Spring一开始就提供的配置方式,因此很多旧代码还是采用xml,所以在维护旧代码时会免不了用到xml。
- 注解用起来非常地简洁,代码量十分少,因此是项目的第一选择。只有当需要注入代码不是自己维护的第三方jar包中的类时,或者需要更为灵活地注入,比如说需要调用某个接口,查询数据,然后把这个数据赋值给要注入的对象,那么这时候就需要用到Java Config。
三种方式区别参考博客:https://blog.csdn.net/qq_39331713/article/details/82225056
11. 代理模式
代理:代理就是不改变源码的情况下对原有功能进行增强
为什么要学习代理模式?因为这就是SpringAOP的底层。【SpringAOP 和 SpringMVC】
代理模式的分类:
- 静态代理
- 动态代理
代理模式的优势:
- 可以使真实角色的操作更加纯粹,不用去关注一些公共业务
- 公共业务也就交给代理角色,实现了业务分工
- 公共业务发生扩展的时候,方便集中管理
代理模式的缺点:一个真实角色就会产生一个代理角色,代码量会方便,开发效率会降低
11.1 静态代理
角色分析:
- 抽象角色:一般会使用接口或者抽象类来解决
- 真实角色:被代理的角色
- 代理角色:代理真实角色,代理真实角色后,我们一般会做一些附属操作
- 客户:访问代理对象的人
上代码
- 租房接口
// 租房业务接口
public interface Rent {
public void rent();
}
- 真实角色,这里是房东,因为他有租房这项业务,所以继承了租房业务的接口,
// 房东类
public class Host implements Rent{
@Override
public void rent() {
System.out.println("房东要出租房子!");
}
}
- 代理角色,他拥有租房的业务功能,也有其他扩展的业务
public class Proxy implements Rent{
// 有租房业务,需要对接房东,所以需要房东对象
private Host host;
public Proxy() {
}
public Proxy(Host host) {
this.host = host;
}
@Override
public void rent() {
host.rent();
}
// 看房
public void seeHose(){
System.out.println("中介带你看房");
}
// 签合同
public void hetong(){
System.out.println("签租赁合同");
}
// 收中介费
public void fare(){
System.out.println("收中介费");
}
}
- 我要租房(客户端访问代理角色)
public class Client {
public static void main(String[] args) {
// 房东要租房子
Host host = new Host();
// 代理,中介帮房东出租房子,代理角色会有一些附属操作
Proxy proxy = new Proxy(host);
// 你不用面对房东,直接找中介租房即可
proxy.rent();
}
}
11.2 动态代理
-
动态代理是动态生成的,不是我们直接写好的
-
动态代理分为两大类:1. 基于接口的动态代理, 2. 基于类的动态代理
1.基于接口 — JDK动态代理- 要求:被代理类至少一个接口
- 提供者:JDK官方
- 涉及者:Proxy
- 创建代理对象的方法:newProxyInstance
- 方法中的参数:
- ClassLoader:类加载器,负责加载代理对象的字节码,和被代理对象使用相同的类加载器(固定写法)
- Class<?>[]:字节码数组,负责让生产的代理对象具有和被代理对象相同的方法。写什么要看被代理对象是一个接口还是一个实现类;
1 : 如果是一个接口:new Class[ ]{接口}
2 :如果是一个实现类:XXX.getClass().getInterfaces() ----这是固定写法 - InvocationHandler:一个接口,需要我们提供该接口的实现;作用是用于对方法增强;增强的代码,谁用谁写;写的是一个接口的实现类;通常是一个匿名内部类,但是不绝对。
2. 基于子类的动态代理 — cglib
- 要求:需要导入cglib的坐标。被代理类不是最终类(不能被final修饰)
- 提供者:cglib(第三方)
- 涉及者:Enhancer
- 创建代理对象的方法:create
- 方法中的参数:
- Class:字节码对象,用于加载代理对象字节码;写的是被代理对象的字节码(固定写法);
- Callback:如何代理,提供增强代码的,它是个接口,需要自己写实现;
需要了解两个类:
3. Proxy:代理
4. InvocationHandler:
11.2 AOP
AOP(Aspect Oriented Programming)意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
spring使用aop需要导入包:
<!-- AOP包支持-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.7</version>
</dependency>
AOP概念
- 切面(Aspect):一个关注点的模块化,这个关注点可能会横切多个对象。它是一个类。
- 连接点(Join point):与切入点匹配的执行点
- 通知(Advice):切面必须要完成的工作。它是一个类中的方法。
- 切入点(Pointcut):切面通知执行的地点的定义。
- 目标对象(Target object):目标对象
- AOP代理(AOP proxy):向目标对象应用通知之后创建的对象
通知(Advice)的类型:
- 前置通知(Before advice):在某个连接点(Join point)之前执行的通知,但这个通知不能阻止连接点的执行(除非它抛出一个异常)。
- 返回后通知(After returning advice):在某个连接点(Join point)正常完成后执行的通知。例如,一个方法没有抛出任何异常正常返回。
- 抛出异常后通知(After throwing advice):在方法抛出异常后执行的通知。
- 后置通知(After(finally)advice):当某个连接点(Join point)退出的时候执行的通知(不论是正常返回还是发生异常退出)。
- 环绕通知(Around advice):包围一个连接点(Join point)的通知,如方法调用。这是最强大的一种通知类型。环绕通知可以在方法前后完成自定义的行为。它也会选择是否继续执行连接点或直接返回它们自己的返回值或抛出异常来结束执行。
方式一:使用Spring的API接口
log类:
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;
public class Log implements MethodBeforeAdvice {
/**
* method:要执行的目标对象的方法
* objects:参数
* o:目标对象
*/
@Override
public void before(Method method, Object[] objects, Object o) throws Throwable {
System.out.println(o.getClass().getName() + " 的 " + method.getName() + " 被执行了!");
}
}
afterlog类:
import org.springframework.aop.AfterReturningAdvice;
import java.lang.reflect.Method;
public class AfterLog implements AfterReturningAdvice {
/**
* returnValue:返回值结果
* method:要执行的对象的方法
* args:参数
* target:目标对象
*/
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("对象:" + target.getClass().getName() + "执行了方法:" + method.getName() + " 返回值为:" + returnValue);
}
}
applicationContex.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: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-->
<bean id="userService" class="cn.sdablog.service.UserServiceImpl"/>
<bean id="log" class="cn.sdablog.log.Log"/>
<bean id="afterlog" class="cn.sdablog.log.AfterLog"/>
<!--方式一:使用原生Spring API 接口
配置aop:导入aop的约束-->
<aop:config>
<!-- 切入点
expression:切入点
execution(要执行的位置。 * * * * *):方法内填入需要执行的位置,带有5个参数
-->
<aop:pointcut id="pointcuts" expression="execution(* cn.sdablog.service.UserServiceImpl.*(..))"/>
<!-- 执行环绕增加
advice-ref:应用模板
pointcut-ref:切入点(什么时候执行)
-->
<aop:advisor advice-ref="log" pointcut-ref="pointcuts"/>
<aop:advisor advice-ref="afterlog" pointcut-ref="pointcuts"/>
</aop:config>
</beans>
测试类:
import cn.sdablog.service.UserService;
import cn.sdablog.service.UserServiceImpl;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest1 {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
// 动态代理的是接口类
UserService userservice = context.getBean("userService", UserService.class);
userservice.add();
}
}
输出结果:
方式二:自定义实现
DiyPoincut类:
// 自定义aop
public class DiyPoinCut {
public void before(){
System.out.println("==================方法执行前===============");
}
public void after(){
System.out.println("==================方法执行后===============");
}
}
applicationContex.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: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-->
<bean id="userService" class="cn.sdablog.service.UserServiceImpl"/>
<!-- 方式二:自定义类-->
<bean id="diy" class="cn.sdablog.diy.DiyPoinCut"/>
<aop:config>
<aop:aspect ref="diy">
<!-- 切入点-->
<aop:pointcut id="point" expression="execution(* cn.sdablog.service.UserServiceImpl.*(..))"/>
<!-- 通知-->
<aop:before method="before" pointcut-ref="point"/>
<aop:after method="after" pointcut-ref="point"/>
</aop:aspect>
</aop:config>
</beans>
测试类:
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest1 {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
// 动态代理的是接口类
UserService userservice = context.getBean("userService", UserService.class);
userservice.add();
}
}
输出结果:
11.3 使用注解实现AOP
- applicationContext.xml配置:
需要打开AOP注解支持(AOP自动代理aspectj-autoproxy)
<?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-->
<bean id="userService" class="cn.sdablog.service.UserServiceImpl"/>
<bean id="log" class="cn.sdablog.log.Log"/>
<bean id="afterlog" class="cn.sdablog.log.AfterLog"/>
<!-- 方式三:注解使用AOP-->
<bean id="diypoinCut1" class="cn.sdablog.diy.DiyPoinCut1"/>
<!-- 开启AOP注解支持-->
<aop:aspectj-autoproxy/>
</beans>
diypoinCut1类:
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
//标注这个类是一个切面
@Aspect
public class DiyPoinCut1 {
@Before("execution(* cn.sdablog.service.UserServiceImpl.*(..))")
public void befor(){
System.out.println("======方法执行前======");
}
@After("execution(* cn.sdablog.service.UserServiceImpl.*(..))")
public void after(){
System.out.println("======方法执行后======");
}
//环绕增强
@Around("execution(* cn.sdablog.service.UserServiceImpl.*(..))")
public void around(ProceedingJoinPoint pj) throws Throwable {
System.out.println("环绕前");
Object proceed = pj.proceed();
System.out.println("环绕后");
}
}
测试类:
import cn.sdablog.service.UserService;
import cn.sdablog.service.UserServiceImpl;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest1 {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
// 动态代理的是接口类
UserService userservice = context.getBean("userService", UserService.class);
userservice.add();
}
}
输出结果: