什么是IOC
IOC,译名控制反转,是一种降低具有依赖关系的对象间的耦合性的设计理念,“简单来说就是把复杂系统分解成相互合作的对象,这些对象类通过封装以后,内部实现对外部是透明的,从而降低了解决问题的复杂度,而且可以灵活地被重用和扩展。”
Spring中的控制反转,具体表现为,程序不再主动地创建对象,而是交由Spring容器来完成,之后再由容器将属性注入对象,程序做的是被动地接收对象,而这个对象,被称作Bean
具体参考这篇博文
简单的IOC创建对象
Spring是一种容器,当我们加载Spring的配置文件时,所有容器中的对象就已经被初始化了,并且初始化对象默认调用无参构造,如果没有无参构造(只有有参),则需要做出相应的配置
导入maven依赖
Spring的各种依赖之间没有强联系,新手上路,先导入webmvc包
<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.9</version>
</dependency>
xml文件配置
Spring通过配置文件实现IOC,官方文档中有详细的说明
直接Ctrl + CV过来用:
<?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">
</beans>
实体类的创建
拿我们最爱的Teacher和Student来举例,为了省事直接在java目录下新建类,用@Data偷懒简化一下Getter&Setter
import lombok.Data;
@Data
public class Student {
private int age;
private String name;
public Student() {
System.out.println("My name's XiaoMing");
}
}
import lombok.Data;
import java.util.List;
@Data
public class Teacher {
private int age;
private String name;
private List<Student> students;
public Teacher() {
System.out.println("XiaoMing get out!");
}
}
在xml中实现IOC创建对象
<?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="teacher" class="Teacher">
<property name="age" value="44"/>
<property name="name" value="Lao Wang"/>
<property name="students" ref="student"/>
</bean>
<bean id="student" class="Student">
<property name="age" value="16"/>
<property name="name" value="Xiao Ming"/>
</bean>
</beans>
注意的点:
- Spring容器在加载Beans时顺序加载,也就是说谁写在上面谁先加载
- 普通的属性可以用
value
直接赋值,而引用类型则用ref
- Spring配置默认调用类的无参构造,若要使用有参构造则需要对其中的参数做出对应配置,比如下面几种方法:
<!-- 通过参数下标赋值 --> <bean id="student" class="Student"> <constructor-arg index="0" value="16"/> <constructor-arg index="1" value="Xiao Ming"/> </bean>
<!-- 通过参数名称赋值 --> <bean id="student" class="Student"> <constructor-arg name="age" value="16"/> <constructor-arg name="name" value="Xiao Ming"/> </bean>
<!-- 通过参数类型赋值,注意要写全名,且多个参数类型必须不同 --> <bean id="student" class="Student"> <constructor-arg type="int" value="16"/> <constructor-arg type="java.lang.String" value="Xiao Ming"/> </bean>
- 别名:
一种是在bean
标签外用alias
标签取别名,比如<alias name="student" alias="Alice"/>
一种是直接在bean
中用name
取别名(感觉意义不大),比如<bean id="student" class="Student" name="Alice,Bob">...</bean>
,内部用空格、分号或逗号分隔- 导入:
使用import
标签可以实现在一个配置文件中导入多个配置文件
导入配置通常用于团队开发,此配置文件的规范命名为applicationContext.xml
使用这些对象需要先获取Spring容器:
ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
然后从容器中获取Bean(以Teacher为例):
Teacher teacher = (Teacher) context.getBean("teacher");
或者
Teacher teacher = context.getBean("teacher", Teacher.class);
什么是DI
- 依赖注入(DI)是控制反转(IOC)的一种具体实现方式
- ……(小编也讲不清楚)
参考一下这篇博客
开始奇怪的注♂入
集合注入
以Spring官方文档为例:
<bean id="moreComplexObject" class="example.ComplexObject">
<!-- results in a setAdminEmails(java.util.Properties) call -->
<property name="adminEmails">
<props>
<prop key="administrator">administrator@example.org</prop>
<prop key="support">support@example.org</prop>
<prop key="development">development@example.org</prop>
</props>
</property>
<!-- results in a setSomeList(java.util.List) call -->
<property name="someList">
<list>
<value>a list element followed by a reference</value>
<ref bean="myDataSource" />
</list>
</property>
<!-- results in a setSomeMap(java.util.Map) call -->
<property name="someMap">
<map>
<entry key="an entry" value="just some string"/>
<entry key ="a ref" value-ref="myDataSource"/>
</map>
</property>
<!-- results in a setSomeSet(java.util.Set) call -->
<property name="someSet">
<set>
<value>just some string</value>
<ref bean="myDataSource" />
</set>
</property>
</bean>
<props>
代表配置类型的集合,相当于Java的Properties
<list>
代表列表,对应Java的List<>
<map>
代表图,对应Java的Map<>
<set>
代表set集合,对应Java的Set<>
合并集合
依旧以Spring官方文档为例:
<beans>
<bean id="parent" abstract="true" class="example.ComplexObject">
<property name="adminEmails">
<props>
<prop key="administrator">administrator@example.com</prop>
<prop key="support">support@example.com</prop>
</props>
</property>
</bean>
<bean id="child" parent="parent">
<property name="adminEmails">
<!-- the merge is specified on the child collection definition -->
<props merge="true">
<prop key="sales">sales@example.com</prop>
<prop key="support">support@example.co.uk</prop>
</props>
</property>
</bean>
<beans>
这里的parent作为父类bean是一个抽象bean,child继承了parent且令merge为true,即合并了父类bean中的集合,若存在键冲突,则合并后保留子类的值
简单地讲,合并后就相当于
<beans>
<bean id="child" parent="parent">
<property name="adminEmails">
<props>
<prop key="administrator">administrator@example.com</prop>
<prop key="sales">sales@example.com</prop>
<prop key="support">support@example.co.uk</prop>
</props>
</property>
</bean>
<beans>
其他集合的合并也是相似的方式
当然,list和set没有键只有值,故合并只是简单相加
命名空间注入
Spring的命名空间分为p命名空间,对应set注入,和c命名空间,对应构造器注入
使用命名空间需要在xml中添加对应约束
在bean
标签中添加xmlns:p="http://www.springframework.org/schema/p"
或xmlns:c="http://www.springframework.org/schema/c"
示例:
<?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:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
</beans>
Bean的作用域
作用域,即限定程序可用性的范围,而Bean的作用域(Scope),代表此Bean产生对象(实例)可用的范围
来份官方表格↓
其中前两个是常见作用域,而后四个只有在Web中用到(看名字就是这样)
单例模式(singleton)
见名知义,Spring容器只会创建一个Bean实例,从容器中取出的对象完全相同,这是Spring的默认机制
<bean id="xxx" class="xxx" scope="singleton">...</bean>
原型模式(prototype)
每次向容器发出新的get请求时,容器都会创建一个新的Bean实例,或者说,原来的Bean被注入到一个新的Bean中
<bean id="xxx" class="xxx" scope="prototype">...</bean>
剩下的作用域还没学到,以后再补
自动装配
以上的例子都是由人工装配Bean的属性,而这样做非常麻烦,于是Spring产生了自动装配(autowire)的技术
byName
Spring自动在配置文件上下文中查找与对应属性名有相同id的bean,例如:
<bean id="dog" class="xxx">...</bean>
<!-- master的属性中有dog,自动装配前面的dog -->
<bean id="master" class="xxx" autowire="byName"/>
byType
Spring自动在配置文件上下文中查找与对应属性类型相同的bean,例如:
<bean id="dogge" class="xxx">...</bean>
<!-- master的属性中没有dogge,但有dogge对应类型的属性,自动装配前面的dogge -->
<bean id="master" class="xxx" autowire="byType"/>
注意,如果要使用byType,必须保证需要被自动装配的bean的class唯一
注解@Autowired
前置工作
为beans
添加约束xmlns:context="http://www.springframework.org/schema/context"
在xsi:schemaLocation
中添加两行:http://www.springframework.org/schema/context
和https://www.springframework.org/schema/context/spring-context.xsd
在bean
中开启注解支持:<context:annotation-config/>
然后就可以在代码中使用注解实现自动装配了
继续用Teacher和Student为例
Teacher:
import lombok.Data;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.List;
@Data
public class Teacher {
private int age;
private String name;
@Autowired
private List<Student> students;
public Teacher() {
System.out.println("XiaoMing get out!");
}
}
Student:
import lombok.Data;
@Data
public class Student {
public Student(int age, String name) {
this.age = age;
this.name = name;
System.out.println("My name's XiaoMing");
}
private int age;
private String name;
}
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:p="http://www.springframework.org/schema/p"
xmlns:c="http://www.springframework.org/schema/c"
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/>
<bean id="teacher" class="Teacher" p:age="44" p:name="Lao Wang"/>
<bean id="student1" class="Student" c:age="16" c:name="Xiao Ming"/>
<bean id="student2" class="Student" c:age="15" c:name="Xiao Hong"/>
<bean id="student3" class="Student" c:age="16" c:name="Xiao Li"/>
</beans>
注意:
@Autowired
注解不仅可以在属性上使用,在Setter方法上同样可用,效果相同(我直接用lombok偷懒了,所以没有写Setter方法),而在属性上使用则无需写Setter方法
@Autowired
自动装配优先使用byType,查询匹配类型的bean,若有多个,则在这、多个bean中使用byName
注解@Qualifier(value = “xxx”)
这个注解是配合@Autowired
使用的,适用于注解的Java属性在Spring中有多个对应实例(此时容器中存在多个有相同class的bean)且id均与属性名不同的情况,比如上面那个例子中的Student,因为Teacher的students是一个列表,故自动装配时会找到所有对应class的bean,也就是student1、student2、student3,而如果我使用了@Qualifier(value = "student1")
注解后,只会装配名为student1
的bean,这样Teacher的students中只有一个Student对象了
同理,如果在Spring容器中有多个有相同class的bean,当配置@Qualifier
后就会限定死一个bean,也就不会使自动装配失败了
注解@Resource
jdk1.8后的版本都需要导包来使用这个注解(大概,反正15、16都要),直接添加Maven依赖,虽然不知道为什么,jdk16对应的依赖本来应该是jakarta包下的而不是javax包下的(至少servlet是这样),但事实证明还是用的javax,想不明白,知道这是为什么的欢迎在评论区教我_(:з」∠)_
Maven仓库搜annotation
,直接Ctrl + CV
<!-- https://mvnrepository.com/artifact/javax.annotation/javax.annotation-api -->
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.3.2</version>
</dependency>
然后快乐地使用@Resource
面向注解开发
上面提到的三种注解都是Spring注解开发的一部分
@Component及其衍生注解
对一个Java类使用该注解可以将该类注册到Spring容器中,避免在xml配置文件中显式定义bean
使用该注解需要先在xml配置文件声明扫描包的位置,如
<context:component-scan base-package="包名"/>
来份Teacher:
package com.xxx.xxxx; //这里就是包名
import lombok.Data; //lombok狗都不用
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
@Data
@Component
@Scope("protoType") //还记得Bean的作用域🐎,这就是对应注解
public class Teacher {
@Value("44") // 注入属性
private int age;
@Value("Lao Wang")
private String name;
@Resource()
private Student student;
public Teacher() {
System.out.println("XiaoMing get out!");
}
}
然后是衍生注解:@Repository
、@Service
、@Controller
注意:这些衍生注解与@Component
作用完全相同,区别在于作用对象分别对应mvc架构中的pojo、service、controller层
在实践中,一般使用xml来实现bean的管理,使用注解来实现属性的注入
@Configuration
想要完全省略Spring配置文件的使用,需要用到这个注解,对一个类使用此注解后,会令Spring容器接管这个类,这个类就是取代Spring配置文件的配置类
示例:
package com.hoppi.config;
import com.hoppi.pojo.Student;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan("com.hoppi.pojo")
public class BeansConfig {
//test
@Bean
public Student get_Student() {
return new Student();
}
}
上面那个
@ComponentScan("com.hoppi.pojo")
对应xml中的配置包扫描路径,@Bean
则是配置一个容器中的Bean
使用这种配置类获取Spring容器的方法是:
ApplicationContext context = new AnnotationConfigApplicationContext(BeansConfig.class);
其中AnnotationConfig
意为注解配置,dddd
@Import
是引用配置类的意思,对一个配置类使用该标签,可以引用(合并)另一个配置类,具体用法为@Import(xxx.class)
……
代理模式
代理,即给一个真实角色提供一个代理角色,并由代理角色实现真实角色的功能,比如房屋中介、婚介公司
常见的代理,如浏览器代理,举个例子,我们在使用Burp Suite抓包的时候要开启代理,然后才能实现网页抓包,因为我们在发送、接收网页数据的时候会经过第三方,BP可以代理我们向网页发送请求,同样浏览器在传输数据时会被代理浏览器的BP截获
当我们不满足于仅仅是实现代理类的功能,而添加一些我们想要实现的功能,例如中介公司收中介费,这种在不影响真实角色的基础上添加功能的代理,就是我们需要设计并实现的代理,也叫做AOP动态代理
什么是AOP
Aspect Oriented Programming,AOP,意为面向切面编程
那么什么是切面呢?小编以为,切面就是多个相似类的共同部分,被抽取出来就叫一个切面,比如Teacher和Student都会打招呼(Ohayo~),抽取出来就是一个打招呼切面
这么做的好处是可以降低代码耦合性,便于维护,便于修改,简化代码,程序员只需要写一遍这种大家都有的代码,交给AOP容器去组合到它们应该处于的地方即可,比如Teacher只需要会askQuestion,Student只需要会answer就行
静态代理与动态代理的区别
简单来说,静态代理的代理类在程序运行前就已存在,而动态代理通过反射的方法可以实现动态生成代理类
动态代理和静态代理都是为了实现给一个真实角色提供一个代理角色,并由代理角色实现真实角色的功能的目的的
注意:动态代理必然是用于某一类事物的代理,这类事物要有相似的特性,这些特性可以被抽象成一个统一的接口
基于接口的动态代理
基于接口的动态代理也叫基于JDK的动态代理,一般通过Proxy
和InvocationHandler
两个类实现,前者通过静态方法得到代理类,后者则是接口
实现动态代理的具体步骤为:确定要代理的对象->实现代理类的调用处理类->通过代理类的调用处理类得到代理实例->通过代理实例使用被代理对象的方法
之所以称作基于接口的,是因为必须提供被代理类的方法接口,简单地说,被代理类必须先继承一个我们写的接口
来份通用处理类↓
package com.hoppi.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
//代理类的调用处理类
public class ProxyInvocationHandler implements InvocationHandler {
//被代理接口
private Object target;
public void setTarget(Object target) {
this.target = target;
}
//得到代理类
public Object getProxy(){
//newProxyInstance传入参数:类加载器,被代理类的接口,动态代理关联
return Proxy.newProxyInstance(this.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
}
//处理代理示例并返回结果,这里可以添加功能
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//实现被代理类的所有功能
Object result = method.invoke(target, args);
return result;
}
}
实际使用示例:
假如小明是一个学生,学习非常差劲,突然有一天他想到了通过代理的方法让假的自己代理自己回答老师问题,这样他就可以在上课的时候睡大觉了
package com.hoppi.pojo;
//这是学生接口
public interface Student {
void answer();
}
package com.hoppi.pojo;
//这是小明
public class StudentImpl implements Student{
@Override
public void answer() {
System.out.println("老师……这题我不会");
}
}
//修改代理类的调用处理类的invoke方法,实现代理
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//把这句注释掉是因为要取消掉原来的方法
//Object result = method.invoke(target, args);
System.out.println("就是这样再这样再那样啊,太简单了");
return null;
}
接下来来份测试Demo:
import com.hoppi.pojo.Student;
import com.hoppi.pojo.StudentImpl;
import com.hoppi.proxy.ProxyInvocationHandler;
import org.junit.Test;
public class Demo {
@Test
public void test() {
StudentImpl student = new StudentImpl();
ProxyInvocationHandler invocationHandler = new ProxyInvocationHandler();
invocationHandler.setTarget(student);
Student fakeStudent = (Student) invocationHandler.getProxy();
fakeStudent.answer();
}
}
基于类的动态代理
(还没学)
Spring实现AOP的方式一
利用Spring容器自带API接口接管确定切入点、切面的织入等操作
- 第一步 导包
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.7</version>
<scope>runtime</scope>
</dependency>
- 第二步 在Spring配置文件中导入aop约束
完成大概长这样↓
<?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: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/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
</beans>
- 第三步 创建被代理类、切面类并注册到Spring中
被代理类就拿之前的Student
来两个切面(前切和后切
package com.hoppi.advice;
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;
//前切
public class beforeMethods implements MethodBeforeAdvice {
@Override
public void before(Method method, Object[] objects, Object o) throws Throwable {
assert o != null;
System.out.println(o.getClass().getName() + "'s " + method.getName() + " will be executed");
}
}
package com.hoppi.advice;
import org.springframework.aop.AfterReturningAdvice;
import java.lang.reflect.Method;
//后切
public class afterMethods implements AfterReturningAdvice {
@Override
public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable {
assert o1 != null;
System.out.println(o1.getClass().getName() + "'s " + method.getName() + " was executed, the return result is " + o);
}
}
来注册三个类↓
<?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: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/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="student" class="com.hoppi.pojo.StudentImpl"/>
<bean id="beforeMethods" class="com.hoppi.advice.beforeMethods"/>
<bean id="afterMethods" class="com.hoppi.advice.afterMethods"/>
</beans>
- 第四步 在Spring容器中配置切入点和执行环绕增加
<?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: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/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="student" class="com.hoppi.pojo.StudentImpl"/>
<bean id="beforeMethods" class="com.hoppi.advice.beforeMethods"/>
<bean id="afterMethods" class="com.hoppi.advice.afterMethods"/>
<aop:config>
<!-- execution(* com.hoppi.pojo.StudentImpl.*(..))表示在任意位置的com.hoppi.pojo.StudentImpl的传入任意参数的任意方法处定义一个切入点 -->
<aop:pointcut id="pointcut" expression="execution(* com.hoppi.pojo.StudentImpl.*(..))"/>
<!-- 执行环绕增加-->
<aop:advisor advice-ref="beforeMethods" pointcut-ref="pointcut"/>
<aop:advisor advice-ref="afterMethods" pointcut-ref="pointcut"/>
</aop:config>
</beans>
- 来份Demo↓
import com.hoppi.pojo.Student;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Demo {
@Test
public void test() {
ApplicationContext context = new ClassPathXmlApplicationContext("springConfig.xml");
Student student = context.getBean("student", Student.class);
student.answer();
}
}
输出结果:
Spring实现AOP的方式二
上面的方式一是通过Spring的API接口实现的,而第二种方式是完完全全的自定义,与AOP的代理类彻底分割开来
这样做丧失了很多反射有关的方便的功能,因为无法自动与代理类产生交互,好处是更大的自由度(可能)
- 前两步与第一种方式相同
- 第三步 建一个切面类(或者多个),包含需要切入切入点的方法(比如前切后切)
package com.hoppi.advice;
public class MyAdvice {
public void before() {
System.out.println("=== Answer ===");
}
public void after() {
System.out.println("===End Answer===");
}
}
- 第四步 注册切面并配置切入点和执行环绕增加
<?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: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/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="student" class="com.hoppi.pojo.StudentImpl"/>
<bean id="beforeMethods" class="com.hoppi.advice.BeforeMethods"/>
<bean id="afterMethods" class="com.hoppi.advice.AfterMethods"/>
<bean id="methods" class="com.hoppi.advice.MyAdvice"/>
<aop:config>
<aop:aspect ref="methods">
<aop:pointcut id="pointcut" expression="execution(* com.hoppi.pojo.StudentImpl.*(..))"/>
<aop:before method="before" pointcut-ref="pointcut"/>
<aop:after method="after" pointcut-ref="pointcut"/>
</aop:aspect>
</aop:config>
</beans>
来份Demo↓还是原来的Demo
import com.hoppi.pojo.Student;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Demo {
@Test
public void test() {
ApplicationContext context = new ClassPathXmlApplicationContext("springConfig.xml");
Student student = context.getBean("student", Student.class);
student.answer();
}
}
输出结果:
Spring实现AOP的方式三
在方式二的基础上,可以简化开发,使用注解完成注册切面、配置切入点和执行环绕增加的操作
简单描述一下操作为:在切面类上加注解@Aspect
, 在定义切入点方法上加注解@Pointcut()
,,在前切方法上加注解Before()
,,后切方法则是@AfterReturning()
,异常抛出方法加@AfterThrowing()
,final方法加@After
,环绕通知方法加@Around()
显然这都是废话,但这些方法的参数什么的很烦,实在懒得打了,建议来点源码阅读,或者参考这篇博客⇨Spring AOP配置 之 @Aspect
需要注意的是环绕通知方法,类似于方式一和基于接口的动态代理,方法中必须要传入一个ProceedingJoinPoint对象
- 来份切面类↓
package com.hoppi.advice;
import org.aspectj.lang.annotation.*;
@Aspect
public class MyAspect {
@Pointcut("execution(* com.hoppi.pojo.StudentImpl.*(..))")
public void pointCut(){}
@Before("pointCut()")
public void before() {
//来点二次元
System.out.println("こんにちは");
}
@AfterReturning("pointCut()")
public void after(){
//来点二次元
System.out.println("大丈夫です");
}
@After("pointCut()")
public void final_method(){
System.out.println("请坐下");
}
}
- 然后将这个切面类注册到Spring的配置文件中,并开启aop:
<aop:aspectj-autoproxy proxy-target-class="true"/>
(proxy-target-class
属性表示动态代理的技术,默认为false,表示使用JDK动态代理技术织入增强,设置为true则表示使用CGLIB动态代理技术织入增强,但如果代理类没有声明接口,则自动使用CGLIB动态代理技术) - 还是原来的Demo
- 输出结果:
- 使用Around环绕增强是可以得到方法的各种属性,甚至是被代理对象的,因此它具有非常强大的功能,来份切面类↓
package com.hoppi.advice;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
@Aspect
public class MyAspect {
@Pointcut("execution(* com.hoppi.pojo.StudentImpl.*(..))")
public void pointCut(){}
// @Before("pointCut()")
// public void before() {
// System.out.println("こんにちは");
// }
// @AfterReturning("pointCut()")
// public void after(){
// System.out.println("大丈夫です");
// }
// @After("pointCut()")
// public void final_method(){
// System.out.println("请坐下");
// }
@Around("pointCut()")
public void around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("こんにちは");
//得到方法签名
Signature signature = joinPoint.getSignature();
//得到连接点方法所在类文件中的位置
SourceLocation sourceLocation = joinPoint.getSourceLocation();
//得到目标对象
Object target = joinPoint.getTarget();
joinPoint.proceed();
System.out.println("大丈夫です\n请坐下");
System.out.println("======" + target.getClass() + "======");
System.out.println(signature + "\n" + sourceLocation + "\n=============");
}
}
- 巴拉巴拉
- 输出结果:
Spring + Mybatis
Mybatis-Spring
会将Mybatis整合到Spring中,它允许Mybatis参与Spring的事务管理等
官方文档
导入项目依赖
除了Spring和Mybatis所需依赖,还需要导入一个mybatis-spring
包
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis-spring -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.6</version>
</dependency>
以及一个Spring操作jdbc的依赖
<!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.10</version>
</dependency>
Spring管理数据源
使用Spring代管数据源替换Mybatis的数据源配置
在Spring配置文件中注册DataSource
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf-8&userSSL=true&serverTimezone=GMT%2B8"/>
<property name="username" value="root"/>
<property name="password" value="112159"/>
</bean>
Spring管理SqlSession生成
众所周知,Mybatis中进行jdbc操作需要通过SqlSessionFactory产生SqlSession对象来实现
在Spring配置文件中注册SqlSessionFactory
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- 这里是之前配置的数据源 -->
<property name="dataSource" ref="dataSource"/>
<property name="configLocation" value="classpath:mybatis-config.xml"/>
</bean>
在Spring配置文件中注册SqlSession(其实是SqlSessionTemplate)
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg ref="sqlSessionFactory"/>
</bean>
来点事务层↓
TeacherMapper
package com.hoppi.Mapper;
import com.hoppi.pojo.Student;
import org.apache.ibatis.annotations.Select;
import java.util.List;
public interface TeacherMapper {
@Select("select id, name from student where teacher_id = #{id}")
List<Student> getStudents(@Param("id") int id);
}
TeacherMapperImpl
package com.hoppi.Mapper;
import com.hoppi.pojo.Student;
import org.mybatis.spring.SqlSessionTemplate;
import java.util.List;
public class TeacherMapperImpl implements TeacherMapper {
//需要在Spring中注入配置好的SqlSessionTemplate
private SqlSessionTemplate sqlSession;
//用于注入的Setter方法
public void setSqlSession(SqlSessionTemplate sqlSession) {
this.sqlSession = sqlSession;
}
@Override
public List<Student> getStudents(int id) {
TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class);
return mapper.getStudents(id);
}
}
在Spring中注册好TeacherMapperImpl并注入SqlSessionTemplate
<bean id="teacherMapperImpl" class="com.hoppi.Mapper.TeacherMapperImpl">
<property name="sqlSession" ref="sqlSession"/>
</bean>
然后直接Test开冲
import com.hoppi.Mapper.TeacherMapper;
import com.hoppi.pojo.Student;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest {
@Test
public void Test(){
ApplicationContext context = new ClassPathXmlApplicationContext("springConfig.xml");
TeacherMapper teacherMapper = context.getBean("teacherMapperImpl", TeacherMapper.class);
for (Student student : teacherMapper.getStudents(1000000001)) {
System.out.println(student.getId() + student.getName());
}
}
}
偷懒的方法
让TeacherMapperImpl继承SqlSessionDaoSupport
类,内置getSession,只需注入一个SqlSessionFactoryBean
即可(当然也可以注入SqlSessionTemplate
,当注入SqlSessionTemplate
时若注入了SqlSessionFactoryBean
则会自动忽略)
来份TeacherMapperImpl2
package com.hoppi.Mapper;
import com.hoppi.pojo.Student;
import org.mybatis.spring.support.SqlSessionDaoSupport;
import java.util.List;
public class TeacherMapperImpl2 extends SqlSessionDaoSupport implements TeacherMapper {
@Override
public List<Student> getStudents(int id) {
TeacherMapper mapper = getSqlSession().getMapper(TeacherMapper.class);
return mapper.getStudents(id);
}
}
注册Bean&注入SqlSessionFactoryBean
<bean id="teacherMapperImpl2" class="com.hoppi.Mapper.TeacherMapperImpl2">
<property name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>
或注入SqlSessionTemplate
<bean id="teacherMapperImpl2" class="com.hoppi.Mapper.TeacherMapperImpl2">
<property name="sqlSessionTemplate" ref="sqlSession"/>
</bean>
但是讲道理用了这种方式谁还会配置一个SqlSessionTemplate
呢( ̄へ ̄)
还是原来的Test(TeacherMapperImpl换成TeacherMapperImpl2)冲
Spring的事务
这里的事务我粗浅地理解为在执行JDBC操作数据库的一组指令时若这些指令中有一条不成功,则会令数据库回滚到执行前的样子,以此保证事务的ACID原则
笑死,什么牛马理解
建议来康康这个:java事务的原理和应用
谢谢,康不懂,好在我不需要自己来实现这些
狂神教的Mybatis-Spring管理事务
- 第一步 注册一个
DataSourceTransactionManager
类型的Bean
<bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
- 第二步 导入tx约束
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="https://www.springframework.org/schema/tx
https://www.springframework.org/schema/tx/spring-tx.xsd"
Spring配置文件开头类似于(这里也导入了aop约束):
<?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:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
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
http://www.springframework.org/schema/tx
https://www.springframework.org/schema/tx/spring-tx.xsd">
- 配置事务通知
<tx:advice id="interceptor" transaction-manager="dataSourceTransactionManager">
<tx:attributes>
<!-- 配置所有方法的传播特性为REQUIRED -->
<!-- 然后配置query方法为只读 -->
<tx:method name="*" propagation="REQUIRED"/>
<tx:method name="query" read-only="true"/>
</tx:attributes>
</tx:advice>
- 配置事务的AOP织入(需要导入AOP依赖包和约束)
<aop:config>
<aop:pointcut id="pointcut" expression="execution(* com.hoppi.Mapper.*.*(..))"/>
<aop:advisor advice-ref="interceptor" pointcut-ref="pointcut"/>
</aop:config>
差不多就这样了
886( ゜- ゜)つロ