我们学习spring之前首先进行一些调试点击IDEA - 偏好设置 准备设置
如果之前学习了本人发布的IDEA - 偏好设置 准备设置之后还是对maven的概念以及作用域还不是很清楚的可以查看Spring前言 - 环境调试 maven详解,希望对大家有所帮助
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。
public class Dog {
public void hello(){
System.out.println("你好,我是小狗汪汪汪");
}
}
public class Cat {
public void hello(){
System.out.println("你好,我是小猫喵喵喵");
}
}
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();
}
}
由于Dog和Cat都属性Pet宠物, 并且都有hello方法. 则能否将该方法抽取到接口中. 实现代码的解耦呢?
package com.jt.demo2;
public interface Pet {
void hello(); //定义共同的hello接口方法
}
public class Dog implements Pet{
public void hello(){
System.out.println("你好,我是小狗汪汪汪");
}
}
public class Cat implements Pet{
public void hello(){
System.out.println("你好,我是小猫喵喵喵");
}
}
package com.jt.demo2;
public class User {
//通过定义接口对象.以后用户直接使用属性,后续操作无需修改属性名
//接口中赋值的实现类可以灵活的变化. 所以在一定程度上 降低了耦合性
private static Pet pet = new Cat();
public static void main(String[] args) {
pet.hello();
}
}
暴露的问题说明:
使用面向接口开发,虽然能在一定程度上降低代码的耦合性. 但是属性与类绑定的事实,并没有解决.
如何解决该问题呢?
说明: 虽然面向接口编程可以在一定程度上解决代码耦合性的问题.但是根源问题没有解决.
当前的User类与Cat等对象 紧紧的耦合在一起.如果后期维护,则必然要修改源码.
Ioc全称Inversion of Control,即“控制反转”,这是一种设计思想。对象创建的权利由Spring框架完成.由容器管理对象的生命周期.
package com.jt.demo2;
public class Dog {
public void hello(){
System.out.println("小狗 交给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>
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();
}
}
说明: 默认条件下,Spring容器启动时,就会创建对象,如果创建对象的过程中,出现问题.则容器启动失败.
/**
* 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();
}
}
传统Spring框架采用xml配置文件的方式进行维护.但是随着springboot框架的崛起,注解开发渐渐的成为主流.所以将来以注解开发为准.
组成部分:
package com.jt.demo3;
public class Cat {
public Cat(){
System.out.println("我是demo3的无参构造");
}
public void hello(){
System.out.println("小花猫 喵喵喵");
}
}
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();
}
}
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();
}
}
单例模式: 内存中的对象就一份.
多例模式: 内存中的对象有多份.
概念说明: Spring中的对象默认是单例的.
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();
}
}
默认条件下,Spring容器启动,则会创建对象.(类比:饿汉式),如果开启了懒加载.则用户什么时候使用.则对象什么时候创建(类比:懒汉式).
注解: @Lazy
@Bean
@Scope("prototype") //表示多例对象
//@Lazy //开启懒加载
//@Scope("singleton") //表示单例对象
public Cat cat(){
return new Cat();
}
关于多例模式和懒加载说明:
@Lazy 只能控制单例模式, 多例模式都是懒加载.
阶段划分: 对象创建/初始化/业务调用/对象销毁
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("打蛇七寸,一命呜呼!!!");
}
}
@Bean
public Snake snake(){
return new Snake();
}
/**
* 测试Bean的生命周期
* 接口中没有提供close的方法,需要使用实现类进行操作.
*/
public static void main(String[] args) {
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(SpringCatConfig.class);
Snake snake = context.getBean(Snake.class);
snake.eat();
//关闭容器即可
context.close();
}
package com.jt.demo4;
public interface Pet {
void hello();
}
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("快到圣诞节了!!!");
}
}
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();
}
}
package com.jt.demo4;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration //配置类注解
@ComponentScan("com.jt.demo4") //扫描包,配置类
public class SpringConfig {
}
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();
}
}
一般条件下 Spring中的接口,都是单实现,如果遇到多实现,则如图所示
由于没有做其它操作,所以程序必然报错.
异常解决:
说明: 如果将所有的业务代码都写到一个方法中,则导致后期维护耦合性高,为了提高程序的扩展性.将程序按照MVC设计思想 进行管理.
M: Model 数据层
V: View 视图层
C: Control 控制层
总结: MVC 主要的目的降低代码的耦合性,提高扩展性.方便后续开发.
说明: 基于MVC设计思想的启发,在后端为了提高代码的扩展性,一般将后端代码分为三层.
分层:
包名: mapper 类2个 一个接口UserMapper/一个实现类 UserMapperImpl
包名: service 类2个 一个接口UserService/ 一个实现类UserServiceImpl
包名: controller 一个类: UserController
知识说明: 被调用的一般会有接口和实现类
public interface UserMapper {
void addUser();
}
@Repository //标识持久层 该类交给Spring容器管理 key:userMapperImpl value:对象
public class UserMapperImpl implements UserMapper{
@Override
public void addUser() {
System.out.println("新增用户成功!!!!");
}
}
public interface UserService {
void addUser();
}
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();
}
}
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();
}
}
@Configuration
@ComponentScan("com.jt.demo5")
public class SpringConfig {
}
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();
}
}
# 1.数据结构: key=value
# 2.无需添加多余的引号
# 3.注意多余的空格
# 4.程序读取properties文件时,默认采用ISO-8859-1编码! 中文必定乱码
name=张三
public interface UserMapper {
void addUser();
}
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);
}
}
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();
}
}
什么是IOC: 控制反转, 将对象交给Spring容器管理,由容器管理对象的生命周期.
什么是DI: 依赖注入 为当前对象注入属性(属性也是对象).
扩展问题: 如果采用配置文件xml的方式进行注入,则注入的方式有多种.
1.set方式注入
2.构造方法注入
3.工厂模式注入.
总结: 使用IOC-DI可以极大程度上实现代码的松耦合(解耦)
在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
AOP主要作用: 在不修改原有代码的条件下 对方法进行扩展
事务特性: 1. 原子性 2. 一致性 3.隔离性 4.持久性
业务说明: 在增/删除/修改的操作过程中添加事务控制.
Demo效果演示:
结论:
说明: 在业务层不方便做,但是又不得不做的事情,可以放到代理对象中. 通过这样的设计就可以解决业务层耦合的问题. 代理对象看起来和真是的对象 一模一样.所以用户使用不会察觉
类比:
说明1: 一般我们将业务层中的耦合性高的代码,采用动态代理的方式进行解耦.使得程序更加具有扩展性. (业务逻辑的解耦)
说明2: Spring专门针对动态代理的规则.封装了一套API 起名 AOP
Java中提供了动态代理的机制,可以利用API在运行期动态生成一个和原对象 “看起来” 一模一样的对象(代理对象). 用户的扩展功能,在动态代理中完成.
动态代理要求被代理的对象必须是实现类,代理对象实现对应的接口,与被实现类达成兄弟关系
要求被代理者必须有(实现)接口.
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;
}
};
}
}
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();
}
}
历史原因: JDK动态代理要求必须"有接口",但是某些类它没有接口,则无法使用JDK代理生成代理对象. 所以为了填补知识的空缺,则引入cglib代理.
问题说明: cglib动态代理 要求有无接口都可以创建代理对象. 问题? 如何保证和被代理者"相同"
答案(特点): 要求cglib动态代理继承被代理者.代理对象是被代理者的子类.
代理对象是目标对象的子类.
由于特殊原因,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("数据库事务提交");
}
}
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;
}
};
}
}
@Test
void testCGB(){
ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
UserService userService = context.getBean(UserService.class);
//使用代理对象 调用方法
UserService proxy = (UserService) CglibProxy.getObject(userService);
proxy.addUser();
}
JDK或者CBLIB都是根据原对象创建出来的代理对象. 并且主要的功能在代理对象中进行扩展.所有起到了代码解耦的作用.
在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
总结: Spring中的AOP 利用代理对象在不修改源代码的条件下,对方法进行扩展.
1).连接点: 用户可以被扩展的方法
2).切入点: 用户实际扩展的方法
3).通知: 扩展方法的具体实现
4).切面: 将通知应用到切入点的过程
@Pointcut()
1). bean(bean的Id号) 按照bean匹配
2). within(包名.类名) 可以使用通配符
3). execution(返回值类型 包名.类名.方法名(参数列表))
4). @annotation(包名.注解名称)
1).before通知 目标方法执行前执行
2).afterRrturning 通知 目标方法执行之后执行
3).afterThrowing 通知 目标方法报错时执行
4).after通知 目标方法执行之后 最后执行
5).around通知 目标方法执行前后都要执行
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-aopartifactId>
dependency>
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("你好,我是前置通知");
}
}
说明: 编辑配置类,添加@EnableAspectJAutoProxy,让AOP机制有效
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();
}
}
说明: AOP是一种抽象的一种概念,看不见/摸不着.所以需要大家对概念有自己的认知.
@Pointcut(“bean(userServiceImpl)”) 只匹配ID为userServiceImpl的对象
@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开头的方法
@Target(ElementType.METHOD) //注解对方法有效
@Retention(RetentionPolicy.RUNTIME) //运行期有效
public @interface cgb2110 { //注解起标记作用
}
@Pointcut("@annotation(com.jt.demo2.anno.CGB2110)")
public void pointcut(){
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Find {
int id() default 0;
}
利用前置通知,打印注解中的Id值!!!
/**
* 知识点:
* 1.如果切入点表达式只对当前通知有效.则可以按照如下方式编辑
* 要求: 动态的拦截Find注解,并且要获取Find注解中的参数Id
* 难点: 动态获取注解的对象!!
* 代码解释:
* 1.@annotation(find) 拦截find变量名称对应类型的注解
* 2.当匹配该注解之后,将注解对象当做参数传递给find
* 优势: 可以一步到位获取注解的内容.避免了反射的代码
*/
@Before("@annotation(find)")
public void before2(Find find){
System.out.println("ID的值为:"+find.id());
}
1.前置通知 在目标方法执行之前执行.
2.后置通知 在目标方法执行之后执行.
3.异常通知 在目标方法执行之后抛出异常时执行.
4.最终通知 都要执行的通知
说明: 上述的四大通知一般用于记录程序的运行状态.只做记录.
5.环绕通知 在目标方法执行前后都要执行的通知
API:JoinPoint ,JoinPoint对象意为连接点,其真实代表的是目标对象
记录程序的状态:
@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));
}
@Override
@CGB2110 //测试获取返回值的!!!!
public int findCount() {
return 1000;
}
//注意事项: 如果多个参数,joinPoint必须位于第一位!!!
@AfterReturning(value = "pointcut()",returning = "result")
public void afterReturn(JoinPoint joinPoint,Object result){
//如果需要获取当前的方法信息.则可以通过joinPoint获取
System.out.println("我是后置通知,获取方法的返回值:"+result);
}
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(); //测试带返回值的方法
}
throwing:获取异常信息,之后进行传递
//后置通知与异常通知是互斥的.只能有一个
@AfterThrowing(value = "pointcut()",throwing = "exception")
public void afterThrow(JoinPoint joinPoint,Exception exception){
//打印异常
//exception.printStackTrace();
System.out.println("我是异常通知:"+exception.getMessage());
}
说明: 最终通知,不管方法执行是否有误.则都会执行该通知方法.
//最终通知
@After("pointcut()")
public void after(){
System.out.println("我是最终通知方法!!!");
}
/**
* 环绕通知:
* 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;
}
说明: 编辑第二个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. 环绕通知 控制目标方法是否执行. 环绕通知是未来使用最多的,功能最为强大的.
需求1: 需要对方法的执行时间,进行监控?
通知的选择: 首选环绕通知
需求2: 利用AOP可以实现缓存控制
通知类型: 环绕通知
业务思路:
需求3: 利用AOP控制事务.
通知类型: 环绕通知
需求4: 利用AOP控制方法的权限!
通知类型: 环绕通知
业务思路:
关于AOP现状说明: 一般工作中很少直接编辑AOP底层代码.绝大部分的业务逻辑都是使用的高级API不需要从底层写起…
知识总结:
1.Spring的作用: Spring可以整合其它的第三方框架!从架构的角度,实现了代码的松耦合!
2. Spring-IOC/DI IOC控制反转/数据结构Map集合
DI: 依赖注入 类型注入/名称注入/一般接口都是单实现.
3. Spring-AOP 在不修改源码的条件下对方法进行扩展!!!
4. 动态代理 JDK动态/Cglib动态代理 method.invoke()
5. 切面 = 切入点表达式 + 通知方法
6. AOP中因为切面较多,每个切面都完成特定的功能,所以一般不会研究顺序. @Order注解 可有控制顺序.
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-aopartifactId>
dependency>