本文素材来自动力结点王妈妈的课程
Spring 是于 2003 年兴起的一个轻量级的 Java 开发框架,它是为了解 决企业应用开发的复杂性而创建的。Spring的核心是控制反转(IoC)和面向切面编程(AOP)。Spring 根据代码的功能特点,使用 Ioc 降低业务对象之间耦合度。通过 Spring 提供的 AOP 功能,方便进行面向切面的编程。
控制反转(IoC,Inversion of Control),是一种思想。指将传统上由程序代码直接操控的对象调用权交给容器,通过容器来实现对象的装配和管理。Spring 框架使用依赖注入(DI)实现 IoC。
1、创建maven项目
2、引入 maven 依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
3、定义接口与实体类
public interface SomeService {
void doSome();
}
public class SomeServiceImpl implements SomeService {
public SomeServiceImpl() {
super();
System.out.println("SomeServiceImpl无参数构造方法");
}
@Override
public void doSome() {
System.out.println("doSome业务方法");
}
}
4、创建 Spring 配置文件
在 src/main/resources/目录现创建一个 xml 文件,文件名可以随意,但Spring 建议的名称为applicationContext.xml。注意要将resources文件将指定为资源文件夹(这个文件夹中的文件编译之后都会放在类路径下),如何指定?
右键文件夹 → Mark Directory as → Resources Root
<?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 http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!--
注册bean对象
id:自定义对象的名称
class:类的全限定名称,不能是接口
-->
<bean id="someService" class="com.why.SomeServiceImpl"></bean>
</beans>
5、定义测试类
public class MyTest {
@Test
public void test01(){
//指定spring配置文件的位置和名称
String resource = "applicationContext.xml";
//创建spring容器对象
ApplicationContext ac = new ClassPathXmlApplicationContext(resource);
//使用id从spring容器中获取对象
SomeService someService = (SomeService) ac.getBean("someService");
//执行对象的方法
someService.doSome();
}
}
public class School {
private String schoolName;
private String addr;
/*setter*/
/*toString*/
}
public class Student {
private String name;
private int age;
private School school;
/*setter*/
/*toString*/
}
<!--使用set方法注入参数-->
<bean id="mySchool" class="com.why.School">
<!--简单类型-->
<property name="schoolName" value="浙江中医药大学"></property>
<property name="addr" value="浙江杭州"></property>
</bean>
<bean id="zs" class="com.why.Student">
<property name="name" value="张三"></property>
<property name="age" value="20"></property>
<!--引用类型-->
<property name="school" ref="mySchool"></property>
</bean>
//Student构造函数
public Student(String stuName, int stuAge, School stuSchool) {
this.name = stuName;
this.age = stuAge;
this.school = stuSchool;
}
<!--构造注入-->
<bean id="ls" class="com.why.Student">
<constructor-arg name="stuName" value="李四"></constructor-arg>
<constructor-arg name="stuAge" value="20"></constructor-arg>
<constructor-arg name="stuSchool" ref="mySchool"></constructor-arg>
</bean>
<!--index:指明该参数对应着构造器的第几个参数,从0开始(少用)-->
<bean id="ww" class="com.why.Student">
<constructor-arg index="0" value="王五"></constructor-arg>
<constructor-arg index="1" value="20"></constructor-arg>
<constructor-arg index="2" ref="mySchool"></constructor-arg>
</bean>
<!--index属性不要也行,但要注意:若参数类型相同或之间有包含关系,则需要保证赋值顺序要与构造器中的参数顺序一致-->
<bean id="zl" class="com.why.Student">
<constructor-arg value="赵六"></constructor-arg>
<constructor-arg value="20"></constructor-arg>
<constructor-arg ref="mySchool"></constructor-arg>
</bean>
容器是通过调用者的 bean 类的属性名与配置文件的被调用者 bean 的id 进行比较而实现自动注入的
public class Student {
private String name;
private int age;
private School schoolXXX;
/*setter*/
/*toString*/
}
byName方式自动注入时Spring会根据用于类型的变量名去调用相应的setter方法
(比如这里我们要注入zy对象 Spring根据autowire的属性发现是根据byName方式自动注入。Spring会找到Student中的Schoo引用类型,根据schoolXXX属性名在容器中找到名为schoolXXX的对象(如果在找不到schoolXXX对象Spring就会去找引用类型名首字母小写的对象本例中为school 如果两个都找不到就会报错)然后去调用setSchoolXXX方法将引用类型注入)
<bean id="schoolXXX" class="com.why.School">
<property name="schoolName" value="温州大学"></property>
<property name="addr" value="浙江温州"></property>
</bean>
<bean id="zy" class="com.why.Student" autowire="byName">
<property name="name" value="张扬"></property>
<property name="age" value="20"></property>
</bean>
使用 byType 方式自动注入,要求:配置文件中被调用者 bean 的 class 属性指定的类,要与代码中调用者 bean 类的某引用类型属性类型同源。即要么相同,要么有 is-a 关系(子类或是实现类)。
<bean id="cb" class="com.why.Student" autowire="byType">
<property name="name" value="陈八"></property>
<property name="age" value="20"></property>
</bean>
如果使用byType方式自动注入要求同源的被调用 bean只能有一个否则会报以下错误
Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type ‘com.why.School’ available: expected single matching bean but found 2: mySchool,school
在实际应用里,随着应用规模的增加,系统中 Bean 数量也大量增加,导致配置文件变得非常庞大、臃肿。为了避免这种情况的产生,提高配置文件的可读性与可维护性,可以将 Spring 配置文件分解成多个配置文件。
spring-school.xml
<bean id="middleSchool" class="com.why.School">
<property name="schoolName" value="温州中学"></property>
<property name="addr" value="浙江温州"></property>
</bean>
spring-student.xml
<bean id="lh" class="com.why.Student">
<property name="name" value="林浩"></property>
<property name="age" value="20"></property>
<!--引用类型-->
<property name="school" ref="middleSchool"></property>
</bean>
<!--引入spring-school.xml-->
<import resource="classpath:spring-school.xml"/>
applicationContext.xml(主配置文件)
<import resource="classpath:spring-school.xml"/>
<import resource="classpath:spring-student.xml"/>
<!--也可使用通配符*-->
<!-- <import resource="classpath*:spring-*.xml"/> -->
对于 DI 使用注解,将不再需要在 Spring 配置文件中声明 bean 实例。需要在 Spring 配置文件中配置组件扫描器,用于在指定的基本包中扫描注解。
<!--声明组件扫描器:指定注解所在的包-->
<context:component-scan base-package="com.why"/>
<!-- 指定多个包
<context:component-scan base-package="com.why,com.ehy"/> 第1种方式:逗号分隔
<context:component-scan base-package="com.why;com.ehy"/> 第2种方式:分号分隔
<context:component-scan base-package="com.why com.ehy"/> 第3种方式:空格分隔
<context:component-scan base-package="com"/> 第4种方式:指定到父包,也会扫描到子包下级的子包-->
需要在类上使用注解@Component,该注解的 value 属性用于指定该bean 的 id 值
//@Component 不指定value属性,bean的id是类名的首字母小写school
@Component("collage")//类似xml中的<bean id="collage" class="com.why.School"></bean>
public class School {
private String schoolName;
private String addr;
}
使用该注解完成属性注入时,类中无需 setter。当然,若属性有 setter,则也可将其加到 setter 上。
@Component("collage")
public class School {
@Value("浙江大学")
private String schoolName;
@Value("浙江杭州")
private String addr;
/*toString()*/
}
@Component("tq")
public class Student {
@Value("田七")
private String name;
@Value("20")
private int age;
@Autowired
private School school;
/*相当于
<bean id="tq" class="com.why.Student" autowire="byType">
<property name="name" value="田七"></property>
<property name="age" value="20"></property>
</bean>
*/
}
需要在引用属性上联合使用注解@Autowired 与@Qualifier。@Qualifier的 value 属性用于指定要匹配的 Bean 的 id 值。类中无需 set 方法,也可加到set 方法上。
@Component("tq")
public class Student {
@Value("田七")
private String name;
@Value("20")
private int age;
@Autowired
@Qualifier("middleSchool")
private School school;
@Autowired 还有一个属性 required,默认值为 true,表示当匹配失败后,会终止程序运行。若将其值设置为 false,则匹配失败,将被忽略,未匹配的属性值为 null。
Spring 提供了对 jdk 中@Resource 注解的支持。@Resource 注解既可以按名称匹配 Bean,也可以按类型匹配 Bean。默认是按名称注入,采用默认按名称的方式注入按名称不能注入 bean时,则会按照类型进行 Bean 的匹配注入。
@Component("tq")
public class Student {
@Value("田七")
private String name;
@Value("20")
private int age;
@Resource //byType
//@Resouces(name="middleSchool") byName
private School school;
}
AOP(Aspect Orient Programming),面向切面编程。面向切面编程是从动态角度考虑程序运行过程。利用 AOP 可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
对于 AOP 这种编程思想,很多框架都进行了实现。Spring 就是其中之一,可以完成面向切面编程。然而,AspectJ 也实现了 AOP 的功能,且其实现方式更为简捷,使用更为方便,而且还支持注解式开发。所以,Spring 又将AspectJ 的对于 AOP 的实现也引入到了自己的框架中。
AspectJ 中常用的通知有五种类型:
(1)前置通知 @Before
(2)后置通知 @AfterReturning
(3)环绕通知 @Around
(4)异常通知 @AfterThrowing
(5)最终通知 @After
execution(访问权限类型? 返回值类型 包名类名? 方法名(参数类型和参数个数) 抛出异常类型?)
?表示可选部分
返回值类型和方法名(参数类型和参数数量)是必须要有的
符号 | 意义 |
---|---|
* | 0至多个任意字符 |
… | 用在方法参数中表示任意多个参数<br>用在包名后面表示当前包以及其子包路径 |
+ | 用在类名后面表示当前类及子类<br>用在接口后表示当前接口及其实现类 |
例:
1、创建maven工程
2、引入相关依赖
完整依赖
<?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">
<modelVersion>4.0.0</modelVersion>
<groupId>com.why</groupId>
<artifactId>ch08-Spring-aop</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<!--测试单元-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<!--spring依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<!--AspectJ依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
3、定义业务接口和实现类
public interface SomeService {
void doSome();
int doOther(int x,int y);
}
public class SomeServiceImpl implements SomeService {
@Override
public void doSome() {
System.out.println("执行了doSome业务方法");
}
@Override
public int doOther(int x, int y) {
return x>y ? x : y;
}
}
4、定义切面类
/**
*@Aspect:是AspectJ框架的注解表示当前类是切面类
*/
@Aspect
public class MyAspect {
@Before(value = "execution(* com.why.SomeServiceImpl.doSome(..))")
public void before(){
System.out.println("前置通知:在目标方法之前执行,例如输出日志");
}
}
5、声明目标对象切面类对象
<!--声明目标类对象-->
<bean id="target" class="com.why.SomeServiceImpl"></bean>
<!--声明切面类对象-->
<bean id="myAspect" class="com.why.MyAspect"></bean>
6、注册 AspectJ 的自动代理
<!--声明自动代理生成器,创建代理-->
<aop:aspectj-autoproxy/>
7、测试
@Test
public void test01(){
String resource = "applicationContext.xml";
ApplicationContext ac = new ClassPathXmlApplicationContext(resource);
SomeService target = (SomeService) ac.getBean("target");
target.doSome();
}
结果
前置通知:在目标方法之前执行,例如输出日志
执行了doSome业务方法
Process finished with exit code 0
@Before(value = "execution(* com.why.SomeServiceImpl.doOther(..))")
public void beforeOther(JoinPoint joinPoint){
/*不光前置通知的方法,可以包含一个 JoinPoint 类型参数,所有的通知方法均可包含该参数。*/
System.out.println("连接点的方法定义:"+joinPoint.getSignature());
System.out.println("连接点方法的参数个数:"+joinPoint.getArgs().length);
/*
方法参数信息
Object[] args = joinPoint.getArgs();
*/
}
调用doOther方法的执行结果
连接点的方法定义:int com.why.SomeService.doOther(int,int)
连接点方法的参数个数:2
执行了other业务方法
在目标方法执行之后执行。由于是目标方法之后执行,所以可以获取到目
标方法的返回值。该注解的 returning 属性就是用于指定接收方法返回值的变
量名的。
@AfterReturning(value = "execution(* com.why.SomeServiceImpl.doOther(..))",returning = "result")
public void afterReturning(Object result){
if (result != null) {
Integer i = (Integer) result;
result = i * 100;
}
System.out.println("后置通知:在目标方法执行后的功能增强,如事务的处理");
System.out.println("较大数的100倍:"+result);
}
调用doOther方法后的执行结果
执行了other业务方法
后置通知:在目标方法执行后的功能增强,如事务的处理
较大数的100倍:200
Process finished with exit code 0
在目标方法执行之前之后执行。被注解为环绕增强的方法要有返回值Object 类型。并且方法可以包含一个 ProceedingJoinPoint 类型的参数。接口 ProceedingJoinPoint 其有一个 proceed()方法,用于执行目标方法。若目标方法有返回值,则该方法的返回值就是目标方法的返回值。最后,环绕增强
方法将其返回值返回。该增强方法实际是拦截了目标方法的执行。
//接口方法
String doFirst(String name,int age);
//接口方法实现类
@Override
public String doFirst(String name,int age) {
System.out.println("执行了doFirst业务方法");
return "doFirst";
}
定义切面
@Around(value = "execution(* com.why.SomeServiceImpl.doFirst(..))")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
Object object;
System.out.println("环绕通知:在目标方法之前执行,如输出日志");
//执行目标方法
object = pjp.proceed();
System.out.println("环绕通知:在目标方法之后执行,如事务处理");
System.out.println(object.toString());
return object;
}
在目标方法抛出异常后执行。该注解的 throwing 属性用于指定所发生的异常类对象。当然,被注解为异常通知的方法可以包含一个参数 Throwable,参数名称为 throwing 指定的名称,表示发生的异常对象。
@AfterThrowing(value = "execution(public * com.why.SomeServiceImpl.doSecond(..))",throwing = "ex")
public void afterThrowing(Throwable ex){
/*
异常通知可以做什么?
把异常发生的时间、地点、原因记录到数据库,日志文件等等
可以在异常发生时,把异常信息通过邮件、短信发送给开发人员
*/
System.out.println("异常通知:在目标方法抛出异常执行,异常原因:"+ex.getMessage());
}
异常通知:在目标方法抛出异常执行,异常原因:/ by zero
java.lang.ArithmeticException: / by zero
Process finished with exit code -1
无论目标方法是否抛出异常,该增强均会被执行。
@After("execution(public * com.why.SomeServiceImpl.doSecond())")
public void after(){
System.out.println("最终通知,总是会执行");
}
当较多的通知增强方法使用相同的 execution 切入点表达式时,编写、维护均较为麻烦。AspectJ 提供了@Pointcut 注解,用于定义 execution 切入点表达式。其用法是,将@Pointcut 注解在一个方法之上,以后所有的 execution 的value 属性值均可使用该方法名作为切入点。代表的就是@Pointcut 定义的切入点。这个使用@Pointcut 注解的方法一般使用 private 的标识方法,即没有实际作用的方法。
@After("myPoint()")
public void after(){
System.out.println("最终通知,总是会执行");
}
/**
* @Pointcut:用来定义和管理切面点,简化切入点的定义
*/
@Pointcut(value = "execution(public * com.why.SomeServiceImpl.doSecond())")
public void myPoint(){
//无需写代码
}
版权说明 : 本文为转载文章, 版权归原作者所有 版权申明
原文链接 : https://blog.csdn.net/m0_60117382/article/details/123308385
内容来源于网络,如有侵权,请联系作者删除!