目录
注解配置Bean
Spring的 IOC 容器检查到注解就会生成对象,但这个注解的具体含义不会识别
其他三个注解都是@Component注解的别名
配置自动扫描的包
1. component-scan 要对指定包下的类进行扫描, 并创建对象到容器
2. base-package 指定要扫描的包,当spring容器创建/初始化时,就会扫描com.hspedu.spring.component包下的所有注解,将其实例化,放入到ioc容器bean默认id是类名首字母小写
如果要扫描spring下的包,写成com.hspedu.spring.*
com.hspedu.spring.component会扫描comonent及其子包,牺牲一部分效率多个包可以使用 逗号 隔开
<context:component-scan base-package="com.hspedu.spring.component"/>
如果bean目录有Order类有注解,没有指定value,就相当于
<bean id="order" class="com.spring.bean.Order"/>
选择性实例化Bean
只扫描包下的哪些类
在xml中加上 resource-pattern="User*.class" 表示只扫描spring.component 和它的子包下的User打头的类,用的比较少
<context:component-scan base-package="com.hspedu.spring.component"
resource-pattern="User*.class" />
排除包下某种类型的注解(注解方式)
需求:如果我们希望排除某个包/子包下的某种类型的注解,可以通过exclude-filter来指定
1.use-default-filters="true",默认是true,全部生效
2. context:exclude-filter 指定要排除哪些类,选择让谁失效
3. type 指定排除方式 annotation表示按照注解来排除
4. expression="org.springframework.stereotype.Service" 指定要排除的注解的全路径
<context:component-scan base-package="com.hspedu.spring.component">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service"/>
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Repository"/>
</context:component-scan>
指定自动扫描哪些注解类(注解方式)
需求:如果我们希望按照自己的规则,来扫描包/子包下的某些注解, 可以通过 include-filter
1. use-default-filters="false" 表示不使用默认的过滤机制/扫描机制,所有注解全部失效
2. context:include-filter 表示要去扫描哪些类,选择让谁生效
3. type="annotation" 按照注解方式来扫描/过滤
4. expression="org.springframework.stereotype.Service" 指定要扫描的注解的全路径
<context:component-scan base-package="com.hspedu.spring.component" use-default-filters="false">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Service"/>
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
<context:include-filter type="annotation" expression="org.springframework.stereotype.Repository"/>
</context:component-scan>
自动装配
@Value
用于简单类型,用于属性上,set方法上,构造方法的形参前
如果把@Value注解加到形参前面,必须是构造方法中的形参,其他方法不行
属性都写了@Value,可以不提供set方法
@Service
public class Student {
@Value(value = "tom")
private String name;
private int age;
@Value("18")
public void setAge(int age) {
this.age = age;
}
}
@AutoWired
用于非简单类型,单独使用默认根据类型装配(byType)
作用在属性、set方法、构造方法、构造方法的形参上
规则
IOC容器中有多个对象,要进行查找
1、是否有唯一bean
2、根据id查找根据id查找没找到,报错原因:按id找不到,按类型有多个,类型匹配不上
根据唯一性查找,属性名无所谓,直接按照类型找到
IOC容器中的对象和装配的对象哈希值不一定一样
多个类实现了同一个接口,类型是一样的
<context:component-scan
base-package="com.hspedu.spring.component"/>
<!--配置两个UserService对象-->
<bean class="com.hspedu.spring.component.UserService" id="userService200"/>
<bean class="com.hspedu.spring.component.UserService" id="userService300"/>
@Controller
public class UserAction {
@Autowired
private UserService userService200;//如果不是唯一bean,会根据id查找
}
@Qualifier与@AutoWired
必须搭配使用,相当于@Resource
如果有多个bean,则根据id进行装配
@Service("orderService")
public class OrderService {
@Autowired
@Qualifier("id")
private OrderDao orderDao;
}
@Resource
作用在属性、set方法上
按照name的值在容器中查找,属性名没有作用了
按照type类型查找,属性名没有作用了,要求在容器里只能有一个这样类型的对象
如果没有指定name和type,则先试用byName注入策略,如果匹配不上,再使用byType策略
泛型依赖装配
使用Autowired
把PhoneDao对象装配到PhoneService,通过泛型传入BaseDao,再把指定的泛型对象装配到PhoneService
全注解开发
@Configuration
@ComponentScan({"包1","包2"})
public class Spring6Config {
}
public class T {
@Test
public void m1() {
AnnotationConfigApplicationContext ioc =
new AnnotationConfigApplicationContext(Spring6Config.class);
ioc.getBean();
}
}
动态代理
不改变原有代码的情况下进行对象功能增强,使用代理对象拓展功能
将目标对象作为代理对象的一个属性
静态代理
一个接口就要写一个代理类,会类爆炸,进行优化 => 不写代理类,动态生成class字节码,这个字节码就是代理类
不使用匿名内部类
需要实现InvocationHandler接口
代理对象
Proxy.newProxyInstance这个方法做了两件事
1、在内存中动态生成了一个代理类的字节码class
2、new对象了。通过内存中生成的代理类这个代码,实例化了代理对象
三个参数
1、类加载器,目标类和代理类的加载器使用同一个
2、告诉代理类,目标类要实现哪一个或哪一些接口
3、InvocationHandler(调用处理器,是一个接口),写接口实现类或内部类
当代理对象调用代理方法的时候,invoke()方法会被调用
invoke(Object proxy, Method method, Object[] args)
三个参数
1、代理对象的引用。使用较少
2、目标对象上的目标方法,通过代理对象调用方法时的那个方法
3、目标方法上的实参
invoke要将目标对象的执行结果继续返回
public class Handler implements InvocationHandler {
private Object target;
public Handler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("增强1");
Object retValue = method.invoke(target, args);
System.out.println("增强2");
return null;
}
}
public class Client {
public static void main(String[] args) {
//创建目标对象
OrderService target = new OrderServiceImpl();
//创建代理对象,向下转型
OrderService proxyObj = (OrderService)Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(),new Handler(target));
//调用代理对象的代理方法
proxyObj.run();
}
}
封装
public class ProxyUtil {
public static Object newProxyInstance(Object target) {
return Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(),new Handler(target));
}
}
使用匿名内部类
公共接口
public interface Vehicle {
public void run();
public String fly(int height);
}
目标对象
public class Car implements Vehicle{
@Override
public void run() {
System.out.println("小汽车在路上 running....");
}
@Override
public String fly(int height) {
System.out.println("小汽车可以飞翔 高度=" + height);
return "小汽车可以飞翔 高度=" + height;
}
}
代理对象
public class VehicleProxyProvider {
//target_vehicle 表示真正要执行的对象
//该对象的类实现了Vehicle接口
private Vehicle target_vehicle;
public VehicleProxyProvider(Vehicle target_vehicle) {
this.target_vehicle = target_vehicle;
}
//返回代理对象
public Vehicle getProxy() {
ClassLoader classLoader =
target_vehicle.getClass().getClassLoader();
Class<?>[] interfaces = target_vehicle.getClass().getInterfaces();
//通过匿名对象的方式来创建该对象
InvocationHandler invocationHandler = new InvocationHandler() {
@Override
public Object invoke(Object o, Method method, Object[] args)
throws Throwable {
System.out.println("交通工具开始运行了....");
//method 是?: public abstract void com.hspedu.spring.proxy2.Vehicle.run()
//通过反射+动态绑定机制,执行被代理对象的方法
Object result = method.invoke(target_vehicle, args);
System.out.println("交通工具停止运行了....");
return result;
}
};
Vehicle proxy =
(Vehicle)Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);
return proxy;
}
}
Test类
@Test
public void proxyRun() {
//创建VehicleProxyProvider对象, 并且我们传入的要代理的对象
VehicleProxyProvider vehicleProxyProvider =
new VehicleProxyProvider(new Car());
//获取代理对象, 该对象可以代理执行方法
//proxy的编译类型是 Vehicle, 运行类型是 class com.sun.proxy.$Proxy9
Vehicle proxy = vehicleProxyProvider.getProxy();
//当执行run方法时,会执行到 代理对象的invoke
proxy.run();
String result = proxy.fly(10000);
System.out.println("result=" + result);
}
AOP
面向切面编程
术语
与事务不挂钩的叫做交叉业务
切点表达式
切点表达式用来定义通知(Advice)往哪些方法上切入
切点表达式可以指向类的方法,也可以指向接口的方法,也可以对没有实现接口的类进行切入
execution([访问控制权限修饰符] 返回值类型 [全限定类名]方法名(形式参数列表) [异常])
访问控制权限修饰符:
- 可选项。
- 没写,就是4个权限都包括。
- 写public就表示只包括公开的方法。
返回值类型:
- 必填项。
- * 表示返回值类型任意。
全限定类名:
- 可选项。
- 两个点“..”代表当前包以及子包下的所有类。
- 省略时表示所有的类。
方法名:
- 必填项。
- *表示所有方法。
- set*表示以set开始的所有方法 。
形式参数列表:
- 必填项
- () 表示没有参数的方法
- (..) 参数类型和个数随意的方法
- (*) 只有一个参数的方法
- (*, String) 第一个参数类型随意,第二个参数是String的。
异常:
- 可选项。
- 省略时表示任意异常类型。
复用
//定义一个切入点, 在后面使用时可以直接引用, 提高了复用性
@Pointcut(value = "execution(public float spring.aspectj.SmartDog.getSum(float, float)))")
public void myPointCut() {}
@Before(value = "myPointCut()")
public void showBeginLog(JoinPoint joinPoint) {}
@AfterReturning(value = "myPointCut()", returning = "res")
public void showSuccessEndLog(JoinPoint joinPoint, Object res) {}
@AfterThrowing(value = "myPointCut()", throwing = "throwable")
public void showExceptionLog(JoinPoint joinPoint, Throwable throwable) {}
细节
按接口来获取,多个类实现了接口会报错
UsbInterface interface = ioc.getBean(UsbInterface.class);interface是代理对象,获取Phone类的代理对象,getBean()就相当于之前的getPorxy()
UsbInterface interface = (UsbInterface)ioc.getBean(Phone.class);
UsbInterface interface = (UsbInterface)ioc.getBean("phone");
两种动态代理
jdk的Proxy
面向接口,只能增强实现类中接口中存在的方法
得到的对象是JDK代理对象的实例
生成的代理对象只能转换成接口不能转换成被代理类
Spring的CGlib
面向父类,可以增强父类的所有方法
得到的对象是被代理对象的子类
快速入门
类
@Component
public class OrderService {
// 目标方法
public void generate(){
System.out.println("订单已生成!");
}
}
@Aspect
@Component
public class MyAspect {
@Before("execution(* com.powernode.spring6.service.OrderService.*(..))")
// 这就是需要增强的代码(通知)
public void advice(){
System.out.println("我是一个通知");
}
}
@Test
public void testAOP(){
ApplicationContext ioc = new ClassPathXmlApplicationContext("spring-aspectj-aop-annotation.xml");
OrderService orderService = ioc.getBean("orderService", OrderService.class);
orderService.generate();
}
接口
public interface SmartAnimalable {
//求和
float getSum(float i, float j);
//求差
float getSub(float i, float j);
}
实现类
@Component //使用@Component 当spring容器启动时,将 SmartDog注入到容器
public class SmartDog implements SmartAnimalable {
@Override
public float getSum(float i, float j) {
float result = i + j;
System.out.println("方法内部打印result = " + result);
return result;
}
@Override
public float getSub(float i, float j) {
float result = i - j;
System.out.println("方法内部打印result = " + result);
return result;
}
}
切面类
- showBeginLog方法可以理解成就是一个切入方法
- JoinPoint joinPoint 在底层执行时,由AspectJ切面框架, 会给该切入方法传入 joinPoint对象,通过该方法,程序员可以获取到 相关信息
@Aspect
@Component
public class SmartAnimalAspect {
@Before(value = "execution(public float spring.aspectj.SmartDog.getSum(float, float))")
public void showBeginLog(JoinPoint joinPoint) {
//通过连接点对象joinPoint 可以获取方法签名
//invoke(Object proxy, Method method, Object[] args)整合到joinPoint
Signature signature = joinPoint.getSignature();
System.out.println(signature.getName() + "-参数 "
+ Arrays.asList(joinPoint.getArgs()));
}
}
XML开启AOP功能
开启基于注解的AOP功能,spring容器在扫描类的时候,查看该类上是否有@Aspect注解,如果有,则给这个类生成代理对象
如果不开启,.getClass()不会得到代理对象,只会得到当前运行的对象(相当于new)
proxy-target-class,ture表示强制使用CGLIB动态代理,默认false,使用JDK动态代理
<context:component-scan
base-package="com.hspedu.spring.aop.aspectj"/>
<aop:aspectj-autoproxy proxy-target-class="false"/>
Test类
@Test
public void smartDogTestByProxy() {
ApplicationContext ioc =
new ClassPathXmlApplicationContext("beans08.xml");
//需要通过接口类型来获取到注入的SmartDog对象-就是代理对象
SmartAnimalable smartAnimalable =
ioc.getBean(SmartAnimalable.class);
//SmartAnimalable smartAnimalable =
// (SmartAnimalable)ioc.getBean("smartDog");
smartAnimalable.getSum(10, 2);
}
以前是new一个对象,获得这个类的的代理对象
动态代理返回的对象是个代理对象,强转成接口的形式返回
Animal proxy =
(Animal)Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);
return proxy;
(xml中开启AOP)现在通过接口类型获取的对象就是这个类的代理对象
通知类型
- 前置通知:@Before 目标方法执行之前的通知
- 后置通知:@AfterReturning 目标方法执行之后的通知
- 环绕通知:@Around 目标方法之前添加通知,同时目标方法执行之后添加通知。
- 异常通知:@AfterThrowing 发生异常之后执行的通知
- 最终通知:@After 放在finally语句块中的通知
环绕是最大的通知,发生异常不会有后置通知和后环绕
切面优先级
如果同一个方法,有多个切面在同一个切入点切入,优先级如何控制
注解@Order(value = n) 来控制,n 越小,优先级越高
前置通知通过@Order来判断顺序
JoinPoint常用方法
joinPoint.getSignature()获取方法签名
后置通知中获取结果
在@AfterReturning 增加属性 , 比如 returning = "res",同时在切入方法增加 Object res
注意: returning = "res" 和 Object res 的 res名字一致
异常通知中获取异常
环绕通知
@Aspect
@Component
public class SmartAnimalAspect {
//演示环绕通知的使用-了解
//1. @Around: 表示这是一个环绕通知[完成其它四个通知的功能]
//2. value = "execution(public float com.hspedu.spring.aop.aspectj.SmartDog.getSum(float, float)) 切入点表达式
//3. doAround 表示要切入的方法 - 调用结构 try-catch-finally
@Around(value = "execution(public float com.hspedu.spring.aop.aspectj.SmartDog.getSum(float, float))")
public Object doAround(ProceedingJoinPoint joinPoint) {
Object result = null;
String methodName = joinPoint.getSignature().getName();
try {
//1.相当于前置通知完成的事情
Object[] args = joinPoint.getArgs();
List<Object> argList = Arrays.asList(args);
System.out.println("AOP环绕通知[-前置通知]" + methodName + "方法开始了--参数有:" + argList);
//在环绕通知中一定要调用joinPoint.proceed()来执行目标方法
result = joinPoint.proceed();
//2.相当于返回通知完成的事情
System.out.println("AOP环绕通知[-返回通知]" + methodName + "方法结束了--结果是:" + result);
} catch (Throwable throwable) {
//3.相当于异常通知完成的事情
System.out.println("AOP环绕通知[-异常通知]" + methodName + "方法抛异常了--异常对象:" + throwable);
} finally {
//4.相当于最终通知完成的事情
System.out.println("AOP环绕通知[-后置通知]" + methodName + "方法最终结束了...");
}
return result;
}
}
全注解开发
@Configuration//代替配置文件
@ComponentScan("com.powernode.spring6.service")//组件扫描
@EnableAspectJAutoProxy(proxyTargetClass = true)//开启AOP
public class Spring6Configuration {
}
@Test
public void testAOPWithAllAnnotation(){
ApplicationContext ioc = new AnnotationConfigApplicationContext(Spring6Configuration.class);
OrderService orderService = ioc.getBean("orderService", OrderService.class);
orderService.generate();
}
基于XML配置AOP
public interface SmartAnimalable {
float getSum(float i, float j);
float getSub(float i, float j);
}
public class SmartDog implements SmartAnimalable {
@Override
public float getSum(float i, float j) {return result;}
@Override
public float getSub(float i, float j) {return result;}
}
切面类将注解全部去掉
public class SmartAnimalAspect {
public void showBeginLog(JoinPoint joinPoint) {}
public void showSuccessEndLog(JoinPoint joinPoint, Object res) {}
public void showExceptionLog(JoinPoint joinPoint, Throwable throwable) {}
public void showFinallyEndLog(JoinPoint joinPoint) {}
}
测试类
@Test
public void testAspectByXML() {
ApplicationContext ioc = new ClassPathXmlApplicationContext("beans09.xml");
SmartAnimalable smartAnimalable =
ioc.getBean(SmartAnimalable.class);
smartAnimalable.getSum(10, 2);
}
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:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--使用XML配置,完成AOP编程-->
<!--配置一个切面类对象-bean-->
<bean class="com.hspedu.spring.aop.xml.SmartAnimalAspect" id="smartAnimalAspect"/>
<!--配置一个SmartDog对象-bean-->
<bean class="com.hspedu.spring.aop.xml.SmartDog" id="smartDog"/>
<!--配置切面类, 细节一定要引入 xmlns:aop-->
<aop:config>
<!--配置切入点-->
<aop:pointcut id="myPointCut" expression="execution(public float com.hspedu.spring.aop.xml.SmartDog.getSum(float, float)))"/>
<!--配置切面的前置,返回, 异常, 最终通知-->
<aop:aspect ref="smartAnimalAspect" order="10">
<!--配置前置通知-->
<aop:before method="showBeginLog" pointcut-ref="myPointCut"/>
<!--返回通知-->
<aop:after-returning method="showSuccessEndLog" pointcut-ref="myPointCut" returning="res"/>
<!--异常通知-->
<aop:after-throwing method="showExceptionLog" pointcut-ref="myPointCut" throwing="throwable"/>
<!--最终通知-->
<aop:after method="showFinallyEndLog" pointcut-ref="myPointCut"/>
<!--配置环绕通知-->
<!--<aop:around method=""/>-->
</aop:aspect>
</aop:config>
</beans>