黑马-Spring(IOC&DI) AOP
IOC(控制翻转)
概念
把对象的创建、初始化、销毁等工作交给spring容器来做
案例
环境
步骤
1、 写一个HelloWorld类
2、 写一个配置文件 把hello类放到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" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd"> <!-- beans 一个bean代表一个类 所以beans就是很多个类 --> <!-- 一个类 id 标示符 class 类的全名 --> <bean id="helloWorld" class="com.itheima09.spring.ioc.helloworld.HelloWorld"> </bean> </beans>
3、 客户端
package com.itheima09.spring.ioc.helloworld.test; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import com.itheima09.spring.ioc.helloworld.HelloWorld; public class HelloWorldTest { @Test public void testHello(){ HelloWorld helloWorld = new HelloWorld(); helloWorld.hello(); } @Test public void testHello_Spring(){ /** * 1、启动spring容器 * 2、从spring容器中把对象提取出来 * 3、对象调用方法 */ //启动了spring容器了 ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); //从spring容器中把helloWorld对象提取出来了 HelloWorld helloWorld = (HelloWorld)context.getBean("helloWorld"); helloWorld.hello(); } }
4、 说明:
Spring容器的作用就是为HelloWorld这个类创建对象
Spring容器的结构
创建对象
构造函数(用的最多)
静态工厂
关于静态工厂方法(https://www.jianshu.com/p/ceb5ec8f1174)
实例工厂
说明:
给工厂类创建了一个对象helloWorldFactory,再利用工厂对象调用工厂方法
别名
对象的创建时机
案例
执行步骤为1,3,2
helloword构造函数为什么调用3次
以上两种情况是默认值,当spring容器启动的时候创建对象
在bean有这样一个属性
意义
如果把lazy-init设置为true,则当spring容器启动的时候,检测不到任何错误,这样会存在很大的安全性隐患,所以一般情况下应该设置lazy-init为default/false。但是如果一个bean中有一个属性,该属性含有大量的数据,这个时候不希望该bean过早的停留在内存中。
这个时候需要用到lazy-init为true。
对象的scope
默认情况(scope=singleton)
在默认情况下放入到spring中的bean是单例的
将来service层和dao层所有的类将放入到spring容器中,所以默认情况下这两个层的类的实例都是单例的,所以不能把数据声明到属性中。如果声明在属性中,将会成为共享的。
Scope为prototype
创建时机和scope的结合
Scope为prototype,lazy-init为true
在context.getBean时创建对象
Scopse为prototype,lazy-init为false
在context.getBean时创建对象,lazy-init为false失效
当scpose为prototype时,始终在context.getBean时创建对象
Scopse为singleton
是默认情况
Init和destroy
说明:
1、 init方法是由spring内部执行的
2、 只有当spring容器关闭以后才能执行destroy方法,spring容器一般情况下是不会关闭的。只有当web容器销毁掉的时候才可能关闭掉,所以只要一个对象在spring容器中,在spring容器关闭之前,会一直保留。
3、 如果一个bean的配置是scope为”prototype”,则spring容器不负责销毁。
Spring容器做的事情
总结
创建对象
1、 对象的创建方式
2、 对象的创建时机
3、 对象的创建的模式
4、 Init和destroy
5、 创建时机和创建模式的结合
DI(依赖注入)
概念
给属性赋值
给pid和name赋值的过程就是di
Xml
Setter方法
说明:
1、 spring容器实例化person和student两个对象
2、 利用java的反射机制调用属性的setter方法赋值 property的 name属性由setter决定
3、 在客户端利用context.getBean方法把spring容器中的一个对象获取了。
说明:
1、 启动spring容器
2、 实例化person对象和student对象
3、 给person中的属性赋值
4、 调用person的init方法初始化
5、 客户端利用context.getBean获取对象
说明:
1、 启动spring容器
2、 实例化person对象
3、 因为person对象依赖于student对象,所以在实例化person对象的时候必须实例化student对象,所以这个时候,在student对象上的lazy-init为true将失效。
说明:
1、 启动spring容器
2、 实例化student
3、 在客户端执行context.getBean方法获取person对象
4、 实例化person对象,调用person的构造函数
5、 调用person中的setStudent方法,给person中的student赋值
6、 执行person中的init方法
7、 Person对象调用方法
构造器
说明:
1、 constructor-arg代表指定的构造器函数的其中的一个参数
2、 可以利用index,ref,value,type来指定唯一的构造器
3、 如果一个bean的配置中没有constructor-arg属性,则必须利用默认的构造函数创建对象。
4、 所以在写一个javabean的时候,应该提供属性的setter方法,默认的构造器,带参数的构造器
IOC和DI的意义
案例1
需求
编写一个文档管理系统,在该系统中有如下的结构:
1、 Document:interface
readDocument方法
writeDocument方法
2、 WordDocument 是Document的实现类
readDocument
writeDocument
3、 ExcelDocument
readDocument
writerDocument
4、 PDFDocument
readDocument
writeDocument
5、 DocumentManager
Document document;
readDocument()
writeDocument()
做法1
说明:
上述的代码是不完全的面向接口编程
做法2
说明:
在代码端没有出现具体的类,完全的面向接口编程。
在spring容器的配置文件中决定了documentManager中的接口的实现类是什么。而这个过程和java代码端没有关系。
案例2
需求
把action调用service,service调用dao用spring来完成
实现
意义
实现了完全的面向接口编程,在代码端没有要关系一个接口的实现类是什么。
注解
概念
1、 用来解释说明
2、 注解必须作用在类的某一个部分
3、 注解的作用域范围(java,class,jvm)
4、 注解解析器
自定义的注解
注解的使用
注解解析器
package com.itheima09.annotation; import java.lang.reflect.Method; import org.junit.Test; /** * 注解解析器 * @author zd * */ public class AnnotationParse { public static void parse(){ Class classt = Itheima09.class; //在该类上存在ClassInfo注解 if(classt.isAnnotationPresent(ClassInfo.class)){ //从类上得到类的注解 ClassInfo classInfo = (ClassInfo)classt.getAnnotation(ClassInfo.class); //输出该注解的name属性 System.out.println(classInfo.name()); } //获取该类的所有的方法 Method[] methods = classt.getMethods(); for(Method method:methods){ //如果该方法上存在MethodInfo注解 if(method.isAnnotationPresent(MethodInfo.class)){ //获取该方法上面的methodinfo注解 MethodInfo methodInfo = method.getAnnotation(MethodInfo.class); //输出注解中的value属性 System.out.println(methodInfo.value()); } } } @Test public void test(){ AnnotationParse.parse(); } }
Spring中的注解
<?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-2.5.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd"> <!-- 把person和student放入到spring容器中 --> <bean id="person" class="com.itheima09.spring.di.annotation.Person"></bean> <bean id="student" class="com.itheima09.spring.di.annotation.Student"></bean> <!-- 启动依赖注入的注解解析器 --> <context:annotation-config></context:annotation-config> </beans>
package com.itheima09.spring.di.annotation; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class PersonTest { @Test public void testPerson(){ ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); Person person = (Person)context.getBean("person"); person.say(); } }
在spring的配置文件中
说明:
1、 启动spring容器
2、 spring容器内部创建了两个对象person和student
3、 当spring容器解析到
启动依赖注入的注解解析器:
1、 spring容器在容器中查找所有的bean(prerson,student)
2、 看哪些bean的属性上面是否有Resource注解
3、 如果属性上面有该注解,再次检查是否有name属性
4、 如果没有name属性,则会把该注解标注的属性的名称获取到和spring容器中的id做匹配,如果匹配成功,则赋值,如果匹配不成功,则按照类型(注解的变量的类型 和 所有bean的class类型)进行匹配,如果匹配成功,则赋值(不推荐,类型匹配要求bean类型唯一出现,否则错误),如果匹配不成功,则报错。(赋值指的是person的private属性student的赋值,在Person类中对private属性student前面加上 Resource注解)
5、 如果有name属性,则把name属性的值解析出来和spring容器中的id做匹配,如果匹配成功,则赋值,如果匹配不成功,则报错。
6、 从上述的步骤可以看出注解的效率比较低,xml的效率比较高,注解书写比较简单,xml书写比较复杂。
Spring容器的关于di的注解(spring自己的注解)
按照类型匹配
按照ID匹配
注解只能应用与引用类型
类扫描的注解
<?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-2.5.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd"> <!-- 把一个类放入到spring容器中,该类就是一个component 在base-package指定的包及子包中扫描所有的类 --> <context:component-scan base-package="com.itheima09.spring.scan.annotation"> </context:component-scan> </beans>
package com.itheima09.spring.scan.annotation; import javax.annotation.Resource; import org.springframework.stereotype.Component; @Component("person") public class Person { @Resource(name="student") private Student student; public void say(){ this.student.say(); } }
步骤
说明:在指定的包及子包中扫描
流程分析
1、 启动spring容器
2、 Spring容器解析类扫描的注解解析器,在base-package指定的包及子包中查找所有的类
3、 查看哪些类上面是否含有@Component注解
4、 如果该注解的value的属性的值为空,则把类名的第一个字母变成小写,作为id值,放入到spring容器中
5、 如果该注解的value的属性的值不为空,则用value的属性的值作为id值,放入到spring容器中
6、 再次查找在spring容器中的类的所有的属性,按照@Resource的规则给属性赋值
说明
使用了类扫描机制的做法,配置文件中的配置很简单了,但是效率越来越低。
继承
Xml的继承
注解的继承
Aop
目的
让PersonDao的数据库的操作和Transaction事务的操作分离。
代理模式
静态代理模式
package com.itheima09.dao.proxy; public interface PersonDao { public void savePerson(); }
package com.itheima09.dao.proxy; public class PersonDaoImpl implements PersonDao{ public void savePerson() { System.out.println("save person"); } }
package com.itheima09.dao.proxy; import com.itheima09.dao.Transaction; public class PersonDaoProxy implements PersonDao{ private PersonDao personDao; private Transaction transaction; public PersonDaoProxy(PersonDao personDao,Transaction transaction){ this.personDao = personDao; this.transaction = transaction; } public void savePerson() { /** * 1、开启事务 * 2、进行save操作 * 3、事务提交 */ this.transaction.beginTransaction(); this.personDao.savePerson(); this.transaction.commit(); } }
package com.itheima09.dao.proxy; import org.junit.Test; import com.itheima09.dao.Transaction; public class ProxyTest { @Test public void testProxy(){ /** * 创建PersonDaoImpl对象 * 创建事务对象 * 创建PersonDaoProxy对象 */ PersonDao personDao = new PersonDaoImpl(); Transaction transaction = new Transaction(); PersonDaoProxy personDaoProxy = new PersonDaoProxy(personDao, transaction); personDaoProxy.savePerson(); } }
动态代理模式
Jdk动态代理
接口
目标类
拦截器
客户端
问题
1、 代理对象有多少方法,方法的名称是什么?
因为代理对象和目标类一样,同样的实现了接口,所以接口中有多少方法,代理对象中就有多少个方法,名称和接口中的方法的名称一样。
2、 拦截器中的invoke方法在什么时候执行的?
当在客户端,代理对象调用方法的时候,进入到了invoke方法
3、 拦截器中的invoke方法中的method参数在什么时候传递的值?
当在客户端,代理对象调用方法的时候,进入到了invoke方法,这个时候,method参数就是代理对象调用的方法。
4、 代理对象的方法体的内容是什么?
代理对象的方法体的内容就是invoke方法体的内容
代理对象的方法体:
1、 开启事务
2、 目标方法
3、 事务的提交
4、 代理对象的方法体就把事务和目标方法结合在一起了,这样做的目的就是为了让目标类的目标方法和事务的方法松耦合。
流程图
案例
目标接口
public interface SalaryManager { public void showSalary(); }
目标类
package com.heima.dao.jdkproxy.salary; public class SalaryManagerImpl implements SalaryManager{ @Override public void showSalary() { System.out.println("正在查看工资"); } }
日志
package com.heima.dao.jdkproxy.salary; public class Logger { public void logging(){ System.out.println("logging"); } }
安全性框架
package com.heima.dao.jdkproxy.salary; public class Security { public void security(){ System.out.println("security"); } }
权限类
package com.heima.dao.jdkproxy.salary; public class Privilege { private String access; public void setAccess(String access) { this.access = access; } public String getAccess() { return access; } }
拦截器
package com.heima.dao.jdkproxy.salary; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; public class SalaryManagerInterceptor implements InvocationHandler{ private Object target; private Logger logger; private Security security; private Privilege privilege; public SalaryManagerInterceptor(Object target, Logger logger, Security security, Privilege privilege) { this.target = target; this.logger = logger; this.security = security; this.privilege = privilege; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { this.logger.logging(); this.security.security(); if(this.privilege.getAccess().equals("admin")){ method.invoke(target, args); }else{ System.out.println("权限不足"); } return null; } }
拦截器把这些内容全部结合在一起了。
改进
可以把日志、安全性框架等作为一个接口出现
日志:
在拦截器中:
Cglib代理
1、 产生的代理类是目标类的子类
2、 是用字节码增强技术产生的代理类
案例
jdkProxy缺点, 通知和目标方法结合在一起的过程,放在拦截器中,需要程序员自己指定(耦合性高),所以引入aop,可以再xml中配置,提高松耦合
Aop的概念
切面
事务、日志、安全性的框架,权限等就是切面
通知
切面中的方法就是通知
切入点
只有符合(表达式)切入点的条件,才能让通知和目标方法结合在一起
连接点
客户端调用的方法
织入
形成代理对象方法体的过程
Aop的意义
说明:
1、 在开发的过程中,日志、权限、安全性的框架、目标方法完全是松耦合的
2、 在形成代理对象的方法的过程中就把这几个结合在一起了
切入点表达式
3.1 execution 由于Spring切面粒度最小是达到方法级别,而execution表达式可以用于明确指定方法返回类型,类名,方法名和参数名等与方法相关的部件,并且在Spring中,大部分需要使用AOP的业务场景也只需要达到方法级别即可,因而execution表达式的使用是最为广泛的。如下是execution表达式的语法: execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?) 这里问号表示当前项可以有也可以没有,其中各项的语义如下: modifiers-pattern:方法的可见性,如public,protected; ret-type-pattern:方法的返回值类型,如int,void等; declaring-type-pattern:方法所在类的全路径名,如com.spring.Aspect; name-pattern:方法名类型,如buisinessService(); param-pattern:方法的参数类型,如java.lang.String; throws-pattern:方法抛出的异常类型,如java.lang.Exception; 如下是一个使用execution表达式的例子: execution(public * com.spring.service.BusinessObject.businessService(java.lang.String,..)) 上述切点表达式将会匹配使用public修饰,返回值为任意类型,并且是com.spring.BusinessObject类中名称为businessService的方法,方法可以有多个参数,但是第一个参数必须是java.lang.String类型的方法。上述示例中我们使用了..通配符,关于通配符的类型,主要有两种: *通配符,该通配符主要用于匹配单个单词,或者是以某个词为前缀或后缀的单词。 如下示例表示返回值为任意类型,在com.spring.service.BusinessObject类中,并且参数个数为零的方法: execution(* com.spring.service.BusinessObject.*()) 下述示例表示返回值为任意类型,在com.spring.service包中,以Business为前缀的类,并且是类中参数个数为零方法: execution(* com.spring.service.Business*.*()) ..通配符,该通配符表示0个或多个项,主要用于declaring-type-pattern和param-pattern中,如果用于declaring-type-pattern中,则表示匹配当前包及其子包,如果用于param-pattern中,则表示匹配0个或多个参数。 如下示例表示匹配返回值为任意类型,并且是com.spring.service包及其子包下的任意类的名称为businessService的方法,而且该方法不能有任何参数: execution(* com.spring.service..*.businessService()) 这里需要说明的是,包路径service..*.businessService()中的..应该理解为延续前面的service路径,表示到service路径为止,或者继续延续service路径,从而包括其子包路径;后面的*.businessService(),这里的*表示匹配一个单词,因为是在方法名前,因而表示匹配任意的类。 如下示例是使用..表示任意个数的参数的示例,需要注意,表示参数的时候可以在括号中事先指定某些类型的参数,而其余的参数则由..进行匹配: execution(* com.spring.service.BusinessObject.businessService(java.lang.String,..))
https://www.cnblogs.com/zhangxufeng/p/9160869.html
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)
这里问号表示当前项可以有也可以没有,其中各项的语义如下:
- modifiers-pattern:方法的可见性,如public,protected;
- ret-type-pattern:方法的返回值类型,如int,void等;
- declaring-type-pattern:方法所在类的全路径名,如com.spring.Aspect;
- name-pattern:方法名类型,如buisinessService();
- param-pattern:方法的参数类型,如java.lang.String;
- throws-pattern:方法抛出的异常类型,如java.lang.Exception;
- ..通配符,该通配符表示0个或多个项,主要用于declaring-type-pattern和param-pattern中,如果用于declaring-type-pattern中,则表示匹配当前包及其子包,如果用于param-pattern中,则表示匹配0个或多个参数。
-
- *通配符,该通配符主要用于匹配单个单词,或者是以某个词为前缀或后缀的单词。
?表示可选可不选 可以不填
必须写 有2个: 1 ret-type-pattern:方法的返回值类型,如int,void等;
2
- name-pattern:方法名类型,如buisinessService();
- param-pattern:方法的参数类型,如java.lang.String;
代表所有的公共方法
代表所有的以set开头的方法
代表com.xyz.service包下的AccoutService类的所有的方法
代表com.xyz.service包下的所有的类的所有的方法
代表com.xyz.service包及子包下的所有的类的所有的方法
代表com.itheima.spring.aop.xml包下的所有的类的有三个参数,第一个参数为Long,第二个参数为String,第三个参数为任意类型的所有的方法
(1)Aspect(切面):通常是一个类,里面可以定义切入点和通知
(2)JointPoint(连接点):程序执行过程中明确的点,一般是方法的调用
(3)Advice(通知):AOP在特定的切入点上执行的增强处理,有before,after,afterReturning,afterThrowing,around
(4)Pointcut(切入点):就是带有通知的连接点,在程序中主要体现为书写切入点表达式
(5)AOP代理:AOP框架创建的对象,代理就是目标对象的加强。Spring中的AOP代理可以使JDK动态代理,也可以是CGLIB代理,前者基于接口,后者基于子类
Spring的aop
步骤
目标接口
目标类
切面
Spring的配置文件
执行流程
1、 context.getBean时,如果该类没有生成代理对象,则返回对象本身
2、 如果产生了代理对象,则返回代理对象
如果目标类实现了接口,则采用jdkproxy生成代理对象,如果目标类没有实现接口,则采用cglibproxy生成代理对象,而生成代理对象是由spring容器内部完成的。
通知
前置通知
在目标方法执行之前执行。
后置通知
在目标方法执行之后执行
可以获取目标方法的返回值
当目标方法遇到异常,不执行
最终通知
无论目标方法是否遇到异常都会执行,相当于代码中的finnaly
异常通知
获取目标方法抛出的异常
环绕通知
能够控制目标方法的执行
package com.heima.spring.aop.xml.advice; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; public class Transaction { public void begeinTransaction(JoinPoint joinPoint){ System.out.println(joinPoint.getTarget()); System.out.println(joinPoint.getArgs()); System.out.println(joinPoint.getSignature().getName()); System.out.println("begin transaction"); } public void commit(JoinPoint joinPoint,Object val){ System.out.println(val); System.out.println("commit"); } public void finalyMethod(JoinPoint joinPoint){ System.out.println("finaly method"); } public void throwingMethod(JoinPoint jp,Throwable ex){ System.out.println(ex.getMessage()); System.out.println("异常"); } public void arroundMethod(ProceedingJoinPoint pjp)throws Throwable{ System.out.println("环绕通知aaa"); pjp.proceed(); System.out.println("环绕通知bbbb"); } }
案例:异常处理
技术图
实现
切面
目标类
配置
从配置中可以看出,把service层所有的类当成目标类,只要service层所有的类的所有的方法抛出异常,则exceptionAspect中的异常通知就会获取到目标方法抛出的异常,所以在这里异常通知就是用来处理异常的,而且只有一个方法。并且该切面和所有的其他类都是松耦合的。
案例:权限的处理
技术图
组成
1、 写dao层和service层的类和接口
2、 自定义的注解@PrivilegeInfo
3、 注解解析器:解析目标方法上面的注解的name属性的值
4、 写一个权限类Privilege(name)
5、 写一个关于权限的判断的切面,在切面中写一个环绕通知
实现
参照:/day03-02-itheima09-springAOP-xml-ex-privilege
扩展作业
利用spring的aop的环绕通知,记录如下的信息:
目标类 目标方法 执行时间 方法的起始时间 方法的结束时间
把上述的几个内容保存在数据库中,用jfreechar或者用table显示出来
AOP注解(了解)
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" 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/context http://www.springframework.org/schema/context/spring-context-2.5.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd"> <context:component-scan base-package="com.itheima09.springaop.annotation.transaction"></context:component-scan> <aop:aspectj-autoproxy></aop:aspectj-autoproxy> </beans>
package com.itheima09.springaop.annotation.transaction; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.springframework.stereotype.Component; /** * @Aspect * == * <aop:config> * <aop:pointcut expression="execution(* com.itheima09.springaop.annotation.transaction.PersonDaoImpl.*(..))" id="aa()"/> * </aop:config> * @author zd * */ @Component("transaction") @Aspect//说明该注解标注的类是一个切面类 public class Transaction { @Pointcut("execution(* com.itheima09.springaop.annotation.transaction.PersonDaoImpl.*(..))") private void aa(){} //方法标签 修饰符最好是private 返回值必须是void @Before("aa()") public void beginTransaction(){ System.out.println("begin transaction"); } @AfterReturning(value="aa()",returning="ex") public void commit(JoinPoint joinPoint,Object ex){ System.out.println("commit"); } }