Spring
简介
Spring:春天----> 给软件行业带来了春天
2002,首次推出了Spring框架的出行:interface21框架
Spring框架即以interface21框架为基础,经过重新设计,并不断丰富其内涵,于2004年3月24日正式发布1.0版本
Rod Johnson ,Spring Framework创始人,著名作者,很难想象Rod Johnson的学历,真的让好多人大吃一惊,他是悉尼大学的博士,然而他不是计算机专业的,而是音乐学。
Spring理念:使现有的技术更加容易使用,本身是一个大杂烩。
SSH:Struct2 + Spring + Hibernate
SSM: SpringMVC + Spring + Mybatis
官网: https://spring.io
官网spring核心技术:https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#spring-core
官方下载: https://repo.spring.io/release/org/springframework/spring/
GitHub: https://github.com/spring-projects/spring-framework
依赖:spring-web mvc
<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.9</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.9</version>
</dependency>
优点:
- Spring是开源的免费的容器!
- Spring是一个轻量级的,非入侵式的框架!
- 控制反转(IOC),面向切面编程(AOP)! (面试重点)
- 支持事务处理,对框架整合的支持!
补充:轻量级框架和重量级框架的区分并没有一个明确的定义,它是个相对概念,通常我们会依据启动资源多少、开发程度难易等进行区分。
【一、从启动程序耗费资源来看,EJB(java bean),因为默认提供了EJB规范中所用的功能,所以EJB往往是买一送三,不要也不行,EJB启动会耗费大量资源,内存、CPU等,所有服务都会加载进来。Spring提供了很多服务,但是这些服务默认是不打开的,当我们需要某个服务的时候,只打开某个服务就可以了,定制服务,需要什么加载什么。(当然不是说只要你用了Spring那就一定是轻量级,如果应用中使用了Spring的大量服务,这时他也应该是重量级的)
二、从侵入性的角度来看==,轻量级框架不一定需要继承和实现框架的接口和抽象类来实例化组件==,重量级框架需要继承和实现框架的类或者实现框架的接口,以便使用框架中间件特性。这就可能需要实例化大量的类并注册到应用中,即使他们是没用的。从这个角度看,重量级框架侵入性更高。
侵入性:也称为框架依赖性、耦合性,从软件工程的角度解释,软件工程的设计标准是“高内聚,低耦合”,侵入性是指耦合性太强。有个比较通俗的解释就是复用性,代码的复用性越高,侵入性就越低,反之同理。
举个例子:A是侵入性的,B中使用了A,那么如果后面A要换成C,就需要修改B;
A是非侵入性的,B中使用A,后面把A换成C,只要修改配置文件就好了。
三、轻量级框架一般是一组独立的特性实现集,重量级框架往往依赖某些或其他类型的容器支持框架特性。
四、从开发难易程度来看,轻量级框架在开发中应用简单方便,重量级框架开发时需要写一些框架绑定的类,部署、运行、测试及维护都较为复杂,开发较为困难。
五、从解决问题的侧重点看,轻量级框架侧重减小开发的复杂度,但他处理能力较弱(处理事务能力弱,不具备分布式处理能力),比较适用于开发中小型企业应用,重量级开发适用于开发大型企业应用。】
总结:Spring是一个轻量级的控制反转(IOC)和面向切面编程(AOP)的(非入侵式的)框架!
组成 七大部件
拓展
Spring官网的介绍:现代化的Java开发!说白了就是基于Spring的开发!
- springBoot
- 一个快速开发的脚手架
- 基于SpringBoot可以快速的开发单个微服务
- 约定大于配置
- SpringCloud
- SpringCloud是基本SpringBoot
因为现在大多数公司都在使用SpringBoot进行快速开发,学习SpringBoot的前提,需要完全掌握Spring及SpringMVC!承上启下的作用!
弊端:发展了太久之后,违背原来的理念!配置十分繁琐,人称:”配置地狱!“
IOC 理论推导
引言:原来写业务的步骤
UserDao
UserDaoImp
UserSevice
UserServiceImp
在我们之前的业务中,用户的需求可能会影响我们原来的代码,我们需要根据用户的需求去修改原代码!如果程序代码量十分大,修改一次的成本代价十分昂贵!
我们使用一个Set接口实现.
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
-
之前,程序是主动创建对象,控制权在程序员手上。
-
使用set注入之后,程序不再具有主动性,变成了被动的接收对象。
IOC思想,从本质上解决了问题,程序员不用再去管理对象的创建了,系统的耦合性降低,可以更加专注的业务层
这是IOC的原型,反转就是把主动权交给用户。
//用户实际调的是业务层,Dao层他们不需要接触
UserService userService =new UserServiceImpl();
userService.setUserDao(new UserDaoMysqlImpl());
userService.getUser();
控制反转
控制反转是一种通过描述(XML或注解)并通过第三方去生产或获取特定对象的方式。在Spring中实现控制反转的是loC容器,其实现方法是依赖注入(Dependency Injection,Dl)
控制:谁来控制对象的创建,传统应用是由程序员控制对象的创建,使用spring后,对象是由spring来控制
反转:程序本身不创建对象,而变成被动的接收对象
依赖注入:就是利用set方法来进行注入
IOC是一种编程思想,由主动的编程变成被动的接收
HelloSpring
-
导入依赖
<dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.3.9</version> </dependency> </dependencies>
-
创建实现类 Hello
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 + '\'' + '}'; } }
-
编写配置文件 注册 beans.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 https://www.springframework.org/schema/beans/spring-beans.xsd"> <!--使用Spring来创建对象,在Spring这些都称为Bean--> <bean id="hello" class="com.cxl.pojo.Hello"> <property name="str" value="Spring"/> </bean> </beans>
-
编写测试类 myTest
public class myTest { public static void main(String[] args) { // create and configure beans 获取spring上下文对象 ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); //我们的对象现在都在spring中管理了,我们要使用,直接去里面取出来就可以了 Hello hello = (Hello) context.getBean("hello"); System.out.println(hello.toString()); } }
-
测试结果
思考:
-
Hello 对象是由谁创建的?
hello 对象是由Spring创建的
-
Hello 对象的属性是怎么设置的?
hello 对象的属性是由Spring容器设置的
java中创建对象:
类型 变量名=new 类型();
spring中创建对象:bean=对象
id=变量名
class=new的对象
property相当于给对象的属性设置一个值
这个过程就叫控制反转
- 控制:谁来控制对象的创建,传统应用程序的对象是由程序本身控制创建的,使用Spring之后,对象是由Spring来创建的
- 反转:程序本身不创建对象,而变成被动的接受对象
- 依赖注入:就是利用set方法来进行注入的
IOC是一种编程思想,由主动的编程变成被动的接收
IOC就一句话搞定:对象由Spring来创建,管理,装配!
IOC的创建方式
Spring没有无参构造反射会失败
没有无参构造方法直接就报错了
-
使用无参构造创建对象,默认!
-
假设我们要使用有参构造创建对象。
-
下标赋值
<!--第一种,下标赋值!--> <bean id="user" class="com.cxl.pojo.User"> <constructor-arg index="0" value="狂神说Java"/> </bean>
-
类型
<!--第二种,同类类型创建,不建议使用!--> <bean id="user" class="com.cxl.pojo.User"> <constructor-arg type="java.lang.String" value="qinjiang"/> </bean>
-
参数名
<!--第三种,直接通过参数名设置--> <bean id="user" class="com.cxl.pojo.User"> <constructor-arg name="name" value="秦将"/> </bean>
-
总结:在配置文件加载的时候,容器中管理的对象就已经实例化了。
Spring配置说明
-
别名
<!--别名,如果添加了别名,也可以使用别名获取到这个对象--> <alias name="user" alias="userNew"/>
-
Bean的配置
<!-- id:bean的唯一标识符,也就是相当于我们学的对象名 class:bean对象所对应的权限定名,包名 + 类型 name:也是别名,而且name更高级,可以同时取多个别名(比 alias 好) --> <bean id="userT" class="com.cxl.pojo.UserT" name="user2, u2"> <property name="name" value="西部开源"/> </bean>
-
import
- 这个import,一般用于团队开发使用,他可以将多个配置文件,导入合并为一个
依赖注入
-
构造器注入
-
Set方式注入【重点】
- 依赖注入:Set注入
- 依赖:bean对象的创建依赖于容器
- 注入:bean对象中的所有属性,由容器来注入
【环境搭建】
-
复杂类型
public class Address { private String address; public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } @Override public String toString() { return "Address{" + "address='" + address + '\'' + '}'; } }
-
真实测试对象
public class Student { private String name; private Address address; private String[] books; private List<String> hobbys; private Map<String,String> card; private Set<String> games; private Properties info;//配置类 private String wife; //...以及各自的get/set方法和toString方法 (此处省略) }
-
注册 beans.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 https://www.springframework.org/schema/beans/spring-beans.xsd"> <!--第一种使用普通值注入,value--> <bean id="student" class="com.cxl.pojo.Student"> <property name="name" value="楠神"/> </bean> </beans>
-
测试类
public class Mytest { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); Student student = (Student) context.getBean("student"); System.out.println(student.getName()); } }
测试结果:
-
完善测试
<?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 https://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="address" class="com.cxl.pojo.Address"> <property name="address" value="江苏"/> </bean> <bean id="student" class="com.cxl.pojo.Student"> <!--第一种使用普通值注入,value--> <property name="name" value="楠神"/> <!--bean注入,ref引用--> <property name="address" ref="address"/> <!--数组--> <property name="books"> <array> <value>皮皮虾</value> <value>我在人间凑数的日子</value> <value>像少年啦飞驰</value> <value>愿有人陪你颠沛流离</value> </array> </property> <!--List--> <property name="hobbys"> <list> <value>听歌</value> <value>看电影</value> <value>篮球</value> </list> </property> <!--Map--> <property name="card"> <map> <entry key="身份证" value="510xxxxxxxxxxxxxxxx"/> <entry key="银行卡" value="155465464564654651"/> </map> </property> <!--Set--> <property name="games"> <set> <value>CFM</value> <value>LOL</value> <value>COC</value> </set> </property> <!--null--> <property name="wife"> <null/> </property> <!--Properties--> <property name="info"> <props> <prop key="driver">小明</prop> <prop key="url">201812345678</prop> <prop key="username">root</prop> <prop key="password">151212</prop> </props> </property> </bean> </beans>
-
测试
public class MyTest { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); Student student = (Student) context.getBean("student"); System.out.println(student.toString()); } }
测试结果:
- 依赖注入:Set注入
-
拓展方式注入
使用p命名和c命名空间进行注入
使用:<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:c="http://www.springframework.org/schema/c" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd"> <!--p命名空间注入,可以直接注入属性的值:peoperties--> <bean id="user" class="com.LH.pojo.User" p:name="胡图图" p:age="5"/> <!--c命名空间注入,可以通过构造器注入construct--> <bean id="user2" class="com.LH.pojo.User" c:name="胡英俊" c:age="28 "/> </beans>
注意点:p命名和c命名空间不能直接使用,需要导入xml约束
xmlns:p="http://www.springframework.org/schema/p" xmlns:c="http://www.springframework.org/schema/c"
Bean的作用域
<bean id="student" class="com.cxl.pojo.Student" scope="XXX">
-
单例模式(singleton):(Spring默认的)
多次getBean()同一个对象,调用的始终都是同一个bean实例
-
原型模式(prototype):每次从容器中get的时候,都会产生一个新的对象
其余的 request,session,application只能在web开发中使用!
Bean的自动装配
- 自动装配是Spring满足bean依赖一种方式!
- Spring会在上下文中自动寻找,并自动给bean装配属性!
在Spring中有三种装配的方式
- 在xml中显示的配置
- 在java中显示配置
- 隐式的自动装配bean【重要】
ByType自动装配
<!--
byName:会自动在容器上下文中查找,和自己对象set方法后面的值对应的bean id!
如setcat() 和id=“cat”对应,查找成功
byType:会自动在容器上下文中查找,和自己对象属性类型相同的bean! 不命名id也可以
如 class=“cxl.pojo.Dog”类型和 Dog对应,查找成功
-->
<bean id="people" class="com.cxl.pojo.People" autowire="byName">
<property name="name" value="楠神"/>
<!--这里要重点注意,这里是要获得来源而不是赋值,所以用ref-->
<!--<property name="dog1" ref="dog1"/>-->
<!--<property name="cat" ref="cat"/>-->
</bean>
小结:
- byname的时候,需要保证所有bean的id唯一,并且这个bean需要和自动注入的属性的set方法的值一致!
- bytype的时候,需要保证所有bean的class唯一,并且这个bean需要和自动注入的属性的类型一致!
使用注解实现自动装配
jdk1.5支持的注解,Spring2.5就支持注解
要使用注解须知:
- 导入约束
- 配置注解的支持:context:annotation-config/ 【重要】
<?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>
@Autowired
直接在属性上使用即可! 也可以在set方式上使用!
使用Autowired我们可以不用编写Set方法了,前提是你这个自动装配的属性在IOC (Spring)容器中存在,且符合名字byType!
@Autowired
private Cat cat;
@Autowired
private Pig pig;
private String name;
如果@Autowird自动装配的环境比较复杂,自动装配无法通过一个注解【@Autowired】完成的时候,我们可以使用@Qualifier(value = “xxx”)去匹配@Autowired的使用,指定一个唯一的bean对象注入!
@Autowired
@Qualifier(value ="cat2")//不指定的话,先找type再找name
private Cat cat;
@Autowired
@Qualifier(value ="pig22")
private Pig pig;
private String name;
@Resource注解
@Resource默认按 byName自动注入罢了。@Resource有两个属性是比较重要的,分是name和type,Spring将@Resource注解的name属性解析为bean的名字,而type属性则解析为bean的类型。所以如果使用name属性,则使用byName的自动注入策略,而使用type属性时则使用byType自动注入策略。如果既不指定name也不指定type属性,这时将通过反射机制使用byName自动注入策略。
小结:
@Resouce和@AutoWired的区别:
-
都是用来自动装配的,都可以放在属性字段上
-
@Autowired 默认通过byType的方式实现,而且必须要求这个对象存在,不然就空指针了
-
@Resoutce 默认通过byName的方式实现,如果找不到名字,则通过byType实现!如果两个都找不到,就报错。
-
装配顺序不同:
-
@Autowired 先通过类型,再找名字。
-
@Resoutce 先通过名字,再找类型。
-
补充:@Nullable 字段标记了这个注解,说明这个字段可以为null。
-
使用注解开发
@Component:组件,放在类上,说明这个类被Spring管理了,就是bean!
-
建立User
//等价于 <bean id="user" class="com.cxl.pojo.User"/> 注册 //@Component 组件 @Component public class User { private String name = "楠神"; }
-
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" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" 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 http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd"> <!--指定要扫描的包,这个包下的注解就会生效--> <context:component-scan base-package="com.cxl.pojo"/> <!--开启注解支持--> <context:annotation-config/> <!-- <bean id="user" class="com.cxl.pojo.User"/>--> </beans>
测试:
public class MyTest { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); User user = (User) context.getBean("user"); System.out.println(user.name); } }
-
属性如何注入
//等价于 <bean id="user" class="com.cxl.pojo.User"/> 注册 //@Component 组件 @Component public class User { private String name; @Value("楠神") //相当于<property name="name" value="楠神"/> public void setName(String name) { this.name = name; } }
-
衍生的注解
@Component有几个衍生注解,我们在web开发中,会按照mvc三层架构分层- dao 【@Repository】
- service 【@Service】
- controller 【@Controller】
这四个注解功能都是一样的,都是代表将某个类注册到Spring中,装配Bean
-
自动装配
- @Autowired:自动装配通过类型,名字 如果Autowired不能唯一自动装配上属性,则需要通过 - @Qualifier(value = "xxx") - @Resource:自动装配通过名字,类型 - @Nullable:字段标记了这个注解,说明这个字段可以为null
-
作用域
@Component @Scope("prototype") public class User { public String name; @Value("楠神") //相当于<property name="name" value="楠神"/> public void setName(String name) { this.name = name; } }
-
小结
xml与注解:- xml 更加万能,适用于各种场合,维护简单方便
- 注解 不是自己类用不了,维护相对复杂
xml与注解最佳实践
- xml用来管理bean
- 注解只负责完成属性的注入
- 我们在使用的过程中,只需要注意一个问题:必须让注解生效,就需要开启注解的支持
使用Java方式配置Spring
配置类:
@Configuration
/**
* 在一个类上,加上Configuration 这个类就变成了配置类
* Configuration也会被spring托管,因为他本身就是一个component
*/
@ComponentScan("com.cxl")
@Import(CxlConfig2.class)
public class CxlConfig {
//注册一个bean id 就是方法名 class属性 就是方法的返回值
@Bean
public User getUser(){
return new User();
}
}
实体类:
@Component
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 + '\'' +
'}';
}
}
测试类:
public class MyTest {
public static void main(String[] args) {
//如果完全使用了配置类方式去做,我们就只能通过AnnotationConfig 上下文来获取容器,
// 通过配置类的class对象加载。
ApplicationContext context = new AnnotationConfigApplicationContext(CxlConfig.class);
User user = (User) context.getBean("getUser");//配置类的id名就是方法名
System.out.println(user.getName());
}
}
代理模式AOP
代理模式是SpringAOP的底层!【SpringAOP和SpringMVC】面试重点
代理模式是指代理类对被代理对象的功能进行拓展,修饰器模式是对对象本身的功能进行增强
代理模式的分类:
- 静态代理
- 动态代理
静态代理
角色分析:
- 抽象角色(租房):一般会使用接口或者类来解决
- 真实角色(房东):被代理的角色
- 代理角色(中介): 代理真实角色,代理真实角色后,我们一般会做一些附属操作
- 客户:访问代理对象的人
代理步骤:
-
接口
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() { seeHouse(); hetong(); host.rent(); fare(); } //看房 public void seeHouse() { 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(); } }
代理模式的好处:
- 可以使真实角色的操作更加纯粹!不用去关注一些公共的业务
- 公共业务交给代理角色,实现了业务的分工
- 公共业务发生拓展的时候,方便集中管理
缺点:
- 一个真实角色就会产生一个代理角色;代码里会翻倍,开发效率会变低
**加深理解:**聊聊AOP
动态代理
- 动态代理和静态代理角色一样
- 动态代理的代理类是动态生成的,不是我们直接写好的!
- 动态代理分为两大类:基于接口的动态代理,基于类的动态代理
- 基于接口—JDK动态代理
- 基于类:cglib
- Java字节码实现:Javassist
需要了解两个类:
Proxy:代理,InvocationHandler:调用处理程序
-
proxy这个类用来动态生成代理对象(提供创建动态代理类和实例的静态方法)
-
InvocationHandler用来处理业务 (是由代理实例的 调用处理程序 实现的接口)
-
模板
public class ProxyInvocationHandler implements InvocationHandler { //与业务接口组合 private Object Target;//业务接口对象 如 Rent rent //set方法 注入业务 public void setTarget(Object target) { Target = target; } //生成代理类 //获取当前类的加载器,获取业务的接口,当前对象 public Object getProxy(){ return Proxy.newProxyInstance(this.getClass().getClassLoader(), Target.getClass().getInterfaces(),this); } //处理业务,并返回结果 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //niubi log(method.getName()); Object result = method.invoke(Target, args); return result; } //添加日志 public void log(String msg){ System.out.println("[debug]调用了"+msg+"方法"); } }
public class Client { public static void main(String[] args) { //真实角色 UserServiceImpl userService = new UserServiceImpl(); //代理角色,不存在,找他的处理程序 ProxyInvocationHandler pih = new ProxyInvocationHandler(); pih.setTarget(userService);//设置要代理的对象 //动态生成代理类 UserService proxy = (UserService) pih.getProxy(); proxy.delete(); } }
其中 UserService:
其中 UserServiceImpl:
真实角色只负责业务,代理角色负责加入日志等非核心业务功能
动态代理的好处:
- 可以使真实角色的操作更加纯粹!不用去关注一些公共的业务
- 公共业务交给代理角色,实现了业务的分工
- 公共业务发生拓展的时候,方便集中管理
- 一个动态代理的是一个接口,一般就是对应的一类业务
- 一个动态代理类可以代理多个类,只要是实现了同一个接口即可,复用成本极低
AOP
什么是AOP
AOP(Aspect Oriented Programming):面向切面编程。通过预编译的方式和运行期动态代理实现程序功能的统一维护的一种技术。
AOP在Spring中的作用
提供声明式事务:允许用户自定义切面
- 横切关注点:跨越应用程序多个模块的方法或功能。即:与我们逻辑无关的,但是我们需要专注的部分,就是横切关注点。如:日志,安全,缓存,事务等等
- 切面(aspect):横切关注点被 模块化 的特殊对象。即:它是一个类
- 通知(advice):切面必须要完成的工作。即:它是类中的一个方法
- 目标(target):被通知的对象
- 代理(proxy):向目标对象应用通知之后创建的对象
- 切入点(PointCut):切面通知 执行的 “地点” 的定义
- 连接点(joinPoint):与切入点匹配的执行点
使用Spring实现AOP
使用AOP织入,需要导入一个依赖包
<dependencies>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
</dependencies>
方式一:使用Spring的API接口【主要是SpringAPI接口实现】
动态代理代理的是接口,静态代理代理的是实体类
-
准备了UserService和UserServiceImpl实体类 实现了增删改查
public interface UserService { public void add(); public void delete(); public void update(); public void select(); }
public class UserServiceImpl implements UserService { @Override public void add() { System.out.println("增加了提个用户"); } @Override public void delete() { System.out.println("删除了一个用户"); } @Override public void update() { System.out.println("修改了一个用户"); } @Override public void select() { System.out.println("查询了一个用户"); } }
-
写前置日志类
//前置日志 public class Log implements MethodBeforeAdvice { //method:要执行的目标对象 //args:参数 //target:目标对象 @Override public void before(Method method, Object[] objects, Object target) throws Throwable { System.out.println(target.getClass().getName()+"的"+method.getName()+"被执行了"); } }
-
配置bean:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd"> <!--注册bean--> <bean id="userService" class="com.cxl.service.UserServiceImpl"/> <bean id="log" class="com.cxl.log.Log"/> <!--方式一: 使用原生spring API接口 --> <!--配置aop--> <aop:config> <!--切入点 expression:表达式 execution(要执行的位置! * * * * ) 第一个*表示方法类型--> <aop:pointcut id="pointcut" expression="execution(* com.cxl.service.UserServiceImpl.*(..))"/> <!-- 执行环绕增加 --> <aop:advisor advice-ref="log" pointcut-ref="pointcut"/> </aop:config> </beans>
-
测试
public class MyTest { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); //动态代理 代理的是接口 UserService userService = (UserService) context.getBean("userService"); userService.add(); } }
测试结果:
方式二:使用自定义类实现AOP【主要是切面定义】
-
自定义log类
public class DiyPointCut { public void before(){ System.out.println("========方法执行前========="); } public void after(){ System.out.println("========方法执行后========="); } }
-
配置:
<!--注册bean --> <bean id="userService" class="com.hardy.service.UserServiceImpl"/> <!--方式二: 自定义类 --> <bean id="diy" class="com.cxl.diy.DiyPointCut"/> <aop:config> <aop:aspect ref="diy"> <!-- 切入点--> <aop:pointcut id="point" expression="execution(* com.hardy.service.UserServiceImpl.*(..))"/> <!-- 切面 --> <aop:before method="before" pointcut-ref="point"/> <aop:after method="after" pointcut-ref="point"/> </aop:aspect> </aop:config>
-
测试:
public class MyTest { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); //动态代理代理的是UserService接口 UserService userservice = (UserService) context.getBean("userService"); userservice.query(); } }
测试结果:
使用注解实现AOP
-
自定义一个类充当切面:
//方式三:使用注解方式实现AOP @Aspect //标注这个类是一个切面 public class AnnotationPointCut { @Before("execution(* com.hardy.service.UserServiceImpl.*(..))") public void before(){ System.out.println("========方法执行前========="); } @After("execution(* com.hardy.service.UserServiceImpl.*(..))") public void after(){ System.out.println("========方法执行后========="); } //在环绕增强中,我们可以给定一个参数,代表我们要获取处理切入的点 @Around("execution(* com.hardy.service.UserServiceImpl.*(..))") public void around(ProceedingJoinPoint pj) throws Throwable { System.out.println("========环绕前========="); Signature signature = pj.getSignature();//获得签名 System.out.println("signature:"+signature);//打印调用的方法 //执行方法 Object proceed = pj.proceed(); System.out.println("========环绕后========="); } }
-
配置:
<!--注册bean --> <bean id="userService" class="com.hardy.service.UserServiceImpl"/> <!--方式三: 使用注解实现 --> <bean id="annotationPointCut" class="com.hardy.diy.AnnotationPointCut"/> <!--开启注解支持 --> <aop:aspectj-autoproxy/>
-
测试:
public class MyTest { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); //动态代理代理的是UserService接口 UserService userservice = (UserService) context.getBean("userService"); userservice.query(); } }
-
测试结果:
环绕在最外层最早和最晚执行========环绕前========= signature:void com.hardy.service.UserService.query() ========方法执行前========= 查询了一条数据 ========方法执行后========= ========环绕后=========
整合Mybatis
步骤:
-
导入相关jar包
- Junit
- mybatis
- MySQL
- spring相关
- aop织入
- mybatis-spring 【new】
配置:
<dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.13.1</version> <scope>test</scope> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.47</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.7</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.1.19.RELEASE</version> </dependency> <!--spring操作数据库的话,还需要spring-jdbc--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.1.9.RELEASE</version> </dependency> <dependency> <!--使用事务需要导入的包--> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.4</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>2.0.2</version> </dependency> </dependencies>
Maven资源导出问题:
<build> <resources> <resource> <directory>src/main/java</directory> <includes> <include>**/*.properties</include> <include>**/*.xml</include> </includes> <filtering>false</filtering> </resource> <resource> <directory>src/main/resources</directory> <includes> <include>**/*.properties</include> <include>**/*.xml</include> </includes> <filtering>false</filtering> </resource> </resources> </build>
-
编写配置文件
-
测试
回忆Mybatis
-
编写实体类
@Data public class User { private int id; private String name; private String pwd; }
-
编写核心配置文件
mybatis-config.xml<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <typeAliases> <package name="com.cxl.pojo"/> </typeAliases> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL =true&useUnicode=true&characterEncoding=UTF-8"/> <property name="username" value="root"/> <property name="password" value="123456"/> </dataSource> </environment> </environments> <mappers> <mapper class="com.cxl.mapper.UserMapper"/> </mappers> </configuration>
-
编写接口和.xml文件
UserMapperpublic interface UserMapper { List<User> selectUser(); }
UserMapper.xml
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.cxl.mapper.UserMapper"> <select id="selectUser" resultType="user"> select * from mybatis.user; </select> </mapper>
-
测试
@Test public void test() throws IOException { String resource = "mybatis-config.xml"; InputStream in = Resources.getResourceAsStream(resource); SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(in); SqlSession sqlSession = sessionFactory.openSession(true); UserMapper mapper = sqlSession.getMapper(UserMapper.class); List<User> users = mapper.selectUser(); for (User user : users) { System.out.println(user); } }
测试结果:
注意点:
1.找不到类就是maven资源导出问题 target中没有导出文件
2.编写完mapper.xml文件后,一定要去mybatis-config.xml中绑定mapper
3.数据库UTC设置 Asia/Shanghai
4.工具类放下下面
//sqlSessionFactory -->sqlSession
public class MybatisUtils {
private static SqlSessionFactory sqlSessionFactory;
static {
try{
//使用mybatis第一步:获取sqlSessionFactory对象
String resource = "mybatis-config.xml";//注意
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
}catch (IOException e){
e.printStackTrace();
}
}
//
public static SqlSession getSqlSession(){
return sqlSessionFactory.openSession(true);
}
}
Mybatis-spring (方式一)
官方文档:http://mybatis.org/spring/zh/index.html
-
整合Mybatis后的pom.xml配置文件
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>spring-study</artifactId> <groupId>org.example</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>spring-05-mybatis</artifactId> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.13.1</version> <scope>test</scope> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.47</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.7</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.1.19.RELEASE</version> </dependency> <!--spring操作数据库的话,还需要spring-jdbc--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.1.9.RELEASE</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.4</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>2.0.2</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>RELEASE</version> <scope>compile</scope> </dependency> </dependencies> <properties> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> </properties> <build> <resources> <resource> <directory>src/main/java</directory> <includes> <include>**/*.properties</include> <include>**/*.xml</include> </includes> <filtering>false</filtering> </resource> <resource> <directory>src/main/resources</directory> <includes> <include>**/*.properties</include> <include>**/*.xml</include> </includes> <filtering>false</filtering> </resource> </resources> </build> </project>
-
编写数据源配置
-
sqlSessionFactory
-
sqlSessionTemplete
spring-dao.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd"> <!--Datasource:使用spring的数据源替换mybatis的配置 c3p0 dbcp druid 我们这里使用spring提供的jdbc:org.springframework.jdbc.datasource --> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/mybatis?userSSL=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC"/> <property name="username" value="root"/> <property name="password" value="123456"/> </bean> <!--创建sqlSessionFactory--> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource"/> <!--bound mybatis--> <property name="configLocation" value="classpath:mybatis-config.xml"/> <!--注册usermapper--> <property name="mapperLocations" value="classpath:com/cxl/mapper/*.xml"/> </bean> <!--SqlSessionTemplate,就是我们使用的sqlsession--> <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate"> <!--通过构造器给SqlSessionTemplate 传入参数 它需要一个sqlSessionFactory 且它没有set方法,只能用构造器注入--> <constructor-arg index="0" ref="sqlSessionFactory"/> </bean> </beans>
mybatis-config.xml(变化)
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <typeAliases> <package name="com.cxl.pojo"/> </typeAliases> </configuration>
-
编写一个接口实现类
public class UserMapperImpl implements UserMapper { //原来我们所有操作都使用sqlsession,现在所有都使用SqlSessionTemplate,他俩一样 private SqlSessionTemplate sqlSession; //spring万物皆注入 一定要来个set方法 public void setSqlSession(SqlSessionTemplate sqlSession) { this.sqlSession = sqlSession; } @Override public List<User> selectUser() { UserMapper mapper = sqlSession.getMapper(UserMapper.class); return mapper.selectUser(); } }
-
将自己写的实现类,注入到spring中
applicationContext.xml<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd"> <import resource="spring-dao.xml"/> <!--将实习类 注入到spring中--> <bean id="userMapper" class="com.cxl.mapper.UserMapperImpl"> <!--UserMapperImpl 类中需要SqlSessionTemplate这个参数,传入 --> <property name="sqlSession" ref="sqlSession"/> </bean> </beans>
-
测试
public class Mytest01 { @Test public void test() throws IOException { ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); UserMapper mapper = (UserMapper) context.getBean("userMapper",UserMapper.class); for (User user : mapper.selectUser()) { System.out.println(user); } } }
测试结果:
架构:
方式二 SqlSessionDaoSupport
-
spring-dao.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:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" 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 http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd"> <!--Datasource:使用spring的数据源替换mybatis的配置 c3p0 dbcp druid 我们这里使用spring提供的jdbc:org.springframework.jdbc.datasource --> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/mybatis?userSSL=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC"/> <property name="username" value="root"/> <property name="password" value="123456"/> </bean> <!--创建sqlSessionFactory--> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource"/> <!--bound mybatis--> <property name="configLocation" value="classpath:mybatis-config.xml"/> <!--注册usermapper--> <property name="mapperLocations" value="classpath:com/cxl/mapper/*.xml"/> </bean> <!--SqlSessionTemplate,就是我们使用的sqlsession--> <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate"> <!--通过构造器给SqlSessionTemplate 传入参数 它需要一个sqlSessionFactory 且它没有set方法,只能用构造器注入--> <constructor-arg index="0" ref="sqlSessionFactory"/> </bean> </beans>
-
实现类:UserMapperImpl2
public class UserMapperImpl2 extends SqlSessionDaoSupport implements UserMapper{ @Override public List<User> selectUser() { // SqlSession sqlSession = getSqlSession(); // UserMapper mapper = sqlSession.getMapper(UserMapper.class); // return mapper.selectUser(); return getSqlSession().getMapper(UserMapper.class).selectUser(); } }
-
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" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" 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 http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd"> <import resource="spring-dao.xml"/> <!--方式二:将实现类 注入到spring中--> <bean id="userMapper2" class="com.cxl.mapper.UserMapperImpl2"> <!--UserMapperImpl 类中需要SqlSessionTemplate这个参数,传入 --> <property name="sqlSessionFactory" ref="sqlSessionFactory"/> </bean> </beans>
-
测试
@Test public void test() throws IOException { ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); UserMapper mapper = (UserMapper) context.getBean("userMapper2"); for (User user : mapper.selectUser()) { System.out.println(user); } }
声明式事务
回顾事务
- 把一组业务当成一个业务来做;要么都成功,要么都失败
- 事务在项目开发中,十分重要,设计到数据的一致性,不能马虎!
- 确保完整性和一致性
事务ACID原则:
- 原子性(atomic):要么都成功,要么都失败
- 一致性(consistency):操作前和操作后,数据总量不变
- 隔离性(isolation):多个业务操作同一资源,不能互相干扰,防止数据损坏
- 持久性(durability):事务一旦提交,不可回滚,无论系统发生什么问题,结果都不受影响,被持久化的写到存储器中
spring中的事务管理
增删改需要事务,查询不需要(查询设置为只读)
- 声明式事务:AOP 【交由容器管理事务】
- 编程式事务:需要在代码中,进行事务的管理 【需要改变代码】
补充:spring中的七种事务传播属性:propagation 传播
required(依赖):支持当前事务,如果当前没有事务,就新建一个事务 【默认】
supports(支持):支持当前事务,如果当前没有事务,就以非事务的方式执行
mandatory(强制):支持当前事务,如果当前没有事务,就抛出异常
required_new:新建事务,如果当前存在事务,把当前事务挂起
not_supported:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起
never:以非事务方式执行,如果当前存在事务,则抛出异常
nested(嵌套):支持当前事务,如果当前事务存在,则执行一个嵌套事务,如果当前没有事务,就新建一个事务
思考:为什么需要事务?
- 如果不配置事务,可能存在数据提交不一致的情况
- 如果我们不在spring中去配置声明式事务,我们就需要在代码中手动配置事务
- 事务在项目的开发中十分重要,涉及到数据的一致性和完整性问题