Spring相关知识补充

目录

一、将Bean存储到spring(容器)中

1、使用spring-config的方式将对象存储到spring容器中

2、使用类注解的方式将Bean对象存储到容器中

1️⃣、配置扫描路径(使用注解的方式存对象的前提)

2️⃣、使用五大类注解存储Bean对象

3、使用方法注解注入对象@Bean

3.1、添加@Bean注解的方法的重命名

二、从Spring容器中获取Bean对象

1、通过getBean方法获取Bean对象

2、通过依赖注入的方式获取Bean对象

1.1、通过在spring-config.xml文件中配置实现依赖注入

1>、属性注入(Setter注入)

2> 、构造方法注入

1.2、通过注解的方式实现依赖注入

1>、 属性注入

2>、setter注入

3>、构造方法注入

三、Spring的AOP(面向切面编程)

1、静态代理

2、动态代理

1、JDK代理和CGLIB代理的区别

2、模拟实现动态代理

1、JDK动态代理

2、CGLIB动态代理

3、AOP的组成

1、实现AOP需要添加的依赖和配置

2、切入点表达式


一、将Bean存储到spring(容器)中

1、使用spring-config的方式将对象存储到spring容器中

①首先需要创建一个类,也就是创建一个Bean.

@Data
public class User {private Integer id;private String name;private String password;
}

②在resource目录中,创建一个spring-config.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:content="http://www.springframework.org/schema/context"xmlns:context="http://www.springframework.org/schema/context"xmlns:aop="http://www.springframework.org/schema/aop"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/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">
</beans>

然后将Bean对象通过spring-config.xml配置文件,注入到Spring中。当然在配置文件中针对一个Bean可以注入多个对象,不过对象id要不同。

<bean id="user" class="com.it.pojo.User"></bean>

这里的id表示的存储到容器中bean对象的名称,class表示bean对象的路径(包名+类名),这里只是进行了声明并没有正真的注入到spring中,只是使用到这个对象中的方法时,才会正真的注入到spring中。

③从容器中获取bean对象

public class UserTest {@Testpublic void test(){ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("spring-config.xml");User user = ctx.getBean("user", User.class);System.out.println(user);}
}

2、使用类注解的方式将Bean对象存储到容器中

1️⃣、配置扫描路径(使用注解的方式存对象的前提)

我们需要在spring-config.xml文件当中配置下面的代码。用来配置Bean的扫描根路径。

<content:component-scan base-package="com.it"></content:component-scan>

这里的base-package属性的值,就是配置的扫描路径。只有在这个目录中的所有类才会被扫描是否添加了注解,如果添加了注解,就将这些类存放到IoC容器中。

2️⃣、使用五大类注解存储Bean对象

想要将对象存储到Spring中,有两种注解类型可以实现

  • 类注解:@Controller(控制器),@Service(服务)、@Repository(仓库)、@Component(组件),@Configuration(配置)。

  • 方法注解:@Bean

使用五大类注解将Bean对象注入到Spring容器中。在使用类注解将对象注入到spring容器中,从spring中获取该对象的时候,需要根据Bean对象的名称来获取,这个时候这个对象的名称必须是以小驼峰格式来命名,因为Bean对象名默认为小驼峰形式

@Controller
public class UserController {public void hello(){System.out.println("hello world");}
}@Testpublic void test01(){ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("spring-config.xml");//这里需要注意,在获取userController对象的使用需要使用小驼峰的命名格式,不然就获取不到这个对象UserController userController = ctx.getBean("userController", UserController.class);userController.hello();}

这里只是用一个类注解来演示一下即可。

3、使用方法注解注入对象@Bean

使用方法注解@Bean需要注意一些事项,我们在使用类注解的时候,只需要将类注解加载类上就可以直接使用依赖查找的方式获取Bean对象,但是方法注解的使用和类注解存在很大的差异。

1. 使用方法注解@Bean,需要方法的返回值是一个对象。
2. 方法注解@Bean不能单独使用,需要和五大类注解搭配使用。
3. 使用方法注解@Bean将对象注入到容器中,获取对象时默认的对象名为方法名。

这里使用User实体类演示,这里不给User类添加五大类注解,以及不在配置文件中声明User类的Bean对象。所以我们是不能直接通过getBean方法来获取到User对象的。

@Data
public class User {private Integer id;private String name;private String dept;
}
@Controller
public class UserController {@Beanpublic User test(){User user = new User();user.setId(101);user.setName("张三");user.setDept("经理");return user;}
}@Testpublic void test02(){ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");Object user = context.getBean("test");System.out.println(user);}

3.1、添加@Bean注解的方法的重命名

使用依赖查找获取使用方法注解@Bean存储的对象时,传入的对象默认情况下为方法名,虽说这样获取没有问题,但是在一个项目中的方法起名时有getXXX获取setXXX,用这种默认的方式获取对象,总是看起来不是很舒服。所以这里可以使用@Bean注解的name属性来设置对象别名

//方式一
@Bean("abc");
//方式二
@Bean(name="abc")
//方式三
@Bean(value="abc")
//方式四:支持指定多个名称
@Bean(name={"abc","ddd","ccc"})
//方式五
@Bean(value={"aabc","ddd","eee"})
//方式六
@Bean({"aaa","bbb"})

这里name和value属性的作用相同。

使用@Bean注解需要注意的细节

1. 给Bean重命名了之后,就不能使用Bean默认的对象名了。
2. 在给Bean重命名时,不能和方法所在的当前类起相同的名字,也就是当前类名的小驼峰形式。否则在获取对象的时候,对象名重复了,就会报错。
3. 对同一类中多个方法返回的对象Bean重命名了相同的名字,程序执行的时候,由上而下,只会将第一个方法返回的Bean存储到Spring中,之后的相同名称的Bean就不会存储到Spring中了,容器会忽略掉。
4. 不同类中的方法在使用了@Bean注解向spring中注入对象,并且重命名起了相同的名字,这个时候容器中只能存储一个同名的Bean对象,可以使用@Order()注解,来指定哪个类里面的方法先执行,那么Spring容器中也就保存了谁。
5.在Spring中方法添加了@Bean注解后,这个方法就不能设置参数,否则执行之后就会报错。

二、从Spring容器中获取Bean对象

在spring框架中,获取容器中的对象(Bean)主要有两种方式:一种是通过applicationContext的getBean方法直接获取,另一种是通过依赖注入(DI)自动获取。

1、通过getBean方法获取Bean对象

@Testpublic void test02(){//读取spring上下文,并读取spring-config.xml文件ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");//根据对象名获取Bean对象Object user = context.getBean("test");//使用Bean对象System.out.println(user);}

getBean方法有三种不同的用法(参数不同,用法不同)

①根据对象的名称获取Bean对象

//通过对象名来获取Bean对象,他的返回值Object类型,需要我们将其转换为想要的类型
User user = (User)context.getBean("user")

②根据类型来获取Bean对象

User user = context.getBean(User.class)

这种方式获取Bean对象,当Beans中只有一个类的实例是没有问题的,但是当有这个类的多个实例,就会报NoUniqueBeanDefinitionException异常,表示注入的对象不是唯一的。

③根据对象名和类型来获取Bean对象

User user = context.getBean("user",User.class)

这种方式相比于第一种更优雅,不需要强制类型转换。相比第二种也不会出现方式二中的问题。

2、通过依赖注入的方式获取Bean对象

1.1、通过在spring-config.xml文件中配置实现依赖注入
1>、属性注入(Setter注入)

spring-config.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:content="http://www.springframework.org/schema/context"xmlns:context="http://www.springframework.org/schema/context"xmlns:aop="http://www.springframework.org/schema/aop"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/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">  <!--定义被依赖的Bean--><bean id="userDao" class="com.it.dao.impl.userDaoImpl"></bean><!--定义依赖注入的Bean--><bean id="userService" class="com.it.service.impl.UserServiceImpl"><!--使用property元素进行setter注入--><property name="userDaoImpl" ref="userDao"></property></bean>
</beans>

如果一个类的Bean对象依赖于其他的类的Bean对象,那么使用属性赋值的方式,将依赖的对象通过赋值的方式给到当前类的Bean对象。这里使用ref属性进行复制。

Bean对象的普通属性赋值,使用下面这种方式

    <bean id="user" class="com.it.pojo.User"><constructor-arg name="id" value="1"></constructor-arg><constructor-arg name="name" value="张三"></constructor-arg><constructor-arg name="password" value="123"></constructor-arg></bean>

UserDaoImpl类

@Data
public class userDaoImpl  implements UserDao {public void print() {System.out.println("userDaoImpl");}
}

UserServiceImpl类

@Data
public class UserServiceImpl implements UserService {private UserDao userDaoImpl;public void print(){System.out.println("userServiceImpl");}
}

测试代码

public class UserTest {@Testpublic void test(){ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");UserServiceImpl userService = ctx.getBean("userService",UserServiceImpl.class);userService.print();}
2> 、构造方法注入

构造注入是通过Bean的构造函数来注入依赖项。在Spring-config.xml中,可以使用<constructor-arg>元素来指定构造函数的参数。

spring-config.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:content="http://www.springframework.org/schema/context"xmlns:context="http://www.springframework.org/schema/context"xmlns:aop="http://www.springframework.org/schema/aop"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/contexthttps://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd"><!--被依赖的Bean对象--><bean id="userMapperImpl" class="com.it.mapper.impl.UserMapperImpl"></bean><!--使用构造注入,将依赖对象注入到userServiceImpl对象中--><bean id="userServiceImpl" class="com.it.service.impl.UserServiceImpl" ><!-- 使用constructor-arg标签来指定构造方法的参数,当只有一个参数的时候可以直接使用ref属性来指定传入构造方法中的参数对象 --><constructor-arg ref="userMapperImpl"></constructor-arg></bean>
</beans>

被依赖的对象的类

public class UserMapperImpl implements UserMapper {public void print() {System.out.println("hello world");}
}

依赖注入的类

public class UserServiceImpl implements UserService {private UserMapper userMapperImpl;//构造方法public UserServiceImpl(UserMapper userMapperImpl){this.userMapperImpl = userMapperImpl;}public void print() {userMapperImpl.print();}
}

如果构造方法中存在多个形参,可以使用这种方式

<!--使用构造注入,将依赖对象注入到userServiceImpl对象中--><bean id="userServiceImpl" class="com.it.service.impl.UserServiceImpl" ><!--通过参数的位置(索引)来注入--><constructor-arg index="0" ref="userMapperImpl"></constructor-arg><constructor-arg index="1" ref="userMapperImpl"></constructor-arg></bean>
1.2、通过注解的方式实现依赖注入
1>、 属性注入

属性注入只需要在选哟注入对象的属性上添加@Autowired或者@Resource注解即可。

1️⃣容器中同类型的对象只有一个:直接将获取到的对象注入到当前属性中。

@Repository
public class UserMapperImpl implements UserMapper {public void print() {System.out.println("hello world");}
}

将容器中的UserMapperImpl类的Bean对象注入到UserServiceImpl类的Bean对象中

@Service
public class UserServiceImpl implements UserService {@Autowiredprivate UserMapper userMapperImpl;public void print() {userMapperImpl.print();}
}

2️⃣容器中同类型的对象存在多个:根据属性的名字来进行匹配

通过下面的例子来来了解容器中存在多个同类型对象,在获取对象时没有给属性指定注入那个对象,程序会出现错误。

@Data
public class User {private Integer id;private String name;private String dept;
}

@Component
public class UserBean {@Bean("user1")public User addBean(){User user = new User();user.setId(1);user.setName("张三");user.setDept("经理");return user;}@Bean("user2")public User addBean2(){User user = new User();user.setId(2);user.setName("李四");user.setDept("职员");return user;}
}
@Service
public class UserServiceImpl implements UserService {@Autowiredprivate User user;public void print() {System.out.println(user.toString());}
}
 @Testpublic void test04(){ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");UserMapperImpl userMapperImpl = context.getBean("userMapperImpl", UserMapperImpl.class);userMapperImpl.print();}

这个时候由于我们使用@Bean存入了多个User类的对象,在执行这个程序的测试代码就会出现错误。

①这个时候就需要我们在注入User类的对象的时候,在UserServiceImpl类中,让属性名与存入容器时的对象名一致即可。

②使用@Autowired注解和@Qualifier搭配使用,根据@Qualifier注解的参数筛选对象

③也可以使用@Resource注解,这个注解的参数中存在name属性,可以通过name的值设置为这些对象中的一个即可。

2>、setter注入

使用setter注入,在setXXX方法上添加一个@Autowired或者@Resource注解即可,当然setter注入也存在和属性注入一样的问题,解决方法也和上述一样

@Repository
public class UserMapperImpl implements UserMapper {public void print() {System.out.println("你好世界");}
}

@Service
public class UserServiceImpl implements UserService {private UserMapper userMapperImpl;@Autowiredpublic void setUserMapperImpl(UserMapper userMapperImpl){this.userMapperImpl = userMapperImpl;}public void print() {userMapperImpl.print();}
}
3>、构造方法注入

使用构造方法注入,标准的写法在构造方法上添加一个@Autowired注解或者在构造方法上不加注解也可以实现注入效果,当然构造注入也存在和属性注入一样的容器中相同类型的对象个数问题。解决方法和上述一样。但是构造方法不支持使用@Resource注解。

1️⃣标准写法,添加注解

2️⃣不太标准的写法,构造方法上不加注解,这种写法是当前类中只有一个构造方法的时候可以使用,但是有多个构造方法的时候,构造方法上的@Autowired注解是不可以省略的。

三、Spring的AOP(面向切面编程)

AOP是一种编程思想,是对面向对象编程(OOP)的一种补充,它通过预编译方式和运行期动态代理方式实现,在不修改源代码的情况下,给程序系统统一添加额外功能的一种技术。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各个部分之间的耦合度降低,提高程序的可重用性,同时提高开发的效率。

AOP的底层是通过动态代理来实现的。Spring采用JDK Proxy和CGLib动态代理。

1、静态代理

静态代理需要我们在程序运行之前,手动的为每个类或者接口的实现类实现代理类,这样操作起来非常的麻烦,当我们的类没增添一个新的方法,代理类中相应的也要为新方法编写增强方法。

接口类型

public interface UserService2 {public void save();public int update(User user);public int test(int a,int b);
}

接口的实现类

@Service
public class UserServiceImpl implements UserService2 {public void save() {System.out.println("UserServiceImpl中的save方法");}public int update(User user) {System.out.println("UserServiceImpl中的update方法");return 0;}public int test(int a, int b) {System.out.println("UserServiceImpl中的test方法");return 0;}
}
public class UserService2Impl implements UserService2 {public void save() {System.out.println("userService2中的save方法");}public int update(User user) {System.out.println("userService2中的update方法");return 0;}public int test(int a, int b) {System.out.println("userService2中的test方法");return 0;}

接口的代理类

public class StaticProxy implements UserService2{//这里使用接口,是为了适应这个接口所有的实现类UserService2 dto;//通过构造方法可以将该接口的不同实现类赋值给dtopublic StaticProxy(UserService2 dto){this.dto = dto;}//在这个方法中就可以为具体的实现类的方法实现增强public void save() {System.out.println("save方法执行之前。。。。。。。。。");dto.save();System.out.println("save方法执行之后。。。。。。。。。");}public int update(User user) {System.out.println("update方法执行之前。。。。。。。。。");dto.update(user);System.out.println("update方法执行之后。。。。。。。。。");return 0;}public int test(int a, int b) {System.out.println("test方法执行之前。。。。。。。。。");dto.test(1,2);System.out.println("test方法执行之后。。。。。。。。。");return 0;}
}

测试类

public class ProxyTest {@Testpublic void test(){StaticProxy staticProxy = new StaticProxy(new UserServiceImpl());staticProxy.save();}
}

❓看到这里存在一些小问题为什么staticProxy类,需要继承接口,不继承接口,根据类中的代码,也能够为传输的实现类实现静态代理?

主要作用是为了让代理类的方法签名严格遵循接口定义,防止运行是发生错误。

2、动态代理

当想要给实现了某个接口的类中的方法,加一些额外的处理,比如日志,事务等。可以给这个类创建一个代理,也就是创建一个新的类,这个类不仅包含原来类方法的功能,而且还在原来的基础上添加了额外处理的新方法,这个代理类并不是定义好的,而是在程序运行时动态生成的,具有解耦意义,灵活、扩展性强。

Java中,动态代理通常使用Java.lang.reflect.Proxy类和Java.lang.reflect.InvocationHandler接口来实现。

1、JDK代理和CGLIB代理的区别
  1. 接口要求:JDK动态代理只能对实现了接口的类生成代理;而CGLIB代理可以为没有实现接口的类生成代理,是通过继承被代理类,在运行时动态的生成代理对象。

  2. 生成方式:JDK代理使用Java的反射机制来完成代理对象,而CGLIB代理使用CGLIB库生成代理对象,通过修改目标类的字节码来实现。所以该类不能被final修饰。

2、模拟实现动态代理
1、JDK动态代理

1️⃣第一种实现方式

测试类

这里是通过实现InvocationHandler接口的匿名内部类来实现UserServiceImpl类的动态代理。

 @Testpublic void test02(){UserServiceImpl service = new UserServiceImpl();//第一个参数是类加载器,这里使用当前测试类的类加载器//第二个参数是被代理对象实现的接口数组,也就是被代理对象实现了那些接口,还有一种写法是new Class<?>[]{UserServie.class},直接指定代理对象将要实现那些接口。//第三个参数是InvocationHandler的实现,他会在每个方法调用时被调用UserService2 proxy=(UserService2) Proxy.newProxyInstance(ProxyTest.class.getClassLoader(),                                                         service.getClass().getInterfaces(), new InvocationHandler() {//InvocationHandler的invoke方法会在代理对象的方法被调用时执行//这里的Object[] args代表的意思是一个对象数组,包含调用代理对象方法时传递的参数public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("方法执行之前------");//这里的args表示的是调用被代理对象的方法时传递的参数数组。Object res = method.invoke(service, args);System.out.println("方法执行之后******");return res;}});proxy.save();}

这种写法是JDK动态代理的一种实现方式,但是这种方式不够灵活,只能指定给一种对象实现动态代理。

2️⃣第二种实现方式

编写一个JDK动态代理的工具类,不论那个类想要通过JDK动态代理实现代理对象,都可以使用这个工具类

public class LogProxy implements InvocationHandler {//表示可以传递任何的对象private Object target;public LogProxy(Object target){this.target = target;}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;}public Object getProxy(){//第一个参数表示代理类的类加载器//第二个参数是一个接口数组,表示被代理对象实现的接口,也就是代理对象将实现的接口//表示当前对象return  Proxy.newProxyInstance(LogProxy.class.getClassLoader(),target.getClass().getInterfaces(),this);                              }}

测试类

    @Testpublic void test03(){UserService2 service2 = new UserService2Impl();//getProxy方法的所用时创建并返回一个动态代理对象UserService2 proxy = (UserService2)new LogProxy(service2).getProxy();proxy.save();}

❌上面这种测试方法是正确的,但是我们在测试的时候这样编写测试代码就会报ClassCastException异常。

//这段代码是一个错误展示
@Test
public void test03(){UserService2Impl service2 = new UserService2Impl();UserService2Impl proxy = (UserService2Impl)new LogProxy(service2).getProxy();proxy.save();
}

我们将Proxy.newProxyInstance返回的代理对象强制转换为UserService2Impl类型,会导致ClassCastException,这是因为Proxy.newProxyInstance创建的代理对象实现了target对象所实现的接口(在这里也就说实现了UserService接口,可以将其强转为UserService),而不是UserService2Impl类本身。

2、CGLIB动态代理

1️⃣第一种实现方式,通过创建类并继承MethodInterceptor接口的方式。

import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;import java.lang.reflect.Method;
//MethodInterceptor接口是CGLIB提供的,用于拦截对目标对象的方法调用
public class UserServiceHandler_CGLIB implements MethodInterceptor {private Object target;public UserServiceHandler_CGLIB(Object target){this.target = target;}//参数Object o 表示代理对象的实例//参数Method method被调用的方法的Method对象//Object[] objects 调用方法是传入的参数数组//MethodProxy methodProxy:CGLIB提供的MethodProxy实例,用于调用原始方法。使用他比直接使用Java反射更高效public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {System.out.println("方法增强,方法执行之前");//通过Java反射机制调用target对象的method方法,并传入objects参数数组。Object res = method.invoke(target,objects);System.out.println("方法增强,方法执行之后");return res;}/*** 用于生成代理对象* @return*/public Object getProxyInstance(){//由CGLIB提供,用于动态创建类的子类Enhancer enhancer = new Enhancer();//指定Enhancer要增强的目标类的父类,这里直接使用target.getClass方法获取目标对象的类enhancer.setSuperclass(target.getClass());//设置回调为当前UserServiceHandler_CGLIB实例。当通过CGLIB生成的代理对象调用任何方法时,// 都会调用UserServiceHandler_CGLIB的intercept方法enhancer.setCallback(this);//调用enhancer.create方法生成代理对象,并将其返回。这个代理对象会在调用其任何方法时,通过//UserServiceHandler_CGLIB的intercept方法进行拦截和处理return enhancer.create();}
}

测试类 

 @Testpublic void test05(){UserServiceImpl service = new UserServiceImpl();UserServiceImpl proxy =(UserServiceImpl) new UserServiceHandler_CGLIB(service).getProxyInstance();proxy.save();}

2️⃣第二种实现方式:通过MethodInterceptor接口的匿名内部类的方式来实现。

  @Testpublic void test04(){//创建要被代理的对象final UserService2Impl service = new UserService2Impl();//使用这个类,为被代理对象创建代理对象Enhancer enhancer = new Enhancer();//被代理对象的父类类型enhancer.setSuperclass(service.getClass());//回调函数,Enhancer创建的代理对象在调用方法之前都要经过intercept方法enhancer.setCallback(new MethodInterceptor() {//这里通过匿名内部类的方式对MethodInterceptor接口中的intercept方法进行了重写public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {System.out.println("方法增强,方法执行之前");Object result = method.invoke(service, objects);System.out.println("方法增强,方法执行之后");return result;}});//创建代理对象UserService2 proxy =(UserService2) enhancer.create();//执行代理对象的方法proxy.save();}
}

3、AOP的组成

  • 切面:是由切点和通知组成,它既包含了横切逻辑的定义,也包含了连接点的定义。简单来说,切面就是当前AOP功能的类型,比如当前AOP是用户登录和鉴权的功能,那么它就是一个切面。

  • 切点:他的作用就是提供一组规则用来匹配连接点的。

  • 通知:切面中必须完成的工作,在AOP术语中,切面的工作被称之为通知。简单来说,当控制器方法(controller层方法)被拦截之后,触发执行的具体方法就是通知。

  • 连接点:是指在应用程序执行过程中可以被拦截的特定点。换句话说,连接点是在程序执行过程中的某个特定位置,它可以是方法调用,方法执行、异常抛出等。连接点是AOP中的基本单位,它代表了一个可以被切面拦截的位置,允许在这些位置插入额外的逻辑。切面可以通过连接点来捕获并应用他们的横切关注点。

  • 织入:将切面逻辑动态的插入到目标对象的方法执行流程中,实现了代码的解耦和复用。

1️⃣普通类

@Repository
public class EmpMapperImpl{public void save() {System.out.println("empMapper中实现save");}public void delete(int id) {System.out.println("empMapper中实现delete");}public int update() {return 1;}
}

2️⃣切面类

@Component  //交给spring管理
@Aspect //定义切面类
public class AspectProxy {//* com.it.mapper.impl.EmpMapperImpl.save()切入点表达式//前置通知@Before(value = "execution(* com.it.mapper.impl.EmpMapperImpl.*(..))")public void before(){System.out.println("前置通知...");}//后置通知@After(value = "execution(* com.it.mapper.impl.EmpMapperImpl.*(..))")public void after(){System.out.println("后置通知...");}//环绕通知@Around(value = "execution(* com.it.mapper.impl.EmpMapperImpl.*(..))")public Object around(ProceedingJoinPoint joinPoint) throws Throwable {System.out.println("环绕之前...");Object result = joinPoint.proceed();System.out.println("环绕之后...");return result;}//返回通知@AfterReturning(value = "execution(* com.it.mapper.impl.EmpMapperImpl.*(..))")public void afterReturn(){System.out.println("返回通知...");}//抛出异常通知@AfterThrowing(value = "execution(* com.it.mapper.impl.EmpMapperImpl.*(..))")public void afterThrow(){System.out.println("抛出异常通知...");}
}

3️⃣测试类

public class AspectTest {@Testpublic void test() {ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");EmpMapperImpl mapper = ctx.getBean(EmpMapperImpl.class);mapper.save();}

从上面的代码中我们可以看到

🍂连接点:在EmpMapperImpl类中,每一个方法调用都可以视为一个连接点。这一位置,无论我们是否打算对这些方法进行增强,他们都存在与程序中,并可能在某个时间点被执行。🍂切点:我们决定只对save方法进行增强,一遍在每次保存员工信息是记录一些日志信息。为了实现这一点,我们需要在切面类中定义一个切点,该切点通过切入点表达式来指定只配置EmpMapperImpl类中的Save方法🍂织入:在EmpMapperImpl类中想要每个方法在执行之前/之后,记录一个日志,通过AOP的方式,我们可以在不修改源代码的方式,将记录日志的逻辑织入到EmpMapperImpl的save方法中。🍁链接点匹配到切入点表达式,那么这个连接点就是切入点。

🍃织入过程

  1. 当Spring容器启动时,他会识别并注册所有的切面、切点和通知
  2. 当应用程序中的某个组件(可能时服务层、控制器层和其他任何层)调用EmpMapperImpl的方法时,Spring Aop框架会拦截这个调用
  3. 框架会检查是否存在与这个调用相匹配的切点。在上述例子中,save方法的调用切入点表达式相匹配。
  4. 一但找到匹配的切点,框架就会执行与该切点相关联的通知,记录日志。
  5. 完成通知的执行后,框架会继续执行原始的save方法。

❗❗❗ 当然我们在定义切入点表达式的时候也可以通过这个方式来实现

package com.it.mapper.proxy;import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Component  //交给spring管理
@Aspect //定义切面类
public class UserAspectProxy {//通过一个没有方法体的方法定义一个切入点表达式@Pointcut(value = "execution(* com.it.mapper.impl.UserMapperImpl.*(..))")public void pointCut() {}//其他的方法去调用这个方法即可//前置通知@Before("pointCut()")public void before(){System.out.println("前置通知...");}//后置通知@After("pointCut()")public void after(){System.out.println("后置通知...");}//环绕通知@Around("pointCut()")public Object around(ProceedingJoinPoint joinPoint) throws Throwable {System.out.println("环绕之前...");Object result = joinPoint.proceed();System.out.println("环绕之后...");return result;}//返回通知@AfterReturning("pointCut()")public void afterReturn(){System.out.println("返回通知...");}//抛出异常通知@AfterThrowing("pointCut()")public void afterThrow(){System.out.println("抛出异常通知...");}
}

🍂通知的执行顺序

①环绕通知最先被执行

②其次是前置通知

③在是目标方法

④再是后置通知

⑤最后所有的方法执行完成之后,环绕通知方法才会执行完成。

1、实现AOP需要添加的依赖和配置

pom.xml文件

<dependencies><!-- spring-aop 提供了面向切面编程的支持,它允许你定义横切关注点 --><dependency><groupId>org.springframework</groupId><artifactId>spring-aop</artifactId><version>5.3.5</version></dependency><!-- aspectjtools 提供了编译和织入方面的工具,它允许你以声明的方式定义切面 --><dependency><groupId>org.aspectj</groupId><artifactId>aspectjtools</artifactId><version>1.9.5</version></dependency><!-- aoplliance是一个标准化的AOP API,它定义了一组用于面向切面编程的接口和类--><dependency><groupId>aopalliance</groupId><artifactId>aopalliance</artifactId><version>1.0</version></dependency><!-- aspectjweaver 是Aspectj的一部分,它通过了在运行时动态织入方面的能力--><dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>1.9.0</version></dependency></dependencies>

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"xmlns:content="http://www.springframework.org/schema/context"xmlns:context="http://www.springframework.org/schema/context"xmlns:aop="http://www.springframework.org/schema/aop"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/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"><!-- 添加扫描路径 --><content:component-scan base-package="com.it"/><!-- 用于在Spring应用程序中启动对AspectJ注解的支持,告诉Spring框架自动检测那些使用了AspectJ注解的类(如@Aspect、@Before、@After等) --><aop:aspectj-autoproxy/>
</beans>

2、切入点表达式

上述的切入点表达式表达的意思是:拦截UserMapperImpl类中所有方法其参数为任意参数并且返回值为任意类型的返回值。

  • execution:表示的意思为执行,执行的是后面()中的规则。

  • ..:表示不定式传参。

🍂常见的表达式示例

  • execution(* .com.it.controller.UserController.*(..)):配置UserController类中所有的方法。

  • execution(* .com.it.controller.UserController+.*(..)):匹配该类的子类包括该类的所有方法。

  • execution(* .com.it.controller.* . *(..)):匹配com.it.controller包下的所有类的所有方法。

  • execution(* .com.it.controller..* .*(..)):匹配com.it.controller包下,子孙包下所有类的所有方法

  • execution(* addUser(String,int)):匹配addUser方法,其第一个参数类型是String,第二个参数类型是int.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/1556997.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

C语言练习

接下来一段时间&#xff0c;博主要参加军训没有时间更新C语言知识点&#xff0c;但博主会每天更新一道C语言的题作为分享。 1.计算并显示整数的差 分析&#xff1a;1.题目并不难&#xff0c;首先我们要知道printf这个库函数&#xff0c;是用来打印数据到屏幕的库函数 2.设置变…

【AI知识点】反向传播(Backpropagation)

反向传播&#xff08;Backpropagation&#xff09; 是训练神经网络的核心算法&#xff0c;它通过反向逐层计算损失函数对每个权重的梯度&#xff0c;来反向逐层更新网络的权重&#xff0c;从而最小化损失函数。 一、反向传播的基本概念 1. 前向传播&#xff08;Forward Propag…

文件丢失一键找回,四大数据恢复免费版工具推荐!

丢失数据的情况虽然不经常出现&#xff0c;但一旦出现都会让人头疼不已&#xff0c;而这时候&#xff0c;要如何恢复丢失的数据呢&#xff1f;一款免费好用的数据恢复工具就派上用场了&#xff01;接下来就为大家推荐几款好用的数据恢复工具&#xff01; 福昕数据恢复 直达链…

Redis list 类型

list类型 类型介绍 列表类型 list 相当于 数组或者顺序表 list内部的编码方式更接近于 双端队列 &#xff0c;支持头插 头删 尾插 尾删。 需要注意的是&#xff0c;Redis的下标支持负数下标。 比如数组大小为5&#xff0c;那么要访问下标为 -2 的值可以理解为访问 5 - 2 3 …

【韩顺平Java笔记】第8章:面向对象编程(中级部分)【272-284】

272. 包基本介绍 272.1 看一个应用场景 272.2 包的三大作用 272.3 包的基本语法 273. 包原理 274. 包快速入门 在不同的包下面创建不同的Dog类 275. 包命名 276. 常用的包 一个包下,包含很多的类,java 中常用的包有: java.lang.* //lang 包是基本包&#xff0c;默认引入&…

农业政策与市场分析:解读当前政策导向下的农业发展趋势

在快速变化的全球经济格局中&#xff0c;农业作为国家稳定发展的基石&#xff0c;其政策走向与市场动态备受瞩目。本文将深入剖析当前的农业政策背景&#xff0c;探讨其对设计的导向作用&#xff0c;以及市场趋势的反馈与影响&#xff0c;为农业可持续发展提供洞见。 1. 政策背…

【大模型理论篇】大模型相关的周边技术分享-关于《NN and DL》的笔记

本文所要介绍的一本书《Neural Networks and Deep Learning》&#xff0c;该书作者Michael Nielsen&#xff0c;Y Combinator Research的研究员&#xff0c;是多年之前自己看的一本基础书籍&#xff0c;很适合入门了解一些关于深度学习的概念知识&#xff0c;当然也包含了一些小…

MyBatis 批量插入方案

MyBatis 批量插入 MyBatis 插入数据的方法有几种&#xff1a; for 循环&#xff0c;每次都重新连接一次数据库&#xff0c;每次只插入一条数据。 在编写 sql 时用 for each 标签&#xff0c;建立一次数据库连接。 使用 MyBatis 的 batchInsert 方法。 下面是方法 1 和 2 的…

三相逆变器中LCL滤波器分析

1.LCL滤波器 传统三相逆变器使用的是L型滤波器&#xff0c;其设计简单&#xff0c;但也存在着一些问题&#xff0c;如在同样的滤波效果下&#xff0c;L型滤波器电感尺寸、重量较大&#xff0c;成本较高&#xff0c;并且随着电感值的增大&#xff0c;其上的电压降增加比较明显&…

【MySQL必知会】事务

目录 &#x1f308;前言&#x1f308; &#x1f4c1; 事务概念 &#x1f4c1; 事务操作 &#x1f4c1; 事务提交方式 &#x1f4c1; 隔离级别 &#x1f4c1; MVCC &#x1f4c2; 3个隐藏列字段 &#x1f4c2; undo日志 &#x1f4c2; Read View视图 &#x1f4c1; RR和R…

【GESP】C++一级练习BCQM3028,输入-计算-浮点型格式化输出

目前的几道题主要围绕浮点型的计算和格式化输出。强化基础语法练习。 详解详见&#xff1a;https://www.coderli.com/gesp-1-bcqm3028/ 【GESP】C一级练习BCQM3028&#xff0c;输入-计算-浮点型格式化输出 | OneCoder目前的几道题主要围绕浮点型的计算和格式化输出。强化基础语…

说说BPMN概念及应用

BPMN&#xff08;Business Process Modeling and Notation&#xff09;即业务流程建模与标注&#xff0c;是一种由OMG&#xff08;Object Management Group&#xff0c;对象管理组织&#xff09;制定的业务流程建模语言。以下是对BPMN标准的详细解释&#xff1a; 一、BPMN的起…

短剧系统源码短剧平台开发(H5+抖小+微小)部署介绍流程

有想法加入国内短剧赛道的请停下脚步&#xff0c;耐心看完此篇文章&#xff0c;相信一定会对您有所帮助的&#xff0c;下面将排序划分每一个步骤&#xff0c;短剧源码、申请资料、服务器选择、部署上架到正常运行等几个方面&#xff0c;整理了一些资料&#xff0c;来为大家举例…

Spring Boot助力医院数据管理

2相关技术 2.1 MYSQL数据库 MySQL是一个真正的多用户、多线程SQL数据库服务器。 是基于SQL的客户/服务器模式的关系数据库管理系统&#xff0c;它的有点有有功能强大、使用简单、管理方便、安全可靠性高、运行速度快、多线程、跨平台性、完全网络化、稳定性等&#xff0c;非常适…

MySQL进阶学习一(2024.10.07版)

2024-10-06 -------------------------------------------------------------------------------------------------------------------------------- 1.一条SQL语句是如何执行的 单进程的多线程模型 MySQL的物理目录 show global variables like "%basedir%"; …

LSTM时序预测 | Python实现LSTM长短期记忆神经网络时间序列预测

本文内容&#xff1a;Python实现LSTM长短期记忆神经网络时间序列预测&#xff0c;使用的数据集为AirPassengers 目录 数据集简介 1.步骤一 2.步骤二 3.步骤三 4.步骤四 数据集简介 AirPassengers 数据集的来源可以追溯到经典的统计和时间序列分析文献。原始数据集由 Box,…

面向对象特性中 继承详解

目录 概念&#xff1a; 定义&#xff1a; 定义格式 继承关系和访问限定符 基类和派生类对象赋值转换&#xff1a; 继承中的作用域&#xff1a; 派生类的默认成员函数 继承与友元&#xff1a; 继承与静态成员&#xff1a; 复杂的菱形继承及菱形虚拟继承&#xff1a; 虚…

VGG16模型实现MNIST图像分类

MNIST图像数据集 MNIST&#xff08;Modified National Institute of Standards and Technology&#xff09;是一个经典的机器学习数据集&#xff0c;常用于训练和测试图像处理和机器学习算法&#xff0c;特别是在数字识别领域。该数据集包含了大约 7 万张手写数字图片&#xf…

喜讯 | 攸信技术入选第六批专精特新“小巨人”企业

日前&#xff0c;根据工信部评审结果&#xff0c;厦门市工业和信息化局公示了第六批专精特新“小巨人”企业和第三批专精特新“小巨人”复核通过企业名单&#xff0c;其中&#xff0c;厦门攸信信息技术有限公司进入第六批专精特新“小巨人”企业培育。 “专精特新”企业是指具有…

图像分割恢复方法

传统的图像分割方法主要依赖于图像的灰度值、纹理、颜色等特征&#xff0c;通过不同的算法将图像分割成多个区域。这些方法通常可以分为以下几类&#xff1a; 1.基于阈值的方法 2.基于边缘的方法 3.基于区域的方法 4.基于聚类的方法 下面详细介绍这些方法及其示例代码。 1. 基…