1、环境配置
- jdk的安装及配置
- 安装IDEA
- IDEA环境配置:0
- maven的安装及配置
https://archive.apache.org/dist/maven/maven-3/ - mysql安装教程
- Navicat及各类常用软件
2、思想阐述
小结:
- mvc是一种设计思想,三层代码结构是这种思想的具体实现,mvc的核心目的就是为了解耦方便后期维护
- ioc是把创建对象的权力交由spring进行管理,动态创建对象,因对象自己来创建对象需要new,new就会导致后期需要new其他对象需要改动源码,静态创建对象
- DI是把spring容器创建好的对象注入到需要它的地方,让那个类可以使用此对象
- 相当与你想租房找了个中介,出了问题和房东没关系了,有问题找中介他会帮你处理
3、ioc控制反转
3-1、入门测试
- 由spring来创建bean对象,自己创建对象后期想改需要改源码并重新编译打包,而通过spring来管理则只需要改xml配置文件,同时存储结构是K-V结构 K是id,V是对象
3-1-1、目录创建
- 1、idea里一个module就是一个界面,所以在正式创建项目前先在文件夹里创建文件,再在idea中open打开此文件夹
- 2、选中上面创建得文件夹,右键module,创建子项目,即正式案例
- 如果想删除项目,右键先remove,移除运行状态,然后才能delete
- 如果普通文件想恢复成Maven项目,右键xml选择Add as Maven Project
- 如果里面的模块里面得文件没有颜色,File—>Project structure手动调配,还能增添项目到右侧目录栏
- 补充:Idea永久配置
File —> New Projects Settings,在此处进行课前的所有配置
3-1-2、具体实现
代码内容:ioc具体实现:3,4
小结:
- maven的install命令保存了两份jar包,一个在target一个在本地仓库,其位置来源于一开始创建时定义好的坐标,也引出了maven坐标的作用:用于检索文件,同时本地也保存一份时为了方便被自己后续的项目进行依赖
- 在导入spring依赖后创建实体类,用xml配置文件绑定实体类,最后在测试类加载使用
3-1-3、依赖导入失败方法
-
问题展示
-
问题分析:包下载不下来,可能是网络或者是下载工具maven的问题,还有就是IDEA的缓存问题
-
方法一:报错:Cannot resolve org.springframework.boot ,检查maven版本和其xml配置文件是否配置了仓库位置和阿里云,IDEA中是否正确配置
maven安装及配置 -
方法二:下载途中网络中断,根据报错的坐标删除报错的依赖,再刷新重新下载,或者直接找到仓库把里面文件删了再重新maven刷新下
-
方法三:报错:Unresolved dependency ,检查是否打开了离线模式
-
方法四:以上问题都不是,则清除IDEA内存重启
3-2、对象的创建
3-2-1、工厂模式
工厂模式:5
在具体实现入门测试后,存在以下问题
- bean标签如何创建的对象?
答:通过反射,加载类,然后实例化出对象,bean标签中有class属性,里面的值是类的全名,就干了这么一件事 ( 类.方法() ) - 问题二:抽象类bean该如何创建对象
答:方式一:通过工厂模式,静态工厂模式是通过静态方法的可以通过类名点调用的特点来在自定义方法里创建对象
方式二:通过实例化工厂,不用静态方法,在xml文件中将实例化的对象交给factory进行管理创建,而不是像静态方法直接自己来 ( 对象.方法() )
方式三(常用): Spring工厂模式,通过实现FactoryBean接口,程序会进行回调,按顺序依次执行接口里重写的方法,完成对象的实例化,不用在bean标签中去用factory-method创建对象
3-2-2、单例多例
1、2节
小结:懒加载只对单例有效,懒加载会在需要的时候才创建对象,如很少用户访问耗内存的资源可以设置成懒加载,默认关闭懒加载可用在先行创建服务器对象,因为要先用到加快用户访问速度
3-2-3、生命周期
1.实例化对象
2.初始化操作 (一般对对象的属性赋值)
3.用户使用对象(调用其中的方法)
4.对象销毁 (一般都是释放资源)
3-3、DI依赖注入
- 为属性赋值
- 通过set方法(首选)
<bean id="user" class="com.jt.pojo.User">
<property name="id" value="101"></property>
<property name="name" value="小明"></property>
</bean>
- 通过构造方法
<bean id="user" class="com.jt.pojo.User">
<constructor-arg name="id" value="102"></constructor-arg>
<constructor-arg name="name" value="小明"></constructor-arg>
</bean>
- 为集合赋值
注意Properties作为数据库配置文件存储的都是字符串,内容是写在尖括号里面而不是value属性,map里面存储的是entry对象,对象里面才是k-v
<bean id="user" class="com.jt.pojo.User">
<property name="list">
<list>
<value>李四</value>
<value>王五</value>
<value>张三</value>
</list>
</property>
<property name="set">
<set>
<value>1</value>
<value>2</value>
<value>3</value>
</set>
</property>
<property name="map">
<map>
<entry key="id" value="name"></entry>
<entry key="id2" value="name2"></entry>
<entry key="id3" value="name3"></entry>
</map>
</property>
<property name="pro">
<props>
<prop key="proId">110</prop>
<prop key="proName">米老鼠</prop>
</props>
</property>
</bean>
- 提取公共部分的赋值 : 修改头文件都为的bean为util
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:util="http://www.springframework.org/schema/util"
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/util http://www.springframework.org/schema/util/spring-util.xsd">
<util:map id="map">
<entry key="id" value="name"></entry>
<entry key="id2" value="name2"></entry>
<entry key="id3" value="name3"></entry>
</util:map>
<bean id="user2" class="com.jt.pojo.User">
<property name="map" ref="map">
</property>
</bean>
4、mvc实现3层代码结构
4-1、基础实现
- 用户只复杂调用方法,各层之间通过方法进行关联
- 最顶层没有接口,接口是暴露给外部访问的
- 最低层没有注入对象,因为是要把对象存入数据库
- xml配置文件负责对象的创建和注入
4-2、优化(注解开发)
1、 注入方式
- autowire属性:使用autowire自动注入代替property的手动注入,
原理:autowire的byname底层是通过找到set()方法找到名,再去K-V找Key,名称不对应则用byType的方法找spring中维护的对象有没有它 - @autowire注解,直接加在变量上,原理同属性的用法,只是不需要写在xml文件中了,也不需要写set方法了
- @autowire注解首先根据属性的类型进行注入,如果类型不能匹配,则根据属性的名称进行注入.
如果添加了@Qualifier(“userServiceA”) 则根据属性名称注入 如果名称注入失败,则报错返回.
@Controller
public class UserController {
@Autowired
private User user;
@Autowired
private UserService userService;
public void addUser(){
userService.addUser(user);
}
}
2、创建对象
使用@Controller、@Service、@repository注解来创建对象交给spring管理,然后在xml配置文件加载包扫描()
使用默认过滤规则:会扫描所有注解
不使用:要自己指定规则扫描哪几个
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
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="user" class="com.jt.pojo.User">
<property name="id" value="101"></property>
<property name="name" value="小明"></property>
</bean>
<context:component-scan base-package="com.jt">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
<!-- 需求一:只扫描controller注解-->
<!-- 使用默认过滤器会扫描全部注解,关闭则是按需扫描-->
<context:component-scan base-package="com.jt" use-default-filters="false">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
<!-- 需求二:跳过controller注解-->
<context:component-scan base-package="com.jt">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
</beans>
3、xml配置文件
- 用配置类来代替xml配置文件
- 原来的bean标签使用@bean注解来创建实体类对象
- @ComponentScan来进行包扫描
- properties配置文件来代替进行实体类的赋值
@Configuration
@ComponentScan("com.jt")
@PropertySource(value = "classpath:/user.properties",encoding = "Utf-8")
public class SpringConfig {
@Value("${user.id}")
private Integer id;
@Value("${user.username}")
private String username;
@Bean
public User user(){
User user = new User();
user.setId(id);
user.setName(username);
return user;
}
}
4-3、Spring内存加载机制
- 1、当程序启动Spring容器时 AnnotationConfigApplicationContext 利用beanFactory实例化对象
- 2、根据配置类中的包扫描开始加载指定的注解(4个). 根据配置文件的顺序依次进行加载
- 3、当程序实例化Controller时,由于缺少Service对象,所以挂起线程 继续执行后续逻辑.
当构建Service时,由于缺少Dao对象,所以挂起线程 继续执行后续逻辑.
当实例化Dao成功时,保存到Spring所维护的Map集合中. 执行之前挂起的线程.
所以以此类推 所有对象实现封装.最终容器启动成功
5、AOP
- 底层就是动态代理 ,AOP是对动态代理的优化
5-1、案例引入
- 问题描述:增添用户的方法要增添事务控制的代码,传统方式是直接在service层的addUser()方法书写事务代码,这样操作会导致代码耦合,以后如果是删除用户还需要事务控制,则还要写一遍事务控制的代码。
- 解决方法:事务控制属于功能拓展,引入一个代理类在不影响原有的增添方法下,来负责功能的拓展,并且把代理的名字换成service,让用户认为调用的就是service层本身
1、静态代理
- 代码实现:1.5 静态代理
- 小结:点睛之笔在于修改名称,把真正的对象命名成target,把代理对象命名成UserService。同时代理对象引入了真正对象,同时实现同一接口正常调用了原有方法,拓展实现了事务控制
2、JDK动态代理
- 具体代码实现:1.6 动态代理机制
- JDK动态代理类
public class JDKProxyFactory {
//要求用户传递目标对象
//关于匿名内部类用法说明: 匿名内部类引用外部参数 要求参数必须final修饰
public static Object getProxy(final Object target){
//1.调用java API实现动态代理
/**
* 参数分析: 3个参数
* 1.ClassLoader loader, 类加载器(获取目标对象的Class)
* 2.类<?>[] interfaces, JDK代理要求 必须有接口
* java中可以多实现
* 3.InvocationHandler(调用处理程序) 对目标方法进行扩展
*/
//1.获取类加载器
ClassLoader classLoader = target.getClass().getClassLoader();
//2.获取接口数组
Class[] interfaces = target.getClass().getInterfaces();
//3.通过动态代理创建对象
Object proxy = Proxy.newProxyInstance(classLoader, interfaces, new InvocationHandler() {
//invoke方法: 代理对象调用方法时invoke执行,扩展方法的编辑位置,是InvocationHandler内部类的方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// result 标识目标方法执行的返回值
Object result = null;
try {
//添加事务的控制
System.out.println("事务开始");
//执行目标方法
// target真实的目标对象,method方法对象,args方法参数
//调用目标对象的addUser方法
result = method.invoke(target,args);
System.out.println("事务提交");
}catch (Exception e){
e.printStackTrace();
System.out.println("事务回滚");
}
return result;
}
});
return proxy;
}
}
- 测试类
@Test
public void testJDKproxy(){
//1、加载配置类Springconfig
ApplicationContext context =new AnnotationConfigApplicationContext(SpringConfig.class);
//2、获取真实目标对象UserServiceImpl 别名取为target
UserService target = (UserService) context.getBean("target");
//2、获取代理对象JDKProxyFactory 其中有静态方法getProxy可直接调用
//方法的作用是通过java的API中的Proxy.newProxyInstance(类构造器,类的数组,对目标方法进行拓展),创建代理,返回代理
//事务控制的代码也在拓展方法中书写
UserService userService = (UserService) JDKProxyFactory.getProxy(target);
System.out.println(userService.getClass());
User user =new User();
user.setId(404);
user.setName("小红");
//拓展了事务操作的增添方法
userService.addUser(user);
//以后如果想要拓展了事务操作的删除方法
//只需在此处调用即可,不用写重复的事务操作代码
userService.deleteUser(user);
}
- 执行流程
通过API创建代理,同样也实现了目标对象的接口,但真正要调用方法需要进行回调
- 练习:动态代理实现记录程序运行时间 service中 有 addUser方法/deleteUser方法.
- JDKProxyFactory
public class JDKProxyFactory {
/**
* 此方法用于创建动态代理
* 设置成静态是为了点调用
* 返回类型是object是不确定目标对象target是什么类型
*/
public static Object getproxy(Object target){
//1、通过API Proxy.newProxyInstance(1,2,3) 创建代理,需要三个参数
//2、参数一:类加载器
ClassLoader classLoader = target.getClass().getClassLoader();
//3、参数二:接口数组, 因为JDK代理规定目标对象必须实现接口,同是接口是多实现,所以是接口数组
Class[] interfaces = target.getClass().getInterfaces();
//4、参数三:InvocationHandler(调用处理器) 用于拓展方法,不能写死,
//可以用内部类的方式重写里面的方法,将拓展功能写在重写方法中
Object proxy = Proxy.newProxyInstance(classLoader,interfaces, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//拓展方法:记录程序运行时间
long starttime = System.currentTimeMillis();
Thread.sleep(200);
long endtime = System.currentTimeMillis();
System.out.println("程序运行:"+(endtime-starttime)+"毫秒");
//调用目标对象target的增添和删除方法, 代理类同样实现了UserService接口
//记录返回执行后的结果
Object result = method.invoke(target, args);
return result;
}
});
//返回创建好的代理对象
return proxy;
}
}
- testSpring
public class testSpring {
@Test
public void testProxyFactory(){
ApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
//获取目标对象
UserService target = (UserService) context.getBean("target");
//根据目标对象创建代理对象
UserService userService = (UserService) JDKProxyFactory.getproxy(target);
userService.addUser();
userService.deleteUser();
}
}
补充:
- return返回的一个是代理对象,一个是代理对象调用目标对象方法后的返回值
- CGlib第二个参数注入的是目标对象target,而不是接口数组,因为目标对象和代理对象是父子关系
5-2、AOP正式介绍
- 图示流程
为了实现功能的拓展,而不想造成耦合,使用切面,切面不属于三层代码结构中的任何一项,其在程序运行时才会出现,实现了松耦合
切面就是在内存形成了个代理空间,在里面进行功能的拓展,只有满足我规定要求的目标方法才能进入切面,进入了切面的方法由原来的目标对象转变成了连接点,可以在切面内部获取原有目标对象,加工完成之后在返回到我们的主线程中
5-2-1、需要回答的6个问题
- AOP专业名词
- 通知类型(5个)
- 切入点表达式(4种)
- pointcut(切入点)的作用
- joinPoint(连接点)的作用
- 执行全流程
5-2-2、简单实现
0、创建目录结构,service类中提供了AddDept()和updateDept()方法,并加入注解交由spring进行管理
1、开启AOP需要在要AOP类和config配置类两处添加注解
@Component
@Aspect
public class SpringAop {
}
@Configuration
@ComponentScan("com.jt")
@EnableAspectJAutoProxy(proxyTargetClass = false)//JDK
public class SpringConfig {
}
仅在类中加入@Aspect并不能开启AOP,需要在配置类中加入@EnableAspectJAutoProxy注解,false为GBlib代理
2、书写切入点,判断方法是否能进入通知
-
切入点 = 通知类型 + 切入点表达式
-
通知类型:before、afterReturning、afterThrowing、after、around。(前四种负责记录,后一项负责控制目标方法执行)
-
切入点表达式:bean、within、execution、@annotation
-
简单说明 详情看 原博客
@Component
@Aspect
public class SpringAop {
/**
* 一、切入点表达式:
* 1、bean:
* bean(“bean的ID”) 根据beanId进行拦截 只能匹配一个
* 举例: bean(deptServiceImpl)
* 注意事项:匹配id为deptServiceImpl的对象,注意id是类的首字母小写
*
* 2、within:
* within(“包名.类名”) 可以使用通配符*? 能匹配多个.
* 举例: 1.within(com.jt.*.DeptServiceImpl) 一级包下的类 如:com.jt.service.DeptServiceImpl
* 2.within(com.jt..*.DeptServiceImpl) ..代表多级包下的类 如:com.jt.service.aa.bb.DeptServiceImpl
* 3.within(com.jt..*) 包下的所有的类
*
* 3、execution(返回值类型 包名.类名.方法名(参数列表))
* 举例: 1.execution(* com.jt..*.DeptServiceImpl.add*())
* 注释: 返回值类型任意的, com.jt下的所有(多级)包中的DeptServiceImpl的类的add开头的方法 ,并且没有参数.
* 2.execution(* com.jt..*.*(..))
* 注释: 返回值类型任意,com.jt包下的所有包的所有类的所有方法 任意参数.
* 3.execution(int com.jt..*.*(int))
* execution(Integer com.jt..*.*(Integer))
* 注意事项: 在Spring表达式中没有自动拆装箱功能! 注意参数类型
*
* 4、@annotation(包名.注解名) 只拦截特定注解的内容 注解是一种标记 根据规则标识某个方法/属性/类
* 举例:@annotation(com.jt.anno.Cache)
*
* 注意:以上在传入通知时都要用引号引起
*/
/**
* 二、切入点表达式的具体应用:
* 1、@Before("bean(deptServiceImpl)")
* 2、@Before("within(com.jt..*.DeptServiceImpl)")
* 3、@Before("execution(* com.jt..*.DeptServiceImpl.add*())")
* 4、@Before("@annotation(com.jt.anno.Cache)")//只会对有Cache注解修饰的方法生效
*
* 如:
* @Before("@annotation(com.jt.anno.Cache)") //只会对有Cache注解修饰的方法生效
* public void before(){
* System.out.println("我是before通知");
* }
*/
/**
* 三、测试不同通知类型
* 说明: 不同类型的注解有属于自己特有的属性,注意区别
* 了解每个注解的功能
*/
//1、定义提取公共的切入点表达式
@Pointcut("@annotation(com.jt.anno.Cache)")
public void pointcut(){
}
//2、joinPoint: 连接点(目标对象的方法),由spring容器为其赋值,用其获得目标对象及方法
@Before("pointcut()")
public void before(JoinPoint joinPoint){
System.out.println("获取目标对象的类型:"+joinPoint.getTarget().getClass());
System.out.println("获取方法参数:"+ Arrays.toString(joinPoint.getArgs()));
System.out.println("我是before通知");
}
//3、returning 是该注解特有的属性,用于接受方法的返回值,注意参数列表顺序也要对应
@AfterReturning(pointcut = "pointcut()",returning = "result")//只会对有Cache注解修饰的方法生效
public void afterReturning(JoinPoint joinPoint , Object result){
System.out.println("我是afterReturning通知");
}
//4、throwing: 该注解特有属性,动态接受程序运行时的报错信息
@AfterThrowing(pointcut = "pointcut()",throwing = "e")
public void afterThrowing(Exception e){
System.out.println("我是afterThrowing通知");
}
/**
* 5、作用: 可以控制目标方法是否执行.
* 参数: ProceedingJoinPoint 通过proceed方法控制目标方法执行.
* 注意事项:
* ProceedingJoinPoint is only supported for around advice
*/
@After("pointcut()")
public Object after(ProceedingJoinPoint joinPoint){
Object result = null;
try {
System.out.println("环绕通知开始");
result = joinPoint.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
System.out.println("环绕通知结束");
return result;
}
}
5-3-3、课后练习
-
业务说明
用户有一个缓存的集合 static Map<k:v> key=id号 value=User对象 根据id将User对象进行了缓存
要求:当用户第二次根据Id查询用户时,如果缓存中有数据,则直接返回 -
业务分析
1、使用AOP方式实现,因为缓存查询时通用代码
2、使用环绕的通知方式,原因:环绕才能实现第一次执行目标对象方法,第二次执行查询缓存(自定义)
3、切入点表达式选取常用的execution(…) -
步骤
-
1、搭建基础框架,包含实体类、配置类,service类提供findUser(User user)方法、测试方法,流程由注解开发
-
2、创建切面:切面要先由spring管理,再标识其是个切面,同时配置类要加入启动切面的注解
-
3、确定通知类型,及切入点表达式。通过业务可知我们要改变用户从数据库查询这一方法,通知类型中只有around环绕才能实现,表达式则相当于if判断满足了才能进来,四种写法任选其一。
@Around("execution(* com.jt..*.UserServiceImpl.*(..))")
public Object around(ProceedingJoinPoint JoinPoint){
Object result = null;
return result;
}
- 核心业务实现,从静态map<Integer,User>集合中查找是否是第二次查询
@Component
@Aspect
public class SpringAop {
private static Map map = new HashMap<>();
@Around("execution(* com.jt..*.UserServiceImpl.*(..))")
public Object around(ProceedingJoinPoint JoinPoint) throws Throwable {
//记录方法可能存在的返回值
Object result = null;
//1.获取目标对象的参数
Object[] users = JoinPoint.getArgs();
User user = (User) users[0];
//作为map的key
int id = user.getId();
//第二次查到
if (map.containsKey(id)) {
System.out.println("从缓存中查找");
return map.get(id);//直接返回对象
} else {//第一次查到
result = JoinPoint.proceed();//执行原有从数据库查找
map.put(id, user);
}
return result;
}
}