Spring是什么?
使用Spring
搭建使用环境
- 导入依赖的jar包
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.9.RELEASE</version>
</dependency>
- 新建配置文件
\spring-context.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-2.5.xsd">
- 构建bean映射
<bean id="xxx" class="/classPath/"></bean>
- 测试
//启动公厂
ApplicationContext context = new ClassPathXmlApplicationContext("/spring-context.xml");
//获取对象
Shuiguo shuiguo = (Shuiguo) context.getBean("shuiguo");
名词解释:
- schema:规范
IOC:控制反转
之前
,比如Service里需要一个Dao来实现底层的数据库交互问题,所以,必须要在Service中new一个Dao,但是如果dao发生了更换,这个时候就需要修改Service类的源码,造成一些不必要的麻烦
现在
可以使用依赖反转,先在Service中创建一个未赋值的Dao对象,然后创建它的Get/Set方法,然后再配置中让Spring赋值给Service一个Dao对象
//Servic类中
private Dao dao;
public Dao getDao() {
return dao;
}
public void setDao(Dao dao) {
this.dao = dao;
}
//配置文件中
<bean id="dao" class="Dao.userDao"></bean>
<bean id="service" class="Service.UserService">
//注入属性值,满足依赖关系
<property name="dao" ref="dao"/>
</bean>
IOC思想:
之前是谁需要谁创建,是一个比较主动的事情
现在使用IOC之后,变成了框架为其自动创建,变主动为被动,
实现这种思想的主要方法是注入:
DI注入
Set注入
所谓set注入就是依赖类属性的set方法来进行注入赋值
注入格式:
<bean id="自定义对象名" class="对象类路径">
<!--简单数据类型的注入-->
<property name="属性名" value="值"/>
</bean>
//我现在要创建一个用户类,来演示属性注入
public class User {
//基础类型
private Integer id;
private String password;
private String sex;
private Integer age;
private Date bortdate;
//数组类型
private String[] hobbys;
//集合类型
private Set<String> phones;
private List<String> names;
private Map<String,String> countries;
private Properties files;
//自建类型
private Address address;
//get/set方法就先省略了,太占位置了,要知道一定要有就行了
<!-- 简单数据类型的注入-->
<property name="id" value="01" />
<property name="password" value="1234"/>
<property name="sex" value="man"/>
<property name="age" value="19"/>
<property name="bortdate" value="2020/12/12 10:20:20"/>
<!--数组注入-->
<property name="hobbys">
<array>
<value>抽烟</value>
<value>喝酒</value>
<value>烫头</value>
</array>
</property>
<!--集合注入-->
<!-- list集合-->
<property name="names">
<list>
<value>老大</value>
<value>老二</value>
<value>老三</value>
</list>
</property>
<!-- set集合-->
<property name="phones">
<set>
<value>1234312</value>
<value>342452</value>
</set>
</property>
<!-- map集合-->
<property name="countries">
<map>
<entry key="zh" value="中国"></entry>
<entry key="en" value="英国"></entry>
</map>
</property>
<!-- properties配置文件读取的集合-->
<property name="files">
<props>
<prop key="url">img/01.jpg</prop>
<prop key="username">root</prop>
<prop key="password">1234</prop>
</props>
</property>
<!-- 自建类型的注入-->
<property name="address" ref="add"></property>
</bean>
<bean id="add" class="user.Address">
<property name="id" value="1"></property>
<property name="address" value="dgakldfaidhfak"></property>
</bean>
Set注入总结:
set注入是靠框架回调对象对应的set方法,来传参进去,实现注入的
一般数据类型使用的value
Date类型格式为"YYYY/MM/dd hh:mm:ss"
<property name="属性名" value="值"/>
数组类型是用Array 里面创建Value
<property name="属性名">
<array>
<value>值1</value>
<value>值2</value>
<value>值3</value>
</array>
</property>
集合类型
- List集合 先list然后里面value
<property name="属性名">
<list>
<value>值1</value>
<value>值2</value>
<value>值3</value>
</list>
</property>
- set集合 先set 然后value
<property name="属性名">
<set>
<value>值1</value>
<value>值2</value>
<value>值3</value>
</set>
</property>
- map集合 先map 然后里面是entry 每一个entry有key和value两个属性
<property name="countries">
<map>
<entry key="zh" value="中国"></entry>
<entry key="en" value="英国"></entry>
</map>
</property>
- properties 配置文件, 先props 再prop 有key属性 然后标签内写值
<property name="files">
<props>
<prop key="url">img/01.jpg</prop>
<prop key="username">root</prop>
<prop key="password">1234</prop>
</props>
</property>
- 自建类型 需要先创建bean 然后使用ref注入进去
<bean id="add" class="user.Address">
<property name="id" value="1"></property>
<property name="address" value="dgakldfaidhfak">
</property>
</bean>
- 注入<property name="address" ref="add"></property>
构造注入(了解)
调用构造函数来实现注入
//创建学生类
public class Student {
private int id;
private String name;
private String sex;
private int age;
public Student(int id, String name, String sex, int age) {
this.id = id;
this.name = name;
this.sex = sex;
this.age = age;
}
}
//构造注入
<bean id="stu" class="user.Student">
<constructor-arg name="id" value="1"/>
<constructor-arg name="name" value="张哈哈"/>
<constructor-arg name="sex" value="男"/>
<constructor-arg name="age" value="1"/>
</bean>
自动注入(了解)
<bean id=“service” class=“Service.UserService” autowire="
byName/byType
">
使用自动注入的时候,会根据需要注入的值的对象名,或者对象类型来进行查找自动注入
单例/多例
使用Spring-Context默认是用单例模式创建对象的
使用scopr来控制单例或者多例
- scope=“prototype” 多例模式创建对象
- scope=“singleton”:单例模式创建对象
复杂对象的创建
如connection 数据库连接对象,这种没有办法直接new的对象,就需要用到
- 首先创建复杂对象的工厂bean类,使其实现FactoryBean接口,泛型选择需要使用的对象类,如(Connection)
- 实现其中的三个方法
getObject()
内写获取对象的方法getObjectType()
内写获取对象的类信息isSingleton()
是否以单例模式创建对象
//创建获取Connection的工厂Bean类:
public class MyFactoryBean implements FactoryBean<Connection> {
@Override
public Connection getObject() throws Exception {
Class.forName("com.mysql.cj.jdbc.Driver");
return DriverManager.getConnection("jdbc:mysql://localhost:3306/companydb?serverTimezone=UTC", "root", "1234");
}
@Override
public Class<?> getObjectType() {
return Connection.class;
}
@Override
public boolean isSingleton() {
return false;
}
}
创建Conn的Bean对象获取
<bean id="conn" class="factoryBean.MyFactoryBean"></bean>
复杂对象的创建总结
此方式获取的对象为工厂Bean中getObject()中获取的对象
如果需要获取工厂Bean本身的对象,
MyFactoryBean myFactoryBean = (MyFactoryBean)context.getBean("&conn");
Spring工厂特性
默认采用饿汉式的单例模式,
工厂一旦启动,将会直接创建好配置文件中的所有Bean对象,提高程序的效率,避免多次IO,减少对象的创建时间
生命周期
-
单例
工厂启动时立马创建,生命周期流程为:
构造>set>初始化
工厂关闭时执行销毁 -
多例
在需要调用时创建,生命周期流程为
构造>set>初始化
由JVM垃圾回收机制销毁
AOP:
先了解下代理设计模式
静态代理
举个例子:房东想要把房子租出去,他有个租房的方法,里面要做发布房源,带人看房,然后签合同,收房租,等等,对于房东来说,他的核心的业务其实只有签合同收房租,这时候就可以用代理来帮他先做发布房源,带客看房的操作,业务代码如下
//原本是这样
public class Fangdong implements FangdongService{
public void zufang(){
System.out.println("发布房源");//辅助功能
System.out.println("带人看房");//辅助功能
System.out.println("签合同");//核心功能
System.out.println("收房租");//核心功能
}
}
//引进代理之后 中介类
public class Zhongjie implements FangdongService {
Fangdong fangdong = new Fangdong();
public void zufang(){
System.out.println("发布房源");//辅助功能
System.out.println("带人看房");//辅助功能
//调用核心功能
fangdong.zufang();
}
}
//房东类
public class Fangdong implements FangdongService{
public void zufang(){
System.out.println("签合同");
System.out.println("收房租");
}
}
动态代理模式
JDK实现动态代理(基于接口)
- 首先new一个核心业务类
- 然后使用匿名内部类的方式,重写一个InvocationHandler类,在内部实现其代理的功能
- 使用Proxy.newProxyInstance(本类的类的类加载器,核心业务类的类接口,第二步创建的类对象) 来动态生成代理类
- 返回对象为Object,但是可以强转为核心类的同类对象
代码如下
//原生JDK的动态代理实现
//目标 :核心功能的类
FangdongService fangdong = new Fangdong();
//额外功能
InvocationHandler ih = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("发布房源");
System.out.println("带人看房");
fangdong.zufang();
return null;
}
};
//动态生成代理类
FangdongService proxy = (FangdongService) Proxy.newProxyInstance(TestZufang.class.getClassLoader(),
fangdong.getClass().getInterfaces(),
ih);
proxy.zufang();
CGlib实现动态代理(基于继承)
- 首先new一个核心业务类
- 创建一个Enhancer类对象,
1.设置继承的父类setSuperclass
2.设置回调setCallback,并传入匿名内部类的new org.springframework.cglib.proxy.InvocationHandler() 对象,对象内重写invoke方法,实现代理功能- 使用enhancer.create()动态实现代理类
- 返回对象为Object,但是可以强转为2.1设置的父类
代码如下
//CGlib实现动态代理
//目标 :核心功能的类
FangdongService fangdong = new Fangdong();
//创建
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Fangdong.class);
enhancer.setCallback(new org.springframework.cglib.proxy.InvocationHandler() {
@Override
public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
System.out.println("发布房源");
System.out.println("带人看房");
fangdong.zufang();
return null;
}
});
//动态生成代理类
Fangdong zhongjie = (Fangdong) enhancer.create();
zhongjie.zufang();
原理还是不太明白
先了解吧,后续慢慢摸清原理
AOP面向切面编程
这是一种新的编程思想,个人理解类似于嫁接(可能不太恰当)
- 先定义好一个主业务类,然后自定义一个前置通知类,实现
MethodBeforeAdvice
接口,实现其before()
方法,在内添加额外功能的实现- 在配置文件
(Spring-context.xml)
中
1.引入xmlns:aop="http://www.springframework.org/schema/aop"
2.在xsi:schemaLocation
中添加http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
3.写好核心业务类的bean,和附加功能bean- 使用
<aop:config>
做切入配置
1.先定位切入点,和需要切入的方法<aop:pointcut id="自定义切入点名称" expression="execution(切入方法名)"/>
2.再定义切入的内容,和切入到哪里
<aop:advisor advice-ref="附加功能bean" pointcut-ref="切入点名称"/>
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:aop="http://www.springframework.org/schema/aop"
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-2.5.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
">
<!--//核心业务-->
<bean id="userServiceImpl" class="Service.UserService"/>
<!-- 附加功能-->
<bean id="before" class="advice.MyBeforoAdvice"/>
<!-- 编织-->
<aop:config>
<!-- 定位切入点, 需要切入的方法-->
<aop:pointcut id="qianzhitongzhi" expression="execution(* insert())"/>
<!-- 切入的内容,需要切入到哪里-->
<aop:advisor advice-ref="before" pointcut-ref="qianzhitongzhi"/>
</aop:config>
</beans>
通知类
前置通知
MethodBeforeAdvice
//自定义前置通知类,实现MethodBeforeAdvice接口并重写Before方法
public class MyBeforoAdvice implements MethodBeforeAdvice {
@Override
public void before(Method method, Object[] objects, Object o) throws Throwable {
//前置通知
System.out.println("事务控制");
System.out.println("日志打印");
}
}
后置通知 AfterAdvice
(父类)
- 后置通知(有异常不执行,方法会因为异常而结束,无返回值)
AfterReturningAdvice
(子类)
public class MyAfterAdvice implements AfterReturningAdvice {
@Override
public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable {
System.out.println("这是一个后置通知");
}
}
- 异常通知
ThrowsAdvice
(子类)
//在核心中抛出异常时将会执行此Advice
public class MyThrowsAdvice implements ThrowsAdvice {
public void afterThrowing(Exception ex) {
System.out.println("在核心业务中抛出异常时将会执行此Advice");
}
}
环绕通知
MethodInterceptor
public class MyMethodInterceptor implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("核心开始执行~~");
//执行核心代码,需有返回值
Object o = invocation.proceed();
System.out.println("核心执行结束~~~");
return o;//返回返回值
}
}
通配切入点
首先明确一点,
<aop:pointcut id="自定义切入点名称" expression="execution(切入位置)"/>
这个切入位置的书写方式为
execution(返回值 包名.类名.方法名(参数))
几种通配切入点的写法
- 匹配参数
<aop:pointcut id="p1" expression="execution(* *(user.User))"/>
- 匹配方法名(无参)
<aop:pointcut id="p1" expression="execution(* uodate())"/>
- 匹配方法名(任意参数)
<aop:pointcut id="p1" expression="execution(* uodate(..))"/>
- 匹配返回值类型
<aop:pointcut id="p1" expression="execution(Integer *(..))"/>
- 匹配类名
<aop:pointcut id="p1" expression="execution(* user.User.*(..))"/>
- 匹配包名
<aop:pointcut id="p1" expression="execution(* user.*.*(..))"/>
- 匹配包名及其子包名
<aop:pointcut id="p1" expression="execution(* user..*.*(..))"/>
<!-- user包及其下面的子包的所有类的所有方法 -->
AOP原理
JDK还是CGlib
目标业务类有接口,就用jdk没有接口就用cgLib
后处理器
- 自定义后处理器
新建类使其实现
BeanPostProcessor
接口,并重写其中的两个方法
public class MyBeanPostProcessor implements BeanPostProcessor {
//自定义后处理器,用来理解后处理器的工作原理
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("该后处理程序,运行在set之后,初始化之前");
System.out.println("后处理接收到的Bean:" + bean + ",beanName:" + beanName);
return bean;
}
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("该后处理程序,运行在初始化之后");
System.out.println("后处理接收到的Bean:" + bean + ",beanName:" + beanName);
return bean;
}
}
事务
注解
Bean声明的注解
@Service
Service专用注解@Repository
Dao专用注解@Controller
Controller 专用注解@Component
通用注解
注解的使用方式都是一样的
例如 在一个Service类前打上@Service
就相当于在context中声明了一个bean bean的ID就是当前类的类名首字母小写,class就是当前类的classpath, 如果想要自定义id 也可以这样写:@Service("ID")
@Scope("prototype")
创建多例的Bean
注入的注解
-
@Autowired
基于类型自动注入 相当于ByType
也可以和@Qualifier("name")
联用,意思是基于类型自动注入,并挑选id=name的 -
@Qualifier("name")
限定要自动注入的Beande ID,一般都是和Autowired连用 -
@Resource
基于名称的自动注入 相当于ByName
也可以使用@Resource(name="name")
来自定义name -
@value("value")
简单类型的自动注入(8种基本数据类型+String)
事务注解
@Transactional()
加到类上时等于把类里面的每个方法都加了此注解,加到方法时仅对此方法有效,并且时类的此注解,对此方法失效
注解的配置
扫描注解
告知Spring注解在哪些个类中
<context:component-scan base-package="父包名"></context:component-scan>
@ComponentScan(basePackages = "父包名")
AOP注解
@Aspect
声明此类是一个切面类,会包含切入点和通知@Component
声明组件,进入工厂@Pointcut(execution(切入位置))
定义切入点
打到一个空的方法上,后面的通知可以调用,实际上调用的是此注解@Before
定义前置通知@AfterReturning
定义后置通知@Around
定义环绕通知@AfterThrowing
定义异常通知
@Aspect
@Component
public class MyAspect {
//定义切入点
@Pointcut("execution(* *.*(..))")
public void pc() {
}
//前置通知
@Before("pc()")
public void myBefore(JoinPoint a) {
System.out.println("当前目标:" + a.getTarget());
System.out.println("当前方法的参数表:" + a.getArgs());
System.out.println("当前方法名:" + a.getSignature().getName());
System.out.println("添加前置通知!!!!!!!!");
}
//后置通知(有异常将不执行)
@AfterReturning(value = "pc()", returning = "ret") //意思是核心方法的返回值为ret
public void myAfterReturning(JoinPoint a, Object ret) {
System.out.println("后置通知" + ret);
}
//后置通知,有异常将执行
@AfterThrowing(value = "pc()", throwing = "ex")
public void MyThrows(JoinPoint jp, Exception ex) {
System.out.println("throws");
System.out.println("错误信息:" + ex.getMessage());
}
//环绕通知
@Around("pc()")
public Object MyInterceptor(ProceedingJoinPoint p) throws Throwable {
System.out.println("环绕的前置通知");
Object ret = p.proceed();//执行核心功能
System.out.println("环绕的后置通知");
return ret;
}
}
使AOP注解生效
<!-- 启用AOP注解-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
@EnableAspectJAutoProxy
Spring和Junit的集成
导入Spring-text和Junit的依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.1.6.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
使用Spring-text测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:spring-config.xml")