文章目录
- 前言
- 1.Spring
- 2传统项目开发的弊端
- 3 Spring-IOC(Inversion of Control)
- 4.SpringAOP铺垫
- 5 Spring中的AOP
- 6 Spring 总结
- Spring-AOP jar包
- 常用注解
前言
我们学习spring之前首先进行一些调试点击IDEA - 偏好设置 准备设置
如果之前学习了本人发布的IDEA - 偏好设置 准备设置之后还是对maven的概念以及作用域还不是很清楚的可以查看Spring前言 - 环境调试 maven详解,希望对大家有所帮助
1.Spring
1.1Spring介绍
Spring框架是一个开放源代码的J2EE应用程序框架,由Rod Johnson发起,是针对bean的生命周期进行管理的轻量级容器(lightweight container)。
Spring解决了开发者在J2EE开发中遇到的许多常见的问题,提供了功能强大IOC、AOP及Web MVC等功能。Spring可以单独应用于构筑应用程序,也可以和Struts、Webwork、Tapestry等众多Web框架组合使用,并且可以与 Swing等桌面应用程序AP组合。因此, Spring不仅仅能应用于JEE应用程序之中,也可以应用于桌面应用程序以及小应用程序之中。Spring框架主要由七部分组成,分别是 Spring Core、 Spring AOP、 Spring ORM、 Spring DAO、Spring Context、 Spring Web和 Spring Web MVC。
2传统项目开发的弊端
2.1传统项目编程
2.1.1编辑Dog类
public class Dog {
public void hello(){
System.out.println("你好,我是小狗汪汪汪");
}
}
2.1.2编辑Cat类
public class Cat {
public void hello(){
System.out.println("你好,我是小猫喵喵喵");
}
}
2.1.3编辑测试类
package com.jt.demo1;
public class User {
//说明: 当前类中 Dog对象与User对象绑定.耦合性高
//如果需要切换Cat对象.则需要手动修改代码.
//所以得出结论,如果自己实例化对象,则不能实现很好的解耦.
private static Dog dog = new Dog();
//private static Cat cat = new Cat();
public static void main(String[] args) {
dog.hello();
}
}
2.1.4测试问题总结:
- 如果类中的属性通过new 的方式直接绑定. 则类与属性的耦合性高.
- 如果需要修改属性类型, 则需要修改代码. 后期扩展不方便.
2.2面向接口变成
2.2.1业务说明
由于Dog和Cat都属性Pet宠物, 并且都有hello方法. 则能否将该方法抽取到接口中. 实现代码的解耦呢?
2.2.2封装Pet接口
package com.jt.demo2;
public interface Pet {
void hello(); //定义共同的hello接口方法
}
2.2.3编辑2个实现类
- 编辑Dog类
public class Dog implements Pet{
public void hello(){
System.out.println("你好,我是小狗汪汪汪");
}
}
- 编辑Cat类
public class Cat implements Pet{
public void hello(){
System.out.println("你好,我是小猫喵喵喵");
}
}
2.2.4编辑User测试类
package com.jt.demo2;
public class User {
//通过定义接口对象.以后用户直接使用属性,后续操作无需修改属性名
//接口中赋值的实现类可以灵活的变化. 所以在一定程度上 降低了耦合性
private static Pet pet = new Cat();
public static void main(String[] args) {
pet.hello();
}
}
2.2.5面向接口开发小结
-
使用接口开发,可以在一定程度上 降低类与对象的耦合性. 提高开发效率. 如图所示
修改前:
修改后: -
暴露的问题说明:
使用面向接口开发,虽然能在一定程度上降低代码的耦合性. 但是属性与类绑定的事实,并没有解决.
如何解决该问题呢?
3 Spring-IOC(Inversion of Control)
3.1 面向接口编程弊端
说明: 虽然面向接口编程可以在一定程度上解决代码耦合性的问题.但是根源问题没有解决.
当前的User类与Cat等对象 紧紧的耦合在一起.如果后期维护,则必然要修改源码.
3.2 IOC调用原理图
Ioc全称Inversion of Control,即“控制反转”,这是一种设计思想。对象创建的权利由Spring框架完成.由容器管理对象的生命周期.
3.3 Spring-IOC 配置文件
3.3.1 准备Dog类
package com.jt.demo2;
public class Dog {
public void hello(){
System.out.println("小狗 交给Spring容器管理");
}
}
3.3.2 准备spring的配置文件
说明: spring早期都使用配置文件的方式来管理对象.但是随着软件的升级当下注解的方式已经成为主流. 所以先完成xml配置文件的方式,之后完成注解的方式.
内容说明: 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">
<!--将Dog对象交给Spring容器管理
1.属性id是bean的唯一标识符. 一般类名首字母小写
2.属性class 表示被管理的类
-->
<bean id="dog" class="com.jt.demo2.Dog"></bean>
<!--<bean id="cat" class="com.jt.demo2.Cat"></bean>-->
</beans>
3.3.3 准备SpringGetDog类
package com.jt.demo2;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class SpringGetDog {
//该类表示从spring容器中,动态获取Dog对象
public static void main(String[] args) {
//1.指定spring配置文件路径
String resource = "spring.xml";
//2.启动spring容器
ApplicationContext context =
new ClassPathXmlApplicationContext(resource);
//3.从容器中获取对象 必须强制类型转化
Dog dog1 = (Dog) context.getBean("dog");
Dog dog2 = context.getBean(Dog.class);
System.out.println(dog1);//输出的是地址
System.out.println(dog2);
//4.对象调用方法
dog1.hello();
}
}
3.3.4 核心问题-spring如何创建对象
说明: 默认条件下,Spring容器启动时,就会创建对象,如果创建对象的过程中,出现问题.则容器启动失败.
- 为Dog类添加构造方法
- 反射代码
/**
* Spring实例化对象的核心原理-反射机制
* 注意事项: 反射代码 必然会调用对象的无参构造方法.
*/
public static void getDog(){
try {
Dog dog = (Dog) Class.forName("com.jt.demo2.Dog").newInstance();
dog.hello();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
3.3.5 Spring创建对象的步骤
- 定义spring的配置文件的地址.
- Spring容器启动时加载指定的配置文件.
- 当Spring扫描到bean标签时.加载属性id和class
- 根据反射机制 根据class的路径反射.实例化对象.
- Spring在内部维护了一个大型的Map<k,v>集合(容器),bean中的Id充当Map中的key. 实例化的对象充当Map中的value. 形式: Map<Id,实例化对象> 到此为止 spring容器启动成功
- 从spring容器中通过Id或者class类型 获取对象.
- 根据对象调用业务方法.
3.4 Spring-IOC 注解开发
3.4.1 关于注解开发说明
传统Spring框架采用xml配置文件的方式进行维护.但是随着springboot框架的崛起,注解开发渐渐的成为主流.所以将来以注解开发为准.
组成部分:
- 实体类: Spring容器管理的类(对象)
- 配置类: 相当于早期的xml配置文件
- 测试代码: 利用注解的方式启动spring容器
3.4.2 编辑Cat类
package com.jt.demo3;
public class Cat {
public Cat(){
System.out.println("我是demo3的无参构造");
}
public void hello(){
System.out.println("小花猫 喵喵喵");
}
}
3.4.3 编辑配置类
package com.jt.demo3;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@ComponentScan("com.jt.demo3")
//根据指定的包路径扫描注解,扫描当前包及其子孙包
@Configuration //标识当前类是配置类 其实就是配置文件
public class SpringCatConfig {
/**
* 方法要求:
* 1.必须为公有的
* 2.必须添加返回值,返回值的对象,就是容器管理的对象
* 3.方法的名称就是bean的Id
* 4.方法必须使用@Bean注解标识
*/
@Bean
public Cat cat(){
return new Cat();
}
}
3.4.4 编辑测试类
package com.jt.demo3;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class SpringAnno {
public static void main(String[] args) {
//利用注解启动spring容器
ApplicationContext context =
new AnnotationConfigApplicationContext
(SpringCatConfig.class);
//根据类型获取对象
Cat cat = context.getBean(Cat.class);
cat.hello();
}
}
3.5 Spring中的单例-多例模式
3.5.1概念说明
单例模式: 内存中的对象就一份.
多例模式: 内存中的对象有多份.
概念说明: Spring中的对象默认是单例的.
关于注解@Scope
package com.jt.demo3;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
@ComponentScan("com.jt.demo3")
//根据指定的包路径扫描注解,扫描当前包及其子孙包
@Configuration //标识当前类是配置类 其实就是配置文件
public class SpringCatConfig {
/**
* 注解管理对象--自定义对象:
* 1.方法必须为公有的
* 2.必须添加返回值,返回值的对象,就是容器管理的对象
* 3.方法的名称就是bean的Id
* 4.方法必须使用@Bean注解标识,spring才会执行该方法,标识该对象交给Spring容器管理,
*/
@Bean
@Scope("prototype") //表示多例对象
//@Scope("singleton") //表示单例对象
public Cat cat(){
return new Cat();
}
}
3.6 Spring中懒加载策略
3.6.1关于懒加载说明
默认条件下,Spring容器启动,则会创建对象.(类比:饿汉式),如果开启了懒加载.则用户什么时候使用.则对象什么时候创建(类比:懒汉式).
注解: @Lazy
@Bean
@Scope("prototype") //表示多例对象
//@Lazy //开启懒加载
//@Scope("singleton") //表示单例对象
public Cat cat(){
return new Cat();
}
关于多例模式和懒加载说明:
@Lazy 只能控制单例模式, 多例模式都是懒加载.
3.7 Spring生命周期
阶段划分: 对象创建/初始化/业务调用/对象销毁
3.7.1 创建Snake对象
package com.jt.demo3;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
public class Snake {
//1.无参构造方法
public Snake(){
System.out.println("蛇蛋诞生!!!");
}
//2.初始化方法
@PostConstruct
public void init(){
System.out.println("蛇破壳了,四处溜达");
}
//3.业务方法
public void eat(){
System.out.println("蛇四处觅食!!!");
}
//4.销毁方法
@PreDestroy
public void destroy(){
System.out.println("打蛇七寸,一命呜呼!!!");
}
}
3.7.2 将Snake交给Spring容器管理
@Bean
public Snake snake(){
return new Snake();
}
3.7.3 生命周期方法测试
/**
* 测试Bean的生命周期
* 接口中没有提供close的方法,需要使用实现类进行操作.
*/
public static void main(String[] args) {
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(SpringCatConfig.class);
Snake snake = context.getBean(Snake.class);
snake.eat();
//关闭容器即可
context.close();
}
3.8 SpringIOC - DI
3.8.1 定义接口Pet
package com.jt.demo4;
public interface Pet {
void hello();
}
3.8.2 定义Dog类
package com.jt.demo4;
import org.springframework.stereotype.Component;
//@Component("abc")//也可以自己写key //将该类交给Spring容器管理 key:dog , value:反射机制去创建对象
@Component
public class Dog implements Pet {
@Override
public void hello(){
System.out.println("快到圣诞节了!!!");
}
}
3.8.3 定义User类
package com.jt.demo4;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
//@Component("user")
@Component()
public class User {
/*
注入:将spring容器中的对象进行引用
@Autowired:可以将容器中对象进行注入
1.按照类型注入
如果注入的类型是接口,则自动的查找其实现类对象进行注入
注意事项:一般spring框架内部接口都是单实现,特殊条件下可以实现多实现
2.按照名称注入
*/
@Autowired
private Pet pet;
public void hello(){
pet.hello();
}
}
3.8.4 编辑配置类
package com.jt.demo4;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration //配置类注解
@ComponentScan("com.jt.demo4") //扫描包,配置类
public class SpringConfig {
}
3.8.5 编辑测试代码
package com.jt.demo4;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class SpringDI {
public static void main(String[] args) {
ApplicationContext context =
new AnnotationConfigApplicationContext(SpringConfig.class);
User user = context.getBean(User.class);
user.hello();
}
}
3.9 多实现类案例讲解
3.9.1 关于案例说明
一般条件下 Spring中的接口,都是单实现,如果遇到多实现,则如图所示
由于没有做其它操作,所以程序必然报错.
异常解决:
3.10 MVC 设计思想
3.10.1 传统代码结构
说明: 如果将所有的业务代码都写到一个方法中,则导致后期维护耦合性高,为了提高程序的扩展性.将程序按照MVC设计思想 进行管理.
3.10.2 MVC设计思想说明
M: Model 数据层
V: View 视图层
C: Control 控制层
总结: MVC 主要的目的降低代码的耦合性,提高扩展性.方便后续开发.
3.10.3 "三层"代码结构
说明: 基于MVC设计思想的启发,在后端为了提高代码的扩展性,一般将后端代码分为三层.
分层:
- Controller层 主要与页面进行交互 @Controller
- Service层 主要实现后端的业务逻辑 @Service
- Dao层/Mapper层 主要与数据库进行交互 也把该层称之为 “持久层” @Repository/@Mapper
3.11 "三层"代码结构实现
3.11.1 代码结构说明
包名: mapper 类2个 一个接口UserMapper/一个实现类 UserMapperImpl
包名: service 类2个 一个接口UserService/ 一个实现类UserServiceImpl
包名: controller 一个类: UserController
知识说明: 被调用的一般会有接口和实现类
3.11.2 编辑Mapper
- 编辑UserMapper
public interface UserMapper {
void addUser();
}
- 编辑Mapper 实现类
@Repository //标识持久层 该类交给Spring容器管理 key:userMapperImpl value:对象
public class UserMapperImpl implements UserMapper{
@Override
public void addUser() {
System.out.println("新增用户成功!!!!");
}
}
3.11.3 编辑Service
- 编辑UserService
public interface UserService {
void addUser();
}
- 编辑UserServiceImpl
package com.jt.demo5.service;
import com.jt.demo5.mapper.UserMapper;
import com.jt.demo5.mapper.UserMapperImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl implements UserService{
@Autowired
private UserMapper userMapper; //IOC+DI 解耦!!!!!
@Override
public void addUser() {
userMapper.addUser();
}
}
3.11.4 编辑Controller
package com.jt.demo5.controller;
import com.jt.demo5.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
@Controller //key=userController
public class UserController {
@Autowired
private UserService userService;
public void addUser(){
userService.addUser();
}
}
3.11.5 编辑配置类
@Configuration
@ComponentScan("com.jt.demo5")
public class SpringConfig {
}
3.11.6 编辑测试类
package com.jt.demo5;
import com.jt.demo5.config.SpringConfig;
import com.jt.demo5.controller.UserController;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Spring_MVC {
public static void main(String[] args) {
ApplicationContext context =
new AnnotationConfigApplicationContext(SpringConfig.class);
UserController userController = context.getBean(UserController.class);
userController.addUser();
}
}
3.12 @Value注解
3.12.1 编辑properties文件
# 1.数据结构: key=value
# 2.无需添加多余的引号
# 3.注意多余的空格
# 4.程序读取properties文件时,默认采用ISO-8859-1编码! 中文必定乱码
name=张三
3.12.2 编辑UserMapper
public interface UserMapper {
void addUser();
}
3.12.3 编辑UserMapperImpl
package com.jt.demo6.mapper;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Repository;
@Repository
//spring根据指定的路径,加载properties配置文件 数据添加到spring容器中
@PropertySource(value="classpath:/addUser.properties",encoding = "UTF-8")
public class UserMapperImpl implements UserMapper{
/**
* @Value 注解的作用: 为属性赋值
* 需求: 从spring容器中动态获取数据
*/
@Value("${name}")
//@Value("张三") //直接写法,扩展性不好
private String name;
@Override
public void addUser() {
System.out.println("新增用户:" + name);
}
}
3.12.4 编辑测试类
package com.jt.demo6;
import com.jt.demo6.config.SpringConfig;
import com.jt.demo6.mapper.UserMapper;
import com.jt.demo6.mapper.UserMapperImpl;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class SpringValue {
public static void main(String[] args) {
ApplicationContext context =
new AnnotationConfigApplicationContext(SpringConfig.class);
//获取的对象 实现类和接口都可以.
UserMapper userMapper = context.getBean(UserMapper.class);
userMapper.addUser();
}
}
3.12.5 关于IOC-DI总结
什么是IOC: 控制反转, 将对象交给Spring容器管理,由容器管理对象的生命周期.
什么是DI: 依赖注入 为当前对象注入属性(属性也是对象).
扩展问题: 如果采用配置文件xml的方式进行注入,则注入的方式有多种.
1.set方式注入
2.构造方法注入
3.工厂模式注入.
总结: 使用IOC-DI可以极大程度上实现代码的松耦合(解耦)
4.SpringAOP铺垫
4.1 AOP介绍
在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
AOP主要作用: 在不修改原有代码的条件下 对方法进行扩展
4.2 AOP代码铺垫
4.2.1 创建新项目
4.2.2 业务说明
事务特性: 1. 原子性 2. 一致性 3.隔离性 4.持久性
业务说明: 在增/删除/修改的操作过程中添加事务控制.
Demo效果演示:
结论:
- 如果按照上述的代码进行编辑,则所有增/删除/修改操作的代码都必须按照上述的规则.那么代码冗余.
- UserService与事务控制代码紧紧的耦合在一起.不方便后期扩展. 以后尽可能保证业务的纯粹性.
4.3 代理机制
4.3.1 关于代理模式说明
说明: 在业务层不方便做,但是又不得不做的事情,可以放到代理对象中. 通过这样的设计就可以解决业务层耦合的问题. 代理对象看起来和真是的对象 一模一样.所以用户使用不会察觉
类比:
- 外卖也是一种典型的代理思想
- 游戏代练
- 房屋中介
4.3.2 动态代理的作用
说明1: 一般我们将业务层中的耦合性高的代码,采用动态代理的方式进行解耦.使得程序更加具有扩展性. (业务逻辑的解耦)
说明2: Spring专门针对动态代理的规则.封装了一套API 起名 AOP
4.4 动态代理 - JDK
4.4.1 关于动态代理的说明
Java中提供了动态代理的机制,可以利用API在运行期动态生成一个和原对象 “看起来” 一模一样的对象(代理对象). 用户的扩展功能,在动态代理中完成.
动态代理要求被代理的对象必须是实现类,代理对象实现对应的接口,与被实现类达成兄弟关系
4.4.2 JDK动态代理
要求被代理者必须有(实现)接口.
package com.jt.demo1.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class JDKProxy {
/**
* 获取代理对象
* 参数说明:
* 1. ClassLoader loader 类加载器 读取真实的类数据
* 2. Class<?>[] interfaces, 要求传递接口信息
* 3. InvocationHandler h 当代理对象执行方法时 执行
* 注意事项: JDK代理必须要求 "被代理者"要么有接口(本身就是接口),要么实现接口(实现类).
*/
public static Object getProxy(Object target){
//1.获取类加载器
ClassLoader classLoader = target.getClass().getClassLoader();
//2.获取接口
Class[] interfaces = target.getClass().getInterfaces();
return Proxy.newProxyInstance(classLoader,interfaces,getInvocationHandler(target));
}
//代理对象执行方法时调用
public static InvocationHandler getInvocationHandler(Object target){
//这些代码都是写死的!!!!!!
return new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("事务开始");
//执行真实的业务方法
Object result = method.invoke(target,args);
System.out.println("事务提交");
return result;
}
};
}
}
4.4.3 编辑测试类
package com.jt.demo1;
import com.jt.demo1.config.SpringConfig;
import com.jt.demo1.proxy.JDKProxy;
import com.jt.demo1.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class SpringTx {
public static void main(String[] args) {
ApplicationContext context =
new AnnotationConfigApplicationContext(SpringConfig.class);
UserService userService = context.getBean(UserService.class);
System.out.println(userService.getClass());
//获取代理对象
UserService proxy = (UserService) JDKProxy.getProxy(userService);
System.out.println(proxy.getClass());
//基于代理对象,执行业务操作 实现方法扩展
proxy.addUser();
proxy.deleteUser();
}
}
4.4 动态代理 - CGlib
4.4.1 CGLib动态代理说明
历史原因: JDK动态代理要求必须"有接口",但是某些类它没有接口,则无法使用JDK代理生成代理对象. 所以为了填补知识的空缺,则引入cglib代理.
问题说明: cglib动态代理 要求有无接口都可以创建代理对象. 问题? 如何保证和被代理者"相同"
答案(特点): 要求cglib动态代理继承被代理者.代理对象是被代理者的子类.
4.4.2 CGBLib动态代理
代理对象是目标对象的子类.
4.4.3 编辑Service实现类
由于特殊原因,Service中不能注入Dao. 暂时不管Dog数据的实例化操作
package com.jt.service;
import com.jt.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl implements UserService{
@Autowired
private UserDao userDao;
@Override
public void addUser() {
//System.out.println("数据库事务开始");
//userDao.addUser();
System.out.println("完成用户入库操作");
//System.out.println("数据库事务提交");
}
}
4.4.4 编辑cglib代理对象
package com.jt.proxy;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class CglibProxy {
public static Object getObject(Object target){
//创建增强器对象
Enhancer enhancer = new Enhancer();
//设置父级
enhancer.setSuperclass(target.getClass());
enhancer.setCallback(getMethodInterceptor());
return enhancer.create();
}
public static MethodInterceptor getMethodInterceptor(){
return new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("数据库事务开始");
//执行父类的方法
Object proxy = methodProxy.invokeSuper(obj,objects);
System.out.println("数据库事务提交");
return proxy;
}
};
}
}
4.4.5 编辑测试类
@Test
void testCGB(){
ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
UserService userService = context.getBean(UserService.class);
//使用代理对象 调用方法
UserService proxy = (UserService) CglibProxy.getObject(userService);
proxy.addUser();
}
4.5 关于动态代理模式总结
JDK或者CBLIB都是根据原对象创建出来的代理对象. 并且主要的功能在代理对象中进行扩展.所有起到了代码解耦的作用.
5 Spring中的AOP
5.1 AOP介绍
在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
总结: Spring中的AOP 利用代理对象在不修改源代码的条件下,对方法进行扩展.
5.2 AOP中专业术语(难点)
1).连接点: 用户可以被扩展的方法
2).切入点: 用户实际扩展的方法
3).通知: 扩展方法的具体实现
4).切面: 将通知应用到切入点的过程
5.3 切入点表达式
@Pointcut()
1). bean(bean的Id号) 按照bean匹配
2). within(包名.类名) 可以使用通配符
3). execution(返回值类型 包名.类名.方法名(参数列表))
4). @annotation(包名.注解名称)
5.4 通知类型
1).before通知 目标方法执行前执行
2).afterRrturning 通知 目标方法执行之后执行
3).afterThrowing 通知 目标方法报错时执行
4).after通知 目标方法执行之后 最后执行
5).around通知 目标方法执行前后都要执行
5.5 AOP 入门案例
5.5.1 引入AOPjar包文件
<!--引入AOPjar包文件-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
5.5.2 定义切面类
package com.jt.demo2.aop;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;
@Component //将当前类交给Spring容器管理
@Aspect //我是一个切面类
public class SpringAOP {
/**
* 切面 = 切入点表达式 + 通知方法
* 1.切入点:
* 理解: 可以理解为就是一个if判断
* 判断条件: 切入点表达式
* 规则:
* 如果满足表达式 则判断为true,则执行通知方法
* 如果不满足表达式 则判断为false 则不执行通知方法
*
* 2.切入点表达式
* 2.1 bean("对象的Id")
* 2.2 within("包名.类名")
* 2.3 execution(返回值类型 包名.类名.方法名(参数列表))
* 2.4 @annotation(注解的路径)
*/
@Pointcut("bean(userServiceImpl)")
public void pointcut(){
}
/**
* 定义通知方法:
* 1.前置通知 在目标方法执行之前执行.
* 2.后置通知 在目标方法执行之后执行.
* 3.异常通知 在目标方法执行之后抛出异常时执行.
* 4.最终通知 都要执行的通知
* 5.环绕通知 在目标方法执行前后都要执行的通知
*/
@Before("pointcut()")
public void before(){
System.out.println("你好,我是前置通知");
}
}
5.5.3 让AOP生效
说明: 编辑配置类,添加@EnableAspectJAutoProxy,让AOP机制有效
5.5.4 编辑测试类
package com.jt.demo2;
import com.jt.demo2.config.SpringConfig;
import com.jt.demo2.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Spring_AOP {
public static void main(String[] args) {
ApplicationContext context =
new AnnotationConfigApplicationContext(SpringConfig.class);
//理论值:根据接口获取实现类对象, 但是与切入点表达式匹配,为了后续扩展方便.为其创建代理对象
UserService userService = context.getBean(UserService.class);
//如果是实现类对象,则方法没有被扩展
//如果是代理对象, 则方法被扩展 aop有效的
System.out.println(userService.getClass());
userService.addUser();
}
}
5.5.5 AOP形象化的比喻
说明: AOP是一种抽象的一种概念,看不见/摸不着.所以需要大家对概念有自己的认知.
5.6 关于切入点表达式解析
5.6.1 bean标签写法
@Pointcut(“bean(userServiceImpl)”) 只匹配ID为userServiceImpl的对象
5.6.2 within表达式
@Pointcut(“within(com.jt.demo2.service.*)”) 匹配xx.xx.service下的所有对象
###5.6.3 execution表达式
@Pointcut("execution(* com.jt.demo2.service..*.*(..))")
拦截返回值类型任意 xx.xx.service包下所有子孙包的所有类的任意方法
@Pointcut("execution(* com.jt.demo2.service..*.add*(..))")
拦截返回值类型任意 xx.xx.service包下所有子孙包的所有类.以add开头的方法
5.6.4 按照注解进行拦截
5.6.4.1 自定义注解
@Target(ElementType.METHOD) //注解对方法有效
@Retention(RetentionPolicy.RUNTIME) //运行期有效
public @interface cgb2110 { //注解起标记作用
}
5.6.4.2 切入点表达式写法
@Pointcut("@annotation(com.jt.demo2.anno.CGB2110)")
public void pointcut(){
}
5.7 动态获取注解参数
5.7.1 定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Find {
int id() default 0;
}
使用注解:
5.7.2 需求
利用前置通知,打印注解中的Id值!!!
5.7.3 编辑切面类
/**
* 知识点:
* 1.如果切入点表达式只对当前通知有效.则可以按照如下方式编辑
* 要求: 动态的拦截Find注解,并且要获取Find注解中的参数Id
* 难点: 动态获取注解的对象!!
* 代码解释:
* 1.@annotation(find) 拦截find变量名称对应类型的注解
* 2.当匹配该注解之后,将注解对象当做参数传递给find
* 优势: 可以一步到位获取注解的内容.避免了反射的代码
*/
@Before("@annotation(find)")
public void before2(Find find){
System.out.println("ID的值为:"+find.id());
}
5.8 通知方法
5.8.1 关于通知方法解析
1.前置通知 在目标方法执行之前执行.
2.后置通知 在目标方法执行之后执行.
3.异常通知 在目标方法执行之后抛出异常时执行.
4.最终通知 都要执行的通知
说明: 上述的四大通知一般用于记录程序的运行状态.只做记录.
5.环绕通知 在目标方法执行前后都要执行的通知
5.8.2 关于连接点解析
API:JoinPoint ,JoinPoint对象意为连接点,其真实代表的是目标对象
记录程序的状态:
- 目标对象的class/类路径
- 目标对象的方法名
- 目标对象的方法的参数信息
- 获取目标对象方法的返回值
- 获取目标对象执行报错的异常信息
5.8.3 前置通知案例
@Before("pointcut()")
public void before(JoinPoint joinPoint){
//1.获取目标对象的类型
Class targetClass = joinPoint.getTarget().getClass();
//2.获取目标对象的路径
String path = joinPoint.getSignature().getDeclaringTypeName();
//3.获取目标对象的方法名称
String methodName = joinPoint.getSignature().getName();
//4.获取方法参数
Object[] args = joinPoint.getArgs();
System.out.println("类型:"+targetClass);
System.out.println("类路径:"+path);
System.out.println("方法名:"+methodName);
System.out.println("参数:"+ Arrays.toString(args));
}
5.8.4 后置通知案例
5.8.4.1 添加接口方法
- 编辑接口
- 编辑实现类
@Override
@CGB2110 //测试获取返回值的!!!!
public int findCount() {
return 1000;
}
5.8.4.2 编辑AOP
//注意事项: 如果多个参数,joinPoint必须位于第一位!!!
@AfterReturning(value = "pointcut()",returning = "result")
public void afterReturn(JoinPoint joinPoint,Object result){
//如果需要获取当前的方法信息.则可以通过joinPoint获取
System.out.println("我是后置通知,获取方法的返回值:"+result);
}
5.8.4.3 编辑测试案例
public static void main(String[] args) {
ApplicationContext context =
new AnnotationConfigApplicationContext(SpringConfig.class);
//理论值:根据接口获取实现类对象, 但是与切入点表达式匹配,为了后续扩展方便.为其创建代理对象
UserService userService = context.getBean(UserService.class);
//如果是实现类对象,则方法没有被扩展
//如果是代理对象, 则方法被扩展 aop有效的
System.out.println(userService.getClass());
userService.addUser();
userService.findCount(); //测试带返回值的方法
}
5.9 异常通知案例
5.9.1 让代码报错
5.9.2 异常通知案例
throwing:获取异常信息,之后进行传递
//后置通知与异常通知是互斥的.只能有一个
@AfterThrowing(value = "pointcut()",throwing = "exception")
public void afterThrow(JoinPoint joinPoint,Exception exception){
//打印异常
//exception.printStackTrace();
System.out.println("我是异常通知:"+exception.getMessage());
}
5.10 最终通知
说明: 最终通知,不管方法执行是否有误.则都会执行该通知方法.
//最终通知
@After("pointcut()")
public void after(){
System.out.println("我是最终通知方法!!!");
}
5.11环绕通知
/**
* 环绕通知:
* 1.特点:
* 1.方法执行前后,通知都要执行.
* 2.环绕通知可以控制目标方法是否执行.
* 3.环绕通知必须添加返回值.
* 2. proceed()
* 作用1: 如果有下一个通知,则执行下一个通知
* 作用2: 如果没有下一个通知,则执行目标方法
*/
@Around("pointcut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("环绕通知开始!!!");
Object result = joinPoint.proceed();
System.out.println("环绕通知结束!!!");
return result;
}
5.12Spring中AOP的执行顺序
5.12.1编辑第二个AOP测试类
说明: 编辑第二个AOP测试类
package com.jt.demo2.aop;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
@Component //将当前类.交给Spring容器管理
@Aspect //标识AOP
public class SpringAOP2 {
@Around("@annotation(com.jt.demo2.anno.CGB2110)")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("环绕通知开始2");
Object result = joinPoint.proceed();
System.out.println("环绕通知结束2");
return result;
}
}
5.12.2 AOP执行顺序说明
说明: 如果有多个环绕通知,其中的执行的顺序是嵌套关系.
控制顺序:
5.13 Spring中的通知总结
第一类: 记录程序的运行状态
- 前置通知
- 后置通知 记录目标方法的返回值!!!
- 异常通知 记录目标方法执行之后,抛出的异常信息.
- 最终通知 记录程序最后的状态.
第二类:
5. 环绕通知 控制目标方法是否执行. 环绕通知是未来使用最多的,功能最为强大的.
5.14 Spring中AOP案例
需求1: 需要对方法的执行时间,进行监控?
通知的选择: 首选环绕通知
需求2: 利用AOP可以实现缓存控制
通知类型: 环绕通知
业务思路:
- 用户直接查询缓存,
如果缓存中没有数据 表示第一次查询,让目标方法执行.
如果缓存中有数据. 表示第N次查询.目标方法不执行.从缓存中获取数据即可.
需求3: 利用AOP控制事务.
通知类型: 环绕通知
需求4: 利用AOP控制方法的权限!
通知类型: 环绕通知
业务思路:
- 在AOP的环绕通知中,判断当前用户是否有权限!
- 有权限: 可以执行目标方法. 获取数据
- 没有权限: 不能执行目标方法.通知用户 没有权限!!!
关于AOP现状说明: 一般工作中很少直接编辑AOP底层代码.绝大部分的业务逻辑都是使用的高级API不需要从底层写起…
6 Spring 总结
知识总结:
1.Spring的作用: Spring可以整合其它的第三方框架!从架构的角度,实现了代码的松耦合!
2. Spring-IOC/DI IOC控制反转/数据结构Map集合<id,反射实例化对象>/ xml文件写法/注解写法
DI: 依赖注入 类型注入/名称注入/一般接口都是单实现.
3. Spring-AOP 在不修改源码的条件下对方法进行扩展!!!
4. 动态代理 JDK动态/Cglib动态代理 method.invoke()
5. 切面 = 切入点表达式 + 通知方法
6. AOP中因为切面较多,每个切面都完成特定的功能,所以一般不会研究顺序. @Order注解 可有控制顺序.
Spring-AOP jar包
<!--引入AOPjar包文件-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
常用注解
- @Configuration 标识当前类是配置类
- @ComponentScan 包扫描注解 扫描注解 需要在配置类上面添加
- @Bean 标识该方法的返回值交给Spring容器管理 通常写在配置类中
方法名类型首字母小写,返回值:需要被管理的对象" - @Scope 控制多例和单例 通常与@bean一起使用
@Scope(“singleton”) //标识单例对象
@Scope(“prototype”) //标识多例对象 - @Lazy 懒加载 通常与@bean一起使用 来设置单例下的饿汉式与懒汉式
注意:多例模式是懒加载,单例模式是饿汉式- @PostConstruct 初始化方法 通常写在被Spring管理的对象里面
- @PreDestroy 销毁方法 通常写在被Spring管理的对象里面
- @Component 将当前类未来的对象交给容器管理 一般情况下用了此标签,就不需要在配置类里面配置@bean了
- @Autowired 按照类型进行注入
- @Qualifier 按照名称进行注入 通常跟@Autowired注解一起使用
- @Repository 标识持久层注解
- @Service 标识Service层
- @Controller 标识Controller层
- @Value 为属性赋值 @Value("${key}") 为对象中的属性赋值
- @PropertySource 加载指定路径的配置文件properties 标注在有@Value标签的类上
@PropertySource(value = “classpath:/addUser.properties”,encoding = “UTF-8”)
spring根据指定的路径,加载properties配置文件 数据添加到spring容器中 - @Aspect 标识当前类是一个切面类 注意切面类需要被 Spring管理
- @Pointcut 用于定义切入点表达式 表达式写法4种 如果方法上有此标签 我们把这个方法视为目标方法,也就是被代理的对象需要执行的业务逻辑方法
- @EnableAspectJAutoProxy 让AOP的注解有效果 添加到配置类上
- @Before AOP-前置通知
- @AfterReturning AOP-后置通知
- @AfterThrowing AOP-异常通知
- @After AOP-最终通知
- @Around AOP-环绕通知
- @Order(1) //可以利用order关键字 实现AOP的排序 数字越小越先执行. 意为:当一个方法需要执行多个AOP时,我们可以设置切面的执行顺序,此标签通常加在切面类上 如果遇到排序的数字一样,则还是按照文件夹的上下顺序执行