文章目录
- 1、 Spring 简介
- 1.1 Spring 的优点
- 1.2 Spring的组成
- 2、IOC思想
- 我的一点拙见:
- 3、Spring 初体验
- 3.1 导入maven依赖
- 3.2 在resources文件夹下建立applicationContext.xml文件
- 3.3 书写代码进行测试
- 4、IOC创造对象的方式
- 5、Spring配置
- 5.1 别名
- 5.2 Bean的配置
- 5.3 import
- 5.4 依赖注入(DL)
- 什么是依赖注入
- 5.5 拓展注入
- 6、bean的作用域
- 7、Bean的自动装配(自动注入)(自动依赖autowired)
- 7.1 隐式的自动装配
- 7.2 注解实现spring的自动装配
- 7.3 使用spring注解进行开发
- 8、使用java代码配置spring
- 9、静态代理模式
- 9.1 静态代理
- 9.2 动态代理
- 10、AOP的实现方式
- 10.1 AOP的几大常用概念
- 10.2 AOP的java代码
- execution表达式
- 方式一,通过Spring的API接口
- 方式二,自定义切入类
- 方式三,通过spring注解
- 11、整合Mybatis
- 12、声明式事务
1、 Spring 简介
- Spring 提供了企业级开发java项目中所需要的所有东西
- Spring 从Framework 5.1 开始,需要JDK 8 以上的版本支持
- Spring是开源的,于2003年问世,是JavaEE的补充
- Spring保持了强大的向后兼容性。
1.1 Spring 的优点
- Spring是一种非侵入式(non-invasive)框架,它可以使应用程序代码对框架的依赖最小化。
- 方便解耦、简化开发
- Spring就是一个大工厂,可以将所有对象的创建和依赖关系的维护工作都交给Spring容器的管理,大大的降低了组件之间的耦合性。
- 支持AOP , 提高了程序的复用性。
- Spring提供了对AOP的支持,它允许将一些通用任务,如安全、事物、日志等进行集中式处理,从而提高了程序的复用性。
- 方便程序的测试
- Spring提供了对Junit4的支持,可以通过注解方便的测试Spring程序。
- 方便集成各种优秀框架
- Spring不排斥各种优秀的开源框架,其内部提供了对各种优秀框架(如Struts、Hibernate、MyBatis、Quartz等)的直接支持。
- 降低Jave EE API的使用难度。
- Spring对Java EE开发中非常难用的一些API(如JDBC、JavaMail等),都提供了封装,使这些API应用难度大大降低。
1.2 Spring的组成
spring由几十个组件构成,分别被整合在核心容器(Core Container)、数据访问/集成部分(Data Access/Integration)、AOP(Aspect Oriented Programming)、Aspects/设备支持(Instrumentation)、Web、报文发送(Messaging)、Test等模块中。
总结:Spring是一个轻量级的控制反转(IOC)和面向切面编程(AOP)的框架
两大部分:IOC和AOP
2、IOC思想
什么是控制反转?
- 由程序员主动的new对象,转换成 交给一个创建该对象的工厂来创建
- 由主动转换为被动
- 将创建对象的权力交给程序(或者是其他人) 来创建对象的过程叫做控制反转
什么是描述?
描述就是XML文件或者注解,是指用来定义或对某个对象或功能的解释
为什么需要IOC思想
在任何一个有实际开发项目,我们会使用很多类来描述它们特有的功能,并且通过类与类之间的相互协作来完成特定的业务逻辑。
这个时候,每个类都需要管理与自己有交互的类的引用和依赖,代码会变的异常难以维护和极度的高耦合。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xh6YwGor-1639967248902)(C:\Users\86155\AppData\Roaming\Typora\typora-user-images\1614659912659.png)]
IOC思想的本质
而IOC的出现正是用来解决这个问题:
==我们通过IOC将这些相互依赖对象的创建、协调工作交给spring容器去处理。==每个对象只需要关注其自身的业务逻辑关系就可以了。
在这样的角度上来看,获得依赖的对象的方式,进行了反转,变成了由spring容器控制对象,并处理对象业务
IOC通俗讲解
通俗点说许多应用都是通过彼此间的相互合作来实现业务逻辑的,如类A要调用类B的方法,以前我们都是在类A中,通过自身new一个类B,然后在调用类B的方法,现在我们把new类B的事情交给spring来做,在我们调用的时候,容器会为我们实例化。
我的一点拙见:
IOC就像一个工厂,假设在A对象中需要调用B对象的方法,此时需要在A对象中new出B对象,B对象中的方法才能调用,那么A对象就可以称为**“离不开B对象”**,那么这样的离不开会导致A对象和B对象之间存在高耦合性,为了解耦,使用IOC容器作为所有对象之间的一个调度人,通过调度人可以很方便的调节对象之间的关系。
例如:A对象需要B对象的一个方法,那么IOC中创建出B对象,A对象将数据传递给B对象,B对象执行其中的方法后,再将方法获得的数据传递给A对象,那么这两个对象实际上是平行的关系,可以互相离开。
3、Spring 初体验
3.1 导入maven依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.7.RELEASE</version>
</dependency>
这个包包含了spring的大部分依赖。导入这一个包其他常用的就已经自动依赖了
3.2 在resources文件夹下建立applicationContext.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.2.xsd ">
<!--一个Bean元素就代表了一个类,将类注册到容器中-->
<bean name="user" class="com.hr.pojos.User" >
<!--依赖注入,这个代表了对象的属性,赋值本质上是通过set方法赋值,所以set方法不能有一丝差错-->
<property name="username" value="我是由spring创建的一个对象"/>
</bean>
<bean name="Book" class = "com.hr.pojos.Book">
<!--设置当前属性的引用值是一个对象,对象要用ref,不能用value-->
<property name="bookUser" ref="user"/>
</bean>
</beans>
要想进行属性注入,必须在那个类中要有set方法!!!
name和id的区别,id只能代表有一个,而name可以有多个
3.3 书写代码进行测试
import com.hr.pojos.User;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest {
@Test
public void Test(){
ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
User user = (User) app.getBean("user");
System.out.println(user.getUsername());
}
}
控制台结果:
我是由spring创建的一个对象
Process finished with exit code 0
4、IOC创造对象的方式
- 默认通过无参构造器构造对象,如果一个javaBean没有无参构造器,那么将直接报错
- 最基本的对象创建方式,只需要有一个无参构造函数和字段的set方法。本质上就是使用无参构造器创建对象,然后使用set方法为字段赋值。
- 可以通过有参构造器构造对象
根据参数类型赋值==(不推荐)==
<bean id="user" class="com.hr.User">
<constructor-arg type="java.lang.String" value="张三"/>
<constructor-arg type="int" value="18"/>
<constructor-arg type="com.hr.Book" ref="dog"/>
</bean>
根据参数位置赋值
<bean id="user" class="com.hr.User">
<constructor-arg index="0" value="张三"/>
<constructor-arg index="1" value="18"/>
<constructor-arg index="2" ref="dog"/>
</bean>
直接根据参数名(构造器里的参数名)设置
<bean id="user" class="com.hr.User">
<constructor-arg name="name" value="张三"/>
<constructor-arg name="age" value="18"/>
<constructor-arg name="book" ref="dog"/>
</bean>
总结:Spring创建对象的方式,当一个bean在spring中注册后,就会在容器中通过无参构造器实例化出这个对象(此时如果在无参构造器中输出语句,会在控制台中看到),这个对象始终存在,需要时可以随时召唤,并且召唤的对象始终如一,不会发生变化**(默认是单例,可以通过配置改Bean的作用域)**
5、Spring配置
5.1 别名
给当前javaBean对象起一个别名,并且只能一对一,说实话,用途不大,不如用bean的name配置
<alias name="user" alias="user2"/>
即可通过user2召唤出这个对象
5.2 Bean的配置
- id:Bean唯一标识,可以通过bean召唤对象
- class:bean对象所在的类
- name:同别名,而且name可以取多个别名
<bean name="user,user33" class="com.hr.pojos.User" />
5.3 import
有三个人写了不同的三个applicationContext.xml,每个 applicationContext.xml 文件中有不同的javaBean,可以将三个applicationContext.xml 导入到一个applicationContext.xml文件中,可以通过import,
例:
<import resource = "applicationContext.xml-zhangsan"/>
5.4 依赖注入(DL)
其实就是通过xml文件给对象中的属性或对象赋值,大概有三种方式:
- 构造器注入
- set方法注入【非常重要】
- 拓展方式注入
什么是依赖注入
其实就是set注入,依赖:bean对象的创建都依赖于容器。注入:bean对象中的所有属性,都由IOC容器来注入
为各种java类型实现注入,例如String、int、String[]、List、Map<string,string>等等
下面以一个具体的实例来观察spring是如何对各种java属性进行注入的。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.2.xsd ">
<!--一个Bean元素就代表了一个类,将类注册到容器中-->
<bean id="refermy" class="com.hr.pojos.Refer"/>
<bean id="Uility" name="uility" class="com.hr.pojos.Uility" >
<!--第一种,最简单,简单的类型直接通过property进行注入-->
<property name="name" value="实体姓名"/>
<!--第二种,注入一个自定义的对象,需要在上方已经创建好自定义对象的Bean-->
<property name="refer" ref="refermy"></property>
<!--第三种,数组注入-->
<property name="book">
<array>
<value>第一本书</value>
<value>第二本书</value>
<value>第三本书</value>
</array>
</property>
<!--第四种,list注入-->
<property name="hobbys">
<list>
<value>唱歌</value>
<value>跳舞</value>
<value>打豆豆</value>
</list>
</property>
<!--第五种,map注入-->
<property name="map">
<map>
<entry key="一键关键字" value="一键值"></entry>
<entry key="二键关键字" value="二键值"></entry>
</map>
</property>
<!--第六种,set集合注入-->
<property name="games">
<set>
<value>一个游戏</value>
<value>两个游戏</value>
</set>
</property>
<!--第七种,空值注入-->
<property name="isnull">
<null/>
</property>
<!--第八种,Properties属性注入-->
<property name="properties">
<props>
<prop key="username">root</prop>
<prop key="password">123.com</prop>
<prop key="url">jdbc:mysql://localhost:3306/bdcs</prop>
<prop key="driver">com.mysql.jdbc.Driver</prop>
</props>
</property>
<!--第九种-->
</bean>
</beans>
配置完成后进行junit测试
@Test
public void testSpring(){
ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
Uility uility = (Uility) app.getBean("uility");
System.out.println(uility.getRefer());
System.out.println(uility.getName());
//System.out.println(uility.getBook());
System.out.println(uility.getBook()[0]);
System.out.println(uility.getBook()[1]);
System.out.println(uility.getBook()[2]);
System.out.println(uility.getGames());
System.out.println(uility.getHobbys());
System.out.println(uility.getIsnull());
System.out.println(uility.getMap());
System.out.println(uility.getProperties());
}
控制台结果
Refer{age=0, name='null'}
实体姓名
//[Ljava.lang.String;@33065d67
第一本书
第二本书
第三本书
[一个游戏, 两个游戏]
[唱歌, 跳舞, 打豆豆]
null
{一键关键字=一键值, 二键关键字=二键值}
{password=123.com, driver=com.mysql.jdbc.Driver, url=jdbc:mysql://localhost:3306/bdcs, username=root}
进程已结束,退出代码 0
5.5 拓展注入
通过p命名空间和c命名空间注入
- p命名空间:本质上就是将标签化成了bean标签上的属性,需要在beans标签的属性中写入以下代码:xmlns:p=“http://www.springframework.org/schema/p”
- ,直接通过这种方式进行注入
- c命名空间:本质上就是将constructor-arg构造器函数化成了bean标签上的属性,需要在beans标签的属性中写入以下代码:xmlns:c=“http://www.springframework.org/schema/c”
- ,直接通过这种方式进行构造器注入
6、bean的作用域
bean scope:其实就是决定了一个spring在实例化这个对象“出生”的时候,是如何产生的,是可以在在堆中创建不同的对象,还是在堆中这个对象始终如一的存在(单例模式),还是其他的决定bean所在作用域的方式…
<bean id="refermy" class="com.hr.pojos.Refer" scope=""/>
- singleton:单例模式,只有一个对象存在
<bean id="refermy" class="com.hr.pojos.Refer" scope="singleton"/>
- prototype:原型模式,允许多个对象存在
- request:在一次请求中创建,请求失效后,这个bean对象也跟着失效
- session:在一个session中创建,session失效后,这个session对象也失效
- application:在整个应用层面一直存活
7、Bean的自动装配(自动注入)(自动依赖autowired)
在Spring中有三种装配的方式:
- 在xml中直接通过property标签进行配置
- 用java代码进行配置
- 隐式的自动装配bean**(重要)**
7.1 隐式的自动装配
Spring会在xml上下文中自动寻找相应的name,并自动给bean装配属性
正常代码
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.2.xsd ">
<bean name="driver" class="com.hr.beans.Driver"/>
<bean name="passenger" class="com.hr.beans.Passenger"/>
<bean name="bus" class="com.hr.beans.Bus">
<property name="driver" ref="driver"></property>
<property name="passenger" ref="passenger"></property>
</bean>
</beans>
自动装配的代码
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.2.xsd ">
<bean name="driver" class="com.hr.beans.Driver"/>
<!--自动装配模式设置为通过name属性寻找上下文中的bean-->
<bean name="bus" class="com.hr.beans.Bus" autowire="byName">
<!--
<property name="driver" ref="driver"></property>
<property name="passenger" ref="passenger"></property>
-->
</bean>
<!--写到这里也可-->
<bean name="passenger" class="com.hr.beans.Passenger"/>
</beans>
byName的原理:会自动查找这个bean属性中和其set属性后面值相同的bean,然后注入
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.2.xsd ">
<bean name="driver" class="com.hr.beans.Driver"/>
<!--byType通过set属性返回的类型来自动注入-->
<bean name="bus" class="com.hr.beans.Bus" autowire="byType">
<!--<property name="driver" ref="driver"></property>
<property name="passenger" ref="passenger"></property>-->
</bean>
<bean name="passenger" class="com.hr.beans.Passenger"/>
</beans>
byType的原理:通过这个bean属性返回的数据类型和xml文件中相同类型的bean进行配对
优点:
- 可以不用name属性
缺点:
- byType时必须保证只有一种类型的bean,不能有两种相同类型的bean,即class名唯一
7.2 注解实现spring的自动装配
@Autowired*的使用来消除 set ,get方法。
如果想要用注解,必须添加注解支持,在application-config.xml文件中进行配置下面这句话,同时spring版本必须大于2.5
<context: annotation-config/>
xml代码实例
<?xml version="1.0" encoding="utf-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<!--开启注解的支持-->
<context:annotation-config/>
<bean class="java.lang.String" name="hobby"></bean>
<!--给基本类型也要配置bean,以便下述的自动装配-->
<bean class="com.hr.beans.Age" name="age"></bean>
<bean class="com.hr.beans.Sex" name="sex"></bean>
<bean class="com.hr.beans.User" name="user"></bean>
</beans>
注解:
@Autowired:可以在属性名上使用,也可以在set方法中使用,使用**@Autowired**的好处是,可以不用给此属性设置set方法,但前提是,此属性在spring的配置文件中有配置,即是是基本数据类型,也要配置,否则还是无法省略set方法
package com.hr.beans;
import org.springframework.beans.factory.annotation.Autowired;
import java.io.Serializable;
public class User implements Serializable {
@Autowired
private Age age;
@Autowired
private Sex sex;
@Autowired
private String hobby;//基本数据类型也必须在xml文件中进行bean的配置
public Age getAge() {
return age;
}
public void setAge(Age age) {
this.age = age;
}
public Sex getSex() {
return sex;
}
public void setSex(Sex sex) {
this.sex = sex;
}
public void getData(){
age.setAge(10);
sex.setSex("女");
System.out.println("这个用户的年龄是"+age.getAge()+",性别是"+sex.getSex());
}
}
**科普:**使用@Nullable注解可以允许被标记的属性为空
其中,@Autowired()有一个参数require,当require为false时**@Autowired(require =false)**,说明此对象可以为空,否则不允许为空
注意:@Autowired自动装配的对象,是根据其类型和bean中的字段类型相同才能成功映射,否则失败
默认按类型匹配,类型匹配不成功时才会用name属性进行匹配
如果有多个相同类型的bean时,想要具体选择某个时,可以通过@Qualifier(“name值”)
@Autowired
@Qualifier("age2")
private Age age;
<bean class="com.hr.beans.Age" name="age"></bean>
<bean class="com.hr.beans.Age" id="age2"></bean>
@Resouce注解更为高级,它集合了@Autowired和@Qualifier的功能,当按照bean的名字查找不到时就会按照类型查找
7.3 使用spring注解进行开发
前提:要是非常简单的可以使用注解,复杂的建议还是使用xml文件进行配置
在spring4之后,使用注解开发就必须导入aop的包,还是相同的,要想使用注解,就必须导入注解的支持
<?xml version="1.0" encoding="utf-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<!--指定那个包下的注解会生效-->
<context:component-scan base-package="com.hr.beans"/>
<!--开启注解的支持-->
<context:annotation-config/>
</beans>
然后就可以写java代码了
package com.hr.beans;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.io.Serializable;
@Component//这里相当于<bean class="com.hr.beans.User" name="user"/>
public class User implements Serializable {
@Autowired
// @Qualifier("age2")
private Age age;
@Autowired
private Sex sex;
@Value("跳舞")//这里相当于bean中的property注入
private String hobby;
public void getData(){
age.setAge(10);
sex.setSex("女");
System.out.println("这个用户的年龄是"+age.getAge()+",性别是" +
""+sex.getSex()+",爱好是"+hobby);
}
}
衍生的注解
spring为了用户更好的实现mvc三层架构,为了让用户实现起来更加一目了然,给@Component注解起了一些不同的名字
- dao层下的Component名字改为了 【@Repository】
- service层下的Component名字改为了 【@Service】
- controller层下的Component名字改为了 【@Controller】
这四个注解的功能完全相同
还有一个表示bean作用域的注解 :@Scope(),注解不同的值代表了不同的作用域范围
如果是singleton表示单例,如果是prototype表示原型
总结
spring使用的最佳方式:注册bean还交给xml文件去做,属性的注入交给注解去做
8、使用java代码配置spring
暂定~新特性,原理就是用一个java的class文件替代了xml文件,直接用java代码和注解结合的方式配置spring,关键的两个注解,一个是@Configuration一个是@bean
9、静态代理模式
什么是代理模式: 代理(Proxy)是一种设计模式,提供了对目标对象另外的访问方式;即通过代理对象访问目标对象.这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能.
这里使用到编程中的一个思想:不要随意去修改别人已经写好的代码或者方法,如果需改修改,可以通过代理的方式来扩展该方法 。
Spring AOP的底层其实就是代理模式。
代理模式分类:
- 静态代理
- 动态代理
9.1 静态代理
这里拿房东和中介举例,一个房东,只需将卖房和租房的任务交给中介,中介负责买房的任务,如果有人想要访问房东,那么只需要和中介产生联系即可,即中介成为了房东和租房客之间的一个桥梁。
中介,就是代理人。
静态代理在使用时,需要定义接口或者父类,被代理对象与代理对象一起实现相同的接口或者是继承相同父类.
静态代理的代码过程:
- 抽象角色
- 真实角色
- 代理角色
- 客户端访问代理角色
代理模式的好处:
- 可以使真正的角色更加纯粹,不需要有其他的任务,任务全部交给中介去做
- 实现了任务的分工
- 当任务发生拓展时,可以交给中介去做
缺点:
- 一个真正的角色就会有一个代理人,所以代码量会变多
静态代理的深层次之我的理解:在代码方面,假设一个类中有很多的属性和方法,这是已经写好的业务逻辑代码,此时想要拓宽或增加业务逻辑代码,可以在不改变原型的基础上再创建一个新类,这个新类可以有原型的对象,然后通过调用方法实现原型逻辑的同时再去实现需要拓展的业务,这样既不改变原型的代码模式,又实现了需求,方便后悔的同时还使代码更加简洁明了,耦合性降低了。
9.2 动态代理
动态代理的本质是反射,或者说,动态代理的代理角色都是反射形成的。
动态代理分为三大类:
- 基于接口的动态代理——>jDK动态代理
- 基于类的动态代理——>cglib
- java字节码实现——>javasist
学习动态代理有两个类,一个Proxy类,一个InvocationHandler接口
- Proxy类的作用:用来通过反射动态生成代理类
- InvocationHandler接口:调用代理类中相应的方法
Rent,接口
package com.hr.pojos;
public interface Rent {
public void rent();
public void modify();
}
Host类,实类
package com.hr.pojos;
public class Host implements Rent{
@Override
public void rent() {
System.out.println("我要租房了!!!");
}
@Override
public void modify() {
System.out.println("我要修改房子价钱!!!");
}
}
集代理人生成,和代理人生成器一体的工具类
package com.hr.pojos;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/*
这里相当于写成了一个创建代理对象的工具类,不过有一定的限制,因为是工具类,所以只有许多类都有的部分,没法单独为某个角色拓宽相应的功能,method固定死了,只能执行方法,不能干其他事情
*/
public class MyInvocationHandler implements InvocationHandler {
// 真正的角色(即要为目标生成代理类的角色)
private Object target;
public void setRent(Object target) {
this.target = target;
}
// 生成相应对象的代理对象,三个参数,1、要生成代理的对象的ClassLoader 2、真正角色继承的接口,注意,一定要是接口【非常重要!!!!!!!!!!!!!!!!!】 3、要用那个处理器
public Object getProxy(){
Object proxy = Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
return proxy;//这个返回的对象即是目标对象的代理对象
}
// invoke方法用来调用生成代理的方法,共有三个参数,参数一:那个代理对象 参数二:要调用那个
// 代理对象的方法 参数三:代理对象方法的参数
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = method.invoke(target, args);//这个result即是方法执行后的返回值
return result;
}
}
上面的代码是最重要的,其中invoke方法又采用了java中的一种模式,叫做适配器模式,适配器模式在javaGUI中曾采用过,即相应的动作会触发这个模式,当代理对象调用方法时,就会将proxy调用的方法当作参数传递到invoke方法中
测试环境
public static void main(String[] args) {
MyInvocationHandler invent = new MyInvocationHandler();
Rent host = new Host();
invent.setRent(host);
Rent proxy = (Rent) invent.getProxy();//这个proxy即是host的代理人,这里返回的是接口类型
proxy.modify();
proxy.rent();
System.out.println(proxy.getClass()+":"+host.getClass());
}
**总结:**代理对象不需要实现接口,但是目标对象一定要实现接口,否则不能用动态代理
10、AOP的实现方式
AOP本质上就是一种使类与类之间解耦的技术,这种技术暂且称之为**“横切”**
它剖解开封装的对象内部,将那些影响了多个类的公共部分封装成一个模块,这个模块可以称之为**“Aspect”或“切面”**
所谓"切面",简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。
==AOP的作用:==将核心代码和非核心代码进行分离,在核心代码中切入非核心代码,主要项目中就是完成事物和日志的记录的
10.1 AOP的几大常用概念
- 横切关注点:跨越应用程序多个模块的方法或功能。通俗来讲,就是与我们业务逻辑无关的,但是我们还都需要关注的部分(即没有他也可以,但是没有他会影响开发过程),这种模块就叫做**“横切关注点”**
- 例如:日志,安全,缓存,事务等等
- 切面(Aspect):横切关注点 被抽象出来的那个公共类
- 通知(Advice):切面必须完成的工作,其为公共类中的一个方法
- 目标(Target):即要被代理的对象
- 代理(Proxy):即代理对象
- 切入点(PointCut),连接点(JointPoint):即在何处执行
10.2 AOP的java代码
Spring只支持XML方式而没有实现注解的方式(注解方式也叫AspectJ方式)的AOP,所以要使用@Aspect注解,只能引入AspectJ相关的 jar 包:aopalliance-1.0.jar 和 aspectjweaver.jar
具体maven的依赖如下:
<!-- Spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.8.RELEASE</version>
</dependency>
<!--spring aop + aspectj-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.0.8.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.9</version>
</dependency>
ok,包也导完了,那么接下来就是spring的几大实现aop的方式
execution表达式
在实现aop的过程中,首先要了解一些知识点,比如execution表达式
execution (* com.sample.service.impl…*(…))
execution()是最常用的切点函数,其语法如下所示:
整个表达式可以分为五个部分:
1、execution(): 表达式主体。
2、第一个星号:表示返回类型,星号表示所有的类型。
3、包名:表示需要拦截的包名,后面的两个句点表示当前包和当前包的所有子包,com.sample.service.impl包、子孙包下所有类的方法。
4、第二个号**:表示类名,*星号表示所有的类。
5、*(…):最后这个星号表示方法名,*星号表示所有的方法,后面括弧里面表示方法的参数,两个句点表示任何参数。
方式一,通过Spring的API接口
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-toTUY6tg-1639967248903)(C:\Users\86155\AppData\Roaming\Typora\typora-user-images\1615602151728.png)]
package com.hr.log;
import org.springframework.aop.AfterReturningAdvice;
import java.lang.reflect.Method;
public class AfterLog implements AfterReturningAdvice {
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("执行了"+method.getName()+"返回的结果为:"+returnValue);
}
}
package com.hr.log;
import org.springframework.aop.BeforeAdvice;
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;
public class BeforeLog implements MethodBeforeAdvice {
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println(target.getClass().getName()+"的"+method.getName()+"被执行了");
}
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/beans"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--注册bean,接口无法注册为bean-->
<bean id="userService" class="com.hr.Entitys.UserServiceImpl"></bean>
<bean id="log" class="com.hr.log.BeforeLog"></bean><!--切面-->
<bean id="afterLog" class="com.hr.log.AfterLog"/><!--切面-->
<aop:config>
<aop:pointcut id="pointcut" expression="execution(* com.hr.Entitys.UserServiceImpl..* (..))"></aop:pointcut>
<!--执行环绕增加,将log类中重写的方法代码切入到UserServiceImpl的所有方法执行之前执行
将afterLog类中重写的方法代码切入到UserServiceImpl的所有方法执行完后在执行
-->
<!--这个类继承自那个类,就决定了通知的执行顺序-->
<aop:advisor advice-ref="log" pointcut-ref="pointcut"></aop:advisor>
<aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"></aop:advisor>
</aop:config>
</beans>
方式二,自定义切入类
第二种切入的方式没有第一种强大,因为第一种可以操作被切入对象的方法
package com.hr.aop_class;
public class AopDemo1 {
void before(){
System.out.println("这里是aop的before方法,在执行具体方法之前执行,可以设立日志");
}
void after(){
System.out.println("这个是aop的after方法,在执行具体方法之后执行,可以设立总结");
}
}
<!--第二种,自定义类-->
<bean id="aop" class="com.hr.aop_class.AopDemo1"></bean>
<aop:config>
<!--自定义切面,哪个类当作切面切入,ref要引用的类-->
<aop:aspect ref="aop"><!--切面,这里的ref=aop引用的是上面自定义好的类-->
<!--切入点,即设立被切入的类的具体方法-->
<aop:pointcut id="pointcut" expression="execution(* com.hr.Entitys.UserService..*(..))"/>
<!--通知,即切面中的方法要在何时执行,是在被切面之前执行还是之后?-->
<aop:before method="before" pointcut-ref="pointcut"></aop:before>
<!--这里的spring标签决定了执行时间,method的方法必须和类中的方法名保持一致-->
<aop:after method="after" pointcut-ref="pointcut"></aop:after>
</aop:aspect>
</aop:config>
</beans>
方式三,通过spring注解
代码结构
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WeZFkOjR-1639967248905)(C:\Users\86155\AppData\Roaming\Typora\typora-user-images\1616921834053.png)]
首先,需要开启注解支持
aop:aspectj-autoproxy/
然后需要指定切面类,利用@Aspect标签指定切面类,利用注解实现何时执行切面的方法
xml文件中的配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/beans"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="logaop" class="com.hr.logs.MyLog"/>
<bean id="myservice" class="com.hr.services.MyService"/>
<aop:aspectj-autoproxy/>
<!--<aop:config>
<aop:aspect ref="logaop">
<aop:pointcut id="pc" expression="execution(* com.hr.services..*(..))"/>
<aop:before method="beforeExecuteAdvice" pointcut-ref="pc"/>
<aop:after-returning method="afterReturnAdvice" pointcut-ref="pc"/>
</aop:aspect>
</aop:config>-->
</beans>
java代码中的切面配置
package com.hr.logs;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect
public class MyLog {
@Before("execution(* com.hr.services..*(..))")
void beforeExecuteAdvice(){
System.out.println("方法执行前,您需要知悉一些内容");
}
@AfterReturning("execution(* com.hr.services..*(..))")
void afterReturnAdvice(){
System.out.println("方法执行后,您还需要总结");
}
@Around("execution(* com.hr.services..*(..))")
void aroundAdvice(ProceedingJoinPoint pjp) throws Throwable {//环绕执行的方法,通过参数pjp可以获得执行方法的信息
System.out.println("around方法执行前");
pjp.proceed();
System.out.println("around方法执行后");
}
}
myservice中的代码
package com.hr.services;
public class MyService {
public void executeDemo(){
System.out.println("这个代码正在执行中,请稍等三秒钟");
for (int i = 3; i > 0; i--) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("执行剩余时间:"+ i);
}
}
}
测试类中的写法
@org.junit.Test
public void testAop(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
MyService myservice = applicationContext.getBean("myservice", MyService.class);
myservice.executeDemo();
}
结果
around方法执行前
方法执行前,您需要知悉一些内容
这个代码正在执行中,请稍等三秒钟
执行剩余时间:3
执行剩余时间:2
执行剩余时间:1
方法执行后,您还需要总结
around方法执行后
11、整合Mybatis
首先需要导入一些额外的包
spring操作数据库的话,还需要一个spring-jdbc用来操作数据库
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.1.9RELEASE</version>
</dependency>
spring和Mybatis整合,需要一个mybatis-spring
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.2</version>
</dependency>
12、声明式事务